第1章:PWM输出实战——通用定时器PWM输出配置

各位同学,咱们今天来聊聊PWM输出。说实话,PWM这东西在嵌入式开发里太常见了——调个灯、控个舵机、驱动个电机,哪哪都离不开它。我刚开始学STM32那会儿,总觉得PWM配置挺玄乎的,后来踩了几个坑才明白,其实核心就三件事:定时器怎么配、GPIO怎么设、参数怎么算。

好,咱们直接进入正题。

1.1 通用定时器PWM输出配置(HAL库与标准库)

先说说定时器的选择。STM32的通用定时器(TIM2~TIM5)都支持PWM输出,我一般习惯用TIM3,因为它的通道映射到GPIO比较方便。配置PWM输出,说白了就是让定时器工作在PWM模式1或模式2下。

HAL库配置方式

// 初始化定时器句柄
TIM_HandleTypeDef htim3;

void MX_TIM3_Init(void)
{
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 71;          // 72MHz / (71+1) = 1MHz
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 19999;          // 1MHz / (19999+1) = 50Hz
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim3);
    
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 1500;             // 占空比初始值,对应1.5ms
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
}

标准库配置方式

void TIM3_PWM_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 19999;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1500;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM3, ENABLE);
}
我的小经验:HAL库虽然代码看起来长一点,但可读性更好,适合项目开发。标准库代码简洁,适合快速验证。我个人现在偏向HAL库,因为它的错误处理机制更完善。

1.2 GPIO复用功能配置

定时器配置好了,还得把GPIO口设成复用功能。不然信号出不去,你说气不气人?

以STM32F103为例,TIM3的通道1映射在PA6上。配置时要注意两点:一是开启GPIO时钟,二是设置复用功能。

// HAL库方式
void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      // 复用推挽输出
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 标准库方式
void GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}
注意:我曾经因为忘记配置GPIO的复用功能,折腾了整整一个下午。信号死活出不来,最后发现GPIO模式设成了推挽输出而不是复用推挽输出。嗯,这种低级错误,犯一次就记住了。

1.3 PWM参数计算——以50Hz舵机控制为例

舵机控制是PWM应用的经典案例。标准舵机要求50Hz的PWM信号,也就是周期20ms。占空比范围一般是0.5ms~2.5ms,对应舵机角度0°~180°。

咱们来算算参数。系统时钟72MHz,经过预分频器分频后得到定时器时钟。我一般这么算:

参数 计算公式 示例值
预分频值 PSC = 72MHz / 1MHz - 1 71
自动重装载值 ARR = 1MHz / 50Hz - 1 19999
占空比(0°) CCR = 0.5ms × 1MHz 500
占空比(90°) CCR = 1.5ms × 1MHz 1500
占空比(180°) CCR = 2.5ms × 1MHz 2500

说白了,CCR值就是占空比对应的计数值。你想想看,定时器从0数到19999,当计数值小于CCR时输出高电平,大于CCR时输出低电平。改变CCR,就改变了高电平的宽度。

核心公式:PWM频率 = 定时器时钟 / (PSC+1) / (ARR+1)
占空比 = CCR / (ARR+1) × 100%

1.4 呼吸灯实验——渐变PWM

呼吸灯实验,说白了就是让LED的亮度从暗到亮、再从亮到暗循环变化。实现方式很简单:在主循环里不断改变CCR的值。

void breath_light(void)
{
    uint16_t duty = 0;
    uint8_t direction = 1;  // 1:增加, 0:减少
    
    while(1)
    {
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
        
        if(direction)
        {
            duty += 10;
            if(duty >= 19999) direction = 0;
        }
        else
        {
            duty -= 10;
            if(duty <= 0) direction = 1;
        }
        
        HAL_Delay(10);  // 延时10ms,控制渐变速度
    }
}

这里有个小技巧:步长和延时时间共同决定了呼吸效果。步长太大,呼吸会显得很突兀;步长太小,变化又太慢。我一般用步长10、延时10ms,效果比较自然。

避坑指南:我曾经在呼吸灯实验里直接用HAL_Delay(1),结果LED闪得跟频闪灯似的。为什么?因为延时太短,人眼来不及反应。记住,人眼的视觉暂留效应大约在30ms左右,所以延时至少要在5ms以上才能看到平滑的渐变效果。

知识体系总览

为了让大家更直观地理解本章的知识结构,我画了一张图:

PWM输出实战知识体系 定时器配置 GPIO复用功能 PWM参数计算 HAL库 / 标准库 PWM模式1 / 模式2 预分频 / 自动重装载 复用推挽输出 时钟使能 引脚映射 频率 = 时钟 / (PSC+1) / (ARR+1) 占空比 = CCR / (ARR+1) 舵机:50Hz / 0.5~2.5ms 应用实战:舵机控制 + 呼吸灯

这张图把本章的核心内容串起来了。你看,定时器配置、GPIO设置、参数计算,这三块是基础。把它们搞明白了,舵机控制和呼吸灯实验就是水到渠成的事。

好了,这一章的内容就到这里。记住,PWM配置的核心就三个参数:预分频值、自动重装载值、比较值。把这三个参数算清楚,什么PWM应用都不在话下。


公众号:蓝海资料掘金营,微信deep3321