一、PWM 是什么?用数字信号实现 “模拟控制” 的魔法
在嵌入式系统中,很多外设需要 “连续可调的控制信号”—— 比如让 LED 亮度渐变、让电机转速平滑变化、让舵机旋转到指定角度。但 STM32 作为数字芯片,只能输出高低两种电平(0 和 1),如何用数字信号实现 “模拟量控制”?答案就是PWM(Pulse Width Modulation,脉冲宽度调制)。
PWM 的核心原理是 “高频脉冲的占空比调节”:通过周期性输出高低电平,控制高电平在一个周期内的占比(占空比),从而改变信号的 “平均电压”。举个直观的例子:
-
占空比 100%:持续输出高电平,平均电压 = 电源电压(如 3.3V),LED 最亮;
-
占空比 50%:高低电平各占一半时间,平均电压 = 1.65V,LED 亮度中等;
-
占空比 0%:持续输出低电平,平均电压 = 0V,LED 熄灭。
由于 PWM 频率通常远高于人眼或电机的响应速度(如 1kHz 以上),外设会 “感知” 到平均电压的变化,从而实现平滑的模拟量控制。这就像快速开关水龙头:虽然开关是离散的,但水流的平均大小可通过开关时间占比调节。
STM32 的 PWM 功能主要由通用定时器(TIM2-TIM5) 和高级定时器(TIM1、TIM8) 实现,其中通用定时器可输出 4 路独立 PWM,高级定时器支持带死区控制的互补 PWM,满足从简单调光到复杂电机驱动的各类需求。
二、PWM 的核心参数与 STM32 实现原理
要精准控制 PWM 信号,必须先理解其三大核心参数,以及 STM32 定时器如何生成 PWM 波形。
1. PWM 三大核心参数
| 参数名称 | 定义 | 对控制效果的影响 | 典型取值范围 |
|---|---|---|---|
| 频率(Frequency) | 每秒输出的 PWM 周期数(f=1/T) | 频率过低会导致外设抖动(如 LED 闪烁、电机异响);频率过高会增加功耗 | 100Hz~100kHz |
| 占空比(Duty Cycle) | 一个周期内高电平持续时间占比 | 直接决定平均电压,是控制外设状态的核心参数(如亮度、转速) | 0%~100% |
| 分辨率(Resolution) | 占空比可调节的最小精度 | 分辨率越高,控制越平滑(如 10 位分辨率支持 1024 级调节) | 8 位(256 级)~16 位(65536 级) |
2. STM32 PWM 生成原理(基于定时器)
STM32 的 PWM 本质是 “定时器输出比较功能的应用”,其生成过程依赖定时器的四大核心组件(时钟源、预分频器、计数器、比较寄存器),具体流程如下:
-
时钟源与预分频:定时器时钟经预分频器分频后,得到计数时钟(如 72MHz 时钟经 7199 分频,得到 10kHz 计数时钟)。
-
计数器计数:计数器从 0 开始,每收到一个计数时钟就加 1(向上计数模式),直到达到 “自动重装载寄存器(ARR)” 的值后清零,完成一个周期。
-
比较匹配:计数器的值实时与 “比较寄存器(CCR)” 的值对比:
-
当计数器 < CCR 时:PWM 输出高电平(PWM 模式 1);
-
当计数器 ≥ CCR 时:PWM 输出低电平。
- 波形输出:通过重复上述过程,定时器的 PWM 输出引脚(如 TIM2_CH1 对应 PA0)就会产生连续的 PWM 波形。
关键公式推导:
-
PWM 周期 T = (预分频器值 + 1) × (ARR 值 + 1) / 定时器时钟频率
-
PWM 频率 f = 定时器时钟频率 / [(预分频器值 + 1) × (ARR 值 + 1)]
-
占空比 = (CCR 值 + 1) / (ARR 值 + 1) × 100%
-
分辨率 = ARR 值 + 1(ARR 越大,分辨率越高)
示例:定时器时钟 72MHz,预分频器 = 71,ARR=999,CCR=499
-
频率 f = 72000000 / [(71+1)×(999+1)] = 72000000 / 72000 = 1000Hz(1kHz)
-
占空比 = (499+1)/(999+1)×100% = 50%
-
分辨率 = 1000 级(支持 0.1% 精度调节)
3. 两种常用 PWM 模式
STM32 定时器支持多种 PWM 模式,新手需重点掌握以下两种:
-
PWM 模式 1:向上计数时,计数器 < CCR 输出高电平,≥ CCR 输出低电平(最常用,高电平优先);
-
PWM 模式 2:向上计数时,计数器 < CCR 输出低电平,≥ CCR 输出高电平(低电平优先,适用于特定反相控制场景)。
三、STM32 PWM 输出的硬件基础:引脚与定时器对应关系
PWM 信号需通过特定的 GPIO 引脚输出,这些引脚是定时器的 “复用功能引脚”,不同定时器通道对应固定的 GPIO 引脚(以 STM32F103C8T6 为例):
| 定时器 | 通道 1(CH1) | 通道 2(CH2) | 通道 3(CH3) | 通道 4(CH4) |
|---|---|---|---|---|
| TIM1 | PA8 | PA9 | PA10 | PA11 |
| TIM2 | PA0 | PA1 | PA2 | PA3 |
| TIM3 | PA6 | PA7 | PB0 | PB1 |
| TIM4 | PB6 | PB7 | PB8 | PB9 |
关键注意事项:
-
同一引脚可复用为不同定时器的通道(如 PA0 可作 TIM2_CH1 或 GPIO),需通过 CubeMX 配置为 “复用推挽输出”;
-
高级定时器(TIM1、TIM8)的引脚支持互补 PWM 输出(如 TIM1_CH1 对应 PA8,互补通道 TIM1_CH1N 对应 PB13),适用于电机 H 桥驱动。
四、PWM 配置实操:从基础调光到高级调速
以 STM32F103C8T6 为例,分别演示 “LED 亮度渐变(基础 PWM)”“舵机角度控制(精准 PWM)”“直流电机调速(PWM 驱动)” 三大实战场景,覆盖 PWM 开发的核心要点。
1. 实战 1:LED 亮度渐变(TIM2_CH1 输出 PWM)
(1)硬件连接
-
PA0(TIM2_CH1):接 LED 阳极(串联 1kΩ 限流电阻,阴极接地)
-
电源:3.3V(STM32 引脚输出电压)
(2)CubeMX 配置步骤
-
时钟配置:配置 APB1 总线时钟为 36MHz,TIM2 定时器时钟 = APB1 时钟 ×2=72MHz。
-
定时器配置:
-
选择 TIM2,在 “Mode” 中勾选 “PWM Generation CH1”;
-
配置 “Parameter Settings”:
-
Prescaler(预分频器):71(72MHz/72=1MHz 计数时钟);
-
Counter Period(ARR):999(1MHz/1000=1kHz PWM 频率);
-
PWM Mode:PWM Mode 1;
-
Pulse(CCR 初始值):0(初始占空比 0%,LED 熄灭);
-
Auto-reload Preload:Enable(允许 ARR 自动重装载)。
-
-
GPIO 配置:PA0 自动设为 “Alternate Function Push-Pull”(复用推挽输出)。
-
生成代码。
(3)核心代码
// 定时器PWM初始化(CubeMX自动生成,tim.c)
TIM\_HandleTypeDef htim2;
TIM\_OC\_InitTypeDef sConfigOC = {0};
void MX\_TIM2\_Init(void) {
TIM\_ClockConfigTypeDef sClockSourceConfig = {0};
TIM\_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;
htim2.Init.CounterMode = TIM\_COUNTERMODE\_UP;
htim2.Init.Period = 999;
htim2.Init.ClockDivision = TIM\_CLOCKDIVISION\_DIV1;
htim2.Init.AutoReloadPreload = TIM\_AUTORELOAD\_PRELOAD\_ENABLE;
if (HAL\_TIM\_Base\_Init(\&htim2) != HAL\_OK) Error\_Handler();
sClockSourceConfig.ClockSource = TIM\_CLOCKSOURCE\_INTERNAL;
if (HAL\_TIM\_ConfigClockSource(\&htim2, \&sClockSourceConfig) != HAL\_OK) Error\_Handler();
if (HAL\_TIM\_PWM\_Init(\&htim2) != HAL\_OK) Error\_Handler();
sMasterConfig.MasterOutputTrigger = TIM\_TRGO\_RESET;
sMasterConfig.MasterSlaveMode = TIM\_MASTERSLAVEMODE\_DISABLE;
if (HAL\_TIMEx\_MasterConfigSynchronization(\&htim2, \&sMasterConfig) != HAL\_OK) Error\_Handler();
sConfigOC.OCMode = TIM\_OCMODE\_PWM1;
sConfigOC.Pulse = 0; // 初始CCR值
sConfigOC.OCPolarity = TIM\_OCPOLARITY\_HIGH; // 高电平有效
sConfigOC.OCFastMode = TIM\_OCFAST\_DISABLE;
if (HAL\_TIM\_PWM\_ConfigChannel(\&htim2, \&sConfigOC, TIM\_CHANNEL\_1) != HAL\_OK) Error\_Handler();
}
// main函数实现亮度渐变
int main(void) {
HAL\_Init();
SystemClock\_Config();
MX\_TIM2\_Init();
HAL\_TIM\_PWM\_Start(\&htim2, TIM\_CHANNEL\_1); // 启动PWM输出
uint16\_t ccr\_val = 0;
uint8\_t dir = 1; // 1=亮度增加,0=亮度降低
while (1) {
HAL\_Delay(10); // 控制渐变速度
if (dir) {
ccr\_val++;
if (ccr\_val >= 1000) dir = 0; // 占空比达100%反转
} else {
ccr\_val--;
if (ccr\_val <= 0) dir = 1; // 占空比达0%反转
}
// 更新CCR值(两种方式)
// 方式1:HAL库函数
// TIM\_OC\_InitTypeDef sConfigOC = {0};
// sConfigOC.Pulse = ccr\_val;
// HAL\_TIM\_PWM\_ConfigChannel(\&htim2, \&sConfigOC, TIM\_CHANNEL\_1);
// HAL\_TIM\_PWM\_Start(\&htim2, TIM\_CHANNEL\_1);
// 方式2:直接操作寄存器(更高效)
TIM2->CCR1 = ccr\_val;
}
}
2. 实战 2:舵机角度控制(50Hz 标准 PWM)
(1)舵机工作原理
舵机是典型的 “PWM 受控设备”,其角度由 50Hz(周期 20ms)的 PWM 信号占空比决定:
-
占空比 2.5%(0.5ms 高电平):对应 0°;
-
占空比 7.5%(1.5ms 高电平):对应 90°(中位);
-
占空比 12.5%(2.5ms 高电平):对应 180°。
(2)硬件连接
-
PA6(TIM3_CH1):接舵机信号引脚(通常为橙色 / 黄色线)
-
舵机电源:5V(舵机电流较大,需独立供电,与 STM32 共地)
(3)CubeMX 配置要点
-
定时器选择 TIM3,PWM 频率 50Hz:
预分频器 = 7199(72MHz/7200=10kHz 计数时钟),
ARR=199(10kHz/200=50Hz,周期 20ms)。
-
初始 CCR 值 = 99(1.5ms 高电平,对应 90°)。
(4)核心控制代码
// 舵机角度控制函数(0°\~180°)
void Servo\_Set\_Angle(uint8\_t angle) {
if (angle > 180) angle = 180;
// 角度转CCR值:0°→10(0.5ms),180°→50(2.5ms),线性映射
uint16\_t ccr\_val = (angle \* 40 / 180) + 10;
TIM3->CCR1 = ccr\_val;
}
// main函数测试
int main(void) {
HAL\_Init();
SystemClock\_Config();
MX\_TIM3\_Init(); // 配置50Hz PWM
HAL\_TIM\_PWM\_Start(\&htim3, TIM\_CHANNEL\_1);
while (1) {
Servo\_Set\_Angle(0); // 转到0°
HAL\_Delay(1000);
Servo\_Set\_Angle(90); // 转到90°
HAL\_Delay(1000);
Servo\_Set\_Angle(180); // 转到180°
HAL\_Delay(1000);
}
}
3. 实战 3:直流电机调速(PWM+H 桥驱动)
(1)硬件设计
STM32 引脚输出的 PWM 电流(最大 25mA)无法直接驱动电机,需通过 H 桥驱动模块(如 L298N、TB6612)放大电流:
-
PA0(TIM2_CH1):接 L298N 的 ENA(电机 A 使能端);
-
PA1、PA2:接 L298N 的 IN1、IN2(控制电机转向);
-
电机:接 L298N 的 OUT1、OUT2。
(2)核心控制逻辑
-
转向控制:通过 GPIO 设置 IN1、IN2 电平(IN1=1、IN2=0→正转;IN1=0、IN2=1→反转);
-
转速控制:通过调节 ENA 引脚的 PWM 占空比(占空比越高,转速越快)。
(3)核心代码
// 电机初始化(GPIO+PWM)
void Motor\_Init(void) {
// PA1、PA2配置为推挽输出
GPIO\_InitTypeDef GPIO\_InitStruct = {0};
\_\_HAL\_RCC\_GPIOA\_CLK\_ENABLE();
GPIO\_InitStruct.Pin = GPIO\_PIN\_1|GPIO\_PIN\_2;
GPIO\_InitStruct.Mode = GPIO\_MODE\_OUTPUT\_PP;
GPIO\_InitStruct.Pull = GPIO\_NOPULL;
GPIO\_InitStruct.Speed = GPIO\_SPEED\_FREQ\_LOW;
HAL\_GPIO\_Init(GPIOA, \&GPIO\_InitStruct);
MX\_TIM2\_Init(); // 配置10kHz PWM
HAL\_TIM\_PWM\_Start(\&htim2, TIM\_CHANNEL\_1);
}
// 电机控制函数(dir:1=正转,0=反转;speed:0\~100=占空比%)
void Motor\_Control(uint8\_t dir, uint8\_t speed) {
if (speed > 100) speed = 100;
uint16\_t ccr\_val = (speed \* 1000) / 100; // 1000级分辨率
// 转向控制
if (dir) {
HAL\_GPIO\_WritePin(GPIOA, GPIO\_PIN\_1, GPIO\_PIN\_SET);
HAL\_GPIO\_WritePin(GPIOA, GPIO\_PIN\_2, GPIO\_PIN\_RESET);
} else {
HAL\_GPIO\_WritePin(GPIOA, GPIO\_PIN\_1, GPIO\_PIN\_RESET);
HAL\_GPIO\_WritePin(GPIOA, GPIO\_PIN\_2, GPIO\_PIN\_SET);
}
// 转速控制
TIM2->CCR1 = ccr\_val;
}
// main函数测试
int main(void) {
HAL\_Init();
SystemClock\_Config();
Motor\_Init();
while\</doubaocanvas>
1万+

被折叠的 条评论
为什么被折叠?



