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函数)。

运行与验证

  1. 将代码编译生成ELF文件,下载到Zynq开发板运行。
  2. 串口终端应输出PWM Breathing LED Demo和Breathing LED started信息。
  3. 观察连接至MIO引脚的LED:亮度应呈现从暗到亮、再从亮到暗的周期性变化,每个完整周期约2秒。
  4. 使用示波器测量MIO引脚波形,应看到频率约1kHz、占空比周期性变化的PWM信号。

通过条件:LED亮度变化平滑、无闪烁,呼吸周期约2秒;示波器波形占空比在5%~95%之间连续变化。

常见问题

  • LED不亮:检查MIO引脚连接是否正确,确认LED正极接MIO、负极经限流电阻接地。
  • 亮度无变化:确认TTC中断已正确触发,可在中断服务函数中添加调试计数器,通过串口打印验证中断频率。
  • 编译错误(sin未定义):在SDK工程属性中添加-lm链接选项,或在BSP设置中启用数学库。
PWM呼吸灯信号时序 0 0.5s 1.0s 1.5s 2.0s 占空比包络 低占空比 高占空比 高电平 低电平 1ms