3. PWM频率与占空比计算
做电机控制,PWM就是你的手和脚。频率和占空比这两个参数,说白了就是控制电机转得快不快、力大不大的核心旋钮。很多新手一上来就对着寄存器手册发懵,其实没那么复杂。
我个人习惯,拿到一个新项目,先拿计算器把频率和占空比的寄存器值算清楚,再动手写代码。这样心里有底,调试起来也快。
3.1 频率与占空比的数学关系
先看一张图,把整个逻辑理清楚。
这张图把整个计算链路串起来了。定时器时钟进来,先经过预分频器(PSC)降频,然后计数器从0数到自动重装载值(ARR),每数完一轮就翻转一次电平。比较寄存器(CCR)则决定了高电平维持多久。
公式其实就两个:
- PWM频率 = 定时器时钟 / (PSC + 1) / (ARR + 1)
- 占空比 = CCR / (ARR + 1) × 100%
嗯,这里要注意,PSC和ARR在寄存器里存的值,实际分频系数要加1。我刚开始做的时候老忘记这个+1,算出来的频率总差一点。
3.2 实战计算:20kHz频率,50%占空比
拿最常见的场景举例。假设定时器时钟是72MHz,我们要输出20kHz的PWM,占空比50%。
先算总的分频系数:
72,000,000 / 20,000 = 3600
也就是说,PSC和ARR的乘积要等于3600。怎么分配?
我个人习惯,先固定PSC,让ARR的值落在合理范围内。ARR太小,分辨率不够;ARR太大,计数器周期太长。
试试PSC = 0,那么ARR = 3600 - 1 = 3599。这样ARR是3599,分辨率还不错。
占空比50%,CCR = (ARR + 1) × 50% = 3600 × 0.5 = 1800。
所以寄存器配置就是:
- PSC = 0
- ARR = 3599
- CCR = 1800
验证一下:频率 = 72M / (0+1) / (3599+1) = 72M / 3600 = 20kHz。完美。
关键点:PSC和ARR的分配没有唯一答案。PSC越小,ARR越大,占空比调节的步进就越细。电机控制一般要求占空比分辨率在0.1%以内,所以ARR最好不低于1000。
3.3 寄存器配置方法
STM32的定时器配置,我习惯用结构体方式,清晰明了。以TIM1为例:
// 定时器句柄
TIM_HandleTypeDef htim1;
// 定时器时钟源配置
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
// 使能定时器时钟
__HAL_RCC_TIM1_CLK_ENABLE();
// 基本配置
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0; // PSC = 0
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 3599; // ARR = 3599
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim1);
// PWM通道配置
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 1800; // CCR = 1800
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
// 启动PWM输出
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
这段代码跑起来,PA8(TIM1_CH1)上就能量到20kHz、50%占空比的方波了。
小技巧:调试阶段,我习惯先用逻辑分析仪或者示波器抓一下波形,确认频率和占空比都对得上。有一次我算错了PSC,波形出来频率差了10倍,示波器一看就发现了。
3.4 常见参数组合参考
不同电机对PWM频率要求不一样。直流有刷电机一般用10~20kHz,无刷电机(BLDC)常用16~20kHz,步进电机细分驱动可能用到20~100kHz。下面这张表是我常用的参数组合:
| 目标频率 | 定时器时钟 | PSC | ARR | 分辨率 | 适用场景 |
|---|---|---|---|---|---|
| 10 kHz | 72 MHz | 0 | 7199 | 0.014% | 直流有刷电机 |
| 20 kHz | 72 MHz | 0 | 3599 | 0.028% | BLDC / 直流有刷 |
| 50 kHz | 72 MHz | 0 | 1439 | 0.069% | 高频应用 |
| 100 kHz | 72 MHz | 0 | 719 | 0.139% | 步进电机细分 |
| 20 kHz | 72 MHz | 71 | 49 | 2.0% | 低分辨率(不推荐) |
最后一行是我故意放进去的反面教材。PSC设成71,ARR只有49,分辨率只有2%。这意味着占空比只能以2%为步进调节,电机转起来会一抖一抖的。我曾经在一个项目里踩过这个坑,后来老老实实把PSC调回0,ARR放大,问题就解决了。
避坑指南:千万不要为了省事直接把PSC设得很大。ARR太小会导致占空比调节粗糙,电机低速时抖动明显。我曾经在调试一个风机项目时,PSC设了71,ARR只有49,结果电机在30%占空比以下根本转不起来。后来把PSC改回0,ARR调到3599,低速性能立马好了。
3.5 动态调整占空比
实际运行中,占空比肯定要动态调的。用HAL库的话,直接改CCR就行:
// 动态修改占空比,比如调到75%
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 2700);
或者用寄存器操作:
// 直接写CCR1寄存器
TIM1->CCR1 = 2700;
两种方式效果一样。我个人习惯用宏定义的方式,代码可读性更好:
#define PWM_PERIOD 3600
#define SET_DUTY(x) __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint32_t)((x) * PWM_PERIOD / 100))
// 设置50%占空比
SET_DUTY(50);
// 设置75%占空比
SET_DUTY(75);
这样写,占空比直接传百分比数值,不用每次手动算CCR。嗯,代码维护起来也方便。