实验 03:PWM 呼吸灯
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 理解 PWM(脉宽调制) 控制 LED 亮度的原理:平均电压 = 占空比 × VDD。
- 掌握 FPGA 中三角波发生器的设计方法,用于占空比平滑变化。
- 学习
assign组合逻辑和always时序逻辑的混合使用。 - 理解人眼视觉暂留效应——PWM 频率 >100Hz 时人眼感觉不到闪烁,只感受到等效亮度变化。
二、实验过程
2.1 硬件连接
使用任意一个 LED(如 Pin 8),其余保持不变。
2.2 设计思路
PWM 原理:
PCLK ──→ [PWM周期计数器 0→255] ──→ 比较器 ──→ LED
│
[占空比发生器 0%→100%→0%]
- 每个 PWM 周期(256 个时钟节拍)中,LED 导通的时间占比 = 占空比。
- 占空比 0% → LED 全灭;100% → LED 最亮。
- 占空比用三角波平滑变化,形成"呼吸"效果。
呼吸周期:占空比从 0% 渐变到 100% 再回到 0%,约 4 秒一个完整呼吸周期。
2.3 Verilog 模块设计
模块名:pwm_breath
端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 50MHz |
led | output | 1 | PWM 输出 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
pwm_cnt | 8 | PWM 周期计数器,0→255 循环 (PWM 频率 ≈ 195kHz) |
duty | 8 | 当前占空比,0→255→0 三角波渐变 |
duty_dir | 1 | 占空比方向:0=递增,1=递减 |
duty_timer | 17 | 占空比变化定时器,每 2.6ms 改变一次占空比 |
逻辑描述:
pwm_cnt每个时钟 +1,0→255 自由运行,PWM 频率 ≈ 50M÷256 ≈ 195kHz(远超 100Hz)。duty_timer每 2.6ms 触发一次占空比更新。duty从 0 递增到 255,再递减到 0,循环往复(三角波)。assign led = (pwm_cnt < duty)— 组合逻辑比较,当前计数值小于占空比时 LED 亮。
2.4 引脚约束
同实验 01,LED 约束到 Pin 8。
三、实验现象
| 阶段 | 占空比 | LED 亮度 | 持续时间 |
|---|---|---|---|
| 渐亮 | 0% → 100% | 灭 → 最亮 | ~2 秒 |
| 渐暗 | 100% → 0% | 最亮 → 灭 | ~2 秒 |
| 循环 | ... | 亮暗交替 | 持续 |
LED 如呼吸般明暗交替,亮度变化丝滑连续。肉眼完全感觉不到 PWM 频闪。
四、掌握知识点
| 知识点 | 说明 |
|---|---|
| PWM 调制 | 周期固定,调节占空比控制等效输出电压 |
assign 连续赋值 | 组合逻辑实时比较 pwm_cnt < duty |
| 三角波发生器 | duty 递增到顶后方向翻转递减 |
| 视觉暂留 | PWM 频率 >100Hz 人眼无闪烁感 |
| 参数化设计 | 修改 localparam 可调呼吸速度和 PWM 分辨率 |
| 时序+组合混合 | always 算控制量,assign 驱动输出 |
五、思考题
- 如果把 PWM 分辨率从 8 位改成 10 位(0~1023),需要改哪些地方?
- 为什么 PWM 频率不能太低(比如 50Hz)?
- 如何实现不是对称呼吸——比如亮的时间长、暗的时间短?
- 能同时让 3 个 LED 以不同的呼吸相位工作吗?如何实现?
附录:源码清单
top.v
// =============================================================================
// top.v - PWM呼吸灯 (PWM Breath LED)
// 芯片: GW1N-LV9LQ144C6/I5
// 时钟: 50MHz (Pin 98)
// LED: Pin 8
// 功能: PWM调制LED亮度,三角波占空比形成"呼吸"效果
// =============================================================================
module pwm_breath (
input wire clk_50m, // 50MHz 系统时钟
output wire led // PWM 输出 (组合逻辑驱动)
);
// =========================================================================
// 参数定义
// PWM 分辨率: 8位 (0~255)
// PWM 频率: 50MHz / 256 ≈ 195kHz (>100Hz, 人眼无闪烁)
//
// 呼吸周期: 完整一次"渐亮→渐暗"约 4 秒
// 渐亮阶段: 0%→100% 占空比, 约2秒, 256步, 每步约7.81ms
// 渐暗阶段: 100%→0% 占空比, 约2秒, 256步, 每步约7.81ms
// 每步计数值: 50MHz × 7.81ms ≈ 390,625 → 阈值 390,624
// (19位计数器: 2^19 = 524,288 > 390,625)
// =========================================================================
localparam PWM_WIDTH = 8; // PWM位宽
localparam PWM_MAX = (1 << PWM_WIDTH) - 1; // 255
localparam DUTY_STEP_MAX = 19'd390_624; // 占空比变化间隔 (~7.81ms)
// =========================================================================
// 内部信号
// =========================================================================
reg [PWM_WIDTH-1:0] pwm_cnt; // PWM周期计数器 (0→255循环)
reg [PWM_WIDTH-1:0] duty; // 当前占空比 (0→255→0三角波)
reg duty_dir; // 占空比方向: 0=递增(渐亮), 1=递减(渐暗)
reg [18:0] duty_timer; // 占空比变化定时器 (0→390,624)
// =========================================================================
// 1. PWM 周期计数器: 每个时钟+1, 0→255 自由运行
// PWM频率 = 50MHz / 256 ≈ 195.3kHz
// =========================================================================
always @(posedge clk_50m) begin
if (pwm_cnt == PWM_MAX)
pwm_cnt <= {PWM_WIDTH{1'b0}};
else
pwm_cnt <= pwm_cnt + 1'b1;
end
// =========================================================================
// 2. 占空比变化定时器: 每 ~7.81ms 产生一次更新脉冲
// =========================================================================
wire duty_tick;
assign duty_tick = (duty_timer == DUTY_STEP_MAX);
always @(posedge clk_50m) begin
if (duty_tick)
duty_timer <= 19'd0;
else
duty_timer <= duty_timer + 1'b1;
end
// =========================================================================
// 3. 占空比三角波发生器 (在 duty_tick 脉冲时更新)
//
// 渐亮阶段 (duty_dir=0): duty 从 0 递增到 255
// 渐暗阶段 (duty_dir=1): duty 从 255 递减到 0
//
// 边界处理:
// - duty==254 且递增 → duty=255, 翻转方向为递减
// - duty==1 且递减 → duty=0, 翻转方向为递增
// - 中间值 → duty±1 正常增减
// =========================================================================
always @(posedge clk_50m) begin
if (duty_tick) begin
if (!duty_dir) begin
// ---- 渐亮阶段: 占空比递增 ----
if (duty == PWM_MAX - 1) begin
duty <= PWM_MAX;
duty_dir <= 1'b1; // 到达最大值,翻转为递减
end else begin
duty <= duty + 1'b1;
end
end else begin
// ---- 渐暗阶段: 占空比递减 ----
if (duty == 1) begin
duty <= {PWM_WIDTH{1'b0}};
duty_dir <= 1'b0; // 到达最小值,翻转为递增
end else begin
duty <= duty - 1'b1;
end
end
end
end
// =========================================================================
// 4. PWM 输出: 组合逻辑比较
// 当 PWM 计数器 < 当前占空比时 LED 亮
// 例如: duty=128 (50%), 则 pwm_cnt 在 0~127 期间 LED=1, 128~255 期间 LED=0
// 等效亮度 = 占空比 / 256 × 100%
// =========================================================================
assign led = (pwm_cnt < duty);
// =========================================================================
// 上电初始值
// =========================================================================
initial begin
duty = {PWM_WIDTH{1'b0}}; // 从灭开始
duty_dir = 1'b0; // 先递增(渐亮)
end
endmodule