使用电机时通常会遇到编码器的使用,编码器通过码盘和相关传感器可以测量电机的转动速度,因此读取编码器的数据是十分重要的。
stm32集成了硬件编码器模式,因此只需要配置相关的时钟就可以使用编码器。但是在没有stm32的时候,我们就需要用软件来实现编码器数据的解析。
编码器输出为AB两个电平信号,当电机正转时,A滞后B90度相位,正向计数;当电机反转时,A超前B90度相位,反向计数。此外还有抖动的情况,抖动意味着A会出现一个单独的脉冲或者B会出现一个单独的脉冲。如下图所示:
一般来说我们在AB的每个边沿都计数,这样可以获得最大的精度。stm32提供了三种计数方法,我们在软件实现的时候也可以实现这三种不同的方法。
现在我们考虑一下软件解析编码器的思路应该是怎么样的(在AB的每个边沿都计数),我们要解决下面三个问题(AB相差90度,一般的抖动长度不会超过这么长):
- 正向计数:电机正转意味着会出现这样的波形:A↓-B↑-A↑-B↓-…,按照这样的顺序每次计数加一就行了;
- 反向计数:电机反转意味着会出现这样的波形:B↑-A↓-B↓-A↑-…,按照这样的顺序每次计数减一就行了;
- 抖动时不计数:对于抖动而言必然是:A(或B)出现了一个上升沿(或下降沿)必然会接着出现一个下降沿(或上升沿),对于这样的抖动,我们只需要保证在两个边沿过后计数为零就可以了。
下面是程序的流程图,我们使用有限状态机,使用四个状态,在每个边沿触发状态的改变,结果如下:
下面是摘自msp430论坛上的一段代码,实现了上述功能:
#include "includes.h"
void init(void)
{
P2IES |= BIT4 + BIT6; //P1.4、P1.6设为下降沿中断
P2IES &= ~(BIT5 + BIT7); //P1.5、P1.7设为上升沿中断
P2IE |= BIT4 + BIT5 + BIT6 + BIT7; //允许P1.4567中断
P2IFG = 0; //避免第一次误动作
timerB_Init();
_EINT(); //总中断允许
}
unsigned int EncoderCnt = 2001; //旋转角度计数值,全局变量,供其他程序访问
uchar EncoderStatus = 1; //旋转时序状态变量
#pragma vector = PORT2_VECTOR //P1口中断源
__interrupt void P2_ISR(void) //声明一个中断服务程序,名为P1_ISR();
{
_BIC_SR(SCG0); //如果从LPM3唤醒,恢复时钟准确性
if (P2IFG & BIT4) //-------------------A 下降中断(P1.4中断入口)-------------//
{
if (EncoderStatus == 1)
{
EncoderStatus = 2;
EncoderCnt++;
} //A 下沿,1->2
if (EncoderStatus == 4)
{
EncoderStatus = 3;
EncoderCnt--;
} //A 下沿,4->3
}
if (P2IFG & BIT5) //-------------------A 上升中断(P1.5中断入口)-------------//
{
if (EncoderStatus == 3)
{
EncoderStatus = 4;
EncoderCnt++;
} //A 上沿,3->4
if (EncoderStatus == 2)
{
EncoderStatus = 1;
EncoderCnt--;
} //A 上沿,2->1
}
if (P2IFG & BIT6) //-------------------B 下降中断(P1.6中断入口)-------------//
{
if (EncoderStatus == 4)
{
EncoderStatus = 1;
EncoderCnt++;
} //B 下沿,4->1
if (EncoderStatus == 3)
{
EncoderStatus = 2;
EncoderCnt--;
} //B 下沿,3->2
}
if (P2IFG & BIT7) //-------------------B 上升中断(P1.7中断入口)-------------//
{
if (EncoderStatus == 2)
{
EncoderStatus = 3;
EncoderCnt++;
} //B 上沿,2->3
if (EncoderStatus == 1)
{
EncoderStatus = 4;
EncoderCnt--;
} //B 上沿,1->4
}
//if(P2IFG&BIT1) EncoderCnt=2200;//转一圈,清零标志
P2IFG = 0; //清楚P1口中断标志位
if (EncoderCnt <= 1)
EncoderCnt = 1;
LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽
}