stm32 定时器输出比较(OC)与PWM的理解和应用
2025-01-06 来源:elecfans
1. 定时器TIM简介
TIM 是 stm32 微控制器中的定时器模块。stm32 包含多个定时器模块,每个定时器模块有不同的功能和配置,适用于各种应用场景。
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从模式等多种功能
定时器分类:

image.png
基本定时器结构(时基单元):

image.png
2. TIM 定时器相关概念
计数模式
向上计数模式(Up-counter mode)
向下计数模式(Down-counter mode)
中心对齐模式(Center-aligned mode)
预分频器(Prescaler)
主要作用是控制(降低)计数器时钟频率。
计数器时钟频率 = 定时器输入时钟频率(CK_PSC) / (预分频值 + 1) = CK_PSC / (PSC + 1)
通常 CK_PSC 为内部时钟CK_INT,即为72MHz
自动重装寄存器(ARR)
设置计数器的周期值。当计数器达到该值时,会产生一个更新时间(中断或 DMA 请求),计数器重新从 0 开始计数。输出比较(Output Compare, OC)
定时器的输出比较功能可以用来产生精准的输出信号。通过设置比较寄存器(CCR),可以控制输出引脚的状态。输入捕获(Input Capture)
定时器的输入捕获功能可以测量外部信号的周期和脉宽。PWM 模式(Pulse Width Modulation)
定时器 PWM 模式可以产生占空比可调的 PWM 信号,常用于电机控制、LED调光等。
3. 输出比较(Output Compare, OC)
框图如下:
image.png
输出比较可以通过比较 CNT 与 CCR 寄存器值的关系,来对输出电平进行置1、置0或翻转等操作,用于输出一定频率和占空比的 PWM 波形;
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
输出比较产生的信号模式:

image.png
4. PWM
PWM: Pulse Width Modulation 脉冲宽度调制。在具有惯性的系统中,可以通过一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常用于电机控速等领域。
频率:PWM 信号的周期倒数。
f = 1 / Ts = CK_PSC / (PSC + 1) / (ARR + 1)
占空比:高电平时间(Ton)与周期时间(Ts)的比值,通常用百分比表示。
占空比 = Ton / Ts = Ton / (ARR + 1) = CCR / (ARR + 1)
占空比决定了输出信号在一个周期内为高电平的时间比例
分辨率:分辨率是占空比的最小变化步距。分辨率通常由定时器的计数位数(如8位、16位等)决定,影响 PWM 信号的精细度。
分辨率 = 1 / (计数器最大值 + 1) = 1 / (ARR + 1)
见图:

image.png
PWM 基本结构图,也是作为后续编程的参考图:

image.png
5. 应用举例
5.1 使用 PWM 驱动 LED 呼吸灯
这个例子主要通过 PWM 实现 LED 呼吸灯的效果。
基本思想:通过 TIM2 定时器,输出 PWM 脉宽给到 LED ,LED 呈现呼吸闪烁的过程。
实现思维逻辑:
根据 PWM 基本结构图,基本步骤为:
初始化时基单元
配置 OC
由于需要控制一个 LED 灯,所以需要配置一个 GPIO 用于输出 PWM 的信号
运行控制逻辑
硬件电路:
由于使用的是 TIM2 定时器的通道1即 TIM2_CH1_ETR 输出信号,而 TIM2_CH1_ETR 输出复用的是 PA0 口,如图:

image.png
PA0 口插上 LED 的正极,负极接在 GND 上。
代码:
PWM.c
void PWM_Init(void){
// 初始化时基单元
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 在 APB1 总线
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; // 时基单元结构体
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式
TIM_TimeBaseStruct.TIM_Period = 100 - 1; // 计数周期 ARR
TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1 ; // 预分频器 PSC
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0; // 重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// OC 配置 定时器通道配置
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // OC 输出模式 见图7
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性 见图7 REF 置有效电平
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // 输出使能状态
TIM_OCInitStruct.TIM_Pulse = 0; // CCR 比较寄存器 0 ~ 100
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
// GPIO 配置
// 这里需要复用 PA0 口,所以 AFIO 一并启用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 开启 TIM2
TIM_Cmd(TIM2, ENABLE);}void SetCompare1(uint16_t Compare1){
TIM_SetCompare1(TIM2, Compare1);}
main.c:
int main() {
PWM_Init();
int8_t i = 0;
while(1) {
for(i = 0; i <= 100; i++) {
SetCompare1(i);
Delay_ms(10);
}
for(i = 100; i >= 0; i--) {
SetCompare1(i);
Delay_ms(10);
}
}}5.1 使用 PWM 驱动 舵机
PWM 驱动舵机与 PWM 驱动 LED 呼吸灯整个流程基本一致,不同的是要根据舵机的特性来控制 PWM 输出的脉宽信号。
舵机的特性:

image.png
舵机预分频器计算:
预分频器:72,TIM_CLK = 72MHz / 72 = 1MHz
定时周期: TIM_Period = PWM周期 * TIM_CLCK频率 - 1
其中 PWM 周期为 20ms, 定时器时钟频率为 1MHz,因此 TIM_Period = 20ms * 1MHZ - 1 = 20000 - 1
角度到脉宽的线性变换公式:
[图片上传失败...(image-254-1717677170131)]
代码:
大部分的代码都与 LED 呼吸灯类似,就是几个参数有所改变:
void PWM_Init(void)
{
//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = 20000 - 1;
TIM_TimeBaseStruct.TIM_Prescaler = 72 - 1;
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// OC
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
TIM_Cmd(TIM2, ENABLE);
}
void Servo_Angle(float angle)
{
TIM_SetCompare1(TIM2, angle / 180.0 * 2000 + 500);
}
main.c
int main() {
OLED_Init();
PWM_Init();
Key_Init();
float angle = 0;
Servo_Angle(0);
char str[100] = {0};
int8_t is_rt = 0;
while(1) {
if(Key_Read() ==1) {
if(is_rt == 1) angle -= 20;
else angle += 20;
}
Servo_Angle(angle);
sprintf(str, 'angle: %d ', (int)angle);
OLED_ShowString(1, 1, str);
if(angle == 180) is_rt = 1;
if(angle == 0) is_rt = 0;
}}
上一篇:STM32识别肌电信号
- 意法半导体中国本地造STM32微控制器启动规模量产
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 基于机智云与STM32的智能拐杖安全监测系统在养老物联网中的应用
- 内置全栈安全,一站式满足CRA法案与IEC 62443标准——米尔STM32MP257核心板
- 如何用 STM32 FLASH 实现等效 100 万次擦写的 EEPROM 功能?
- 实战解析:通过一个小项目掌握STM32所有外设
- STM32学了两年半,却还是不会做项目
- 意法半导体推出最新STM32MP21微处理器,兼具高性价比、低功耗、高灵活性
- 基于STM32的矿井作业环境监测系统设计与实现
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




