아두이노에서 로터리엔코더를 이용하여 회전과 버튼 클릭을 SSD1306 디스플레이로 확인하도록 하자.
결선도
NewNode V3 (ESP-12E)를 이용했다.
연결은 다음과 같다.
NewNode V3 핀 이름 |
RotaryEncoder 핀 이름 |
SSD1306 핀 이름 |
VIN (5V) | 5V | |
GND | GND | |
D4 | KEY | |
D7 | S1 | |
D8 | S2 | |
3V | VDD | |
GND | GND | |
D2 | SDA | |
D3 | SCK |
로터리 엔코더 특징 분석 및 구현 방법
로터리 엔코더의 특징은 이 글을 참고하자.
요약하면 로터리 엔코더는 두 개의 센서가 있고, 로터리 엔코더를 돌리면 두 센서 부분이 HIGH, LOW를 반복한다. 이때 회전 방향에 따라서 센서 A와 B 조합의 나타나는 순서가 다르다.
로터리 엔코더는 홈이 있어서 돌릴 때 어느 홈에 딱 멎게 된다. 이 상태를 기준으로 A, B 점의 상태 조합을 해보자.
S1 핀을 A, S2 핀을 B라고 하고 상태 조합은 다음과 같이 정리한다.
센서 출력 = 2 * A 값 + B 값
A 와 B가 모두 HIGH 라면 3이며, A와 B 가 모두 LOW 라면 0이 출력된다.
그렇다면 회전 방향에 대해서 다음과 같은 센서 출력 순서를 가지게 된다.
1) 시계 방향(오른쪽 방향)으로 돌릴 때 센서 출력
1 -> [3 -> 2 -> 0 -> 1] -> 3
2) 반시계 방향 (왼쪽 방향)으로 돌릴 때 센서 출력
2 -> [3 -> 1 -> 0 -> 2] -> 3
시계 방향은 3-2-0-1 순서, 반시계 방향은 3-1-0-2 순서를 가진다.
그러면 이 순서를 검사하게 만들면 회전 방향을 알 수 있다.
로터리 엔코더 회전 인식 구현 방법
홈에 멎는 상태의 센서 출력은 3이므로 회전을 처음 알게 되는 이벤트는 FALLING edge 이다.
S1 점의 FALLING edge 인터럽트를 걸면 3-2, 0-1 순서일 때 시계 방향 회전임을 알 수 있으며
S2 점의 FALLING edge 인터럽트를 걸면 1-0, 2-3 순서일 때 반시계 방향 회전임을 알 수 있다.
그리고 돌릴 때 접점에 순간 여러번 붙었다 떨어지면서 인터럽트가 수 번 발생할 수 있으므로, 인터럽트 내부에 타이머를 하나 넣어서 일정 시간이 지나야 검사하도록 하자.
코드 구현
main.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
/************************************/
// Include Headers
/************************************/
#include "oled.h"
#define GPIO_S1 8
#define GPIO_S2 9
#define GPIO_S3 10
#define GPIO_D0 16 // PWM
#define GPIO_D3 0 // PWM
#define GPIO_D4 2
#define GPIO_D5 14
#define GPIO_D6 12
#define GPIO_D7 13 // PWM
#define GPIO_D8 15 // PWM
void setRotaryRight();
void setRotaryLeft();
void setRotaryNeutral();
void updateRotary();
// Simple Scheduler
unsigned long last_loop_time = micros();
unsigned long now_loop_time = micros();
unsigned long size_loop_time = 100*1000;
unsigned long delta_loop_time = 0;
/************************************/
// Display Settings
/************************************/
OLED* monitor = new OLED();
/************************************/
// Rotary Button
/************************************/
// Rotary Encoder Inputs
#define BUTTON_KEY GPIO_D4
#define BUTTON_S1 GPIO_D7
#define BUTTON_S2 GPIO_D8
volatile int rotary_pulse_s1 = 0;
volatile int rotary_pulse_s2 = 0;
volatile unsigned long rotary_last_pulse_s1 = 0;
volatile unsigned long rotary_last_pulse_s2 = 0;
volatile int rotary_state = 0;
volatile int rotary_state_prev = 0;
volatile int rotary_state_s1 = 0;
volatile int rotary_state_s2 = 0;
volatile int rotary_counter = 0;
volatile int rotary_right = false;
volatile int rotary_left = false;
volatile int rotary_event = true;
volatile int button_counter = 0;
volatile int button_clicked = false;
volatile int did_button_clicked = false;
volatile unsigned long button_last_press = 0;
void setup() {
int system_on = true;
/************************************/
// Display Initialization
/************************************/
int monitor_on = monitor->setup_display();
system_on = system_on & monitor_on;
sprintf(monitor->msg[2], "1. Display is ON");
monitor->print();
/************************************/
// Rotary Button Settings
/************************************/
// Set encoder pins as inputs
sprintf(monitor->msg[5], "4. Intialize Buttons");
monitor->print();
pinMode(BUTTON_S1, INPUT);
pinMode(BUTTON_S2, INPUT);
pinMode(BUTTON_KEY, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_S1), isr_rotary_s1, FALLING);
attachInterrupt(digitalPinToInterrupt(BUTTON_S2), isr_rotary_s2, FALLING);
attachInterrupt(digitalPinToInterrupt(BUTTON_KEY), isr_button_click, RISING);
sprintf(monitor->msg[5], "4. Buttons are ON");
monitor->print();
}
void loop() {
/************************************/
// Display
/************************************/
sprintf(monitor->msg[2], "Direction L %d / R %d", rotary_left, rotary_right);
if (did_button_clicked == true){
sprintf(monitor->msg[4],"Button click!");
did_button_clicked = false;
}
else
sprintf(monitor->msg[4],"Button released!");
if(rotary_event == true){
rotary_event = false;
setRotaryNeutral();
}
sprintf(monitor->msg[0], "button_cnt %4d", button_counter);
sprintf(monitor->msg[1], "rotary_cnt: %4d", rotary_counter);
sprintf(monitor->msg[5], "S1 %d S2 %d = %d",digitalRead(BUTTON_S1),
digitalRead(BUTTON_S2),
2*digitalRead(BUTTON_S1) + digitalRead(BUTTON_S2));
monitor->print();
/************************************/
// Simple Scheduler
/************************************/
while(1){
now_loop_time = micros();
// Clock Overflow Case, each time after about 70 mins
if(now_loop_time < last_loop_time){
last_loop_time = 0;
break;
}
// Normal Case
else{
delta_loop_time = now_loop_time - last_loop_time;
if(delta_loop_time > size_loop_time){
last_loop_time = last_loop_time + size_loop_time;
break;
}
}
}
}
//3201 3201
// 0 -> 4 (2(0+1)+2) -> 10 (2*(4+1)+0) -> 23 (2*(10+1)+1)
void setRotaryRight(){
rotary_counter++;
rotary_left = true;
rotary_right = false;
rotary_event = true;
}
// 3102 3102
// 0 -> 3 (2*(0+1)+1) -> 8 (2*(3+1)+0) -> 20 (2*(8+1)+2)
void setRotaryLeft(){
rotary_counter--;
rotary_left = false;
rotary_right = true;
rotary_event = true;
}
void setRotaryNeutral(){
rotary_event = false;
rotary_left = false;
rotary_right = false;
}
void updateRotary(){
rotary_state_s1 = digitalRead(BUTTON_S1);
rotary_state_s2 = digitalRead(BUTTON_S2);
rotary_state = 2 * rotary_state_s1 + rotary_state_s2;
if(rotary_state_prev == 3 && rotary_state == 2){
setRotaryRight();
}
if(rotary_state_prev == 0 && rotary_state == 1){
setRotaryRight();
}
if(rotary_state_prev == 1 && rotary_state == 0){
setRotaryLeft();
}
if(rotary_state_prev == 2 && rotary_state == 3){
setRotaryLeft();
}
rotary_state_prev = rotary_state;
}
/************************************/
// Interrupt Service Routines
/************************************/
// CW (+, R) : S1 true, S2 Rising
// CCW (-, L) : S1 Rising, S2 true
IRAM_ATTR void isr_rotary_s1(){
// prevent being clicked over twice at once
if(rotary_last_pulse_s1 - millis() < 30)
rotary_pulse_s1 = false;
else{
rotary_pulse_s1 = true;
}
rotary_last_pulse_s1 = millis();
// processing rotary action
if(rotary_pulse_s1 == true){
rotary_pulse_s1 = false;
updateRotary();
}
}
IRAM_ATTR void isr_rotary_s2(){
// prevent being clicked over twice at once
if(rotary_last_pulse_s2 - millis() < 30)
rotary_pulse_s2 = false;
else{
rotary_pulse_s2 = true;
}
rotary_last_pulse_s2 = millis();
// processing rotary action
if(rotary_pulse_s2 == true){
rotary_pulse_s2 = false;
updateRotary();
}
}
// Button Interrupt Routine
IRAM_ATTR void isr_button_click(){
// prevent being clicked over twice at once
if(button_last_press - millis() < 500)
button_clicked = false;
else
button_clicked = true;
button_last_press = millis();
// processing button action
// throw main loop the fact being clicked
if(button_clicked == true){
button_clicked = false;
did_button_clicked = true;
button_counter++;
}
}
|
cs |
oled.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include "oled.h"
OLED::OLED()
{
display_device = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
for (int i = 0; i < DISP_LINE_COLUMN; i++){
for (int j = 0; j < DISP_LINE_ROW; j++)
msg[i][j] = '\0';
}
}
int OLED::setup_display()
{ /************************************/
// Display Settings
/************************************/
display_device.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display_device.clearDisplay();
delay(100);
display_device.setTextSize(1); // Normal 1:1 pixel scale
display_device.setTextColor(SSD1306_WHITE); // Draw white text
display_device.setCursor(0,0); // Start at top-left corner
display_device.println(F("Display Initialize"));
delay(100);
display_device.display();
return true;
}
void OLED::print()
{
/************************************/
// Display Settings
/************************************/
display_device.clearDisplay();
display_device.setCursor(0,0); // Start at top-left corner
display_device.println(msg);
display_device.display();
}
|
cs |
oled.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#pragma once
#ifndef _OLED_H_
#define _OLED_H_
// Display Library
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
/************************************/
// Display Settings
/************************************/
#define DISP_LINE_ROW 21
#define DISP_LINE_COLUMN 8
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
class OLED{
public: // Functions
OLED();
~ OLED();
public : // Variables
int setup_display();
void print();
public:
char msg[DISP_LINE_COLUMN][DISP_LINE_ROW];
private:
Adafruit_SSD1306 display_device;
};
#endif // _OLED_H_
|
cs |
Reference
[1] "How Rotary Encoder Works and How To Use It with Arduino", https://howtomechatronics.com/tutorials/arduino/rotary-encoder-works-use-arduino/
** EOF **
'SW > Arduino' 카테고리의 다른 글
[Arduino] RotaryEncoder, SSD1306, PWM 기능 구현 (0) | 2024.01.21 |
---|---|
[Arduino] ESP8266-12F - GPS & OLED & 로터리 버튼 & MPU6500 (0) | 2023.01.01 |
[Arduino] 실행 성능 계측 방법, millis()와 micros() 활용하기 (0) | 2023.01.01 |
[Arduino] ESP8266-12F - GPS & Display (1) | 2022.12.31 |
[Arduino] ESP8266-12F - Rotary Button & Display (0) | 2022.12.30 |