1. STM32定时器输出比较功能深度解析
在嵌入式系统开发中,定时器是最基础也最强大的外设之一。STM32的定时器功能丰富,其中输出比较(Output Compare)功能尤为重要,它是生成PWM波形的基础。本文将深入剖析STM32定时器的输出比较功能,从原理到实践,带你全面掌握这一关键技术。
1.1 定时器基础架构
STM32的通用定时器(TIM2-TIM5)包含多个功能模块,其中与输出比较相关的核心部件包括:
- 时基单元:由计数器(CNT)、预分频器(PSC)和自动重装载寄存器(ARR)组成,负责定时器的基本计时功能
- 捕获/比较通道:每个定时器通常有4个独立通道,每个通道有自己的捕获/比较寄存器(CCR)
- 输出控制电路:决定比较结果如何反映到输出引脚
这些模块协同工作,共同实现输出比较功能。理解这个架构是掌握后续内容的基础。
1.2 输出比较工作原理详解
输出比较功能的本质是通过硬件自动比较计数器值(CNT)和预设值(CCR),根据比较结果控制输出引脚电平。整个过程可以分为四个关键阶段:
1.2.1 时基单元运行
定时器启动后,计数器CNT在时钟驱动下开始运行。STM32支持三种基本计数模式:
- 向上计数模式:CNT从0递增至ARR,然后归零重新开始
- 向下计数模式:CNT从ARR递减至0,然后重新加载ARR
- 中央对齐模式:CNT先递增至ARR,再递减至0,循环往复
在PWM应用中,向上计数模式最为常用。ARR的值决定了计数周期,也就是PWM波形的频率。
1.2.2 比较单元判定
在CNT运行过程中,硬件比较器实时比较CNT与CCR的值,产生三种可能的比较结果:
- CNT < CCR
- CNT = CCR
- CNT > CCR
这个比较结果是生成PWM信号的原始依据。CCR的值决定了电平翻转的时间点,也就是PWM波形的占空比。
1.2.3 输出模式控制
比较结果需要经过输出模式控制器的处理,根据配置生成中间参考信号OCxREF。STM32提供了8种输出模式,通过TIMx_CCMRx寄存器中的OCxM位配置:
| 模式编码 | 模式名称 | 功能描述 |
|---|---|---|
| 000 | 冻结 | 比较匹配时不改变输出 |
| 001 | 匹配时置有效电平 | CNT=CCR时强制OCxREF为高 |
| 010 | 匹配时置无效电平 | CNT=CCR时强制OCxREF为低 |
| 011 | 匹配时翻转 | CNT=CCR时翻转OCxREF电平 |
| 100 | 强制为无效电平 | 强制OCxREF为低 |
| 101 | 强制为有效电平 | 强制OCxREF为高 |
| 110 | PWM模式1 | 向上计数时CNT<CCR输出高,CNT≥CCR输出低 |
| 111 | PWM模式2 | 向上计数时CNT<CCR输出低,CNT≥CCR输出高 |
其中PWM模式1和PWM模式2是最常用的两种模式,它们产生的波形极性相反。
1.2.4 输出控制电路
OCxREF信号最后经过输出控制电路,这个阶段主要完成两项功能:
-
极性选择:通过TIMx_CCER寄存器的CCxP位控制输出极性
- CCxP=0:OCxREF高对应引脚高电平
- CCxP=1:OCxREF高对应引脚低电平
-
输出使能:通过TIMx_CCER寄存器的CCxE位控制输出开关
- CCxE=1:开启输出
- CCxE=0:关闭输出
1.3 关键寄存器详解
配置输出比较功能需要操作多个寄存器,以下是核心寄存器及其功能:
| 寄存器 | 功能描述 |
|---|---|
| TIMx_CR1 | 控制寄存器1,包含定时器使能、计数方向等全局控制位 |
| TIMx_PSC | 预分频器,决定计数器的时钟频率 |
| TIMx_ARR | 自动重装载值,决定计数周期和PWM频率 |
| TIMx_CCRx | 捕获/比较寄存器,决定PWM占空比 |
| TIMx_CCMRx | 捕获/比较模式寄存器,配置输出比较模式和预装载使能 |
| TIMx_CCER | 捕获/比较使能寄存器,控制输出极性和通道使能 |
| TIMx_EGR | 事件生成寄存器,可手动产生更新事件 |
| TIMx_SR | 状态寄存器,包含各种事件和中断的标志位 |
| TIMx_DIER | DMA/中断使能寄存器,控制各种事件的中断使能 |
理解这些寄存器的功能对于正确配置定时器至关重要。
2. PWM技术原理与应用
2.1 PWM基本概念
PWM(Pulse Width Modulation,脉冲宽度调制)是一种用数字信号模拟模拟量的技术。其核心思想是通过调节脉冲的宽度(占空比)来等效不同的电压值。
PWM的三个关键参数:
- 频率:每秒完成的周期数,单位Hz
- 占空比:高电平时间占整个周期的百分比
- 分辨率:占空比可调节的最小步长
在STM32中,PWM频率由ARR和PSC决定,占空比由CCR决定,分辨率则由ARR决定。
2.2 PWM参数计算
2.2.1 频率计算
PWM频率公式:
[ f_{PWM} = \frac{f_{CK_PSC}}{(PSC+1) \times (ARR+1)} ]
其中:
- ( f_{CK_PSC} ):定时器时钟频率(如72MHz)
- PSC:预分频器值
- ARR:自动重装载值
2.2.2 占空比计算
占空比公式:
[ Duty = \frac{CCR}{ARR+1} \times 100% ]
2.2.3 分辨率计算
分辨率公式:
[ Resolution = \frac{1}{ARR+1} ]
2.2.4 配置实例
假设我们需要在72MHz系统时钟下产生1kHz、分辨率1%的PWM:
-
确定ARR:
分辨率1% → ARR+1=100 → ARR=99 -
计算PSC:
( 1000 = \frac{72,000,000}{(PSC+1) \times 100} ) → PSC=719 -
初始CCR:
50%占空比 → CCR=50
2.3 PWM在嵌入式系统中的应用
PWM技术广泛应用于各种嵌入式场景:
- LED调光:通过调节占空比控制亮度
- 电机控制:调节直流电机转速或舵机角度
- 电源管理:开关电源的电压调节
- 音频生成:配合滤波器产生模拟音频信号
这些应用都利用了PWM的数字模拟转换能力,结合系统的"惯性"(如人眼的视觉暂留、电机的机械惯性等)实现平滑的模拟效果。
3. STM32 PWM输出配置实战
3.1 硬件设计考虑
在使用STM32输出PWM时,需要考虑以下硬件因素:
- 引脚分配:确认定时器通道对应的GPIO引脚
- 引脚复用:正确配置GPIO为复用功能
- 重映射需求:必要时使用引脚重映射功能
- 外设冲突:特别是调试接口与GPIO的冲突
3.2 软件配置步骤
配置PWM输出的标准流程如下:
- 开启时钟:使能定时器和GPIO时钟
- GPIO配置:设置为复用推挽输出
- 时基单元配置:设置PSC和ARR
- 输出比较配置:设置PWM模式和其他参数
- 启动定时器:使能计数器
3.2.1 基础配置代码示例
c复制// 1. 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // TIM2_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 时基单元配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 99; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 719; // PSC
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 输出比较配置
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 50; // 初始CCR
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 启动定时器
TIM_Cmd(TIM2, ENABLE);
3.3 动态调整占空比
在实际应用中,经常需要动态调整PWM的占空比。STM32提供了直接修改CCR的函数:
c复制void PWM_SetDuty(TIM_TypeDef* TIMx, uint32_t Channel, uint16_t Duty) {
switch(Channel) {
case TIM_Channel_1:
TIM_SetCompare1(TIMx, Duty);
break;
case TIM_Channel_2:
TIM_SetCompare2(TIMx, Duty);
break;
// 其他通道类似
}
}
使用示例(实现呼吸灯效果):
c复制while(1) {
// 渐亮
for(int i=0; i<=100; i++) {
PWM_SetDuty(TIM2, TIM_Channel_1, i);
Delay_ms(10);
}
// 渐暗
for(int i=0; i<=100; i++) {
PWM_SetDuty(TIM2, TIM_Channel_1, 100-i);
Delay_ms(10);
}
}
3.4 预装载与影子寄存器机制
STM32的定时器寄存器大多采用"预装载"机制,即用户写入的值不会立即生效,而是先存入预装载寄存器,待更新事件(UEV)发生时才会传输到影子寄存器(实际工作的寄存器)。
这种机制的好处是:
- 避免在计数过程中修改参数导致波形异常
- 确保参数修改的原子性
- 提高波形生成的稳定性
在PWM应用中,通常需要使能CCR和ARR的预装载功能:
c复制TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // CCR预装载
TIM_ARRPreloadConfig(TIM2, ENABLE); // ARR预装载
4. 高级应用与问题排查
4.1 引脚重映射技术
当默认引脚被占用时,可以使用STM32的引脚重映射功能。以TIM2_CH1从PA0重映射到PA15为例:
- 开启AFIO时钟
- 配置重映射
- 处理调试接口冲突(PA15默认是JTDI)
- 配置GPIO
- 其他配置与常规相同
关键代码:
c复制// 1. 开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 2. 配置重映射
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
// 3. 释放PA15从JTAG
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
// 4. 配置GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; // PA15
// ...其他GPIO配置...
重要提示:禁用JTAG时要保留SWD功能,否则将无法通过ST-Link调试。绝对不要使用GPIO_Remap_SWJ_Disable,这会导致芯片无法再通过调试接口编程。
4.2 多通道同步输出
某些应用需要多个PWM通道同步输出,STM32提供了多种同步机制:
- 主从模式:一个定时器作为主设备触发其他定时器
- 定时器级联:一个定时器的输出作为另一个定时器的输入
- 同步更新:使用TIM_EGR寄存器的UG位同时更新多个定时器
配置示例(同步更新CCR1和CCR2):
c复制// 禁止预装载
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
// 修改CCR
TIM_SetCompare1(TIM2, newCCR1);
TIM_SetCompare2(TIM2, newCCR2);
// 手动产生更新事件
TIM_GenerateEvent(TIM2, TIM_EventSource_Update);
4.3 常见问题与解决方案
4.3.1 无PWM输出
排查步骤:
- 确认定时器和GPIO时钟已开启
- 检查GPIO模式是否正确(必须为复用推挽输出)
- 验证定时器是否已使能
- 检查输出比较通道是否使能
- 确认没有其他外设占用该引脚
4.3.2 PWM频率不正确
可能原因:
- PSC或ARR计算错误
- 定时器时钟源频率不正确
- 预分频器未正确加载(可手动产生更新事件)
4.3.3 占空比调节不灵敏
解决方案:
- 增加ARR值提高分辨率
- 检查CCR写入时机,确保在合适的时间更新
- 使用DMA自动更新CCR值
4.3.4 波形抖动或毛刺
处理方法:
- 启用预装载功能
- 优化中断优先级,避免PWM相关中断被延迟
- 检查电源稳定性
- 增加适当的滤波电路
5. 典型应用案例
5.1 LED呼吸灯实现
呼吸灯是PWM最典型的应用之一,完整实现步骤如下:
- 配置PWM输出(频率建议100Hz-1kHz)
- 编写占空比调节函数
- 设计呼吸效果算法(线性或非线性变化)
- 添加适当的延时控制变化速度
进阶技巧:
- 使用查表法实现非线性亮度变化(符合人眼感知)
- 采用DMA自动更新CCR,减少CPU开销
- 结合中断实现复杂灯光效果
5.2 舵机控制
SG90舵机控制要点:
- PWM频率必须为50Hz(周期20ms)
- 脉宽范围0.5ms-2.5ms对应0°-180°
- 需要较高的时间精度(±0.5us以内)
配置示例:
c复制// 时基配置(72MHz主频)
TIM_TimeBaseStructure.TIM_Period = 19999; // ARR
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC
// 分辨率:72MHz/(71+1)/(19999+1) = 50Hz
// 角度设置函数
void Servo_SetAngle(TIM_TypeDef* TIMx, uint32_t Channel, float angle) {
uint16_t pulse = 500 + angle * (2500-500)/180.0;
switch(Channel) {
case TIM_Channel_1: TIM_SetCompare1(TIMx, pulse); break;
// 其他通道类似
}
}
5.3 直流电机控制
直流电机控制方案:
- 单路PWM:控制速度,方向由额外IO控制
- 双路PWM(H桥):一路PWM控制速度,另一路控制方向
- 专用驱动芯片(如TB6612):简化电路设计
使用TB6612的示例:
c复制// 初始化
void Motor_Init(void) {
// PWM引脚配置
// 方向控制引脚配置
// TB6612的STBY引脚置高
}
// 电机控制
void Motor_SetSpeed(int8_t speed) {
if(speed >= 0) {
DIR_PIN = 0; // 正转
PWM_SetDuty(MOTOR_TIM, MOTOR_CH, speed);
} else {
DIR_PIN = 1; // 反转
PWM_SetDuty(MOTOR_TIM, MOTOR_CH, -speed);
}
}
6. 性能优化技巧
6.1 减少CPU开销
- 使用DMA自动更新CCR
- 合理设计中断服务程序
- 利用定时器的从模式减少软件干预
- 使用硬件触发自动生成复杂波形
6.2 提高PWM质量
- 优化时钟树配置,选择干净的时钟源
- 适当增加死区时间(互补PWM)
- 使用定时器的突发模式生成特殊波形
- 合理布局PCB,减少信号干扰
6.3 扩展应用思路
- 结合输入捕获实现频率测量
- 使用多个定时器协同工作
- 开发基于PWM的简易DAC
- 实现数字电源控制算法
通过本文的全面介绍,相信你已经对STM32的定时器输出比较功能有了深入理解。从基础原理到高级应用,从寄存器配置到实际问题解决,这些知识将帮助你在嵌入式开发中更好地利用PWM技术。记住,实践是掌握这些知识的关键,建议通过实际项目来巩固所学内容。