【发布于2019-02-18】最近更新:2022-03-07,请在页面尾部查看更新内容。
最近翻出来一个用于音量调节的旋转编码器,我以为这玩意儿不同方向旋转只会导通其中一个引脚。后来发现并不是这么回事,每旋转一个刻度会先后导通两只脚,你需要通过判断两只脚的信号顺序来判断正向旋转还是反向旋转。
通过对网上部分资料的学习,发现没有几个能符合实际的,大多数通过定时器来判断,这样的一个问题就是旋转速度过快时,定时器未能采集到信号导致误码,再就是定时器与原有的串口发送数据功能冲突。后来经过两天自己研究,通过死循环调用该函数采集信号,测试完美,无论旋转多快或者多慢都不会误码,由于是死循环,占用CPU较高,适合要求不高的场合,比如控制LED亮度,电机无极调速等等。
这是年前写的代码,年后竟然忘了变量是干嘛的,算了,大家复制粘贴稍微改改就能使。
//AT:正向引脚,BT:反向引脚,BTN:按下。 sbit AT = P1^2; sbit BT = P1^3; sbit BTN = P1^4;//这里无用 unsigned char BC,ZX; void Delay100us() //@11.0592MHz { unsigned char i, j; _nop_(); _nop_(); i = 2; j = 15; do { while (--j); } while (--i); } void Test() { if(AT==1 & BT==1){ Delay100us();//延时100微秒,根据实际情况去掉或缩短延时时间或增加延时时间 if(AT==1 & BT==1){ BC = 0; ZX = 1; } } if(AT==0 & BT==0){ Delay100us(); if(AT==0 & BT==0){ BC = 0; ZX = 0; } } if(AT==1 & BC == 0){ Delay100us(); if(AT==1 & BC == 0){ if(BT==0){ Delay100us(); if(BT==0){ BC = 1; if(ZX==1){ SendString("-"); //串口发送数据,可以不用 } else{ SendString("+"); } } } } } if(AT==1 & BT==1){//这两个判断和前面那段一样的代码,只是为了确保稳定,实际去掉这一段也能正常使用。 Delay100us(); if(AT==1 & BT==1){ BC = 0; ZX = 1; } } if(AT==0 & BT==0){ Delay100us(); if(AT==0 & BT==0){ BC = 0; ZX = 0; } } if(BT==1 & BC == 0){ Delay100us(); if(BT==1 & BC == 0){ if(AT==0){ Delay100us(); if(AT==0){ BC = 1; if(ZX==0){ SendString("-"); } else{ SendString("+"); } } } } } }
【2020年06月04日更新】
最近写个数控电源驱动,用到旋编,以上代码测试发现在旋编损坏(用久了的旋编不一定刚好卡在停止位)时会误触发,特地优化代码,放在中断里即可,代码如下。
unsigned char BC,ZX;//时隔两年,仍想不起当时写的这俩变量干嘛的,ZX应该是判断正向还是反向旋转的,BC完全想不起来。 void Encoder() { if(AT==1 && BT==1)//AT为旋编A脚,BT为旋编B脚,旋编C脚接地。 { BC = 0; ZX = 1; } if(AT==0 && BT==0) { BC = 0; ZX = 0; } if(AT==1 && BC == 0) { if(BT==0) { BC = 1; if(ZX==1) { temp--;//反向-- } else { temp++;//正向++ } } if(AT==0) { BC = 1; if(ZX==0) { temp--;//反向-- } else { temp++;//正向++ } } } }
代码很简单,比网上一堆神秘莫测的代码简单多了,佩服自己瞎折腾出来的,将该函数放在10us-100us的中断里调用即可,如果放在主函数死循环里要加延时防抖,参考最顶上的代码。(垃圾代码编辑框缩进自动给去了)
【2020年11月18日更新】
闲来无事,再次研究旋编,没想到让我写出更精简的检测代码,放在10us的中断里调用即可。经测试误码率极低,除非特别快旋转,测试正常使用没有发现误码,理论上中断检测时间越快,就越不会出现误码,而且测试旋编用久了接触不良也能正常解码,短短几行代码,吊打全网的解码程序,看得懂的就知道为什么如此简单了。
当然,这里还是稍微说一下程序的思想,一般旋编转一格会输出以下四个信号:11 10 00 01 11(这个是回到下一个11了),同理反过来旋转就是11 01 00 10 11。
发现没有,正向和反向实际上是10和01的检测,于是就有了如下代码。
首先编码器锁定时AT BT一定会同时为1,就从11开始判断,如果第一时刻AT为0(此时为AT:BT为01),可以假设是正向(不同的编码器不同,也可能是反向),但这个信号有可能是接触不良抖了一下,先插个ZX标记,如果第二时刻BT为0(此时为AT:BT为00),就可以确定一定是正向旋转了,所以输出正向。实际上旋编的旋转过程并没有完成,还会有第三时刻AT:BT为10信号,最后回到第四时刻AT:BT为11信号。完整的解码程序会在第四时刻AT:BT为11时才响应,中途需要记录3个状态再加以判断,如果旋编用久了坏了接触不良,产生各种毛刺,基本上就无法解码了。再精简一点的会在第三时刻响应,这样容错就很高了,我之前的代码就是在这一时刻响应。而现在最新的程序仅仅在第二时刻就做出响应,程序更加精简,而且再烂的旋编都能有反应,不管你转到一半往回转还是各种花式旋转,至少都有响应,为自己的创新加个鸡腿,吊打全网不是吹的。
void EncodeScan() { static unsigned char BC,ZX=0,FX=0;//2022年03月07日勘误,如果把这3个变量放在函数内,请加上static作为静态变量,或者将这3个变量放在函数外作为全局变量。 if(AT==1 && BT==1) //AT和BT即旋编AB两脚 { BC = 0; //保持 ZX = 0; //正向标志 FX = 0; //反向标志 } if(AT==0 && FX == 0) { ZX = 1; if(BT==0 && BC == 0) { BC = 1; Forward();//正向旋转 } } if(BT==0 && ZX == 0) { FX = 1; if(AT==0 && BC == 0) { BC = 1; Reverse();//反向旋转 } } }
【2022年03月07日更新】
最近从老五那里淘来了一些编码器,竟然是30定位15脉冲的半波ec11编码器,这种编码器相对于普通20定位20脉冲的全波ec11编码器没有什么本质区别,全波编码器A、B通道定位时一定在高电平11,转一格产生4个信号11 10 00 01 11(见上一个程序分析),半波编码器A、B通道定位时有可能在高电平11,也有可能在低电平00,正向转一格只产生2个信号11 01 00或00 10 11,反向旋转就是11 10 00或00 01 00,共4种情况,所以程序要分2步解码,话不多说直接上代码,还是放在中断内调用,中断速度看个人需求了,中断速度越快采样速度越快响应速度也越快,但滤波能力降低,如果需要过滤毛刺信号不要太快。
void EncodeScan() { static unsigned char HL,BC=1,ZX=0,FX=0; //对比全波解码程序多了个HL变量用来区分初始状态为高电平或低电平,特别注意BC变量初始化为1,全波时不必指定,如果把这4个变量放在函数内,请加上static作为静态变量,或者将这4个变量放在函数外作为全局变量。 if(HL==1) { if(AT==0 && FX == 0) //A通道超前置0时如果不是反向旋转标志 { ZX = 1; //正向标志置1 if(BT==0 && BC == 0) //B通道随后置0时如果没有保持使能 { BC = 1; //保持使能防止多触发 Forward();//正向旋转 } } if(BT==0 && ZX == 0) //B通道超前置0时如果不是正向旋转标志 { FX = 1; //反向标志置1 if(AT==0 && BC == 0) //A通道随后置0时如果没有保持使能 { BC = 1; //保持使能防止多触发 Reverse();//反向旋转 } } } else { if(AT==1 && FX == 0) //A通道超前置1时如果不是反向旋转标志 { ZX = 1; //正向标志置1 if(BT==1 && BC == 0) //B通道随后置1时如果没有保持使能 { BC = 1; //保持使能防止多触发 Forward();//正向旋转 } } if(BT==1 && ZX == 0) //B通道超前置1时如果不是正向旋转标志 { FX = 1; //反向标志置1 if(AT==1 && BC == 0) //A通道随后置1时如果没有保持使能 { BC = 1; //保持使能防止多触发 Reverse();//反向旋转 } } } if(AT==1 && BT==1) //两个触点归1时检测下一个循环 相比较全波解码程序归1或归0判断放在旋转过程判断之后了,不要交换程序流程 { HL = 1; //高电平定位 BC = 0; //保持 ZX = 0; //正向标志 FX = 0; //反向标志 } if(AT==0 && BT==0) //两个触点归0时检测下一个循环 { HL = 0; //低电平定位 BC = 0; //保持 ZX = 0; //正向标志 FX = 0; //反向标志 } }
转载请注明来源:bikey 密钥 » 一段简单的EC11旋转编码器解码程序