1. 编码器基础:从分类到工作原理
第一次接触编码器是在大四的智能车竞赛上,当时为了精确控制小车的电机转速,不得不硬着头皮研究这个"会输出脉冲的小玩意儿"。现在回想起来,编码器确实是运动控制系统中不可或缺的"眼睛"。
编码器本质上是一种将机械运动转换为电信号的传感器。根据不同的分类标准,编码器可以分为多种类型:
- 按工作原理:增量式、绝对式、霍尔式
- 按检测方式:光电式、磁电式
- 按运动形式:旋转式、直线式
1.1 增量式编码器:工业界的"脉搏计数器"
增量式编码器就像是一个精密的脉搏计数器。它通过输出连续的脉冲信号来反映位移变化,每转过一个固定角度就会输出一个脉冲。我实验室里用的逐飞科技编码器就是典型的增量式,有A、B、Z三相信号:
- A、B相:相位差90°的方波,用于计数和判断方向
- Z相:每转一圈输出一个脉冲,作为零位参考
实际使用中发现,这种编码器的优势是结构简单、成本低,但断电后需要重新找零位。在智能车项目中,我们就是通过Z相信号来校准初始位置的。
1.2 绝对式编码器:自带"记忆功能"的高端货
绝对式编码器更像是自带GPS定位的装置。它的码盘上刻有独特的二进制编码,每个位置都有唯一的数字编码。这就好比摩斯电码,不同的排列组合对应不同的信息。
我拆解过一个多圈绝对编码器,内部采用齿轮组结构记录圈数。这种编码器最大的优点就是断电后位置信息不会丢失,在工业机械臂上应用广泛。不过价格也确实"美丽",一个高精度的绝对编码器能顶我一个月工资。
1.3 霍尔编码器:经济实用的选择
霍尔编码器利用霍尔效应检测磁场变化,输出两路正交信号。相比前两种,它的结构更简单、抗干扰能力更强。我在淘宝上买的20元一个的霍尔编码器,用在直流减速电机上效果就很不错。
实测发现,霍尔编码器虽然分辨率较低(常见16-64PPR),但对于一般的速度控制已经足够。而且它不怕灰尘油污,在恶劣环境下也能稳定工作,这点在工业现场特别重要。
2. 编码器测速原理深度剖析
2.1 脉冲计数:编码器测速的核心
编码器测速的本质就是统计脉冲数量。想象一下高速公路的车辆计数器:通过统计单位时间内经过的车辆数,就能估算出车流量。编码器测速也是这个道理,只不过"车辆"变成了电脉冲。
在实际项目中,我常用三种测速方法:
- M法(频率法):固定时间内统计脉冲数
- T法(周期法):测量相邻脉冲的时间间隔
- M/T法:结合前两种的优势
2.2 四倍频技术:精度提升的秘诀
刚开始做项目时,我发现编码器的实际分辨率比标称值高很多。后来才知道这是用了四倍频技术——通过检测A、B相的上升沿和下降沿,将分辨率提高4倍。
以常见的100PPR编码器为例:
- 原始分辨率:100脉冲/转
- 四倍频后:400脉冲/转
这个技巧在低速测量时特别有用。我曾经用STM32的编码器模式实现了四倍频,代码配置后面会详细介绍。
2.3 方向判断:AB相的秘密
AB相信号的相位差不仅用于倍频,还能判断旋转方向。这里有个简单规律:
- 正转时:A相上升沿对应B相低电平
- 反转时:A相上升沿对应B相高电平
在STM32中,通过读取计数器的计数方向(CR1寄存器的DIR位)就能知道电机转向,非常方便。
3. STM32硬件配置实战
3.1 定时器编码器模式配置
STM32的定时器外设内置了编码器接口,配置起来比用外部中断方便多了。以TIM3为例,最关键的几个配置步骤:
- GPIO初始化:配置PA6、PA7为浮空输入
- 时基设置:ARR设为最大值,PSC为0
- 编码器模式:选择TI1和TI2边沿计数
- 滤波器设置:根据信号质量调整ICF值
void Encoder_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 1. 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 2. GPIO配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3); // 3. 时基配置 TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 4. 编码器接口配置 TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 6; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_Cmd(TIM3, ENABLE); }3.2 中断配置与计数处理
为了处理计数器溢出,我们需要配置更新中断。在中断服务函数中记录溢出次数:
volatile int32_t Encoder_Overflow = 0; void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) { if(TIM_GetCounter(TIM3) > 0x7FFF) Encoder_Overflow--; else Encoder_Overflow++; TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } }3.3 速度计算:从脉冲到转速
速度计算的关键是理解几个参数的关系:
- PPR:编码器每转脉冲数(物理分辨率)
- 倍频数:通常为4(四倍频)
- 采样周期:计算速度的时间窗口
速度计算公式:
转速(rpm) = (ΔCount / (PPR×4)) × (60 / 采样时间(s))实现代码:
int32_t Get_Speed(uint32_t PPR, float sample_time) { static int32_t last_count = 0; int32_t current_count = TIM_GetCounter(TIM3) + Encoder_Overflow * 65536; int32_t delta = current_count - last_count; last_count = current_count; return (delta * 60) / (PPR * 4 * sample_time); }4. 常见问题与优化技巧
4.1 信号抖动:硬件滤波很重要
在第一个智能车项目上,我遇到过信号抖动导致计数不准的问题。后来发现是接线太长引入了干扰。解决方法有两个:
硬件方面:
- 使用屏蔽线
- 在信号线上加100pF电容
- 缩短走线距离
软件方面:
- 调整输入捕获滤波器
- 增加软件去抖算法
4.2 低速测量:T法的优势
当电机转速很低时,M法会出现分辨率不足的问题。这时可以切换到T法测量:
float Get_Speed_T_Method(uint32_t PPR) { uint32_t period = Get_Pulse_Period(); // 获取脉冲周期(us) return 60000000.0f / (PPR * 4 * period); // rpm }4.3 多电机同步:定时器资源分配
在四轮驱动项目中,需要同时测量四个电机的转速。STM32F103的定时器资源有限,我是这样分配的:
| 电机 | 定时器 | 引脚 |
|---|---|---|
| 左前 | TIM2 | PA0-PA1 |
| 右前 | TIM3 | PA6-PA7 |
| 左后 | TIM4 | PB6-PB7 |
| 右后 | TIM5 | PA0-PA1(重映射) |
对于没有足够编码器接口的MCU,可以考虑使用外部中断+定时器的方式,不过这会增加CPU负担。
5. 完整项目实战:智能车测速系统
5.1 硬件连接示意图
[电机] -> [编码器] A相 -> TIMx_CH1 B相 -> TIMx_CH2 VCC -> 5V GND -> GND5.2 软件架构设计
底层驱动层:
- 编码器初始化
- 定时器配置
算法层:
- 速度计算
- 滤波处理
应用层:
- PID控制
- 数据传输
5.3 关键代码实现
typedef struct { int32_t raw_count; int32_t last_count; float speed_rpm; uint32_t ppr; } Encoder_TypeDef; Encoder_TypeDef encoder[4]; void Encoder_Update(Encoder_TypeDef *enc, TIM_TypeDef *TIMx) { enc->raw_count = TIMx->CNT + Encoder_Overflow * 65536; int32_t delta = enc->raw_count - enc->last_count; enc->last_count = enc->raw_count; // 低通滤波 enc->speed_rpm = 0.2f * (delta * 60.0f / (enc->ppr * 4 * 0.01f)) + 0.8f * enc->speed_rpm; } void Control_Loop(void) { for(int i=0; i<4; i++) { Encoder_Update(&encoder[i], TIMx[i]); PID_Calculate(&pid[i], encoder[i].speed_rpm, target_speed); Set_PWM(MOTOR[i], pid[i].output); } }6. 进阶话题:从速度到位置控制
当项目要求更高时,单纯的测速就不够用了。我在机械臂项目中实现了位置控制,关键点包括:
位置计算:
position = (count_total / (PPR×4)) × 轮周长多圈计数:
- 利用Z相信号校准
- 软件记录圈数
闭环控制:
- 位置环+速度环双闭环
- 梯形加减速算法
这些年在不同项目中使用过各种编码器,从几块钱的霍尔编码器到上万元的绝对式编码器都有接触。实测下来,STM32的编码器接口模式确实稳定可靠,配合适当的滤波算法,完全能满足大多数工业应用的需求。