4、PWM输出实验:使用STM32输出PWM波形、使用示波器测量PWM、PWM频率与占空比计算
说实话,PWM(脉冲宽度调制)是我在嵌入式开发中用得最多的功能之一。调个LED亮度、控制个舵机角度、驱动个直流电机,几乎都离不开它。这一章,我们就拿STM32来实际输出PWM波形,再用示波器看看它长什么样,最后把频率和占空比的计算彻底搞明白。
本章核心目标:通过STM32定时器的PWM模式,输出一个频率可调、占空比可调的方波信号,并用示波器验证其参数。
4.1 PWM的基本概念回顾
PWM说白了就是一种“假装模拟”的数字信号。它通过快速切换高低电平,让负载感受到的平均电压发生变化。你想想看,一个5V的方波,如果高电平占了50%的时间,低电平占50%,那平均下来就是2.5V的效果。
这里有两个关键参数:
- 频率(Frequency):一秒内PWM波形重复的次数,单位Hz。频率决定了信号的“刷新速度”。
- 占空比(Duty Cycle):高电平时间占整个周期的百分比。占空比决定了平均电压的大小。
我在项目中遇到过一个问题:用PWM控制电机时,频率选得太低,电机就会“咔咔”地抖动;选得太高,驱动管的开关损耗又上来了。所以频率的选择是个权衡,后面我们会细说。
4.2 STM32定时器PWM输出原理
STM32的PWM输出,本质上就是利用定时器的“自动重装载”和“比较输出”功能。我画了一张图,帮你理清这个逻辑:
工作流程其实很简单:
- 时钟源经过预分频器(PSC)分频后,驱动计数器(CNT)递增。
- 计数器从0一直数到自动重装载值(ARR),然后归零重新开始。
- 在计数过程中,比较寄存器(CCR)会跟CNT的值做比较。
- 当CNT小于CCR时,输出高电平;当CNT大于等于CCR时,输出低电平。
嗯,这里要注意:ARR决定了PWM的周期,CCR决定了占空比。两者配合,就能输出你想要的波形。
4.3 频率与占空比的计算公式
这是硬核部分,我建议你直接记住这两个公式:
PWM频率计算公式:
PWM频率 = 定时器时钟频率 / ((PSC + 1) × (ARR + 1))
占空比计算公式:
占空比 = CCR / (ARR + 1) × 100%
举个例子,假设STM32的定时器时钟是72MHz:
- 如果PSC = 71,ARR = 999,那么PWM频率 = 72,000,000 / (72 × 1000) = 1000Hz = 1kHz
- 如果CCR = 500,那么占空比 = 500 / 1000 × 100% = 50%
我曾经在调试一个呼吸灯项目时,把ARR设成了65535,结果LED闪烁频率低得肉眼可见,像在“喘气”一样。后来一算,频率只有1Hz左右。所以,算清楚再动手,能省不少调试时间。
4.4 实战:STM32输出PWM波形
我们以STM32F103系列为例,用定时器TIM2的通道1输出PWM。硬件连接很简单:
- PA0(TIM2_CH1)作为PWM输出引脚
- 示波器探头接PA0,GND接板子GND
下面是完整的初始化代码:
// 定时器PWM初始化函数
void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置PA0为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置定时器时基
TIM_TimeBaseStructure.TIM_Period = 999; // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC = 71
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // CCR = 500,占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
小技巧:如果你想在运行时动态调整占空比,可以直接修改CCR的值:
TIM_SetCompare1(TIM2, new_ccr_value); // 新值范围0~ARR
这样就能实现呼吸灯、电机调速等效果。
4.5 使用示波器测量PWM波形
代码烧进去之后,别急着高兴。你得用示波器看看实际波形对不对。我一般按这个步骤来:
- 连接探头:探头钩子勾住PA0,地线夹子夹住GND。
- 设置垂直档位:先设成2V/div,因为STM32输出是3.3V逻辑电平。
- 设置水平档位:根据目标频率来。1kHz的话,周期是1ms,水平档位设成200μs/div或500μs/div比较合适。
- 触发设置:用上升沿触发,触发电平设在1.5V左右。
- 读取参数:用示波器的“测量”功能,直接读出频率和占空比。
我记得有一次,我调了一个PWM程序,示波器上死活看不到波形。折腾了半天,发现是GPIO模式配成了推挽输出,而不是复用推挽输出。嗯,这种低级错误,犯过一次就不会再犯了。
4.6 常见参数配置速查表
为了方便你快速上手,我整理了一个常用配置表。假设定时器时钟为72MHz:
| 目标频率 | PSC | ARR | 分辨率 | 适用场景 |
|---|---|---|---|---|
| 1kHz | 71 | 999 | 0.1% | LED调光、舵机控制 |
| 10kHz | 71 | 99 | 1% | 直流电机调速 |
| 50kHz | 7 | 179 | 0.56% | 开关电源控制 |
| 100kHz | 3 | 179 | 0.56% | 高频逆变器 |
注意:分辨率 = 1 / (ARR + 1) × 100%。ARR越小,分辨率越低,占空比调节的精细度就越差。比如ARR=99时,你只能以1%的步进调整占空比。所以,在频率和分辨率之间要做好权衡。
4.7 避坑指南
做PWM实验时,有几个坑我踩过,分享给你:
- GPIO模式别配错:一定要用复用推挽输出(GPIO_Mode_AF_PP),不是普通推挽输出。
- 定时器时钟源别搞混:TIM1和TIM8挂在APB2上,其他定时器挂在APB1上。APB1的时钟默认是36MHz,不是72MHz。
- 占空比100%或0%:当CCR = ARR + 1时,输出一直为高;当CCR = 0时,输出一直为低。这在某些场景下有用,但要注意边界。
- 示波器带宽:如果PWM频率很高(比如几百kHz),普通示波器可能测不准。确保你的示波器带宽足够。
我曾经在一个电机驱动项目里,因为没注意APB1时钟是36MHz,算出来的频率整整差了一倍。电机转得忽快忽慢,排查了好久才发现是时钟树的问题。所以,拿到一个STM32型号,第一件事就是看时钟树图。
4.8 本章小结
这一章我们从原理到实战,把PWM输出这件事彻底讲透了。你掌握了:
- STM32定时器产生PWM的完整流程
- 频率和占空比的计算方法
- 如何用示波器验证波形参数
- 常见配置参数和避坑经验
PWM这个功能,说难不难,但想用好,还是得动手多试。找个开发板,接上示波器,把上面那段代码跑一遍,看看波形是不是跟你算的一样。实践出真知,这话在嵌入式领域尤其适用。