3. PWM呼吸灯
任务目标
在Zynq平台上,通过ARM处理器配置FPGA逻辑产生的PWM信号占空比,驱动LED实现呼吸灯效果。本案例将构建一个AXI-Lite接口的PWM外设,由ARM侧软件动态调节占空比,使LED亮度周期性变化。
硬件设计(FPGA端)
创建Vivado工程,添加Zynq PS核,并通过AXI GPIO IP核输出PWM信号。使用AXI GPIO的通道1作为PWM输出(连接到LED),通道2作为占空比控制寄存器输入。PWM频率设定为1kHz,通过计数器比较产生占空比可调的方波信号。
在Vivado中完成以下连接:
- Zynq PS的M_AXI_GP0接口连接到AXI GPIO IP核
- AXI GPIO通道1(GPIO[0])连接到PL端LED引脚
- AXI GPIO通道2(GPIO[7:0])作为占空比控制输入
- 设置AXI GPIO为双通道模式,通道1输出,通道2输入
完整代码实现
以下为ARM侧C语言代码,通过Xilinx SDK实现PWM呼吸灯控制:
#include "xgpio.h"
#include "xparameters.h"
#include "sleep.h"
#define PWM_GPIO_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define PWM_OUTPUT_CHANNEL 1
#define DUTY_CYCLE_CHANNEL 2
#define PWM_PERIOD_MS 1 // 1kHz PWM频率
#define BREATH_CYCLE_MS 2000 // 呼吸周期2秒
XGpio Gpio;
// 将占空比(0-255)映射到PWM输出
void SetDutyCycle(u8 duty) {
XGpio_DiscreteWrite(&Gpio, DUTY_CYCLE_CHANNEL, duty);
}
int main() {
u8 duty = 0;
int direction = 1; // 1=增加, 0=减少
int step = 1; // 占空比步进值
// 初始化GPIO
if (XGpio_Initialize(&Gpio, PWM_GPIO_DEVICE_ID) != XST_SUCCESS) {
return XST_FAILURE;
}
// 设置通道方向:通道1输出,通道2输入
XGpio_SetDataDirection(&Gpio, PWM_OUTPUT_CHANNEL, 0x0);
XGpio_SetDataDirection(&Gpio, DUTY_CYCLE_CHANNEL, 0xFF);
// 呼吸灯主循环
while (1) {
// 更新占空比
SetDutyCycle(duty);
// 根据方向调整占空比
if (direction) {
duty += step;
if (duty >= 250) {
duty = 250;
direction = 0;
}
} else {
duty -= step;
if (duty <= 5) {
duty = 5;
direction = 1;
}
}
// 延时控制呼吸速度
usleep(BREATH_CYCLE_MS * 1000 / 250);
}
return 0;
}
FPGA端Verilog实现
若需自定义PWM生成逻辑而非使用AXI GPIO,可参考以下核心模块:
module pwm_controller (
input wire clk, // 100MHz系统时钟
input wire rst_n,
input wire [7:0] duty, // 占空比控制(0-255)
output reg pwm_out
);
reg [15:0] counter;
reg [7:0] duty_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 16'd0;
duty_reg <= 8'd0;
pwm_out <= 1'b0;
end else begin
duty_reg <= duty;
if (counter == 16'd99999) begin // 1kHz @100MHz
counter <= 16'd0;
end else begin
counter <= counter + 1'b1;
end
pwm_out <= (counter < duty_reg * 392) ? 1'b1 : 1'b0;
end
end
endmodule
运行与验证
硬件平台:Zynq-7000系列开发板(如ZedBoard、PYNQ-Z2等)
运行步骤:
- 在Vivado中创建Block Design,添加Zynq PS和AXI GPIO IP
- 完成引脚约束(LED连接到AXI GPIO通道1的GPIO[0])
- 生成比特流并导出硬件,启动Xilinx SDK
- 创建C工程,将上述C代码复制到main.c中
- 编译并下载到开发板运行
验证方法:
- 观察LED亮度是否呈现从暗到亮再到暗的周期性变化
- 完整呼吸周期约2秒(从最暗到最亮再回到最暗)
- 修改
BREATH_CYCLE_MS宏定义可调整呼吸速度 - 修改
step变量可改变亮度变化平滑度
预期结果:LED亮度以约2秒为周期平滑变化,呈现呼吸效果。若使用示波器测量PWM输出引脚,可观察到频率为1kHz、占空比在2%~98%之间周期性变化的方波信号。
常见问题
- LED不亮:检查AXI GPIO通道1方向设置是否正确,确认引脚约束是否匹配
- 亮度跳变:减小
step值(如改为1),增加延时时间 - PWM频率异常:确认AXI GPIO输出频率与预期一致,可通过修改计数器上限调整