之前用CRT显示器的时候,调整显示器的时候用一个圆盘转动和点击的方法就能够实现选择菜单和修改设置项的值,比多个按钮的方式方便不少。git
鼠标滚轮也是这种操做方法,旋转+点击,只是方向不一样。最近在网上买了旋转编码器模块,想把它用到实际制做中。在网上找了不少资料,测试发现其中的代码或多或少都有问题。因而决定本身研究一下旋转编码器的原理,只涉及高低电平应该会比较简单。函数
我买的旋转编码器模块有5个引脚,分别是VCC, GND, SW, CLK, DT。其中VCC和GND用来接电源和地,按缩写SW应该是Switch(开关)、CLK是Clock(时钟)、DT是Data(数据)。oop
网上的资料虽然代码不是很理想,但介绍的原理基本是没问题的。旋转编码器的操做是旋转和按压转轴,在按下转轴的时候SW引脚的电平会变化,旋转的时候每转动一步CLK和DT的电平是有规律的变化。在只接电源的状况下先测一下各类操做时引脚电平的变化,没有示波器只好用万用表测电压。测试
点击:SW(红)+GND(黑)时按下和松开按钮没有任何变化,VCC(红)+SW(黑)松开时表针指向0,按下时高电平。据此能够推测SW平时为高阻态,按下时接地。用Arduino检测的方法是设置链接SW的引脚为INPUT并上拉输出高电平,检测到引脚为低电平则表示按钮按下,如下代码能够正确检测出按钮的变化。ui
//定义引脚链接 int SW= 4; // SW->D4 bool lastButtonStatus = false; void setup() { pinMode(SW, INPUT); digitalWrite(SW, HIGH);//链接按钮的引脚设为上拉 Serial.begin(9600); } void loop() { bool buttonStatus = !digitalRead(SW);//高电平时未按下,状态为false if (buttonStatus != lastButtonStatus) { Serial.println(buttonStatus ? "pressed" : "released"); lastButtonStatus = buttonStatus;//保存当前状态 } delay(100); }
旋转:CLK(红)+GND(黑),每旋转一次(和方向无关),电平转换一次,DT(红)+GND(黑),变化状况和上一种状况一致,而且CLK和DT的电平保持一致。VCC(红)+CLK(黑),VCC(红)+DT(黑)也是一样的状况。CLK(红)+DT(黑)或者CLK(黑)+DT(红)时,每次旋转(和方向无关)指针都会轻微摆动而后归零,而且相邻两步指针的摆动方向相反。结论:每次旋转CLK和DAT引脚的电平都会变化,电平变化有时间差,但没法区分往哪一个方向旋转。编码
编写测试代码,在按下按钮的时候读取CLK和DT的值:spa
1 //定义引脚链接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 int SW = 4;//SW->D4 5 6 void setup() 7 { 8 pinMode(SW, INPUT); 9 digitalWrite(SW, HIGH);//链接按钮的引脚设为上拉 10 pinMode(CLK, INPUT); 11 pinMode(DT, INPUT); 12 Serial.begin(9600); 13 } 14 15 void loop() 16 { 17 if (!digitalRead(SW)) //读取到按钮按下时读取CLK和DT的值 18 { 19 int clkValue = digitalRead(CLK);//读取CLK引脚的电平 20 int dtValue = digitalRead(DT);//读取DT引脚的电平 21 Serial.print("CLK:"); 22 Serial.print(clkValue); 23 Serial.print("; DT:"); 24 Serial.println(dtValue); 25 delay(1000); 26 } 27 }
测试发现无论顺时针仍是逆时针旋转,每次按下按钮以后读取的CLK和DT的值都是同样的,而且相邻两步之间的值是不同的,符合用万用表测量的结果。3d
万用表测量时发现CLK和DT的变化有必定的时间差,能够用Arduino在CLK电平变化的瞬间读取DT的值,可能会找到其中的规律。改为经过中断0监控CLK上的电平变化,读取CLK和DT的电平值:指针
1 //定义引脚链接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 const int interrupt0 = 0;// Interrupt 0 在 pin 2 上 5 6 void setup() 7 { 8 pinMode(CLK, INPUT); 9 pinMode(DT, INPUT); 10 attachInterrupt(interrupt0, ClockChanged, CHANGE);//设置中断0的处理函数,电平变化触发 11 Serial.begin(9600); 12 } 13 14 void loop() 15 { 16 } 17 18 //中断处理函数 19 void ClockChanged() 20 { 21 int clkValue = digitalRead(CLK);//读取CLK引脚的电平 22 int dtValue = digitalRead(DT);//读取DT引脚的电平 23 Serial.print("CLK:"); 24 Serial.print(clkValue); 25 Serial.print("; DT:"); 26 Serial.println(dtValue); 27 }
顺时针旋转一步:code
顺时针旋转3步(用横线分隔):
逆时针旋转3步(用横线分隔):
根据以上测试结果,每旋转一次触发的中断次数不一致,多是硬件自己引发的,相似按钮抖动。屡次测试以后,查看每次变化的最后一组值,顺时针旋转时CLK和DT的值不一致,逆时针旋转时CLK和DT的值一致。修改代码,顺时针时对计数值加1,逆时针时对计数值减1,按下按钮时计数值清零。
1 //定义引脚链接 2 int CLK = 2;//CLK->D2 3 int DT = 3;//DT->D3 4 int SW = 4;//SW->D4 5 const int interrupt0 = 0;// Interrupt 0 在 pin 2 上 6 int count = 0;//计数值 7 int lastCLK = 0;//CLK历史值 8 9 void setup() 10 { 11 pinMode(SW, INPUT); 12 digitalWrite(SW, HIGH); 13 pinMode(CLK, INPUT); 14 pinMode(DT, INPUT); 15 attachInterrupt(interrupt0, ClockChanged, CHANGE);//设置中断0的处理函数,电平变化触发 16 Serial.begin(9600); 17 } 18 19 void loop() 20 { 21 if (!digitalRead(SW) && count != 0) //读取到按钮按下而且计数值不为0时把计数器清零 22 { 23 count = 0; 24 Serial.print("count:"); 25 Serial.println(count); 26 } 27 } 28 29 //中断处理函数 30 void ClockChanged() 31 { 32 int clkValue = digitalRead(CLK);//读取CLK引脚的电平 33 int dtValue = digitalRead(DT);//读取DT引脚的电平 34 if (lastCLK != clkValue) 35 { 36 lastCLK = clkValue; 37 count += (clkValue != dtValue ? 1 : -1);//CLK和DT不一致时+1,不然-1 38 Serial.print("count:"); 39 Serial.println(count); 40 } 41 }
测试发现大多数时候能够正确输出:
偶尔旋转不是很顺畅会出现跳动的状况,这时候能感受到旋钮在两步之间跳动了一下。看到有人说在引脚和地之间接上滤波电容会好一些,实际测试发现并无改善。推测因为旋钮是D字型的,用手旋转的时候确实会出现跳动的状况,装上旋钮帽以后应该会避免这种状况。
旋转编码器能够用于须要精确调整值(电位器不许确),操做菜单等场合。后续会使用旋转编码器制做一些小东西,也欢迎你们分享旋转编码器相关代码。