玩转电机驱动——电机编码器

玩转电机驱动——电机编码器


文章目录

  • 玩转电机驱动——电机编码器
  • 前言
  • 一、旋转编码器
    • 1. 光学编码器
    • 2. 光学旋转编码器与Arduino连接
    • 3. 程序
  • 二、Arduino Encoder.h库相关知识
    • 1.硬件要求
    • 2. 基本用法
    • 3. 了解正交编码信号
    • 4. 示例程序
    • 5. 中断延迟要求
    • 6. 优化中断选项
    • 7. 最大速度和 CPU 使用率
    • 8. 低性能轮询模式
  • 总结


前言

编码器(encoder)是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。(对于电机测速来说应该需要将角位移转变为电信号,所以应该是码盘。)按照信号原理编码器可分为增量式和绝对式两类。

增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。这种类型的编码器提供脉冲作为输出,可以将其视为增量信号。因为它没有任何唯一位置的唯一值,这意味着当该编码器断电时,它失去了位置参考并从零开始。

淘宝上搜“带编码器的电机”大多是这种类型的编码器,增量式编码器有有光电编码器和霍尔编码器两种,两者的主要区别是:
(1)检测方式不同:霍尔编码器是电磁检测位置,光电编码器是光电检测位置;
(2)精度不同:霍尔编码器一般是精度不高,用作粗略的位置反馈,而光电编码器精度高,可以实现高精度的位置检测。
玩转电机驱动——电机编码器_第1张图片
玩转电机驱动——电机编码器_第2张图片
绝对式编码器的每一个位置对应一个确定的数字码,因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。这种类型的编码器比增量编码器更为先进。同时它们具有磁盘来代替插槽磁盘,因此它在每个位置都有独特的价值,因此即使在断电后也能记住它的位置。
玩转电机驱动——电机编码器_第3张图片


一、旋转编码器

旋转编码器是一种光电式或电磁式旋转测量装置,它将被测的角位移直接转换成数字信号(高速脉冲信号)。我们通常用的是增量型编码器,不同型号的旋转编码器,其输出脉冲的相数也不同,有的旋转编码器输出A、B、Z三相脉冲,有的只有A、B相两相,最简单的只有A相。
(1)如单相联接,用于单方向计数,单方向测速。
(2)A.B两相联接,用于正反向计数、判断正反向和测速。
(3)A、B、Z三相联接,用于带参考位修正的位置测量。Z即圈数。

1. 光学编码器

光学旋转编码器是一种机械设备,在圆柱形外壳的内部有一个旋转轴,其结构与电机相同。一个圆形的平盘,上面有两组插槽。光学传感器安装在此光盘的两侧,发射器设置在一侧,接收器设置在一侧。

因此,当开槽光盘在传感器之间旋转时,它会切断光学传感器,并在接收器末端生成信号。

接收器还与微控制器连接以处理生成的信号,这样我们就可以知道旋转了多少轴。

我们还可以通过比较两个输出的信号极性来确定轴的旋转方向。因为两组插槽之间有一定的偏移,光学旋转编码器一般有两个输出“ A”和“ B”。

下图是了解每转400脉冲编码器如何产生脉冲的图像,它使每转总计1600过渡。这意味着它可以提供非常高的精度。
玩转电机驱动——电机编码器_第4张图片

2. 光学旋转编码器与Arduino连接

白色(OUT A):PIN 3( arduino的中断器引脚)
绿色(OUT B):PIN 2( arduino的中断器引脚)
红色:5V
黑色:GND
在这里,我们必须注意,编码器的A、B相输出必须有一个连接到Aorduino的中断引脚。否则,arduino无法记录来自编码器的脉冲。
玩转电机驱动——电机编码器_第5张图片

3. 程序

1.	volatile long temp, encoderCounter =0; //这个变量将根据编码器的旋转增加或减少  
2.	int encoderPinA = 2; //interrupt pin 2   
3.	int encoderPinB = 3; //interrrupt pin 3  
4.	  
5.	void setup() {  
6.	Serial.begin (115200);  
7.	pinMode (encoderPinA, INPUT);   
8.	pinMode (encoderPinB, INPUT);   
9.	  
10.	//设者中断函数  
11.	//将中断连接到Arduino的引脚encoderPinA和encoderPinB,  
12.	//当脉冲在CHANGE边缘时,调用函数doEncoderA()/doEncoderB()  
13.	attachInterrupt (digitalPinToInterrupt(encoderPinA), doEncoderA, CHANGE);  
14.	//编码器的A上升沿脉冲触发了中断。  
15.	attachInterrupt (digitalPinToInterrupt(encoderPinB), doEncoderB, CHANGE);  
16.	//编码器的B上升沿脉冲触发了中断。  
17.	  
18.	/*中断函数的使用 
19.	attachInterrupt(digitalPinToInterrupt(pin), ISR, mode); 
20.	pin: 中断引脚号 
21.	ISR: 中断服务程序名 
22.	mode:中断模式 
23.	 
24.	中断模式(mode)有以下几种形式: 
25.	LOW: 当引脚为低电平时触发中断服务程序 
26.	CHANGE: 当引脚电平发生变化时触发中断服务程序 
27.	RISING: 当引脚电平由低电平变为高电平时触发中断服务程序 
28.	FALLING: 当引脚电平由高电平变为低电平时触发中断服务程序 
29.	*/  
30.	}  
31.	  
32.	void loop() {  
33.	  // Send the value of counter  
34.	if ( encoderCounter!= temp){  
35.	  Serial.println (encoderCounter);  
36.	  temp = encoderCounter;  
37.	}  
38.	}  
39.	  
40.	void doEncoderA(){  
41.	  
42.	  // 在A脉冲中寻找上升沿信号,若A编码器引脚为高电平,  
43.	  // 若B编码器引脚为低电平,encoderCounter计数+1,否则-1  
44.	  if (digitalRead(encoderPinA) == HIGH) {   
45.	    // check channel B to see which way encoder is turning  
46.	    if (digitalRead(encoderPinB) == LOW) {    
47.	      encoderCounter = encoderCounter + 1;         // CW  
48.	    }   
49.	    else {  
50.	      encoderCounter = encoderCounter - 1;         // CCW  
51.	    }  
52.	  }  
53.	  else   // 在A脉冲中寻找下降沿信号                                         
54.	  {   
55.	    // check channel B to see which way encoder is turning    
56.	    if (digitalRead(encoderPinB) == HIGH) {     
57.	      encoderCounter = encoderCounter + 1;          // CW  
58.	    }   
59.	    else {  
60.	      encoderCounter = encoderCounter - 1;          // CCW  
61.	    }  
62.	  }  
63.	  //Serial.println (encoder0Pos, DEC);            
64.	  //这段用于调试,记得删去  
65.	}  
66.	  
67.	void doEncoderB(){  
68.	  
69.	  // 在B脉冲中寻找上升沿信号  
70.	  if (digitalRead(encoderPinB) == HIGH) {     
71.	   // check channel A to see which way encoder is turning  
72.	    if (digitalRead(encoderPinA) == HIGH) {    
73.	      encoderCounter = encoderCounter + 1;         // CW  
74.	    }   
75.	    else {  
76.	      encoderCounter = encoderCounter - 1;         // CCW  
77.	    }  
78.	  }  
79.	  // Look for a high-to-low on channel B  
80.	  else {   
81.	    // check channel B to see which way encoder is turning    
82.	    if (digitalRead(encoderPinA) == LOW) {     
83.	      encoderCounter = encoderCounter + 1;          // CW  
84.	    }   
85.	    else {  
86.	      encoderCounter = encoderCounter - 1;          // CCW  
87.	    }  
88.	  }  
89.	} 

将代码上传到arduino后,打开串行监视器

并旋转编码器轴,如果沿顺时针方向旋转编码器,则值会增加;如果沿逆时针方向旋转,则值会减小。

进一步通过,下列函数可以获取电机转动角度。

double angle = encoderCounter*360/172032.0;//ppr=172032  

注:具体使用时,AB相编码器,只需要用一个中断引脚即可,例如:A编码器引脚触发中断后,B相引脚只要接发高低电平即可,无需再进入中断了。

A脉冲为上升沿,B脉冲为高电平,方向为负;
A脉冲为上升沿,B脉冲为低电平,方向为正。

二、Arduino Encoder.h库相关知识

原文:https://www.pjrc.com/teensy/td_libs_Encoder.html

1.硬件要求

编码器有2个信号,必须2个引脚,接法有3种选择:

(1)最佳性能:两个信号都连接到中断引脚。

(2)良好的性能:第一个信号连接到中断引脚,第二个信号连接到非中断引脚。

(3)低性能:两个信号都连接到非中断引脚。

常见Arduino开发板的中断引脚选择如下表所示,记住不要选择13号引脚,13号引脚是自带指示灯引脚。
玩转电机驱动——电机编码器_第6张图片
低成本编码器仅将其引脚接地,此时编码器将激活片上上拉电阻。如果连接较长的电线,添加 1K 上拉电阻可能会提供更好的信号。

2. 基本用法

(1)Encoder myEnc(pin1, pin2);
使用 2 个引脚创建一个编码器对象。您可以创建多个Encoder对象,每个对象使用自己的 2 个引脚。第一个引脚应该能够中断。如果两个引脚都具有中断功能,则两者都将用于获得最佳性能。如果两个引脚都没有中断,编码器也将在低性能轮询模式下工作。

(2)myEnc.read();
返回累积位置。这个数字可以是正数或负数。

(3)myEnc.write(newPosition);
将累积位置设置为新数字。

3. 了解正交编码信号

编码器可以通过检测孔或标记移动超过2个位置来感知任一方向的移动。下图中的蓝色圆盘顺时针旋转时,首先由引脚 1 检测到变化,然后由引脚 2 检测到变化。当它逆时针旋转时,引脚 2 首先检测到变化。这种方案被称为“正交编码”,因为 2 个引脚检测到的波形相位相差 90 度。

编码器库监视 2 个引脚并更新位置相对变化的计数。库在每次更改时更新其计数,这通常称为 4X 计数,因为编码器硬件中的每个物理标记或孔都有 4 个计数可用。

顺时针转动
玩转电机驱动——电机编码器_第7张图片
玩转电机驱动——电机编码器_第8张图片
玩转电机驱动——电机编码器_第9张图片
玩转电机驱动——电机编码器_第10张图片
逆时针转动
玩转电机驱动——电机编码器_第11张图片
玩转电机驱动——电机编码器_第12张图片
玩转电机驱动——电机编码器_第13张图片
玩转电机驱动——电机编码器_第14张图片

4. 示例程序

该示例程序可从以下菜单获得:文件 > 示例 > Encoder> TwoKnobs。

1.	#include <Encoder.h>  
2.	  
3.	// 将这些引脚号更改为连接到编码器的引脚  
4.	// 最佳性能:两个引脚都具有中断能力  
5.	// 良好性能:只有第一个引脚具有中断能力  
6.	// 低性能:两个引脚都没有中断能力  
7.	  
8.	Encoder knobLeft(5, 6);  
9.	Encoder knobRight(7, 8);  
10.	  
11.	// 避免使用带有 LED 的引脚,一般为13号引脚  
12.	  
13.	void setup() {  
14.	  Serial.begin(9600);  
15.	  Serial.println("TwoKnobs Encoder Test:");  
16.	}  
17.	  
18.	long positionLeft  = -999;  
19.	long positionRight = -999;  
20.	  
21.	void loop() {  
22.	  long newLeft, newRight;  
23.	  newLeft = knobLeft.read();  
24.	  newRight = knobRight.read();  
25.	  if (newLeft != positionLeft || newRight != positionRight) {  
26.	    Serial.print("Left = ");  
27.	    Serial.print(newLeft);  
28.	    Serial.print(", Right = ");  
29.	    Serial.print(newRight);  
30.	    Serial.println();  
31.	    positionLeft = newLeft;  
32.	    positionRight = newRight;  
33.	  }  
34.	  // 如果从串行监视器发送一个字符,将两者knobLeft、knobRight都重置为零  
35.	  if (Serial.available()) {  
36.	    Serial.read();  
37.	    Serial.println("Reset both knobs to zero");  
38.	    knobLeft.write(0);  
39.	    knobRight.write(0);  
40.	  }  
41.	} 

5. 中断延迟要求

编码器需要对信号变化的低延迟响应。使用带中断的第一个或两个引脚效果很好。但是,如果中断被您的代码或其他库长时间禁用,编码器可能会错过更改。结果是计数不正确。接下来的 1、2 或 3 次更改可能会产生错误的结果。

SoftwareSerial 和 NewSoftSerial 很可能会导致问题。

6. 优化中断选项

在Arduino种,Encoder 使用以汇编语言编写的非常优化的中断例程。通常,Encoder 使用 attachInterrupt (),它允许将函数动态附加到每个中断。动态函数调用会增加一些开销overhead。要消除此额外开销,您可以使用此选项。

1.	// 此选项可以设置使Encoder使用更优化的代码,  
2.	// 必须放在Encoder.h前定义。 
3.	#define ENCODER_OPTIMIZE_INTERRUPTS  
4.	#include <Encoder.h>  

注:overhead是系统开销,本来直接走路不用花钱,可是浪费时间。有时候,路远了,花点钱坐车还是必要的。在计算机里面,系统开销可能会暂用一些资源,但有时候能提高效率。

编码器将直接定义中断的最小开销。但是如果任何其他代码或您使用的任何库需要 attachInterrupt()函数,将会发生冲突。

7. 最大速度和 CPU 使用率

SpeedTest 示例,在File > Examples > Encoder > SpeedTest中,提供了一种简单的方法来验证 Encoder 消耗了多少 CPU 时间。测量了以下 SpeedTest 结果:
玩转电机驱动——电机编码器_第15张图片

8. 低性能轮询模式

如果两个引脚都没有中断能力,编码器仍然可以工作。仅在每次使用 read () 函数期间检查信号。只要你的程序继续足够快地调用read (),你就会得到准确的结果。用于仅由人类手指转动的刻度盘或旋钮的低分辨率旋转编码器是低性能轮询模式的良好候选者。连接到电机的高分辨率编码器通常需要中断!

对于 Arduino Uno、Duemilanove 或 Mega,Serial.print() 可能会造成麻烦。Arduino 1.0 提供传输缓冲,比 Arduino 0023 和更早版本的效果要好得多。无论哪种方式,都应使用高速波特率,并且最大限度地减少传输的数据量会有所帮助。


总结

本文介绍了编码器相关的知识,以及ARDUINO Encoder.h库的使用。

你可能感兴趣的:(玩转硬件,单片机,嵌入式硬件,驱动开发)