Arduino教程——旋转编码器

 

参考资料:https://www.arduino.cn/thread-2423-1-1.html

 

1. 所需的材料

  • 旋转编码器(KY-040)
  • Arduino UNO开发板
  • 字符型图形点阵液晶1602
  • 电位器10k
  • 面包板

2. 连接导线旋转编码器是如何工作的?

旋转编码器是一种机电换能器,意味着它将机械运动转换为电子脉冲。它由旋钮组成,当旋转时,旋钮将逐步移动并产生一系列脉冲序列,每个步骤具有预定义的宽度。有许多类型的编码器,每个编码器都有自己的工作机制,稍后我们将了解这些类型,但现在让我们只关注KY040增量编码器,因为我们将它用于我们的教程。

编码器的内部机械结构如下所示。它基本上由圆盘(灰色)和放置在该圆盘顶部的导电垫(铜色)组成。这些导电焊盘以相同的距离放置,如下所示。输出引脚固定在该圆盘的顶部,这样当旋钮旋转时,导电垫与输出引脚接触。这里有两个输出引脚,输出A和输出B,如下图所示。
在这里插入图片描述

输出引脚A和输出B产生的输出波形分别以蓝色和绿色显示。当导电焊盘直接位于引脚下方时,它会变高,导致导通时间,当导电焊盘移开时,引脚变低,导致上面所示波形的关闭时间。现在,如果我们计算脉冲数,我们将能够确定编码器移动了多少步。

现在可能会出现这样的问题:当一个脉冲信号足以计算旋转旋钮时所采取的步数时,为什么我们需要两个脉冲信号。这是因为我们需要确定旋钮旋转的方向。如果您看一下这两个脉冲,您会注意到它们都是90°异相。因此,当顺时针旋转旋钮时,输出A将首先变高,当旋钮逆时针旋转时,输出B将首先变高。

3. 旋转编码器的类型

市场上有很多种类型的旋转编码器,设计工程师可以根据自己的应用选择一种。最常见的类型如下所示

  • 增量编码器
  • 绝对值编码器
  • 磁编码器
  • 光学编码器
  • 激光编码器

这些编码器基于输出信号和传感技术进行分类,增量编码器和绝对值编码器基于输出信号进行分类,磁、光和激光编码器基于传感技术进行分类。这里使用的编码器是增量型编码器。

4. KY-040旋转编码器引脚和说明

KY-040增量式旋转编码器的引脚分布如下所示

 


前两个引脚(接地和Vcc)用于为编码器供电,通常使用+ 5V电源。除了以顺时针方向和逆时针方向旋转旋钮外,编码器还有一个开关(低电平有效),按下内部的旋钮可以按下该开关。来自此开关的信号通过引脚3(Switch)获得。最后它有两个输出引脚,产生如上所述的波形。

正传波形

 

反转波形

 

 

 

5. Arduino与旋转编码器的连接电路图

旋转编码器与Arduino连接的完整电路图如下图所示

 


旋转编码器有5个引脚,顺序如上图的标签所示。前两个引脚是接地和Vcc,它连接到Arduino的地和+ 5V引脚。编码器的开关连接到数字引脚D8,并通过1k电阻拉高。两个输出引脚分别连接到D9和D8。

要显示通过旋转Rotary编码器增加或减少的变量值,我们需要一个显示模块。这里使用的是常用的字符型图形点阵液晶1602。我们已将连接的显示屏设置成4位工作模式,并使用Arduino的+ 5V引脚为其供电。电位计用于调整LCD显示屏的对比度。完整的电路可以在面包板上进行搭建,一旦完成所有的连接后,效果看起来类似下图。

 


说明,采用单片机内部的上拉输入可能带来较大功耗,因为单片机内部电阻较大,arduino实现的基本原理为已知每圈脉冲数为2500,则当计数大道2500时,计数一圈,若为stm32则进行溢出中断。通过A、B相的高低电平关系,判断AB相的先后关系,进而判断转向。
为了区分正反转及检测零点,通常包括三个部分:A相,B相和Z相,A相与B相相差1/4周期(相位差90度),可以用来区分正转还是反转;Z相为单圈脉冲,码盘转一圈产生一次,可以用作编码器的参考零位,如下图:

 

6. 编写用于旋转编码器的Arduino程序

如果您了解旋转编码器的工作原理,那么编程Arduino开发板以便将旋转编码器连接到它是相当容易的。我们只需要读取脉冲数来确定编码器的转动数,以及首先检查哪个脉冲高,以找到编码器旋转的方向。在本篇文章中,我们将在LCD第一行上显示增加或减少的数字以及在第二行上显示编码器的方向。

由于我们使用了一个LCD显示屏,因此包含了Arduino IDE中默认存在的液晶库。然后我们定义用于连接LCD和Arduino的引脚。最后,我们初始化这些引脚上的LCD显示屏。

#include <LiquidCrystal.h> //Default Arduino LCD Library is included
const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
lcd.begin(16, 2); //Initialise 16*2 LCD

接下来在setup函数中,我们在LCD屏幕上显示介绍消息,然后等待2秒钟,以便用户可以读完该消息。这是为了确保LCD能够正常工作。

#include <LiquidCrystal.h> //Default Arduino LCD Library is included
const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
lcd.begin(16, 2); //Initialise 16*2 LCD

 

Rotary编码器有三个输出引脚,对于Arduino来说,它们都是INPUT引脚。这三个引脚分别是开关(Switch)、输出A(Output A)和输出B(Output B)。这些引脚使用pinMode函数声明为Input,如下所示。

//pin Mode declaration
  pinMode (Encoder_OuputA, INPUT);
  pinMode (Encoder_OuputB, INPUT);
  pinMode (Encoder_Switch, INPUT);

  

在void setup()函数中,我们读取输出A引脚的状态以检查引脚的最后状态。然后,我们将使用此信息与新值进行比较,以检查哪个引脚(输出A或输出B)变高。

Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A

最后在loop函数内,我们必须将输出A和输出B的值与先前输出进行比较,以检查哪一个先变高。这可以通过简单地将A和B的当前输出值与先前输出进行比较来完成,如下所示。

if (digitalRead(Encoder_OuputA) != Previous_Output)
   {
     if (digitalRead(Encoder_OuputB) != Previous_Output)
     {
       Encoder_Count ++;
       lcd.clear(); 
       lcd.print(Encoder_Count);
       lcd.setCursor(0, 1); 
       lcd.print("Clockwise");
     }

 

在上面的代码中,如果输出B已从先前的输出改变,则执行第二个if条件。在这种情况下,编码器变量的值递增,LCD显示编码器以顺时针方向旋转。类似地,如果if条件不符合,则在随后的其他条件中,我们递减变量并显示编码器沿逆时针方向旋转。代码如下所示。

  else
     {
       Encoder_Count--;
       lcd.clear(); 
       lcd.print(Encoder_Count);
       lcd.setCursor(0, 1); 
       lcd.print("Anti - Clockwise");
     }
   }

  

最后,在loop函数结束时,我们必须使用当前输出值更新先前的输出值,以便可以使用相同的逻辑重复循环。

Previous_Output = digitalRead(Encoder_OuputA);

  

另一个可选方法是检查编码器上的开关是否被按下。这可以通过检查旋转编码器上的开关销来监控。该引脚是低电平有效引脚,意味着按下该按钮时它将变为低电平。如果没有按下引脚保持高电平,我们也使用了一个上拉电阻,以确保在未按下开关时保持高电平,从而避免浮空状态。

if (digitalRead(Encoder_Switch) == 0)
   {
     lcd.clear(); 
     lcd.setCursor(0, 1); 
     lcd.print("Switch pressed");
   }

  

 

Arduino控制旋转编码器的工作过程

一旦硬件和代码准备就绪,只需将代码上传到Arduino开发板板,然后向Arduino供电。您可以通过USB电缆为其供电,也可以使用12V适配器。上电时,LCD应显示介绍消息,然后变为空白。现在旋转旋转编码器,您应该看到值根据旋转方向递增或递减。第二行将显示编码器是以顺时针方向还是逆时针方向旋转。下图显示了实际的工作过程:

 

此外,当按下按钮时,第二行将显示按下按钮。这只是一个旋转编码器与Arduino连接的示例程序,检查它是否按预期工作。现在,您应该可以将编码器用于任何项目并进行相应的编程。

7.3 完整代码

/*
   Interfacing Rotary Encoder with Arduino

   Power LCD and Rotary encoder from the +5V pin of Arduino
   LCD RS -> pin 7
   LCD EN -> pin 6
   LCD D4 -> pin 5
   LCD D5 -> pin 4
   LCD D6 -> pin 3
   LCD D7 -> pin 2
   Encoder Switch -> pin 10
   Encoder Output A -> pin 9
   Encoder Output B -> pin 8
*/

int Encoder_OuputA  = 9;
int Encoder_OuputB  = 8;
int Encoder_Switch = 10;

int Previous_Output;
int Encoder_Count;



#include <LiquidCrystal.h>  //Default Arduino LCD Librarey is included 

const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Mention the pin number for LCD connection
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  lcd.begin(16, 2); //Initialise 16*2 LCD

  lcd.print(" Rotary Encoder "); //Intro Message line 1
  lcd.setCursor(0, 1);
  lcd.print("  With Arduino  "); //Intro Message line 2

  delay(2000);
  lcd.clear();

  //pin Mode declaration
  pinMode (Encoder_OuputA, INPUT);
  pinMode (Encoder_OuputB, INPUT);
  pinMode (Encoder_Switch, INPUT);

  Previous_Output = digitalRead(Encoder_OuputA); //Read the inital value of Output A
}

void loop() {
  //aVal = digitalRead(pinA);

  if (digitalRead(Encoder_OuputA) != Previous_Output)
  {
    if (digitalRead(Encoder_OuputB) != Previous_Output)
    {
      Encoder_Count ++;
      lcd.clear();
      lcd.print(Encoder_Count);
      lcd.setCursor(0, 1);
      lcd.print("Clockwise");
    }
    else
    {
      Encoder_Count--;
      lcd.clear();
      lcd.print(Encoder_Count);
      lcd.setCursor(0, 1);
      lcd.print("Anti - Clockwise");
    }
  }

  Previous_Output = digitalRead(Encoder_OuputA);

  if (digitalRead(Encoder_Switch) == 0)
  {
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print("Switch pressed");
  }
}

  

8. 补充其他的网友的代码

#define PinA 2 //外部中断0
#define PinZ 3 //外部中断1
#define PinB 8 //编码器的OUT_B信号连接到数字端口8

//变量初始化
unsigned long time1 = 0; // 时间标记
float time_cw;
float time_ccw;
long count = 0;
const float d = 75.7 / 1000; //轮子的直径
const float pi = 3.141592654;//圆周率
int num = 0;//圈数
double t;//一圈的运动时间
float velocity;
double time3;//外部中断1产生时的时间,即捕捉到Z相的置零信号时,用于在loop循环内进行一圈时间长短的计算

void setup()
{
  pinMode(PinA, INPUT_PULLUP);//因为编码器信号为欧姆龙E6B2-CWZ6C,为开漏输出,因此需要上拉电阻,此处采用arduino的内部上拉输入模式,置高
  pinMode(PinB, INPUT_PULLUP);//同上
  pinMode(PinZ, INPUT_PULLUP);//同上
  attachInterrupt(0, Encode, FALLING);//脉冲中断函数:捕捉A相信号,并判断A、B相先后顺序
  attachInterrupt(1, Set_state , FALLING);//用于在捕捉到Z的零信号时,进行状态置零

  Serial.begin (9600);
}

void loop()
{
  double distance;
  //正转
  if (count == 2500)
  {
    //      Serial.println("ok");//调试用
    num = num + 1;
    time_cw = millis();
    t = time_cw - time3;
    t = t / 1000;
    distance = num * d * pi;
    velocity = d * pi / t;
    Serial.print("The wheel has run ");
    Serial.print(distance);
    Serial.println("m.");
    Serial.print("The cw_speed is ");
    Serial.print(velocity);
    Serial.println("m/s.");
    Serial.print("The time is ");
    Serial.print(t);
    Serial.println("s.");
  }
  //反转
  if (count == -2500)
  {
    //      Serial.println("ok");//调试用
    num = num + 1;
    time_ccw = millis();
    t = time_ccw - time3;
    t = t / 1000;
    distance = num * d * pi;
    velocity = d * pi / t;
    Serial.print("The wheel has run ");
    Serial.print(distance);
    Serial.println("m.");
    Serial.print("The ccw_speed is ");
    Serial.print(velocity);
    Serial.println("m/s.");
    Serial.print("The time is ");
    Serial.print(t);
    Serial.println("s.");
  }
}

// 编码器计数中断子程序

void Encode()
{
  //为了不计入噪音干扰脉冲,
  //当2次中断之间的时间大于5ms时,计一次有效计数
  if ((millis() - time1) > 5)
  {
    //当编码器码盘的OUTA脉冲信号下跳沿每中断一次,
    if ((digitalRead(PinA) == LOW) && (digitalRead(PinB) == HIGH))
    {
      count--;
    }
    else
    {
      count++;
    }
  }
  time1 == millis();
}
void Set_state() {
  count = 0;
  time3 = millis();//发生中断时的时间
}

  

 
posted @ 2022-01-15 14:09  xiaoberber  阅读(6557)  评论(1编辑  收藏  举报