
學習目標 : 認識faya編碼器模組的功能及使用方式
學習時間 : 45min
示範模組 : (1) faya brickNano
(2) faya 串列七段顯示模組
(3) faya編碼器模組
工具 : (1) 樂高積木底板 (相容)
====================功能介紹====================
旋鈕式的編碼器,擁有有數位化的輸出,能夠精準的回饋所要控制的目標,日常生活中如音量的調整,頻道的選擇,都有它的蹤跡。
faya編碼器模組使用了市面上常見的兩相式旋鈕編碼器,旋轉時埠A和埠B會輸出脈波訊號,埠PB為旋鈕上外帶的開關訊號,訊號的產生與解釋請參見知識原理。
- 順時針旋轉編碼器時,埠AB會產生11=>01=>00=>10順序的訊號
- 逆時針旋轉編碼器時,埠AB會產生10=>00=>01=>11順序的訊號
- 當壓下編碼器時,輸出埠PB產生HIGH

====================原理知識====================
以下解釋提供給有需要知道背後原理的人,首先看一下編碼器的內部構造 (來源):

編碼器的旋鈕能夠完整的旋轉360度,旋鈕的內外圍具有相同空間的金屬片(上方右圖粗線處)連到Vcc,外圈可和端點A接觸,內圈可和端點B接觸。端點A和端點B接觸到金屬片時,會產生0和1的狀態(相對於COM點),由於內外圍的金屬片只重疊了一半面積,因此一個旋轉週期(one cycle)時會有00, 01, 10, 11 四種狀態的組合產生。
我們用上圖來解釋一個旋轉週期如何產生不同的狀態:
(1) 以上圖為起始位置,我們可以看出A的狀態是1,B的狀態也是1(兩者都接觸到金屬片)
(2) 當順時針旋轉時,A會先離開金屬片,B還停留在金屬片上,因此A=0,B=1
(3) 再旋轉一些些時,A還不會接觸到金屬片,但B會離開金屬片,此時A=0,B=0
(4) 最後再旋轉一些時,A又開始碰到金屬片,B還位於離開金屬片的狀態,此時A=1,B=0
因此,當順時針旋轉編碼器時,AB的輸出會經歷11 => 01 => 00 => 10 的順序
反之,當逆時針旋轉編碼器時,AB的輸出會經歷10 => 00 => 01 => 11 的順序
我們可以依據此輸出順序判斷旋轉方向(順時針或逆時針)

faya編碼器模組的電路圖如下所示,圖中Encoder Switch下方的圈圈代表的就是COM點,左上方的圈圈代表外圈金屬片,連接到埠A,右上方的圈圈代表內圈金屬片,連接到埠B,當接觸到金屬片時,會產生Vcc電壓的HIGH訊號,另外當按下按鈕PushButton時,會產生接近Vcc電壓的HIGH訊號。

===================範例實作-1===================
了解模組功能(原理)後,我們用以下範例來展示模組的功能:
目標:
(1)當順時針旋轉faya編碼器模組時,七字節顯示器+1
(2)當逆時針旋轉faya編碼器模組時,七字節顯示器-1
(3)數值最大值100,最小值0
(4)當按下編碼器按鈕時,七字節顯示器歸零
接線:
(1) 電源線連接
如下圖所示,連接的說明請看這篇文章 或簡易版,由於電源線不夠長,我們用了點小技巧,串接了電源線,可以參考這邊。

(2) 訊號線連接
Arduino_12 ===> 串列七段顯示模組_DIN
Arduino_11 ===> 串列七段顯示模組_CLK
Arduino_10 ===> 串列七段顯示模組_LOAD

Arduino_3 ===> 編碼器模組_PB
Arduino_4 ===> 編碼器模組_A
Arduino_5 ===> 編碼器模組_B

範例程式:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 2017/8/17 | |
// Faya-Nugget 範例程式 (Encoder_Switch_1.ino) | |
// 單元: 模組介紹:faya編碼器模組 | |
// 網址: https://fayalab.blogspot.com/2017/08/encoderswitch.html | |
// 目標: (1)當順時針旋轉faya編碼器模組時,七字節顯示器+1 | |
// (2)當逆時針旋轉faya編碼器模組時,七字節顯示器-1 | |
// (3)數值最大值100,最小值0 | |
// (4)當按下編碼器按鈕時,七字節顯示器歸0 | |
// 接線: Arduino ==> faya模組 | |
// D10 ==> LOAD (7段顯示器模組) | |
// D11 ==> CLK (7段顯示器模組) | |
// D12 ==> DIN (7段顯示器模組) | |
// D3 ==> PB (編碼器模組) | |
// D4 ==> A (編碼器模組) | |
// D5 ==> B (編碼器模組) | |
#include "LedControl.h" | |
//定義Arduino與七字節模組的連接腳位與參數 | |
LedControl faya7seg=LedControl(12,11,10,1); // DIN, CLK, LOAD, 串列數量 | |
int encoder_PB = 3; // Push Button 腳位 | |
int encoder_A = 4; // A相腳位 | |
int encoder_B = 5; // B相腳位 | |
int encoder_A_Last = LOW; //相位A最後的狀態 | |
int number = 0; //編碼器顯示數字 | |
int state_A; // 相位A目前狀態 | |
int state_PB; // 按鈕目前狀態 | |
void setup() | |
{ | |
//設定編碼器A,B,PB為輸入埠 | |
pinMode (encoder_A,INPUT); | |
pinMode (encoder_B,INPUT); | |
pinMode (encoder_PB,INPUT); | |
// 初始化七字節顯示器 | |
faya7seg.shutdown(0,false); // 第0個元件不要進入省電模式 | |
faya7seg.setIntensity(0,8); // 第0個的元件亮度8 (0~15) | |
faya7seg.clearDisplay(0); // 清除第0個元件 | |
show4digits(0); // 輸出數字000 | |
} | |
void loop() { | |
state_A = digitalRead(encoder_A); // 讀取相位A狀態 | |
state_PB = digitalRead(encoder_PB); // 讀取PB狀態 | |
if((encoder_A_Last == LOW) && (state_A == HIGH)) // 相位A從 Low 跳到 High時 | |
{ | |
if(digitalRead(encoder_B) == LOW) // When B = LOW ==> 順時針轉,數字加1 | |
{ | |
if(number == 100) //數字上限 = 100 | |
number = 100; | |
else | |
number = number + 1; | |
} | |
else // When B = High ==> 逆時針轉,數字減1 | |
{ | |
if(number == 0) //數字下限 = 0 | |
number = 0; | |
else | |
number = number - 1; | |
} | |
show4digits(number); //運算結束後顯示數字 | |
} | |
else if (state_PB == HIGH) // 按下旋鈕時歸零 | |
{ | |
number=0; | |
show4digits(number); //顯示數字 | |
delay(150); // 除彈跳 | |
} | |
encoder_A_Last = state_A; // 把A的目前狀態存到最後狀態 | |
} | |
void show4digits(int number) // 顯示數字副程式 | |
{ | |
int num1,num2,num3,num4; | |
num1=number/1000; //千位數 | |
num2=(number%1000)/100; //百位數 | |
num3=(number%100)/10; //十位數 | |
num4=number%10; //個位數 | |
// faya7seg.setDigit(0,3,num1,false); //點亮千位數 | |
faya7seg.setDigit(0,2,num2,false); //點亮百位數 | |
faya7seg.setDigit(0,1,num3,false); //點亮十位數 | |
faya7seg.setDigit(0,0,num4,false); //點亮個位數 | |
} |
備注:
- 串列七段顯示模組的用法,請參考faya 串列七段顯示模組
- L49+L51: 由原理知識中的編碼器訊號時序圖,可以歸納出當相位A由LOW轉HIGH時,如果相位B的狀態在LOW時,代表編碼器是順時針旋轉的狀態

- L49+L58: 反之,當相位A由LOW轉HIGH時,如果相位B的狀態在HIGH時,代表編碼器是逆時針旋轉的狀態

範例結果:
討論:
以上的編碼器判斷方式很容易理解,但缺點就是只利用了1/4的旋轉週期來判斷,對於較精密的編碼器,或者旋轉速度過快時,會有沒判斷到的的情形發生,另外編碼器的判讀也耗掉MCU的資源。雖然如此,此範例對於展示編碼器的運作方式已經綽綽有餘了。下一個實作,我們用中斷的方式,判斷整個週期的四個狀態,讓判讀更即時。
===================範例實作-2===================
目標:
(1)編碼器利用中斷腳位達到以下功能
(2)當順時針旋轉faya編碼器模組時,七字節顯示器+1
(3)當逆時針旋轉faya編碼器模組時,七字節顯示器-1
(4)當按下編碼器按鈕時,七字節顯示器歸0
接線:
(1) 電源線連接 ==> 和範例實作-1相同
(2) 訊號線連接(串列七段顯示模組) ==> 和範例實作-1相同
訊號線連接(編碼器模組 ) ==> 因為中斷腳位需要用到2、3腳,請改成以下
Arduino_4 ===> 編碼器模組_PB
Arduino_2 ===> 編碼器模組_A
Arduino_3 ===> 編碼器模組_B
範例程式:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 2017/8/18 | |
// Faya-Nugget 範例程式 (Encoder_Switch_2.ino) | |
// 單元: 模組介紹:faya編碼器模組 | |
// 網址: https://fayalab.blogspot.com/2017/08/encoderswitch.html | |
// 目標: (1)編碼器利用中斷腳位達到以下功能 | |
// (2)當順時針旋轉faya編碼器模組時,七字節顯示器+1 | |
// (3)當逆時針旋轉faya編碼器模組時,七字節顯示器-1 | |
// (4)當按下編碼器按鈕時,七字節顯示器歸0 | |
// 接線: Arduino ==> faya模組 | |
// D10 ==> LOAD (7段顯示器模組) | |
// D11 ==> CLK (7段顯示器模組) | |
// D12 ==> DIN (7段顯示器模組) | |
// D3 ==> PB (編碼器模組) | |
// D4 ==> A (編碼器模組) | |
// D5 ==> B (編碼器模組) | |
#include "LedControl.h" | |
//定義Arduino與七字節模組的連接腳位與參數 | |
LedControl faya7seg=LedControl(12,11,10,1); // DIN, CLK, LOAD, 串列數量 | |
#define encoder_A 2 | |
#define encoder_B 3 | |
int encoder_PB = 4; // Push Button 腳位 | |
int number = 0; //編碼器顯示數字 | |
void setup() | |
{ | |
//設定編碼器A,B,PB為輸入埠 | |
pinMode (encoder_A,INPUT); | |
pinMode (encoder_B,INPUT); | |
pinMode (encoder_PB,INPUT); | |
//中斷設定 (A相位連2腳位0號中斷、B相位連3號腳位1號中斷) | |
attachInterrupt(0, doEncoderA, CHANGE); | |
attachInterrupt(1, doEncoderB, CHANGE); | |
// 初始化七字節顯示器 | |
faya7seg.shutdown(0,false); // 第0個元件不要進入省電模式 | |
faya7seg.setIntensity(0,8); // 第0個的元件亮度8 (0~15) | |
faya7seg.clearDisplay(0); // 清除第0個元件 | |
show4digits(0); // 輸出數字000 | |
} | |
void loop() { | |
if(digitalRead(encoder_PB) == HIGH) { | |
number = 0; | |
show4digits(0); | |
} | |
} | |
//A相位中斷副程式 | |
void doEncoderA() { | |
// 狀態1:相位A從 LOW 跳到 HIGH時 | |
if (digitalRead(encoder_A) == HIGH) | |
{ | |
if (digitalRead(encoder_B) == LOW) // 如果 B = LOW ==> 順時針轉,數字加1 | |
{ | |
number = number + 1; | |
} | |
else // 如果 B = HIGH ==> 逆時針轉,數字減1 | |
{ | |
number = number - 1; | |
} | |
} | |
// 狀態2:相位A從 High 跳到 Low 時 | |
else | |
{ | |
if (digitalRead(encoder_B) == HIGH) // 如果 B = HIGH ==> 順時針轉,數字加1 | |
{ | |
number = number + 1; | |
} | |
else // 如果 B = LOW ==> 逆時針轉,數字減1 | |
{ | |
number = number - 1; | |
} | |
} | |
show4digits(number); | |
} | |
//B相位中斷副程式 | |
void doEncoderB() { | |
// 狀態3:相位B從 LOW 跳到 HIGH時 | |
if (digitalRead(encoder_B) == HIGH) | |
{ | |
if (digitalRead(encoder_A) == HIGH) // 如果 A = HIGH ==> 順時針轉,數字加1 | |
{ | |
number = number + 1; | |
} | |
else // 如果 A = LOW ==> 順時針轉,數字減1 | |
{ | |
number = number - 1; | |
} | |
} | |
// 狀態4:相位B從 HIGH 跳到 LOW時 | |
else { | |
if (digitalRead(encoder_A) == LOW) // 如果 A = LOW ==> 順時針轉,數字加1 | |
{ | |
number = number + 1; | |
} | |
else // 如果 A = HIGH ==> 逆時針轉,數字減1 | |
{ | |
number = number - 1; | |
} | |
} | |
show4digits(number); | |
} | |
void show4digits(int number) // 顯示數字副程式 | |
{ | |
int num1,num2,num3,num4; | |
num1=number/1000; //千位數 | |
num2=(number%1000)/100; //百位數 | |
num3=(number%100)/10; //十位數 | |
num4=number%10; //個位數 | |
faya7seg.setDigit(0,3,num1,false); //點亮千位數 | |
faya7seg.setDigit(0,2,num2,false); //點亮百位數 | |
faya7seg.setDigit(0,1,num3,false); //點亮十位數 | |
faya7seg.setDigit(0,0,num4,false); //點亮個位數 | |
} |
備注:
- 為了讓程式簡潔一點,本範例沒有設定旋轉最大和最小值
- L35+L36 : 設定A相位為0號中斷(pin2)、B相位1號中斷(pin3)
- 範例實作-1中,我們只利用1/4的旋轉週期判斷順逆轉,這個範例中,我們把整個旋轉週期可能發生的狀態全部判斷了,大家可以對照程式內容和上個範例的時序圖舉一反三
- L55+L57+L61: 相位A由LOW轉HIGH時 (範例實作-1只判斷了這個狀態)
- L67+L69+L73: 相位A由HIGH轉LOW時
- L84+L86+L88: 相位B由LOW轉HIGH時
- L96+L97+L101: 相位B由HIGH轉LOW時
範例結果:
討論:
(1) 第二個範例大家可以發現數字增減的速度變快了(快4倍),在某些應用中,例如音量的大小的控制,我們可以取第2和第3位數來當有效輸入即可,大家可以再看一次影片,如果只取第2和第3位數時,視覺和手的反應是能夠跟上的。
(2) 兩個範例都未加入除彈跳的機制(副程式),因此轉很慢時會發現數字一次跳好幾下,但是如果只取第2和第3位數當有效數值的應用時,彈跳的影響是可以被忽略的。
歡迎大家到我們的FB留言板討論!
====================================
fayalab 粉絲團
FB本篇留言版
沒有留言:
張貼留言