mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1825 字
5 分钟
STM32-09:TIM定时器-PWM模式
2026-07-25
2026-09-24

在PWM方波中,通过调整高电平的占比,并以更快的速度切换高低电平,可以用数字信号模拟输出模拟信号。
本文中,将会使用PWM波控制LED小灯和电机作为示例。


输出比较模式#

在输出比较模式下,原有的捕获寄存器变为比较寄存器,用于输入的GPIO口也变为输出脉冲信号。此模式下,我们需要先向比较寄存器中写入一个数值,然后根据选择模式输出不同的信号:

  • 冻结模式(Frozen):输出通道的GPIO口会无视比较结果,保留旧的输出状态;(何意味)
  • 强制有效(Forced Active):输出通道的GPIO口会无视比较结果,持续输出高电平;(何意味x2)
  • 强制无效(Forced Inactive):输出通道的GPIO口会无视比较结果,持续输出低电平;(何意味x3)
  • 匹配时有效(Active Level on Match):仅当计数器值和比较寄存器值相等时,输出有效电平,但不会自动重设为无效电平;
  • 匹配时无效(Inactive Level on Match):仅当计数器值和比较寄存器值相等时,输出无效电平,但不会自动重设为有效电平;
  • 匹配时翻转(Toggle on Match):仅当计数器值和比较寄存器值相等时,翻转当前的输出电平。

要不直接跳过这段吧


PWM原理#

在PWM方波中,通过调整高电平的占比,并以非常快的速度切换高低电平,可以用数字信号模拟输出模拟信号。 例如,PWM波占空比为60%时,由于高低电平的切换非常迅速,在宏观上就可以认为输出了1.98V的电压。
此外,通过调整PWM波的占空比,频率等属性,还可以控制电机的转速,舵机的角度。


PWM模式#

在默认的向上计数模式下:

  • PWM模式1:当计数器值小于比较寄存器的值时,输出有效电平;当计数器的值大于或等于比较寄存器的值时,输出无效电平;
  • PWM模式2:与PWM模式1相反,当计数器值小于比较寄存器的值时,输出有效电平;当计数器的值大于或等于比较寄存器的值时,输出无效电平。

红石比较器
上述的有效电平和无效电平,在从比较寄存器发出之后,还会进入输出控制器,默认地,有效电平对应高电平,无效电平对应低电平,我们也可以配置输出控制器使其翻转,使有效电平对应低电平,无效电平对应高电平。相当于加了个非门

TIP

补充: 计数器除了向上计数模式之外,还有向下计数模式和中央对齐模式。
在向下计数模式中,则与向上计数模式相反:

  • PWM模式1:当计数器值大于比较寄存器的值时,输出无效电平;当计数器的值小于或等于比较寄存器的值时,输出有效电平;
  • PWM模式2:当计数器值大于比较寄存器的值时,输出有效电平;当计数器的值小于或等于比较寄存器的值时,输出无效电平。

中央对齐模式则是前半段向上计数模式,后半段向下计数模式。
这两种模式不常被使用,无需过多考虑。


芯片配置#

在CubeMX中,左侧Timers点开任意定时器,先勾选Internal Clock,在右侧的Channel1到Channel4中可以选择不同的输出模式。例如Output Compare CH1表示在CH1处开启输出比较模式,PWM Generater CH1则表示在CH1开启PWM波输出;
例如选择TIM3的通道,并启用CH1、CH2、CH3的PWM输出,可以看到右侧的PA6,PA7,PB0已经分别被启用为TIM3_CH1,TIM3_CH2,TIM3_CH3,我们在这三个GPIO口接入LED灯,即可利用PWM波做出呼吸灯的效果。
在底下的Configuration栏目,可以进行更多的设置,例如此处将Prescaler设置为72-1,将Counter Period设置为100-1,则每秒计数72M/72=1M次,每秒输出1000000/100=10000个PWM波。继续向下则是PWM的设置:

  • Mode:选择PWM模式1或者PWM模式2;
  • Pulse:比较寄存器初始的值;
  • Output compare preload:比较寄存器的影子寄存器机制,与自动重装载寄存器的影子寄存器机制相同;
  • Fast Mode:是否启用高速模式;
  • CH Polarity:极性翻转。

代码配置#

呼吸灯#

先启用CH1、CH2、CH3通道的PWM波输出:

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);

由上述的原理可知,PWM输出的占空比取决于比较寄存器,因此我们只需要修改比较寄存器中的值,即可修改输出PWM波的占空比,进而修改最终LED的亮度。
用于写入比较寄存器值的宏函数:

__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__);

其中:

  • __HANDLE__:要操作的定时器的句柄,即我们启用的&htim3;
  • __CHANNEL__:要操作的通道,例如TIM_CHANNEL_1和TIM_CHANNEL_4;
  • __COMPARE__:设置比较寄存器的值。

在死循环中写入对小灯的控制程序:

while (1)
{
for(int i=0;i<=100;i++){
if(i<=25){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -2*i+50);//B
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, -2*i+100);//G
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, 2*i+50);//R
}else if((i>25)&&(i<=50)){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 2*i-50);//B
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, -2*i+100);//G
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, -2*i+150);//R
}else if((i>50)&&(i<=75)){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 2*i-50);//B
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 2*i-100);//G
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, -2*i+150);//R
}else if((i>75)&&(i<=100)){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -2*i+250);//B
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 2*i-100);//G
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, 2*i-150);//R
}
HAL_Delay(20);
}
}

烧录程序后,可以看见小灯以一种十分炫酷的方式在闪烁。

控制电机#

电机的输入电流通常达到一个很高的值,与芯片输出信号的小电流相差甚远,因此要驱动电机,我们还需要一块电机驱动,他能够基于单片机发送的小电流的信号,向电机输出大电流,避免单片机直接输出大电流。这里以drv8833电机驱动为例。

drv8833电机驱动使用两个输入通道IN1和IN2,因此我们需要为其配置两个PWM输出通道,其余的事务交给现成的电机驱动即可。

配合下一篇文章的旋转编码器,我们可以制作一个通过旋钮控制速度,按下旋钮直接使速度归零并自然减速的电机。

先引入需要的驱动和宏定义:

main.c
#include "drv8833.h"
#include "oled.h"
#include <stdint.h>
#include <stdio.h>
#define SPEED_MAX 20
#define SPEED_MIN 0
#define SPEED_MID (SPEED_MAX+SPEED_MIN)/2
#define SPEED_INITIAL 10-1
#define BASE 32767
uint8_t speed=SPEED_INITIAL;

然后在main中写入:

main.c
HAL_Delay(100);
OLED_Init();
DRV8833_Init();
HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
__HAL_TIM_SET_COUNTER(&htim1, BASE+1);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
uint16_t counter=__HAL_TIM_GET_COUNTER(&htim1);
int16_t diff=(int16_t)(counter-BASE);
if(diff){
int8_t newspeed=speed+diff;
if(newspeed<0){
speed=SPEED_MIN;
}
else if(newspeed>20){
speed=SPEED_MAX;
}
else{
speed=newspeed;
}
if(speed>SPEED_MID){
DRV8833_Forward((speed-SPEED_MID)*10);
}
else if(speed<SPEED_MID){
DRV8833_Backward((SPEED_MID-speed)*10);
}
else if(speed==SPEED_MID){
DRV8833_Coast();
}
__HAL_TIM_SET_COUNTER(&htim1, BASE);
char message[20]="";
sprintf(message, "speed:%d",speed-SPEED_MID);
OLED_NewFrame();
OLED_PrintString(0, 0, message, &font16x16, OLED_COLOR_NORMAL);
OLED_ShowFrame();
}
}

在中断服务中,写入按下按钮使速度归零的程序:

stm32f1xx_it.c
#include "oled.h"
#include "drv8833.h"
#include <stdint.h>
#include <stdio.h>
#define SPEED_MAX 20
#define SPEED_MIN 0
#define SPEED_MID (SPEED_MAX+SPEED_MIN)/2
extern uint8_t speed;
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin)==GPIO_PIN_RESET){
speed=SPEED_MID;
DRV8833_Coast();
char message[20]="";
sprintf(message, "speed:%d",speed-SPEED_MID);
OLED_NewFrame();
OLED_PrintString(0, 0, message, &font16x16, OLED_COLOR_NORMAL);
OLED_ShowFrame();
}
}
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(KEY_Pin);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}

烧录程序后,旋转旋钮即可对电机速度实现控制,并控制其正转与反转,以及停止。


总结#

相关函数#

HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);//启用PWM波输出
__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__);//设置比较寄存器的值

PWM模式#

通过调整高电平的占比,实现用数字信号模拟输出模拟信号。

STM32-09:TIM定时器-PWM模式
https://nf3eb.github.io/posts/stm32/stm32-09/
作者
NF3eb
发布于
2026-07-25
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录