旋转编码器如何工作并与Arduino接口

旋转编码器是一种位置传感器,可将旋钮的角位置(旋转)转换为用于确定旋钮旋转方向的输出信号。

由于其坚固性和良好的数字控制;它们被用于许多应用中,包括机器人技术,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变量表示每次将旋钮旋转一个定位器(单击)时将修改的计数。

currentStateCLKlastStateCLK的变量保持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 
​
// 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 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);

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