4. PWM呼吸灯
任务目标
使用Zynq SoC的PS端定时器产生PWM信号,通过MIO引脚驱动外部LED,实现呼吸灯效果(亮度从暗到亮、再从亮到暗的周期性变化)。
硬件连接
本案例使用Zynq-7000系列开发板(如Zedboard、ZC706等),LED连接至PS端的MIO引脚。若开发板LED未直接连接MIO,需通过外部杜邦线将MIO引脚连接至LED正极(串联330Ω限流电阻),LED负极接地。
实现原理
利用PS端的三重定时器计数器(TTC)产生周期固定、占空比可调的PWM波形。通过软件在每个PWM周期内更新比较值,使占空比按正弦规律变化,从而获得呼吸灯效果。PWM频率设为1kHz(周期1ms),呼吸周期约2秒。
完整代码
// main.c - Zynq PS定时器PWM呼吸灯
#include <xil_printf.h>
#include <xparameters.h>
#include <xttcps.h>
#include <xscugic.h>
#include <math.h>
#define TTC_DEVICE_ID XPAR_XTTCPS_0_DEVICE_ID
#define TTC_INTR_ID XPAR_XTTCPS_0_INTR_ID
#define PWM_PERIOD 1000 // 周期计数值(1kHz @ 100MHz时钟)
#define BREATH_STEPS 200 // 呼吸周期内步数
#define BREATH_PERIOD_MS 2000 // 完整呼吸周期2秒
static XTtcPs TtcInst;
static XScuGic IntcInst;
static volatile int step = 0;
static volatile int direction = 1;
// PWM占空比查找表(正弦曲线)
static u16 duty_table[BREATH_STEPS];
// 初始化占空比查找表
void InitDutyTable(void) {
int i;
for (i = 0; i < BREATH_STEPS; i++) {
double phase = 2 * M_PI * i / BREATH_STEPS;
// 占空比范围:5% ~ 95%
duty_table[i] = (u16)((0.05 + 0.45 * (1 + sin(phase - M_PI/2))) * PWM_PERIOD);
}
}
// TTC中断服务函数
void TtcIntrHandler(void *CallBackRef) {
XTtcPs *TtcInstPtr = (XTtcPs *)CallBackRef;
u32 status = XTtcPs_GetInterruptStatus(TtcInstPtr);
XTtcPs_ClearInterruptStatus(TtcInstPtr, status);
if (status & XTTCPS_IXR_MATCH0_MASK) {
// 更新下一个周期的比较值
step += direction;
if (step >= BREATH_STEPS - 1) direction = -1;
if (step <= 0) direction = 1;
XTtcPs_SetMatchValue(TtcInstPtr, 0, duty_table[step]);
}
}
// 中断控制器初始化
int InitIntc(void) {
XScuGic_Config *IntcConfig;
int status;
IntcConfig = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID);
status = XScuGic_CfgInitialize(&IntcInst, IntcConfig, IntcConfig->CpuBaseAddress);
if (status != XST_SUCCESS) return XST_FAILURE;
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler, &IntcInst);
Xil_ExceptionEnable();
status = XScuGic_Connect(&IntcInst, TTC_INTR_ID,
(Xil_ExceptionHandler)TtcIntrHandler, &TtcInst);
if (status != XST_SUCCESS) return XST_FAILURE;
XScuGic_Enable(&IntcInst, TTC_INTR_ID);
return XST_SUCCESS;
}
// TTC初始化
int InitTtc(void) {
XTtcPs_Config *TtcConfig;
int status;
TtcConfig = XTtcPs_LookupConfig(TTC_DEVICE_ID);
status = XTtcPs_CfgInitialize(&TtcInst, TtcConfig, TtcConfig->BaseAddress);
if (status != XST_SUCCESS) return XST_FAILURE;
// 时钟源设为PS_CLK(100MHz),分频使计数频率为100MHz
XTtcPs_SetClockDivisor(&TtcInst, 1);
XTtcPs_SetOptions(&TtcInst, XTTCPS_OPTION_INTERVAL_MODE |
XTTCPS_OPTION_MATCH_MODE);
// 设置周期:计数到PWM_PERIOD后归零
XTtcPs_SetInterval(&TtcInst, PWM_PERIOD);
// 初始比较值(50%占空比)
XTtcPs_SetMatchValue(&TtcInst, 0, PWM_PERIOD / 2);
// 使能匹配0中断
XTtcPs_EnableInterrupts(&TtcInst, XTTCPS_IXR_MATCH0_MASK);
return XST_SUCCESS;
}
int main(void) {
int status;
xil_printf("PWM Breathing LED Demo\r\n");
InitDutyTable();
status = InitIntc();
if (status != XST_SUCCESS) {
xil_printf("Intc init failed\r\n");
return -1;
}
status = InitTtc();
if (status != XST_SUCCESS) {
xil_printf("TTC init failed\r\n");
return -1;
}
// 启动TTC
XTtcPs_Start(&TtcInst);
xil_printf("Breathing LED started. Press any key to stop...\r\n");
// 主循环等待
while (1) {
// 空循环,实际工作由中断完成
}
return 0;
}
软件工程配置
在Vivado中创建Zynq PS工程,使能TTC0外设,并将TTC0中断连接到PS中断控制器。SDK中需在BSP设置中使能xiltimer和xilffs库(后者非必需),并添加libm数学库(用于sin函数)。
运行与验证
- 将代码编译生成ELF文件,下载到Zynq开发板运行。
- 串口终端应输出
PWM Breathing LED Demo和Breathing LED started信息。 - 观察连接至MIO引脚的LED:亮度应呈现从暗到亮、再从亮到暗的周期性变化,每个完整周期约2秒。
- 使用示波器测量MIO引脚波形,应看到频率约1kHz、占空比周期性变化的PWM信号。
通过条件:LED亮度变化平滑、无闪烁,呼吸周期约2秒;示波器波形占空比在5%~95%之间连续变化。
常见问题
- LED不亮:检查MIO引脚连接是否正确,确认LED正极接MIO、负极经限流电阻接地。
- 亮度无变化:确认TTC中断已正确触发,可在中断服务函数中添加调试计数器,通过串口打印验证中断频率。
- 编译错误(sin未定义):在SDK工程属性中添加
-lm链接选项,或在BSP设置中启用数学库。