旋转编码器的工作原理及其与 Arduino 的接口

旋转编码器与 arduino 连接的教程

我们被旋转编码器包围着,甚至没有意识到,因为它们被用于许多日常用品中,从打印机和相机到数控机床和机器人。旋转编码器最常见的应用是汽车收音机上的音量旋钮。

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

旋转编码器分为绝对式和增量式两种。绝对编码器报告旋钮的精确位置(以度为单位),而增量编码器报告轴移动的增量数。

本教程中使用的旋转编码器是增量式的。

旋转编码器与电位器

旋转编码器是电位器的现代数字等效物,并且用途更广泛。

旋转编码器可以不停地旋转360°,而电位器只能旋转3/4圈。

电位器用于需要知道旋钮准确位置的情况。另一方面,旋转编码器用于需要知道位置变化而不是确切位置的情况。

旋转编码器如何工作?

编码器内部有一个开槽圆盘,连接到公共接地引脚 C。它还具有两个接触针 A 和 B,如下所示。

旋转编码器内部结构

当您转动旋钮时,A 和 B 按照特定顺序与公共接地引脚 C 接触,具体顺序取决于转动旋钮的方向。

当它们与公共地接触时,会产生两个信号。这些信号存在 90° 异相,因为一个引脚先于另一个引脚接触公共地。它被称为正交编码

旋转编码器工作动画

当顺时针旋转旋钮时,A 引脚先于 B 引脚接地。当逆时针旋转旋钮时,B 引脚先于 A 引脚接地。

通过监控每个引脚何时连接或断开接地,我们可以确定旋钮旋转的方向。这可以通过简单地观察 A 的状态改变时 B 的状态来完成。

当A改变状态时:

  • 如果 B != A,则顺时针转动旋钮。

    旋转编码器顺时针旋转输出脉冲

  • 如果 B = A,则逆时针转动旋钮。

    旋转编码器逆时针旋转输出脉冲

旋转编码器引脚分配

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

旋转编码器模块引脚排列

接地是接地连接。

vcc是正电源电压,通常在 3.3 至 5 伏之间。

SW是按钮开关的输出(低电平有效)。当按下旋钮时,电压变低。

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

CLK(输出A)是用于确定旋转量的主要输出脉冲。每次仅通过一个制动装置(咔嗒声)向任一方向转动旋钮时,“CLK”输出就会经历一个先高后低的周期。

将旋转编码器连接到 Arduino

现在我们了解了旋转编码器的工作原理,是时候使用它了!

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

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

下图显示了接线。

用arduino uno接线旋转编码器

Arduino 示例代码 1 – 读取旋转编码器

我们的第一个例子非常简单;它只是检测编码器的旋转方向以及按下按钮的时间。

首先,尝试草图,然后我们将更详细地讨论它。

// 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(输出 A)和 DT(输出 B)引脚。

代码说明:

该草图首先声明编码器的 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 引脚的当前状态进行比较。

  • 如果这两个值不同,则表明旋钮逆时针转动。然后将counter递减,并将currentDir设为“CCW”。
  • 如果这两个值相同,则表明旋钮是顺时针转动的。counter然后 递增,并将设为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声明之后,我们更新lastStateCLKCLK 的当前状态。

lastStateCLK = currentStateCLK;

下一步涉及读取按钮开关并对其进行去抖。我们首先读取当前按钮状态,当它变为低电平时,我们等待 50 毫秒,让按钮去抖。

如果按钮保持低电平超过50ms,则表明确实被按下。结果,我们打印“按钮按下!” 到串行监视器。

int btnState = digitalRead(SW);

if (btnState == LOW) {
    if (millis() - lastButtonPress > 50) {
        Serial.println("Button pressed!");
    }
    lastButtonPress = millis();
}

然后我们重复这个过程。

Arduino 示例代码 2 – 使用中断

要读取旋转编码器,我们必须不断监视DT和CLK信号的变化。

检测这些变化的一种方法是连续轮询它们,就像我们在之前的草图中所做的那样。然而,由于以下原因,这不是最佳解决方案。

  • 我们必须经常检查该值是否已更改。如果信号电平不改变,就会浪费周期。
  • 事件发生的时间和我们检查的时间之间可能存在延迟。如果我们需要快速反应,我们就会因为这种延迟而被耽搁。
  • 如果变化的持续时间很短,则有可能完全错过信号变化。

处理这个问题的一种方法是使用中断

通过中断,无需连续轮询特定事件。这使得 Arduino 能够腾出时间来执行其他任务,而不会错过任何事件。

接线

由于大多数Arduino板(包括Arduino UNO)只有两个外部中断,因此我们只能监控DT和CLK信号的变化。因此,我们将移除 SW 引脚连接。

有些板(例如 Arduino Mega 2560)比其他板有更多的外部中断。如果您有其中之一,则可以保留 SW 引脚连接并修改下面的草图以包含按钮代码。

更新后的接线布局如下:

使用arduino uno使用中断控制旋转编码器

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)的信号变化。换句话说,它会检测由于转动旋钮而导致电压何时从高变为低或从低变为高。

当发生变化时,Arduino 会立即检测到它,保存其执行状态,执行该函数updateEncoder()(也称为中断服务例程或简称ISR),然后返回到之前正在执行的操作。

以下两行配置中断。该attachInterrupt()函数指示 Arduino 要监视哪个引脚、触发中断时要执行哪个 ISR,以及要查找什么类型的触发器。

attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);

Arduino 示例代码 3 – 使用旋转编码器控制伺服电机

在下面的示例中,我们将使用旋转编码器来控制伺服电机的位置。

该项目在各种情况下都非常有用。例如,如果您想操作机械臂,它可以帮助您准确定位机械臂及其抓握位置。如果您对伺服电机不熟悉,请阅读以下教程。

伺服电机与 Arduino 接口教程

接线

让我们在我们的项目中包含一个伺服电机。将伺服电机的红线连接到外部 5V 电源,将黑/棕色线连接到地,将橙色/黄线连接到启用 PWM 的数字引脚 9。

当然,您可以使用 Arduino 的 5V 输出,但请记住,伺服器可能会在 5V 电源线上感应出电噪声,这可能会损坏您的 Arduino。因此,建议您使用外部电源。

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

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);
  • 14
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值