1. STM32编码器模式基础概念
正交编码器是电机控制系统中常见的传感器,它通过输出两路相位差90度的方波信号(A相和B相)来反映电机的转动状态。STM32的定时器模块内置了专门的编码器接口模式,可以自动处理这两路信号,实现位置和速度的精确测量。
编码器接口模式本质上是一种特殊的输入捕获功能,它能够根据A、B两相信号的边沿变化自动增减计数器值。当电机正转时,计数器递增;反转时则递减。这种硬件级的处理方式相比软件中断计数更加高效可靠,不会因为CPU处理延迟丢失脉冲。
在实际项目中,我常用TIM2/TIM3/TIM4这些通用定时器来实现编码器接口。配置时需要注意:
- 每个定时器有独立的计数器,选择时需考虑测量范围
- 编码器模式支持1倍频、2倍频和4倍频计数
- 通过SMCR寄存器的SMS位选择工作模式
2. 硬件连接与GPIO配置
正确的硬件连接是编码器测量的基础。以常见的霍尔编码器为例,通常需要将:
- A相信号连接到定时器的CH1通道(如TIM3_CH1对应PB4)
- B相信号连接到CH2通道(如TIM3_CH2对应PB5)
- 编码器供电电压一般为3.3V或5V
GPIO配置示例(寄存器版):
// 使能GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // PB4配置为浮空输入(TIM3_CH1) GPIOB->CRL &= ~(0xF << 16); GPIOB->CRL |= (0x04 << 16); // PB5配置为浮空输入(TIM3_CH2) GPIOB->CRL &= ~(0xF << 20); GPIOB->CRL |= (0x04 << 20);如果使用HAL库,配置会更简洁:
GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);3. 定时器编码器模式配置
编码器模式的核心配置涉及三个关键寄存器:
3.1 SMCR寄存器配置
SMCR寄存器的SMS[2:0]位决定编码器模式:
- 0x01:仅在TI2边沿计数
- 0x02:仅在TI1边沿计数
- 0x03:在TI1和TI2边沿都计数(推荐)
3.2 CCMR寄存器配置
需要设置输入捕获滤波器和输入映射:
// 输入捕获1映射到TI1,无滤波 TIM3->CCMR1 |= TIM_CCMR1_CC1S_0; TIM3->CCMR1 &= ~TIM_CCMR1_IC1F; // 输入捕获2映射到TI2,无滤波 TIM3->CCMR1 |= TIM_CCMR1_CC2S_0; TIM3->CCMR1 &= ~TIM_CCMR1_IC2F;3.3 完整初始化代码
void Encoder_Init(TIM_TypeDef* TIMx) { // 时基配置 TIMx->PSC = 0; // 无分频 TIMx->ARR = 0xFFFF; // 16位最大值 // 编码器接口模式3(双边沿计数) TIMx->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1; // 输入捕获配置 TIMx->CCMR1 |= TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_0; // 使能捕获 TIMx->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P); TIMx->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E; // 使能定时器 TIMx->CR1 |= TIM_CR1_CEN; }4. 转速测量算法实现
4.1 M法测速原理
M法(频率法)通过统计固定时间内的脉冲数计算转速,公式为:
转速(rpm) = (脉冲数 × 60) / (编码器线数 × 倍频数 × 采样时间)其中:
- 编码器线数指电机转一圈产生的物理脉冲数
- 倍频数取决于编码器模式(模式3为4倍频)
- 采样时间根据需求设定,通常10-100ms
4.2 代码实现
#define ENCODER_LINES 11 // 编码器线数 #define GEAR_RATIO 34 // 减速比 #define SAMPLE_TIME 0.1f // 100ms采样时间 int32_t Get_Motor_Speed(TIM_TypeDef* TIMx) { static int32_t last_count = 0; int32_t current_count = TIMx->CNT; int32_t delta = current_count - last_count; last_count = current_count; // 计算转速(rpm) float rpm = (delta * 60.0f) / (ENCODER_LINES * GEAR_RATIO * 4 * SAMPLE_TIME); return (int32_t)rpm; }4.3 方向判断技巧
通过CR1寄存器的DIR位可以获取当前计数方向:
if(TIMx->CR1 & TIM_CR1_DIR) { // 反向旋转 } else { // 正向旋转 }5. 抗干扰设计与优化
5.1 硬件滤波设计
在编码器信号线上添加RC滤波电路(如100Ω电阻+100nF电容),可有效抑制高频干扰。STM32内部也支持数字滤波,通过CCMR寄存器的ICxF位配置:
// 设置4个时钟周期的滤波 TIM3->CCMR1 |= (0x02 << 4); // IC1F TIM3->CCMR1 |= (0x02 << 12); // IC2F5.2 软件去抖处理
对于偶尔出现的异常脉冲,可以采用中值滤波算法:
#define FILTER_SIZE 5 int32_t Median_Filter(int32_t new_val) { static int32_t buffer[FILTER_SIZE] = {0}; static uint8_t index = 0; buffer[index++] = new_val; if(index >= FILTER_SIZE) index = 0; // 排序取中值 int32_t temp[FILTER_SIZE]; memcpy(temp, buffer, sizeof(temp)); bubble_sort(temp, FILTER_SIZE); // 实现排序算法 return temp[FILTER_SIZE/2]; }5.3 计数器溢出处理
当转速较高时,计数器可能溢出。解决方案:
- 使用32位变量扩展计数范围
- 启用定时器更新中断,在中断中记录溢出次数
volatile int32_t overflow_count = 0; void TIM3_IRQHandler(void) { if(TIM3->SR & TIM_SR_UIF) { if(TIM3->CR1 & TIM_CR1_DIR) { overflow_count--; } else { overflow_count++; } TIM3->SR = ~TIM_SR_UIF; } }6. 实际应用案例分析
6.1 直流有刷电机控制
在机器人关节控制中,编码器反馈构成闭环系统:
[PID控制器] -> [PWM驱动] -> [电机] -> [编码器] -> [速度计算]实测发现,采用4倍频模式时,11线编码器配合34:1减速电机,100ms采样周期下分辨率可达0.5RPM。
6.2 步进电机闭环控制
传统步进电机开环运行易丢步,加入编码器后可以实现:
- 实时监测实际位置
- 丢步检测与补偿
- 动态调整驱动电流
一个实用的技巧是将编码器Z相信号接外部中断,用作零位校准。
7. 常见问题排查
计数器不变化
- 检查GPIO配置是否正确
- 确认编码器供电正常
- 用示波器观察信号波形
方向判断错误
- 交换A、B相接线
- 检查CCER寄存器的CCxP极性设置
转速波动大
- 增加软件滤波
- 检查机械连接是否松动
- 适当延长采样时间
高速测量不准确
- 降低定时器预分频
- 改用更高线数编码器
- 启用溢出中断处理
记得在调试时先使用最简单的配置,逐步添加功能。我曾在项目初期因同时开启太多功能而难以定位问题,最后发现是滤波参数设置过大导致信号延迟。