Arduino教程——旋转编码器
参考资料:https://www.arduino.cn/thread-2423-1-1.html
1. 所需的材料
- 旋转编码器(KY-040)
- Arduino UNO开发板
- 字符型图形点阵液晶1602
- 电位器10k
- 面包板
2. 连接导线旋转编码器是如何工作的?
旋转编码器是一种机电换能器,意味着它将机械运动转换为电子脉冲。它由旋钮组成,当旋转时,旋钮将逐步移动并产生一系列脉冲序列,每个步骤具有预定义的宽度。有许多类型的编码器,每个编码器都有自己的工作机制,稍后我们将了解这些类型,但现在让我们只关注KY040增量编码器,因为我们将它用于我们的教程。
编码器的内部机械结构如下所示。它基本上由圆盘(灰色)和放置在该圆盘顶部的导电垫(铜色)组成。这些导电焊盘以相同的距离放置,如下所示。输出引脚固定在该圆盘的顶部,这样当旋钮旋转时,导电垫与输出引脚接触。这里有两个输出引脚,输出A和输出B,如下图所示。
输出引脚A和输出B产生的输出波形分别以蓝色和绿色显示。当导电焊盘直接位于引脚下方时,它会变高,导致导通时间,当导电焊盘移开时,引脚变低,导致上面所示波形的关闭时间。现在,如果我们计算脉冲数,我们将能够确定编码器移动了多少步。
现在可能会出现这样的问题:当一个脉冲信号足以计算旋转旋钮时所采取的步数时,为什么我们需要两个脉冲信号。这是因为我们需要确定旋钮旋转的方向。如果您看一下这两个脉冲,您会注意到它们都是90°异相。因此,当顺时针旋转旋钮时,输出A将首先变高,当旋钮逆时针旋转时,输出B将首先变高。
3. 旋转编码器的类型
市场上有很多种类型的旋转编码器,设计工程师可以根据自己的应用选择一种。最常见的类型如下所示
- 增量编码器
- 绝对值编码器
- 磁编码器
- 光学编码器
- 激光编码器
这些编码器基于输出信号和传感技术进行分类,增量编码器和绝对值编码器基于输出信号进行分类,磁、光和激光编码器基于传感技术进行分类。这里使用的编码器是增量型编码器。
4. KY-040旋转编码器引脚和说明
KY-040增量式旋转编码器的引脚分布如下所示
前两个引脚(接地和Vcc)用于为编码器供电,通常使用+ 5V电源。除了以顺时针方向和逆时针方向旋转旋钮外,编码器还有一个开关(低电平有效),按下内部的旋钮可以按下该开关。来自此开关的信号通过引脚3(Switch)获得。最后它有两个输出引脚,产生如上所述的波形。
正传波形
反转波形
5. Arduino与旋转编码器的连接电路图
旋转编码器与Arduino连接的完整电路图如下图所示
旋转编码器有5个引脚,顺序如上图的标签所示。前两个引脚是接地和Vcc,它连接到Arduino的地和+ 5V引脚。编码器的开关连接到数字引脚D8,并通过1k电阻拉高。两个输出引脚分别连接到D9和D8。
要显示通过旋转Rotary编码器增加或减少的变量值,我们需要一个显示模块。这里使用的是常用的字符型图形点阵液晶1602。我们已将连接的显示屏设置成4位工作模式,并使用Arduino的+ 5V引脚为其供电。电位计用于调整LCD显示屏的对比度。完整的电路可以在面包板上进行搭建,一旦完成所有的连接后,效果看起来类似下图。
说明,采用单片机内部的上拉输入可能带来较大功耗,因为单片机内部电阻较大,arduino实现的基本原理为已知每圈脉冲数为2500,则当计数大道2500时,计数一圈,若为stm32则进行溢出中断。通过A、B相的高低电平关系,判断AB相的先后关系,进而判断转向。
为了区分正反转及检测零点,通常包括三个部分:A相,B相和Z相,A相与B相相差1/4周期(相位差90度),可以用来区分正转还是反转;Z相为单圈脉冲,码盘转一圈产生一次,可以用作编码器的参考零位,如下图:
6. 编写用于旋转编码器的Arduino程序
如果您了解旋转编码器的工作原理,那么编程Arduino开发板以便将旋转编码器连接到它是相当容易的。我们只需要读取脉冲数来确定编码器的转动数,以及首先检查哪个脉冲高,以找到编码器旋转的方向。在本篇文章中,我们将在LCD第一行上显示增加或减少的数字以及在第二行上显示编码器的方向。
由于我们使用了一个LCD显示屏,因此包含了Arduino IDE中默认存在的液晶库。然后我们定义用于连接LCD和Arduino的引脚。最后,我们初始化这些引脚上的LCD显示屏。
#include <LiquidCrystal.h> //Default Arduino LCD Library is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); lcd.begin(16, 2); //Initialise 16*2 LCD
接下来在setup函数中,我们在LCD屏幕上显示介绍消息,然后等待2秒钟,以便用户可以读完该消息。这是为了确保LCD能够正常工作。
#include <LiquidCrystal.h> //Default Arduino LCD Library is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); lcd.begin(16, 2); //Initialise 16*2 LCD
Rotary编码器有三个输出引脚,对于Arduino来说,它们都是INPUT引脚。这三个引脚分别是开关(Switch)、输出A(Output A)和输出B(Output B)。这些引脚使用pinMode函数声明为Input,如下所示。
//pin Mode declaration pinMode (Encoder_OuputA, INPUT); pinMode (Encoder_OuputB, INPUT); pinMode (Encoder_Switch, INPUT);
在void setup()函数中,我们读取输出A引脚的状态以检查引脚的最后状态。然后,我们将使用此信息与新值进行比较,以检查哪个引脚(输出A或输出B)变高。
Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A
最后在loop函数内,我们必须将输出A和输出B的值与先前输出进行比较,以检查哪一个先变高。这可以通过简单地将A和B的当前输出值与先前输出进行比较来完成,如下所示。
if (digitalRead(Encoder_OuputA) != Previous_Output) { if (digitalRead(Encoder_OuputB) != Previous_Output) { Encoder_Count ++; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Clockwise"); }
在上面的代码中,如果输出B已从先前的输出改变,则执行第二个if条件。在这种情况下,编码器变量的值递增,LCD显示编码器以顺时针方向旋转。类似地,如果if条件不符合,则在随后的其他条件中,我们递减变量并显示编码器沿逆时针方向旋转。代码如下所示。
else { Encoder_Count--; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Anti - Clockwise"); } }
最后,在loop函数结束时,我们必须使用当前输出值更新先前的输出值,以便可以使用相同的逻辑重复循环。
Previous_Output = digitalRead(Encoder_OuputA);
另一个可选方法是检查编码器上的开关是否被按下。这可以通过检查旋转编码器上的开关销来监控。该引脚是低电平有效引脚,意味着按下该按钮时它将变为低电平。如果没有按下引脚保持高电平,我们也使用了一个上拉电阻,以确保在未按下开关时保持高电平,从而避免浮空状态。
if (digitalRead(Encoder_Switch) == 0) { lcd.clear(); lcd.setCursor(0, 1); lcd.print("Switch pressed"); }
Arduino控制旋转编码器的工作过程
一旦硬件和代码准备就绪,只需将代码上传到Arduino开发板板,然后向Arduino供电。您可以通过USB电缆为其供电,也可以使用12V适配器。上电时,LCD应显示介绍消息,然后变为空白。现在旋转旋转编码器,您应该看到值根据旋转方向递增或递减。第二行将显示编码器是以顺时针方向还是逆时针方向旋转。下图显示了实际的工作过程:
此外,当按下按钮时,第二行将显示按下按钮。这只是一个旋转编码器与Arduino连接的示例程序,检查它是否按预期工作。现在,您应该可以将编码器用于任何项目并进行相应的编程。
7.3 完整代码
/* Interfacing Rotary Encoder with Arduino Power LCD and Rotary encoder from the +5V pin of Arduino LCD RS -> pin 7 LCD EN -> pin 6 LCD D4 -> pin 5 LCD D5 -> pin 4 LCD D6 -> pin 3 LCD D7 -> pin 2 Encoder Switch -> pin 10 Encoder Output A -> pin 9 Encoder Output B -> pin 8 */ int Encoder_OuputA = 9; int Encoder_OuputB = 8; int Encoder_Switch = 10; int Previous_Output; int Encoder_Count; #include <LiquidCrystal.h> //Default Arduino LCD Librarey is included const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); //Initialise 16*2 LCD lcd.print(" Rotary Encoder "); //Intro Message line 1 lcd.setCursor(0, 1); lcd.print(" With Arduino "); //Intro Message line 2 delay(2000); lcd.clear(); //pin Mode declaration pinMode (Encoder_OuputA, INPUT); pinMode (Encoder_OuputB, INPUT); pinMode (Encoder_Switch, INPUT); Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A } void loop() { //aVal = digitalRead(pinA); if (digitalRead(Encoder_OuputA) != Previous_Output) { if (digitalRead(Encoder_OuputB) != Previous_Output) { Encoder_Count ++; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Clockwise"); } else { Encoder_Count--; lcd.clear(); lcd.print(Encoder_Count); lcd.setCursor(0, 1); lcd.print("Anti - Clockwise"); } } Previous_Output = digitalRead(Encoder_OuputA); if (digitalRead(Encoder_Switch) == 0) { lcd.clear(); lcd.setCursor(0, 1); lcd.print("Switch pressed"); } }
8. 补充其他的网友的代码
#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();//发生中断时的时间 }