2. STM32定时器编码器模式:从原理到寄存器配置
编码器模式,说白了就是让定时器自己读懂正交编码信号。你不需要在中断里数脉冲,硬件全帮你干了。我第一次用这个功能时,还傻傻地在EXTI中断里计数,后来发现定时器自带这个能力,真是相见恨晚。
2.1 正交编码信号基础
先说说什么是正交编码信号。电机编码器通常输出两路方波:TI1和TI2。这两路信号相位差90度,所以叫正交。
- TI1(Channel 1):A相信号,通常接定时器的CH1输入
- TI2(Channel 2):B相信号,通常接定时器的CH2输入
为什么用两路?因为单路只能测速度,不能判方向。两路配合,谁先跳变,谁后跳变,方向就出来了。
核心概念:编码器模式本质上是把TI1和TI2的边沿事件当作计数时钟。每检测到一个有效边沿,计数器就加1或减1。方向由两路信号的相位关系决定。
2.2 TI1、TI2计数逻辑详解
STM32的编码器模式支持三种计数方式,我习惯叫它们:
- 模式1:只在TI1边沿计数(TI2判方向)
- 模式2:只在TI2边沿计数(TI1判方向)
- 模式3:TI1和TI2边沿都计数(四倍频)
你想想看,模式3相当于把编码器分辨率翻了4倍。比如编码器本身是1000线,模式3下就是4000个计数/圈。我在做AGV小车时用过这个模式,低速下也能读到稳定的速度值。
具体计数逻辑是这样的:
| TI1电平 | TI2电平 | TI1上升沿 | TI1下降沿 | TI2上升沿 | TI2下降沿 |
|---|---|---|---|---|---|
| 高 | 低 | +1 | -1 | -1 | +1 |
| 高 | 高 | -1 | +1 | +1 | -1 |
| 低 | 高 | +1 | -1 | -1 | +1 |
| 低 | 低 | -1 | +1 | +1 | -1 |
这张表看着复杂,其实规律很简单:TI1和TI2电平不同时,TI1边沿使计数器加1;电平相同时,TI1边沿使计数器减1。TI2的边沿逻辑正好相反。
2.3 计数方向判定
方向判定是编码器模式的核心。定时器内部有个DIR位(在TIMx_CR1寄存器里),硬件自动更新这个位。
- DIR = 0:正转(计数器递增)
- DIR = 1:反转(计数器递减)
我曾经踩过一个坑:电机正转时计数器递增,但反转时计数器递减到0后会溢出到ARR值。如果你用uint16_t读计数器,方向判断就会出错。后来我改用int16_t来读,负值直接表示反转,省事多了。
经验之谈:读取计数器值时,建议用(int16_t)TIMx->CNT强制转换为有符号数。这样正反转一目了然,不用额外判断DIR位。
2.4 配置寄存器详解
配置编码器模式,主要操作三个寄存器:TIMx_SMCR、TIMx_CCMR1和TIMx_CCER。我按配置顺序来讲。
2.4.1 TIMx_SMCR(从模式控制寄存器)
这个寄存器决定定时器的工作模式。编码器模式属于从模式的一种。
// 编码器模式配置(模式3:TI1和TI2都计数)
TIMx->SMCR &= ~TIM_SMCR_SMS; // 先清零
TIMx->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1; // SMS = 011,模式3
SMS[2:0]位取值:
| SMS值 | 模式 | 计数边沿 |
|---|---|---|
| 001 | 模式1 | 仅TI1 |
| 010 | 模式2 | 仅TI2 |
| 011 | 模式3 | TI1和TI2 |
我个人习惯用模式3,分辨率最高。但要注意,模式3下计数器更新频率是编码器线数的4倍,如果你的电机转速很高,要确保定时器不会溢出。
2.4.2 TIMx_CCMR1(捕获/比较模式寄存器1)
这个寄存器配置TI1和TI2的输入滤波和映射。
// 配置TI1为输入,映射到IC1
TIMx->CCMR1 &= ~TIM_CCMR1_CC1S;
TIMx->CCMR1 |= TIM_CCMR1_CC1S_0; // CC1S = 01,TI1映射到IC1
// 配置TI2为输入,映射到IC2
TIMx->CCMR1 &= ~TIM_CCMR1_CC2S;
TIMx->CCMR1 |= TIM_CCMR1_CC2S_0; // CC2S = 01,TI2映射到IC2
这里有个细节:IC1F[3:0]和IC2F[3:0]位用于输入滤波。如果编码器信号有毛刺,可以设置滤波长度。
注意:滤波值越大,抗干扰能力越强,但会引入延迟。高速电机建议用较小的滤波值(如ICF=0或1),低速电机可以适当加大(ICF=4~8)。我在做伺服驱动时,电机转速3000rpm,滤波设到4就够用了。
2.4.3 TIMx_CCER(捕获/使能寄存器)
这个寄存器使能输入通道,并配置极性。
// 使能CC1和CC2,极性不反转
TIMx->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P); // 不反转
TIMx->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E; // 使能
如果编码器A/B相反了,不用改硬件,把CC1P或CC2P置1就行。这个技巧我在现场调试时用过很多次。
2.5 完整配置流程
嗯,这里要注意配置顺序。我总结了一个标准流程:
- 使能定时器时钟:RCC->APB1ENR或APB2ENR
- 配置GPIO:TI1和TI2对应的引脚设为复用功能
- 配置CCMR1:设置输入映射和滤波
- 配置CCER:使能通道,设置极性
- 配置SMCR:选择编码器模式
- 配置ARR和PSC:设置计数上限和预分频
- 使能计数器:CR1的CEN位置1
下面是一个完整的初始化代码示例:
void Encoder_Init(TIM_TypeDef* TIMx) {
// 1. 使能时钟(以TIM3为例)
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// 2. GPIO配置(PA6=TI1, PA7=TI2)
GPIOA->MODER &= ~(GPIO_MODER_MODER6 | GPIO_MODER_MODER7);
GPIOA->MODER |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1; // 复用功能
// 3. 配置CCMR1
TIMx->CCMR1 &= ~(TIM_CCMR1_CC1S | TIM_CCMR1_CC2S);
TIMx->CCMR1 |= TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_0; // TI1->IC1, TI2->IC2
// 4. 配置CCER
TIMx->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P);
TIMx->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E;
// 5. 配置SMCR(模式3)
TIMx->SMCR &= ~TIM_SMCR_SMS;
TIMx->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1;
// 6. 配置ARR(假设编码器1000线,模式3下4000计数/圈)
TIMx->ARR = 4000 - 1;
TIMx->PSC = 0; // 不分频
// 7. 使能计数器
TIMx->CR1 |= TIM_CR1_CEN;
}
2.6 知识结构图
下面这张图展示了编码器模式的核心知识体系,从信号输入到寄存器配置再到计数逻辑:
2.7 避坑指南
最后分享几个我实际项目中遇到的坑:
- 计数器溢出问题:编码器模式没有自动重装载中断,计数器从ARR减到0或从0加到ARR时,不会产生更新事件。你需要用定时器的更新中断来辅助处理。
- 初始值不确定:上电时计数器值不确定。我习惯在初始化后读一次CNT作为零点,后续值都减去这个零点。
- GPIO速度要匹配:编码器信号频率高时,GPIO速度要设到High或Very High。我曾经用Low速度,结果丢了不少脉冲。
调试技巧:用逻辑分析仪同时抓TI1、TI2和CNT值(通过DAC或串口输出),可以直观看到计数逻辑是否正确。这个办法帮我解决过好几个疑难问题。