编码器

作者:hardihuang   hardihuang   
旋转编码器与arduino接口的教程

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

由于其坚固性和良好的数字控制;它们被用于许多应用中,包括机器人技术,CNC机器和打印机。

旋转编码器有两种类型-绝对式和增量式。绝对编码器为我们提供旋钮的精确位置(以度为单位),而增量编码器报告轴已移动了多少增量。

本教程中使用的旋转编码器为增量型。

旋转编码器与电位器 #

旋转编码器是电位计的现代数字等效产品,比电位计功能更广泛。

它们可以完全旋转而无止挡,而电位计只能旋转大约3/4的圆。

电位器最适合您需要了解旋钮确切位置的情况。但是,在您需要知道位置变化而不是确切位置的情况下,旋转编码器是最好的。

旋转编码器如何工作 #

编码器内部是一个与公共接地引脚C以及两个接触引脚A和B连接的带槽磁盘,如下图所示。

旋转编码器内部结构

旋转旋钮时,A和B根据旋转旋钮的方向以特定顺序与公共接地引脚C接触。

当它们接触公共接地时,它们会产生信号。当一个引脚先于另一个引脚接触时,这些信号会彼此错开90°相位。这称为正交编码

旋转编码器工作animation.gif

顺时针旋转旋钮时,首先连接A引脚,然后连接B引脚。逆时针旋转旋钮时,首先连接B引脚,然后连接A引脚。

通过跟踪每个引脚何时与地面连接和与地面断开连接,我们可以使用这些信号变化来确定旋钮的旋转方向。您可以通过在A更改状态时简单地观察B的状态来做到这一点。

当A更改状态时:

  • 如果B!= A,则将旋钮顺时针旋转。顺时针旋转旋转编码器输出脉冲
  • 如果B = A,则将旋钮逆时针旋转。旋转编码器输出脉冲逆时针旋转

旋转编码器引脚排列 #

旋转编码器的引脚排列如下:

旋转编码器模块的引脚排列

地线 是接地连接。

VCC 是正电源电压,通常为3.3或5伏。

西南是低电平有效的按钮开关输出。按下旋钮时,电压变低。

DT(输出B)与CLK输出相同,但比CLK滞后90°。该输出可用于确定旋转方向。

CLK(输出A)是确定旋转量的主要输出脉冲。每次将旋钮向任一方向旋转一个de子(单击),“ CLK”输出都会经过一个先变高然后再变低的周期。

接线–将旋转编码器连接到Arduino #

现在我们已经了解了旋转编码器的所有知识,现在该使用它了!

让我们将旋转编码器连接到Arduino。连接非常简单。首先将模块上的+ V引脚连接到Arduino上的5V,并将GND引脚接地。

现在将CLK和DT引脚分别连接到数字引脚2和3。最后,将SW引脚连接到数字引脚4。

下图显示了接线。

arduino uno接线旋转编码器

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 uno的中断来控制旋转编码器

一些板卡(例如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);

带旋转编码器的控制伺服电机 #

在下一个项目中,我们将使用旋转编码器来控制伺服电机的位置。

该项目在许多情况下都非常有用,例如,当您要操作机器人手臂时,因为它可以让您精确定位手臂及其握持位置。

接线

如接线图所示,您将需要一台伺服电动机。将伺服电机的红色线连接到外部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伺服库,并创建一个表示我们的伺服电机的伺服对象。

#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);

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注