旋转编码器是一种位置传感器,可将旋钮的角位置(旋转)转换为用于确定旋钮旋转方向的输出信号。
由于其坚固性和良好的数字控制;它们被用于许多应用中,包括机器人技术,CNC机器和打印机。
旋转编码器有两种类型-绝对式和增量式。绝对编码器为我们提供旋钮的精确位置(以度为单位),而增量编码器报告轴已移动了多少增量。
本教程中使用的旋转编码器为增量型。
旋转编码器与电位器
旋转编码器是电位计的现代数字等效产品,比电位计功能更广泛。
它们可以完全旋转而无止挡,而电位计只能旋转大约3/4的圆。
电位器最适合您需要了解旋钮确切位置的情况。但是,在您需要知道位置变化而不是确切位置的情况下,旋转编码器是最好的。
旋转编码器如何工作
编码器内部是一个槽形磁盘,该磁盘连接到公共接地引脚C以及两个接触针A和B。旋转旋钮时,A和B根据旋转旋钮的方向以特定顺序与公共接地引脚C接触。当它们接触公共接地时,它们会产生信号。当一个引脚先于另一引脚接触时,这些信号就会彼此错开90°。这称为正交编码。顺时针旋转旋钮时,首先连接A引脚,然后连接B引脚。逆时针旋转旋钮时,首先连接B引脚,然后连接A引脚。通过跟踪每个引脚何时与地面连接或与地面断开,我们可以使用这些信号变化来确定旋钮的旋转方向。您可以通过在A更改状态时观察B的状态来做到这一点。
旋转编码器引脚排列
旋转编码器的引脚排列如下:
GND 是接地连接。
VCC 是正电源电压,通常为3.3或5伏。
SW 是低电平有效的按钮开关输出。按下旋钮时,电压变低。
DT(输出B)与CLK输出相同,但比CLK滞后90°。该输出可用于确定旋转方向。
CLK(输出A)是确定旋转量的主要输出脉冲。每次将旋钮向任一方向旋转一个de子(单击),“ CLK”输出都会经过一个先变高然后再变低的周期。
接线–将旋转编码器连接到Arduino
现在我们已经了解了旋转编码器的所有知识,现在该使用它了!
让我们将旋转编码器连接到Arduino。连接非常简单。首先将模块上的+ V引脚连接到Arduino上的5V,并将GND引脚接地。
现在将CLK和DT引脚分别连接到数字引脚2和3。最后,将SW引脚连接到数字引脚4。
Arduino代码–读取旋转编码器
现在您已经连接了编码器,您将需要一个草图以使其全部正常工作。
下图显示了何时旋转编码器,确定旋转方向以及是否按下按钮。
尝试一下草图;然后我们将对其进行详细分析。
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
#define SW 4
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
}
void loop() {
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
// Read the button state
int btnState = digitalRead(SW);
//If we detect LOW signal, button is pressed
if (btnState == LOW) {
//if 50ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
}
// Remember last button press event
lastButtonPress = millis();
}
// Put in a slight delay to help debounce the reading
delay(1);
}
如果报告的旋转与您期望的相反,请尝试交换CLK和DT线。
代码说明:
草图从声明编码器的CLK,DT和SW引脚连接到的Arduino引脚开始。
#define CLK 2
#define DT 3
#define SW 4
接下来,定义一些整数。该counter
变量表示每次将旋钮旋转一个定位器(单击)时将修改的计数。
的currentStateCLK
和lastStateCLK
的变量保持CLK输出的状态,并且被用于确定旋转量。
currentDir
在串行监视器上打印当前旋转方向时,将使用一个称为的字符串。
该lastButtonPress
变量用于消除开关的抖动。
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
unsigned long lastButtonPress = 0;
现在在“设置”部分,我们首先将编码器的连接定义为输入,然后在SW引脚上启用输入上拉电阻。我们还设置了串行监视器。
最后,我们读取CLK引脚的当前值并将其存储在lastStateCLK
变量中。
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
pinMode(SW, INPUT_PULLUP);
Serial.begin(9600);
lastStateCLK = digitalRead(CLK);
在循环部分,我们再次检查CLK状态并将其与该lastStateCLK
值进行比较。如果它们不同,则意味着旋钮已旋转且发生了脉冲。我们还检查的值是否currentStateCLK
为1,以便仅对一个状态更改做出反应,以避免重复计数。
currentStateCLK = digitalRead(CLK);
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
在if语句中,我们确定旋转方向。为此,我们只需读取编码器模块上的DT引脚,并将其与CLK引脚的当前状态进行比较。
如果它们不同,则表示旋钮逆时针旋转。然后,我们递减计数器,并将其设置currentDir
为“ CCW”。
如果两个值相同,则表示旋钮顺时针旋转。然后,我们增加计数器并设置currentDir
为“ CW”。
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
counter ++;
currentDir ="CW";
}
然后,我们在串行监视器上打印结果。
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
在if语句之外,我们lastStateCLK
以CLK的当前状态更新。
lastStateCLK = currentStateCLK;
接下来是读取和反跳按钮开关的逻辑。我们首先读取当前按钮状态,如果它为LOW,则等待50ms来使按钮去抖动。
如果按钮保持低电平的时间超过50毫秒,我们将打印“按钮按下!” 串行监视器上显示消息。
int btnState = digitalRead(SW);
if (btnState == LOW) {
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
}
lastButtonPress = millis();
}
然后,我们再做一次。
Arduino代码–使用中断
为了使旋转编码器正常工作,我们需要连续监视DT和CLK信号的变化。
为了确定何时发生此类更改,我们可以连续地对其进行轮询(就像在上一个草图中所做的那样)。但是,由于以下原因,这不是最佳解决方案。
- 我们必须忙于执行检查以查看值是否已更改。如果信号电平不变,则会浪费周期。
- 从事件发生到我们检查之间会有延迟。如果我们需要立即做出反应,我们将为此延迟而延迟。
- 如果更改的持续时间很短,则可能会完全错过信号更改。
广泛采用的解决方案是使用中断。
有中断您无需持续轮询特定事件。这使Arduino腾出了时间来完成其他工作,同时又不会错过该事件。
接线
由于大多数Arduino(包括Arduino UNO)只有两个外部中断,因此我们只能监视DT和CLK信号的变化。这就是为什么我们从先前的接线图中删除了SW引脚的连接的原因。
一些板卡(例如Arduino Mega 2560)具有更多的外部中断。如果有其中之一,则可以保留SW引脚的连接,并在草图下方延伸以包括按钮的代码。
Arduino代码
这是演示在读取旋转编码器时使用中断的示意图。
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Call updateEncoder() when any high/low changed seen
// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);
}
void loop() {
//Do some useful stuff here
}
void updateEncoder(){
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
请注意,该程序的主循环保持为空,因此Arduino将无所事事。
同时,该程序监视数字引脚2(对应于中断0)和数字引脚3(对应于中断1)的值变化。换句话说,它寻找电压变化,从高到低或从低到高,这是在您旋转旋钮时发生的。
发生这种情况时,将updateEncoder
调用该函数(通常称为中断服务程序或ISR)。执行此函数中的代码,然后程序返回到之前的操作。
以下两行负责所有这一切。该函数attachInterrupt()
告诉Arduino监视哪个引脚,如果触发了中断则执行哪个ISR以及寻找哪种类型的触发器。
attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);
带旋转编码器的控制伺服电机
在下一个项目中,我们将使用旋转编码器来控制伺服电机的位置。
该项目在许多情况下都非常有用,例如,当您要操作机器人手臂时,它可以让您精确定位手臂及其握持位置。
是否想在不构建电机控制器的情况下将运动添加到下一个Arduino项目?那么伺服电机可能是您的坚实起点。
接线
如接线图所示,您将需要一台伺服电动机。将伺服电机的红色线连接到外部5V电源,将黑色/棕色线接地,并将橙色/黄色线连接到PWM使能引脚9。
当然,您可以使用Arduino 5V输出,但请记住,伺服器可能会在Arduino使用的5V线上感应电噪声,这可能不是您想要的。
因此,建议您使用外部电源。
Arduino代码
这是使用旋转编码器精确控制伺服电机的示意图。每次将旋钮旋转一个(咔嗒声),伺服臂的位置将改变一度。
// Include the Servo Library
#include <Servo.h>
// Rotary Encoder Inputs
#define CLK 2
#define DT 3
Servo servo;
int counter = 0;
int currentStateCLK;
int lastStateCLK;
void setup() {
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Attach servo on pin 9 to the servo object
servo.attach(9);
servo.write(counter);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
}
void loop() {
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
if (counter<0)
counter=0;
} else {
// Encoder is rotating CW so increment
counter ++;
if (counter>179)
counter=179;
}
// Move the servo
servo.write(counter);
Serial.print("Position: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
如果将此草图与我们的第一个草图进行比较,您会注意到许多相似之处,除了几处内容。
首先,我们包括内置的Arduino Servo库,并创建一个表示我们的伺服电机的伺服对象。
#include <Servo.h>
Servo servo;
在设置中,我们将伺服对象连接到引脚9(与伺服电机的控制引脚相连的引脚)。
servo.attach(9);
在循环中,我们限制计数器的范围为0到179,因为伺服电机只接受该范围之间的值。
if (digitalRead(DT) != currentStateCLK) {
counter --;
if (counter<0)
counter=0;
} else {
counter ++;
if (counter>179)
counter=179;
}
最后,计数器值用于定位伺服电机。
servo.write(counter);