3. PWM基础代码生成与验证:HAL库PWM启动函数、占空比调节、逻辑分析仪抓波形

好,咱们进入实战环节。上一章我们把CubeMX配置讲透了,这一章直接上手写代码、调波形。我个人习惯是:配置完先别急着写业务逻辑,先把PWM输出搞出来,用逻辑分析仪看一眼波形再说。你想想看,如果波形都不对,后面调什么都是白搭。

3.1 HAL库PWM启动函数:到底该调哪个?

很多新手拿到HAL库,看到一堆PWM相关的函数就懵了。其实核心就三个:初始化、启动、调占空比。咱们一个一个说。

3.1.1 初始化函数

CubeMX生成代码后,MX_TIMx_Init() 已经帮你配好了时基和输出通道。这个函数在 main() 里会被自动调用,你基本不用动它。但我建议你打开看一眼,确认一下参数对不对。

// HAL库自动生成的初始化代码片段
static void MX_TIM2_Init(void)
{
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 71;          // 72MHz / (71+1) = 1MHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 999;            // 1MHz / (999+1) = 1kHz
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_PWM_Init(&htim2);
}
我的习惯:每次生成代码后,我都会手动核对 Prescaler 和 Period 的值。曾经有一次我忘了改 Period,结果波形频率差了10倍,查了半天才发现是CubeMX默认值没覆盖。

3.1.2 启动函数:HAL_TIM_PWM_Start

初始化完了,怎么让PWM真正输出?答案是 HAL_TIM_PWM_Start()。这个函数有两个参数:定时器句柄和通道号。

// 启动PWM输出
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);   // 启动通道1

这里有个坑——很多人只调了 HAL_TIM_PWM_Init(),忘了调 Start,结果示波器上啥也没有。嗯,我刚开始也犯过这个错。

注意:HAL_TIM_PWM_Start() 必须在 HAL_TIM_PWM_Init() 之后调用。如果你在初始化之前就Start,定时器还没配置好,输出会异常甚至卡死。

3.2 占空比调节:CCR寄存器与__HAL_TIM_SET_COMPARE

PWM的核心参数就两个:频率和占空比。频率由 Prescaler 和 Period 决定,占空比则由 CCR(捕获/比较寄存器)控制。

3.2.1 CCR寄存器原理

说白了,CCR就是一个比较值。定时器从0往上数,数到Period就归零。在这个过程中,当计数值小于CCR时,输出高电平;大于等于CCR时,输出低电平。所以CCR的值直接决定了占空比。

CCR值 占空比 说明
0 0% 一直低电平
Period/2 50% 方波,最常用
Period 100% 一直高电平

3.2.2 用HAL库调节占空比

HAL库提供了两个方法改CCR:

  1. 直接写寄存器:TIM2->CCR1 = 500; —— 简单粗暴,但可读性差。
  2. 用HAL宏:__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 500); —— 推荐,代码更清晰。
// 动态调节占空比的示例
void set_pwm_duty(uint16_t duty)
{
    if(duty > 1000) duty = 1000;   // 限幅,防止溢出
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}

// 在主循环中渐变占空比
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();

    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

    uint16_t duty = 0;
    uint8_t dir = 1;

    while (1)
    {
        set_pwm_duty(duty);
        HAL_Delay(10);

        if(dir) duty += 10;
        else    duty -= 10;

        if(duty >= 1000) dir = 0;
        if(duty == 0)     dir = 1;
    }
}
关键点:CCR的值不能超过Period。如果CCR > Period,输出会一直保持高电平(相当于100%占空比),但定时器不会报错。我曾经在项目里踩过这个坑,电机转得飞快,查了半天才发现是CCR写溢出了。

3.3 逻辑分析仪抓波形:验证你的PWM

代码写完了,怎么知道对不对?别靠猜,上逻辑分析仪。我建议每个做嵌入式的人手边都备一个便宜的8通道逻辑分析仪,几十块钱,但能帮你省下大把调试时间。

3.3.1 接线方法

逻辑分析仪的接线很简单:

  • 通道0:接STM32的PWM输出引脚(比如PA0/TIM2_CH1)
  • GND:接STM32的GND(必须共地!)

不共地的话,波形会乱飘,甚至完全抓不到。我见过有人折腾了一下午,最后发现是地线没接。

3.3.2 软件设置

我用的是Saleae Logic Analyzer(免费版就够用)。设置步骤:

  1. 采样率设到 24MHz 以上(你的PWM是1kHz,24MHz足够)
  2. 采样深度设 1M samples(抓个几秒没问题)
  3. 触发方式选 上升沿(这样每次抓到的波形起始点一致)
  4. 添加一个 PWM分析器,软件会自动算出频率和占空比

3.3.3 波形验证清单

抓到波形后,对照这个清单检查:

检查项 期望值 如果不对怎么办
频率 1kHz ± 5% 检查Prescaler和Period
占空比 与CCR设置一致 检查CCR值是否写对
波形是否稳定 无抖动、无毛刺 检查电源滤波、引脚是否虚焊
占空比渐变 平滑变化 检查循环逻辑、延时是否合理
我的经验:逻辑分析仪抓到的波形如果频率对但占空比不对,90%是CCR写错了。如果频率完全不对,先查时钟树,再查Prescaler和Period。如果啥都没有,检查一下Start函数调了没。

3.4 本章知识体系

下面这张图把PWM从配置到验证的完整流程串起来了,你可以对照着理清思路。

PWM输出与验证流程 CubeMX配置 生成初始化代码 核对Prescaler/Period HAL_TIM_PWM_Init() HAL_TIM_PWM_Start() 设置CCR初始值 __HAL_TIM_SET_COMPARE() 动态调节CCR实现占空比渐变 逻辑分析仪抓波形 检查频率/占空比 验证渐变效果 核心原则:先验证波形,再写业务逻辑 配置→启动→调节→验证,每一步都要确认无误再往下走

3.5 常见问题与避坑

最后,我把这些年遇到的PWM相关坑总结一下,你遇到了可以直接对照排查。

坑1:PWM输出但波形不对
现象:频率对,但占空比始终是50%或100%。
原因:CCR寄存器没写进去,或者写的位置不对。
解决:检查 __HAL_TIM_SET_COMPARE 是否在 Start 之后调用,且通道号是否正确。
坑2:逻辑分析仪抓不到波形
现象:软件上啥也没有,或者全是噪声。
原因:地线没接,或者采样率太低。
解决:先确认GND连接,再把采样率调到24MHz以上。
坑3:占空比渐变不平滑
现象:LED亮度跳变,或者电机转速抖动。
原因:CCR变化太快,或者 HAL_Delay 精度不够。
解决:减小每次CCR的变化量,或者用定时器中断做渐变。