增量式光电编码器详解
基本原理
增量式光电编码器主要由三个部分组成:发光二极管、码盘以及码盘背面的光传感器
码盘
码盘安装在旋转轴上,上面均匀地排列着透光和不透光的扇形区域。当码盘转动时,不透光的部分能够挡住光线,而透光区则允许光线透过,那么码盘背面的光传感器就会周期性地收到光信号,从而输出一列方波
码盘转动一周时,光传感器输出的脉冲个数是固定的,那么通过 检测一定时间内收到的脉冲个数,就可以知道在这段时间内码盘转动了多少圈,进而换算为速度。例如,一个码盘转动一周时会输出 100 个脉冲,在 0.1s 内我们收到了 500 个脉冲,这意味着 0.1s 内码盘转动了 5 周,即码盘的转速为
AB 相
如果编码器只输出一列方波(假设为 A),该怎样判断码盘是正转还是反转?因为无论是正转还是反转,都会产生同样的方波,而它们对速度的贡献显然是相反的。上面我们说过,码盘上均匀地刻着透光和不透光的扇形区域,我们在扇形区域内侧再均匀地刻上一圈透光和不透光的扇形区域,不同的是,外圈和内圈的区域是“交错”的
通过观察上图中的码盘我们可以得到如下结论
- 当外圈处于不透光区域时,内圈对应的一半为透光区域,一半为不透光区域
- 当外圈处于透光区域时,内圈对应的一半为不透光区域,一半为透光区域
我们在内外扇形区域各安装上一套发光二极管、码盘以及光传感器,那么当码盘转动时,编码器就会 输出两列相位差为 90° 方波(习惯称之为 A、B 相)
码盘沿不同方向旋转时 A、B 相输出如下,显然正转和反转输出方波的特征是不同的
通过判断 B 处于上升沿时 A 的电平状态,我们就可以知道码盘旋转的方向了
- 当码盘正转时,在 B 的上升沿,方波 A 恒为高电平
- 当码盘反转时,在 B 的上升沿,方波 A 恒为低电平
注意
- 正/反转是相对而言的,重点在于区分不同旋转方向时的波形特征
- 通过观察 A 上升沿时 B 的电平亦能判旋转方向,参考下文代码实现部分
速度计算
假如编码器码盘旋转一周 A/B 相输出的脉冲数目为 N,在时间 T 内统计到的有效脉冲数目为 S(正转脉冲数 + 1,反转脉冲数 - 1),小车轮子的直径为 D,那么小车的速度换算公式如下
笔者使用的编码器码盘旋转一周输出的脉冲个数为 90,小车轮子的直径为 75mm,假如 1s 内统计得到的有效脉冲数目为 500,代入上式计算小车此时的速度为
代码实现
显然,速度计算的关键在于 统计一定时间内的脉冲数目,这里提供两种思路
- 利用中断检测 B 的上升沿,触发中断时判断 A 的电平,来决定计数值加还是减
- 将定时器设置为编码器模式,直接读取计数值和方向
本文基于 STM32 平台开发驱动,读者了解增量编码器原理后,移植到其它平台应该是难度不大的。下面分别介绍上述两种方法的实现
方法一
实现原理:利用中断检测 B 的上升沿,触发中断时判断 A 的电平,来决定计数值加还是减
打开 CubeMX,设置相关管脚,我这里使用的是 PC2 和 PC3 来接 A、B 相
为方便观察调试,这里启用 USART 串口
打开定时器2,定时器响应时通过 USART 串口把计数值打印出来
设置定时 200ms
导入 Cube 工程,定义相关变量
1 | int encoder_count = 0; // 计数器 |
重写外部中断回调函数
1 | void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) |
定时器 2 响应后,通过 USART 串口向上位机发送数据
1 | void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) |
在主函数中打开定时器 2
1 | HAL_TIM_Base_Start_IT(&htim2); |
测试效果如图
方法二
使用定时器的编码器模式
设置 TIM4 的 Combined Channels 为编码器模式
配置编码模式
-
Prescaler
:分频系数 -
Counter Mode
:计数模式,设置为 UP 时码盘正转计数值增加,反转计数值减小 -
Counter Period
:编码器计数最大值,一般设置为 65535 以防止溢出 -
Encoder Mode
:计数模式,编码器计数有三种模式可选-
T1
只在上升沿计数,例如在一定时间内 A/B 产生了 100 个脉冲,那么编码器计数值为200(A、B 产生脉冲数相等)。由于分频系数 Prescaler 的存在,实际调用函数得到的计数值为
-
T2
只在下降沿计数,计数值与 T1 相等
-
TI and T2
在上升沿、下降沿都计时,计数值为 T1/T2 的两倍
-
导入 CubeMx 工程,定义相关变量
1 | uint8_t encoder_count; // 编码器计数 |
打开定时器 4
1 | HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); |
读取相关值并通过 OLED 显示出来
1 | while (1) |
这里用函数发生器模拟 A、B 两列方波,编码器计数时采用 T1 and T2 模式,分频系数为 3,所以理论上 1s 内采集到的脉冲数目为
代码中每 500ms 读取一次编码器计数值,那么乘以 2 才是方波的真实频率
1 | sprintf((char *)msg, "fre:%4d dir:%d", encoder_count*2, encoder_direction); |