格物致知、诚意正心
旋转编码器是一种位置传感器,可将旋钮的角位置(旋转)转换为用于确定旋钮旋转方向的输出信号。
由于其坚固性和良好的数字控制;它们被用于许多应用中,包括机器人技术,CNC机器和打印机。
旋转编码器有两种类型-绝对式和增量式。绝对编码器为我们提供旋钮的精确位置(以度为单位),而增量编码器报告轴已移动了多少增量。
本教程中使用的旋转编码器为增量型。
旋转编码器是电位计的现代数字等效产品,比电位计功能更广泛。
它们可以完全旋转而无止挡,而电位计只能旋转大约3/4的圆。
电位器最适合您需要了解旋钮确切位置的情况。但是,在您需要知道位置变化而不是确切位置的情况下,旋转编码器是最好的。
编码器内部是一个与公共接地引脚C以及两个接触引脚A和B连接的带槽磁盘,如下图所示。
旋转旋钮时,A和B根据旋转旋钮的方向以特定顺序与公共接地引脚C接触。
当它们接触公共接地时,它们会产生信号。当一个引脚先于另一个引脚接触时,这些信号会彼此错开90°相位。这称为正交编码。
顺时针旋转旋钮时,首先连接A引脚,然后连接B引脚。逆时针旋转旋钮时,首先连接B引脚,然后连接A引脚。
通过跟踪每个引脚何时与地面连接和与地面断开连接,我们可以使用这些信号变化来确定旋钮的旋转方向。您可以通过在A更改状态时简单地观察B的状态来做到这一点。
当A更改状态时:
旋转编码器的引脚排列如下:
地线 是接地连接。
VCC 是正电源电压,通常为3.3或5伏。
西南是低电平有效的按钮开关输出。按下旋钮时,电压变低。
DT(输出B)与CLK输出相同,但比CLK滞后90°。该输出可用于确定旋转方向。
CLK(输出A)是确定旋转量的主要输出脉冲。每次将旋钮向任一方向旋转一个de子(单击),“ CLK”输出都会经过一个先变高然后再变低的周期。
现在我们已经了解了旋转编码器的所有知识,现在该使用它了!
让我们将旋转编码器连接到Arduino。连接非常简单。首先将模块上的+ V引脚连接到Arduino上的5V,并将GND引脚接地。
现在将CLK和DT引脚分别连接到数字引脚2和3。最后,将SW引脚连接到数字引脚4。
下图显示了接线。
现在,您已经连接了编码器,您将需要一个草图以使其全部正常工作。
下图显示了何时旋转编码器,确定旋转方向以及是否按下了按钮。
尝试一下草图;然后我们将对其进行详细剖析。
// 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变量表示每次将旋钮旋转一个定位器(单击)时将修改的计数。
counter
的currentStateCLK和lastStateCLK的变量保持CLK输出的状态,并且被用于确定旋转量。
currentStateCLK
lastStateCLK
currentDir在串行监视器上打印当前旋转方向时,将使用一个称为的字符串。
currentDir
该lastButtonPress变量用于消除开关的抖动。
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(); }
然后,我们重新做一遍。
为了使旋转编码器正常工作,我们需要连续监视DT和CLK信号的变化。
为了确定何时发生此类更改,我们可以连续地对其进行轮询(就像在上一个草图中所做的那样)。但是,由于以下原因,这不是最佳解决方案。
广泛采用的解决方案是使用中断。
有中断您无需持续轮询特定事件。这使Arduino腾出了时间来完成其他工作,同时又不会错过该事件。
由于大多数Arduino(包括Arduino UNO)只有两个外部中断,因此我们只能监视DT和CLK信号的变化。这就是为什么我们从先前的接线图中删除了SW引脚的连接的原因。
所以现在接线看起来像这样:
一些板卡(例如Arduino Mega 2560)具有更多的外部中断。如果有其中之一,则可以保留SW引脚的连接,并在草图下方延伸以包括按钮的代码。
这是演示在读取旋转编码器时使用中断的示意图。
// 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)。执行此函数中的代码,然后程序返回到之前的操作。
updateEncoder
以下两行负责所有这一切。该函数attachInterrupt()告诉Arduino监视哪个引脚,如果触发了中断则执行哪个ISR以及寻找哪种类型的触发器。
attachInterrupt()
attachInterrupt(0, updateEncoder, CHANGE); attachInterrupt(1, updateEncoder, CHANGE);
在下一个项目中,我们将使用旋转编码器来控制伺服电机的位置。
该项目在许多情况下都非常有用,例如,当您要操作机器人手臂时,因为它可以让您精确定位手臂及其握持位置。
如接线图所示,您将需要一台伺服电动机。将伺服电机的红色线连接到外部5V电源,将黑色/棕色线接地,并将橙色/黄色线连接到PWM使能引脚9。
当然,您可以使用Arduino 5V输出,但是请记住,伺服器可能会在Arduino使用的5V线上感应电噪声,这可能不是您想要的。
因此,建议您使用外部电源。
这是使用旋转编码器精确控制伺服电机的示意图。每次旋转旋钮一个棘爪(咔嗒一声),伺服臂的位置将改变一度。
// 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伺服库,并创建一个表示我们的伺服电机的伺服对象。
#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);
您的电子邮箱地址不会被公开。 必填项已用 * 标注
评论 *
显示名称 *
电子邮箱地址 *
网站
在此浏览器中保存我的显示名称、邮箱地址和网站地址,以便下次评论时使用。