STM32 采集差分编码器数据的方法
本文主要介绍 采用STM32采集差分编码器数据(A+ A- B+ B- Z+ Z-)的方法
1.编码器介绍
参考资料:4. 编码器的使用 — [野火]电机应用开发实战指南—基于STM32 文档
首先来看一下增量式编码器的输出信号和它的信号倍频技术。增量式编码器输出的脉冲波形信号形式常见的有两种:
-
一种是占空比50%的方波,通道A和B相位差为90°;
-
另一种则是正弦波这类模拟信号,通道A和B相位差同样为90°。
对于第1种形式的方波信号,如果把两个通道组合起来看的话,可以发现A和B各自的上升沿和下降沿都能计数,至少在1/2个原始方波周期内就可以计数一次, 最多1/4个原始方波周期。这样计数频率就是原始方波信号的2倍或4倍,换句话说就是,将编码器的分辨率提高了2到4倍,具体如下图所示。

图中的方波信号如果只看其中一个通道的上升沿,那计数频率就等于这个通道信号的频率。如果在通道A的上升沿和下降沿都进行计数,计数频率就是通道A的两倍,即2倍频。 如果同时对两个通道的上升沿和下降沿都计数,那计数频率就变成了原始信号的4倍,即4倍频。
假设有个增量式编码器它的分辨率是600PPR,能分辨的最小角度是0.6°,对它进行4倍频之后就相当于把分辨率提高到了600*4=2400PPR,此时编码器能够分辨的最小角度为0.15°。 编码器倍频技术还可用来扩展一些测速方法的速度适用范围。例如电机测速通常使用M法进行测量(M法在下节介绍),编码器4倍频后可以扩展M法的速度下限。
以上就是方波信号的编码器倍频技术,其实输出模拟信号的增量式编码器同样也可以倍频,不过这种倍频原理与方波完全不同,教程当中就不讲解了。
2.差分编码器转为单端编码器
编码器为了长线传输,一般做成差分信号的形式。但是,差分信号并不能被单片机直接识别。所以需要一个硬件转换,此类芯片有很多,比如MC3486,MAX3097,AM26C32之类的,本文以AM26C32为例。
将差分编码器的A+ A- B+ B- Z+ Z-输入到AM26C32的1A 1B 2A 2B 3A 3B。然后将AM26C32的1Y 2Y接到STM32单片机的TIMx_CH1,TIMx_CH2,将3Y接到任意一个中断IO口。
如果制作有困难,可以直接去找TB找成品,比如 RMLS-701 (直接搜索)就是个不错的选择,

3 STM32的编码器接口简介
STM32芯片内部有专门用来采集增量式编码器方波信号的接口,这些接口实际上是STM32定时器的其中一种功能。 不过编码器接口功能只有高级定时器TIM1、TIM8和通用定时器TIM2、TIM3、TIM4、TIM5才有。编码器接口用到了定时器的输入捕获部分, 功能框图如下图所示。输入捕获功能在《STM32 HAL库开发指南》中已有详细讲解,所以这部分内容在此就不再赘述了。
我们重点关注编码器接口是如何实现信号采集和倍频的。《STM32F4xx参考手册》给出了的编码器信号与计数器方向和计数位置之间的关系,如下表所示。

这个表格将编码器接口所有可能出现的工作情况全都列了出来,包括它是如何实现方向检测和倍频的。虽然信息很全面但是乍看上去却不容易看懂。 首先需要解释一下,表中的TI1和TI2对应编码器的通道A和通道B,而TI1FP1和TI2FP2则对应反相以后的TI1、TI2。STM32的编码器接口在计数的时候, 并不是单纯采集某一通道信号的上升沿或下降沿,而是需要综合另一个通道信号的电平。表中“相反信号的电平”指的就是在计数的时候所参考的另一个通道信号的电平, 这些电平决定了计数器的计数方向。
为了便于大家理解STM32编码器接口的计数原理,我们将表中的信息提出转换成一系列图像。首先看下图,下图所展示的信息对应表格中“仅在TI1处计数”。 图中包含TI1、TI2两通道的信号,以及计数器的计数方向,其中TI1比TI2 提前 1/4个周期,以TI1的信号边沿作为有效边沿。 当检测到TI1的上升沿时,TI2为低电平,此时计数器向上计数1次,下一时刻检测到TI1的下降沿时,TI2为高电平,此时计数器仍然向上计数一次,以此类推。 这样就能把TI1的上升沿和下降沿都用来计数,即实现了对原始信号的2倍频。

接下来看如下图像,图中同样包含TI1、TI2两通道的信号,以及计数器的计数方向,其中TI1比TI2 滞后 1/4个周期,以TI1的信号边沿作为有效边沿。 当检测到TI1的上升沿时,TI2为高电平,此时计数器向下计数1次,下一时刻检测到TI1的下降沿时,TI2为低电平,此时计数器仍然向下计数一次,以此类推。 这样同样是把TI1的上升沿和下降沿都用来计数,同样实现了对原始信号的2倍频,只不过变成向下计数了。

以上两幅图像都是只以TI1的信号边沿作为有效边沿,并且根据TI2的电平决定各自的计数方向,然后判断计数方向就能得到编码器的旋转方向,向上计数正向,向下计数反向。 “仅在TI2处计数”也是同样的原理,在这里就不重复讲了。
最后如下图所示,下图所展示的信息对应表格中“在TI1和TI2处均计数”。这种采样方式可以把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考, 相当于原来仅在一个通道处计数的2倍,所以这种就能实现对原始信号的4倍频。


打开STM32G4系列参考手册通用定时器说明,看到通用定时器框图如上图:在从模式控制模式下有编码器接口,编码器的输入连接tim_ti1fp1与tim_ti2fp2,这两个输入经过定时器通道一与定时器通道二通过输入滤波与边沿检测后连接到编码器接口,编码器接口输出通过PSC分频器后连接到CNT计数器。所以可以把定时器增量编码器模式结构图简化一下如下图所示:

在Counting on tim_ti1 only ×1 mode模式下,此模式仅检测tim_ti1的边沿,当tim_ti2是高电平时,tim_ti1上升沿,此时表示编码器逆向旋转,计数器的值减,如果tim_ti1下降沿,计数器的值增,这样编码器每转动一次,计数器的值就会加减1。
再看,在Counting on tim_ti1 and tim_ti2 ×4 mode模式下,此模式检测tim_ti1与tim_ti2两种边沿,假设编码器顺时针旋转,此时tim_ti1对应A与tim_ti2对应B的波形如下,假设只转动一转,也就是A和B的波形只有一个周期,计数器计数如下:
1.A的上升沿,B是低电平,计数器加1
2.B的上升沿,A是高电平,计数器加1
3.A的下降沿,B是高电平,计数器加1
4.B的下降沿,A是低电平,计数器加1

可以看到,在此模式下,编码器顺时针旋转一转,计数器加4,那么逆时针旋转一转,计数器减4。在数据手册中,也列出了此模式下计数器的框图:

同时此种模式也有一个好处,如果编码器一个输出通道存在jitter(抖动),那么双边沿检测可以抑制抖动。
4 程序编写
参考资料:STM32定时器编码器模式的使用-CSDN博客
用到的编码器模块如下,使用STM32开发板,搭建实验平台。接口连接如下:
GND连接GND
+连接3V3
SW连接PA5(普通IO)
A相连接PA6(定时器3通道CH1)
B相连接PA7(定时器3通道CH2)
打开CUBEMX选中芯片型号STM32G431RBT6(你自己的型号),配置好RCC,时钟树,sys。
选择定时器3,编码器模式:

一个计数周期设置为20。

编码器模式选择检测tim_ti1与tim_ti2两种边沿。
Encoder Mode TI1 and TI2 这种模式,脉冲的4倍计数。


接下来配置串口。

配置完成后生成工程。首先重定向串口,方便使用printf打印信息,在魔术棒中勾选Use MicrolLIB。

添加代码
方式1:
参考资料:(88 封私信 / 52 条消息) STM32编码器功能简易实现(STM32CubeMX) - 知乎
.....
int16_t cnt=0,dir=2,tim3_irq_cnt=0;
void main(void)
{
.............HAL_TIM_Base_Start_IT(&htim3);//计数超过设定值,产生中断,进入回调函数HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);//启动编码器定时模式while(1){dir = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3); //0或者1 ,2个方向cnt = __HAL_TIM_GET_COUNTER(&htim3); //计数值,与旋转方向有关,一个方向是加,另一个方向是减。}
}//回调函数在main函数外
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim==(&htim3)){tim3_irq_cnt++;}
}

方式2:
#include "stdio.h"
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);return ch;
}
在使用编码器前需要打开编码器,HAL库中提供了代码,将此代码放在while循环前。
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_1 | TIM_CHANNEL_2);
编写代码获取计数器的值,并且在串口打印出来,编译后下载打开串口观察。
uint16_t TimerEncoder = 0;
void getTimerEncoder(void)
{TimerEncoder = __HAL_TIM_GET_COUNTER(&htim3);printf("Encoder Value %d\r\n",TimerEncoder);HAL_Delay(500);
}
while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */getTimerEncoder();}
当编码器顺时针旋转时,计数器的值加,逆时针旋转时,计数器的值减。

