KY-040旋转编码器接Arduino:判方向、消抖与计数

2026-11-13 07:18:48 15阅读

音量旋钮一转,数字就跳;微波炉调时间、鼠标滚轮滚页面,背后干活的多半是旋转编码器。它还能反过来当传感器用——装到直流减速电机的尾巴上,就能知道电机轴转了多少圈、转得多快,配上驱动器就是一套位置反馈。这次轮到KY-040——37合1传感器套件里的老熟人,五根针、一根能按下去的轴,接上Arduino就能把"转了多少格、往哪边转、轴有没有被按下"全部读出来。这篇把引脚、接线、方向判断、消抖和计数一次讲透,代码照抄就能跑。

旋转编码器接Arduino串口读数演示

先说清楚它和电位器的区别。电位器转到底就停,读的是模拟电压,还会随温度漂;旋转编码器没有尽头,随便转,输出的是数字脉冲,数脉冲就知道转了多少。精度要求高的调节场合,它比电位器靠谱得多。

一、KY-040五个引脚各管什么

模块上五个引脚,从边上一路数过来:

  • GND——接地。
  • +(VCC)——供电,接Arduino的5V或3.3V都行。
  • SW——轴上自带的自复位按键输出。平时被上拉电阻拉在高电平,把轴往下一按,这脚就被拉低到地。
  • CLK——两路正交输出信号之一,接数字输入脚。
  • DT——另一路正交输出,波形和CLK长得一样,就是差了90度相位。

KY-040旋转编码器模块引脚定义图

里面是什么结构?一张开槽的圆形码盘跟着轴一起转,盘上有两个触点A和B,轮流接触公共端C(也就是地)。关键在于两个触点的位置是错开的,永远不会同时接触地——A总比B早碰到或者晚碰到那么一下,两路信号就这么错开了。

旋转编码器模块引脚特写

两相相位差:判断方向的核心

理解了错相位,方向判断就一句话的事:在CLK电平跳变的那个瞬间,去看DT的电平。

如果此刻DT和CLK不一样,说明DT还没跟上,轴在往一个方向转(顺时针);如果两者一样,说明DT先动过了,方向相反(逆时针)。这套玩法有个正式名字,叫正交编码(Quadrature Encoding)。两个信号谁先谁后由旋转方向决定,数脉冲的个数就知道转了多少,比一下谁先动就知道往哪边转。

两路信号长什么样?拿示波器看就是两条漂亮的方波,一模一样的频率,只是错开了一段——国外玩家把电机编码器的两路输出接到示波器上,看到的正是这个。理论上把两路波形组合起来读,还能拼出更高的分辨率,不过对KY-040这种当旋钮用的场合,单路加判向已经够用了。

顺便把编码器家族分个工:KY-040这类叫增量式(也叫相对式)编码器,只报告"转了多少格",不报告轴当前绝对角度,适合当旋钮用;另一种是绝对式,直接给出轴的精确角度。还有装在直流减速电机尾部的电机编码器,比如国外玩家实测过的那颗goBILDA 5201减速电机,一圈能出374个脉冲,专门拿来测转速、做位置闭环——数脉冲的思路一样,用处不同。

接线:五根线照着插

按下面的表接,两分钟的事:

编码器引脚Arduino引脚
CLKD2
DTD3
SWD4
+ (VCC)5V
GNDGND

旋转编码器接Arduino Uno接线图

引脚不是死的,换成别的数字脚完全可以,代码里把宏定义改一致就行。想直观看到方向,还可以学国外教程的接法:两个LED各串一个150到470欧的限流电阻(220欧就行),分别接两个输出口,顺时针亮一个、逆时针亮另一个,调试的时候一眼就能看出判向对不对。

另外提醒一句电源:如果后续要在这个Arduino上挂舵机,舵机最好单独供电。舵机一动就容易把5V线上的噪声搅起来,编码器这种靠电平判读的器件跟着遭殃,读数莫名发飘的时候,先查供电再怀疑代码。

例程:轮询版和中断版各来一个

先上轮询版,在主循环里不停看CLK。它顺带把SW按键也读了:

// 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);
}

烧进去,打开串口监视器,正着转、反着转,看Direction和Counter怎么变;按一下轴,会蹦出Button pressed。跑起来的效果就是下面这样:

旋转编码器与Arduino轮询测试接线实拍

这段代码有三个要点。第一,条件里限了currentStateCLK == 1,也就是只在CLK从低变高的那个沿动作,一个档位只计一次,两个沿都响应就会双计。第二,方向判断就嵌在if里:DT不等于CLK就减计数(CCW),相等就加计数(CW)。第三,SW按键用了50毫秒的时间窗滤抖:读到低电平后记下时间,50毫秒内的重复低电平不算新按键。

轮询版能用,但有三个先天毛病:主循环得一直盯着这两个脚,干不了别的正事;忙起来的时候响应会慢半拍;转快了直接丢步。解法是中断——让硬件盯着CLK,一跳变就暂停主程序去执行读数函数,完事回来接着干。

// 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 a change is seen on CLK pin
  attachInterrupt(digitalPinToInterrupt(CLK), 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;
}

旋转编码器中断方式接线图

和轮询版的差别就两处:把读数逻辑整体搬进updateEncoder()函数,然后在setup里挂上attachInterrupt,指定CLK脚、CHANGE触发(高低跳变都进中断)。注意主循环loop()整个是空的——检测全交给中断了,主循环爱干嘛干嘛。转起来之后串口刷新明显比轮询版跟手,快转也不丢步。

中断版还有个顺手的应用:拿counter去驱动舵机。舵机的信号线接9脚这种PWM脚,红线接5V、棕线接地;舵机只吃0到179度的位置值,所以代码里把counter往负数夹到0、往上夹到179,每转一个档位走一度,旋钮拧到哪舵机指到哪——做机械臂关节微调就是这套思路。

消抖与计数,新手最容易栽的四个坑

坑一:一个档位计两次。原因是CLK两个沿都响应了。记死一条——只在其中一个沿动作,代码里的currentStateCLK == 1就是在干这个。

坑二:按键一按蹦出好几条。机械触点闭合瞬间会抖,一次按压被读成好几次。照例程的写法用millis()卡50毫秒窗口就稳了;轮询读数发飘的话,循环末尾那个1毫秒的delay别嫌它土,留着。

坑三:转快了数字不对。轮询扛不住快速旋转,老老实实上中断版。另外例程为了演示,把Serial.print放在了中断回调里,自己项目里建议回调越短越好,打印挪到主循环去做。

坑四:想测转速却算不准。电机编码器那套玩法是:中断设成RISING、只数一路,每满一秒把计数值取走清零,脉冲数乘60再除以每圈脉冲数就是转速——一秒内的脉冲数乘60是一分钟的脉冲数,除以每圈脉冲数自然就是每分钟转多少圈。每圈脉冲数必须查你手上电机的规格页——实测那颗是374,你的未必是,这个数不查准,RPM就是错的。接线也简单:编码器的VCC和GND照接,信号出一接到中断脚就行,反正不判方向,接A相还是B相无所谓。

坑五:counter用int还是long。KY-040当旋钮慢慢拧,int绰绰有余;但换成电机编码器这种高速场合,脉冲来得又急又密,计数值一路涨,用int早晚溢出,老老实实上long,例程里电机那版用的就是volatile long,中断和主循环共用变量,volatile也别省。

最后收个尾。KY-040几块钱一颗,五根线、一个判断方向的if、一个数脉冲的变量,就构成了从"拧旋钮"到"做菜单、调舵机、测转速"的全部基础。转出来方向反了别慌,CLK和DT两根线对调一下就正过来了。下一步,把这个旋钮装到你自己的项目里去吧。


作者:隔壁老王的教程
链接:https://www.aiddithome.com/p/b596cfd43b702.html
本作版权归作者所有,未经允许请勿转载。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
5条评论
电烙铁爱好者
1楼 · 31分钟前

这篇把CLK和DT那个90度相位差讲得挺透。我第一次玩编码器就是栽在这,两个沿都去数,结果拧一格数字跳两下,折腾了半晚上才明白只认一个沿就行。文里那句"在CLK跳变的瞬间去看DT"算是把正交编码的精髓一句话说完了,新手照这个思路去理解波形图会顺很多。

阿强焊点总飞
2楼 · 31分钟前

转反了对调CLK和DT这招救过我,当时还以为是模块坏了

拆开看看再说
3楼 · 31分钟前

中断版那个空的loop()看着确实舒服,我那个菜单项目改完中断之后快转再也不丢步了。不过提醒一句,回调里放Serial.print确实别学,我打印多了同样掉数,后来只留个计数,打印全挪到loop里才稳。

刷视频进来围观
4楼 · 31分钟前

KY-040几块钱一个,配合舵机做角度微调是真香

好说好说
5楼 · 31分钟前

50毫秒窗口消抖这个数我记下了,之前按键一次蹦三条就是这个毛病,一直懒得查,原来millis卡个窗口就完事