最近由于项目需求,须采用编码器进行小车的速度和里程测量。由于考虑到Arduino的编程简便性,因此采用Arduino作为控制器。搜索了相关资料,发现STM32或者51单片机的编码器程序比较多。其中STM32有编码器接口,简单来说就是用来定时器来做脉冲的捕捉,但是我对于其中的周期还有计数方式一直不是很懂,而且下载的程序虽然能用,但是接线后没有效果。认真思考过后,也许自己基础比较薄弱,对于一些基础电路知识不够了解,针对于欧姆龙e6b2-cwz6c这款编码器,进行了认真的资料搜索及其工作原理的理解学习,又参考网上其他牛人的程序,终于完整的实现了Arduino的编码器程序,由于我搜索的过程中,没有发现能直接用的文章或者程序,因此在这里把我的开发过程记录下来,供大家参考学习。

1.基本思路


由于手术的工频升级机需要自动平层功能,于是着手开始做这方面的工作。硬件选择的是增量式编码器,100脉冲每转,后来了解到stm32的每个定时器的通道1和通道2内置了正交编码器模块,可以直接使用。之前的公司工程师都是用定时器捕捉脉冲,然后自行处理的,我看了下代码挺麻烦的,现在用了stm32自带的感觉就容易多了。找了官方的软件说明,看了下网上已有的例子,一个下午就基本在我的系统架构中添加了这个设备,然后对这个设备初始化,设置上层接口API。最后看些例子将16位计数器软件扩展到32位。就顺利的完成了基本模块的第一步工作了。以后则需要将采集的到数据与楼层做一个好的数据结构结合在一起,方便调用和维护了。

2.增量式编码器

增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。


编码器是把角位移或直线位移转换成电信号的一种装置。前者称为码盘,后者称码尺.按照读出方式编码器可以分为接触式和非接触式两种.接触式采用电刷输出,一电刷接触导电区或绝缘区来表示代码的状态是“1”还是“0”;非接触式的接受敏感元件是光敏元件或磁敏元件,采用光敏元件时以透光区和不透光区来表示代码的状态是“1”还是“0”。


旋转增量式编码器以转动时输出脉冲,通过计数设备来知道其位置,当编码器不动或停电时,依靠计数设备的内部记忆来记住位置。这样,当停电后,编码器不能有任何的移动,当来电工作时,编码器输出脉冲过程中,也不能有干扰而丢失脉冲,不然,计数设备记忆的零点就会偏移,而且这种偏移的量是无从知道的,只有错误的生产结果出现后才能知道。

解决的方法是增加参考点,编码器每经过参考点,将参考位置
<https://baike.baidu.com/item/%E5%8F%82%E8%80%83%E4%BD%8D%E7%BD%AE>
修正进计数设备的记忆位置。在参考点以前,是不能保证位置的准确性的。为此,在工控中就有每次操作先找参考点,开机找零等方法。

增量式编码器转轴旋转时,有相应的脉冲输出,其旋转方向的判别和脉冲数量的增减借助后部的判向电路和计数器来实现。其计数起点任意设定,可实现多圈无限累加和测量。
还可以把每转发出一个脉冲的Z信号,作为参考机械零位。编码器轴转一圈会输出固定的脉冲,脉冲数由编码器光栅的线数决定。需要提高分辨率时,可利用 90 度相位差的
A、B两路信号对原脉冲数进行倍频,或者更换高分辨率编码器。

型号

omRon欧姆龙编码器E6B2-CWZ6C 

分辨率2500P/R

输出相:A/B/Z

相位差:90±45°(1/4±1/8T)

最高响应频率:100KHz

允许最高转速:6000r/min

负载短路保护、电源反接保护

电气最高响应转数:最高响应频率/分辨率*60=(r/min)



 


为了区分正反转及检测零点,实际使用的码盘比上图要复杂些,通常包括三个部分:A相,B相和Z相,A相与B相相差1/4周期(相位差90度),可以用来区分正转还是反转;Z相为单圈脉冲,码盘转一圈产生一次,可以用作编码器的参考零位,如下图:



3.STM32的正交编码

相位差为90° 通过判断哪个信号在前 哪个信号在后 来决定TIM->COUNT是++ 还是 –,360线 AB一圈各为360个,Z信号为一圈一个

编码器信号:

A 脉冲输出

B 脉冲输出

Z 零点信号 当编码器旋转到零点时,Z信号会发出一个脉冲表示现在是零位置 这个零点位置是固定,厂商指定的

VCC 电源通常分为24V的和5V的

GND 地线

4.Arduino的编码器程序
#define PinA 2 //外部中断0 #define PinZ 3 //外部中断1 #define PinB 8
//编码器的OUT_B信号连接到数字端口8 //变量初始化 unsigned long time1 = 0; // 时间标记 float time_cw;
float time_ccw; long count = 0; const float d = 75.7/1000; //轮子的直径 const float
pi = 3.141592654;//圆周率 int num = 0;//圈数 double t;//一圈的运动时间 float velocity;
double time3;//外部中断1产生时的时间,即捕捉到Z相的置零信号时,用于在loop循环内进行一圈时间长短的计算 void setup() {
pinMode(PinA,
INPUT_PULLUP);//因为编码器信号为欧姆龙E6B2-CWZ6C,为开漏输出,因此需要上拉电阻,此处采用arduino的内部上拉输入模式,置高
pinMode(PinB, INPUT_PULLUP);//同上 pinMode(PinZ, INPUT_PULLUP);//同上
attachInterrupt(0, Encode, FALLING);//脉冲中断函数:捕捉A相信号,并判断A、B相先后顺序
attachInterrupt(1, Set_state , FALLING);//用于在捕捉到Z的零信号时,进行状态置零 Serial.begin
(9600); } void loop() { double distance; //正转 if (count == 2500) { //
Serial.println("ok");//调试用 num = num + 1; time_cw = millis(); t = time_cw -
time3; t = t / 1000; distance = num * d * pi; velocity = d * pi / t;
Serial.print("The wheel has run ");Serial.print(distance);
Serial.println("m."); Serial.print("The cw_speed is ");Serial.print(velocity);
Serial.println("m/s."); Serial.print("The time is ");Serial.print(t);
Serial.println("s."); } //反转 if (count == -2500) { //
Serial.println("ok");//调试用 num = num + 1; time_ccw = millis(); t = time_ccw -
time3; t = t / 1000; distance = num * d * pi; velocity = d * pi / t;
Serial.print("The wheel has run ");Serial.print(distance);
Serial.println("m."); Serial.print("The ccw_speed is ");Serial.print(velocity);
Serial.println("m/s."); Serial.print("The time is ");Serial.print(t);
Serial.println("s."); } } // 编码器计数中断子程序 void Encode() { //为了不计入噪音干扰脉冲,
//当2次中断之间的时间大于5ms时,计一次有效计数 if ((millis() - time1) > 5) {
//当编码器码盘的OUTA脉冲信号下跳沿每中断一次, if ((digitalRead(PinA) == LOW) && (digitalRead(PinB)
== HIGH)) { count--; } else { count++; } } time1 == millis(); } void
Set_state(){ count = 0; time3 = millis();//发生中断时的时间

 说明,采用单片机内部的上拉输入可能带来较大功耗,因为单片机内部电阻较大,arduino实现的基本原理为已知每圈脉冲数为2500,则当计数大道2500时,计数一圈,若为stm32则进行溢出中断。通过A、B相的高低电平关系,判断AB相的先后关系,进而判断转向。