一、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 本质是 “定时器输出比较功能的应用”,其生成过程依赖定时器的四大核心组件(时钟源、预分频器、计数器、比较寄存器),具体流程如下:

  1. 时钟源与预分频:定时器时钟经预分频器分频后,得到计数时钟(如 72MHz 时钟经 7199 分频,得到 10kHz 计数时钟)。

  2. 计数器计数:计数器从 0 开始,每收到一个计数时钟就加 1(向上计数模式),直到达到 “自动重装载寄存器(ARR)” 的值后清零,完成一个周期。

  3. 比较匹配:计数器的值实时与 “比较寄存器(CCR)” 的值对比:

  • 当计数器 < CCR 时:PWM 输出高电平(PWM 模式 1);

  • 当计数器 ≥ CCR 时:PWM 输出低电平。

  1. 波形输出:通过重复上述过程,定时器的 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

关键注意事项

  1. 同一引脚可复用为不同定时器的通道(如 PA0 可作 TIM2_CH1 或 GPIO),需通过 CubeMX 配置为 “复用推挽输出”;

  2. 高级定时器(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 配置步骤
  1. 时钟配置:配置 APB1 总线时钟为 36MHz,TIM2 定时器时钟 = APB1 时钟 ×2=72MHz。

  2. 定时器配置

  • 选择 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 自动重装载)。

  1. GPIO 配置:PA0 自动设为 “Alternate Function Push-Pull”(复用推挽输出)。

  2. 生成代码

(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>
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐