第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);
}
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);
}
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,就改变了高电平的宽度。
占空比 = 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,效果比较自然。
知识体系总览
为了让大家更直观地理解本章的知识结构,我画了一张图:
这张图把本章的核心内容串起来了。你看,定时器配置、GPIO设置、参数计算,这三块是基础。把它们搞明白了,舵机控制和呼吸灯实验就是水到渠成的事。
好了,这一章的内容就到这里。记住,PWM配置的核心就三个参数:预分频值、自动重装载值、比较值。把这三个参数算清楚,什么PWM应用都不在话下。
公众号:蓝海资料掘金营,微信deep3321