반응형

로터리 버튼과 SSD1306 128x64 OLED를 이용하여 로터리 버튼의 기능을 개발한다.

 

연결 구성도

연결은 다음과 같다.

SSD1306 128x64 OLED는 3.3V 전압을 인가하여 I2C 통신으로 연결한다.

로터리 버튼은 5V 전압을 인가하여 회전을 검출하는 부분이 파란선 두 줄, 클릭 부분이 노란 선이다.

 

실제 연결한 모습은 다음과 같다.

 

코드의 개요

로터리 버튼의 원리 요약

로터리 버튼의 원리는 다음을 참고하자.[1]

회전에 따라서 A 신호의 High가 들어올 때, B 신호가 High 인지 Low인지에 따라서 방향을 알 수 있고,

이를 세면, 얼마나 회전되었는지 알 수 있다.

 

로터리 버튼 처리 - Interrupt

로터리 버튼을 메인 루프 내에 넣어서 처리하기엔 기약이 좀 없다.

그래서 

1. 인터럽트로 버튼을 눌렀는지얼마나 돌렸는지를 처리하고

2. 메인 루프에서는 이런 기록을 가져다가 쓰는 형태로 써보려고 한다.

 

Arduino Interrupt의 사용 구조

인터럽트의 사용 구조는 다음과 같다.

1
2
3
4
5
6
7
8
void setup(){
  pinMode(PIN_NUMBER, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_NUMBER), ISR_FUNCTION, INTERRUPT_TYPE);
}
 
ICACHE_RAM_ATTR void ISR_FUNCTION(){
  // Place your code
}
cs

원하는 GPIO 핀의 숫자 PIN_NUBMER

인터럽트 서비스 루틴(ISR) 함수 ISR_FUNCTION

어떤 조건에서 인터럽트를 걸 것인지 INTERRUPT_TYPE

를 인자로 하여 인터럽트를 추가한다.

인터럽트 조건은 CHANGE, RISING, FALLING, HIGH 가 있다.

 

이때 ISR의 속성으로 ICACHE_RAM_ATTRICACHE_FLASH_ATTR 을 지정할 수 있다고 한다. [3]

이는 컴파일할 때, 인터럽트 함수를 RAM 혹은 FLASH에 저장할 것인지 선택하는 것인데,

ESP8266이 램이 32KB으로 적으니까, FLASH에 저장하면 RAM을 좀 아껴서 쓸 수 있다고 한다.

그런데 문제는 FLASH에 저장해두면 flash access 동안에 인터럽트가 발생해서 데이터가 깨질 수 있다고 하니,

전역 변수에 접근하는 인터럽트 함수라면 RAM에 반드시 두어야한다고 한다.

코드의 구현

구현되어 액정에 표시되는 모습은 위 그림과 같다.

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
// 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
char msg[DISP_LINE_COLUMN][DISP_LINE_ROW];
 
#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
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
 
int delay_ms = 100;
char led_onoff = true;
int cnt = 1;
 
/************************************/
// Rotary Button Settings
/************************************/
// Rotary Encoder Inputs
#define SW  GPIO_D5
#define CLK GPIO_D6
#define DT  GPIO_D7
 
// Rotary
int counter = 0;
int currentStateDT;
char currentDir = 'X';
unsigned long lastRotary = 0;
 
// Click
int button_clicked = false;
unsigned long lastButtonPress = 0;
 
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, led_onoff);
 
  /************************************/
  // Rotary Button Settings
  /************************************/
// Set encoder pins as inputs
 pinMode(CLK, INPUT);
 pinMode(DT,  INPUT);
 pinMode(SW,  INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(CLK), func_rotary_counter, RISING);
  attachInterrupt(digitalPinToInterrupt(SW),  func_button_click,   CHANGE);
 
  /************************************/
  // Display Settings
  /************************************/
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.clearDisplay();
 
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world! TESTETSTESTET"));
 
  display.display();
}
 
void loop() {
  led_onoff = !led_onoff;
  digitalWrite(LED_BUILTIN, led_onoff);
    
  /************************************/
  // Rotary Button Settings
  /************************************/
  sprintf(msg[0], "Pulse %4d -",   cnt++);
  sprintf(msg[2], "Direction: %c", currentDir);
  sprintf(msg[3], "Counter: %d",   counter);
 
  // 
  if(button_clicked == true){  
    sprintf(msg[4],"Button click!");
    button_clicked = false;
  }
  else
    sprintf(msg[4],"Button released!");
 
  /************************************/
  // Display Settings
  /************************************/
  display.clearDisplay();
  display.setCursor(0,0);             // Start at top-left corner
  for (int i = 0; i < DISP_LINE_COLUMN; i++){
    display.println(msg[i]);
  }
  display.display();
 
 
// Put in a slight delay to help debounce the reading
 delay(delay_ms);
}
 
ICACHE_RAM_ATTR void func_rotary_counter(){
  // lastRotary = millis();
  currentStateDT = digitalRead(DT);
  if (currentStateDT == true) {
    counter --;
    currentDir = 'R';
  } else {
    // Encoder is rotating CW so increment
    counter ++;
    currentDir = 'L';
  }
}
 
ICACHE_RAM_ATTR void func_button_click(){
  if(lastButtonPress - millis() < 300){
    button_clicked = true;
  }
  lastButtonPress = millis();
}
cs

 

 

 

[1] "How Rotary Encoder Works and Interface It with Arduino" https://lastminuteengineers.com/rotary-encoder-arduino-tutorial/

[2] "NodeMCU GPIO Interrupts with Arduino IDE" https://www.electronicwings.com/nodemcu/nodemcu-gpio-interrupts-with-arduino-ide

[3] "ESP8266/Arduino: Why is it necessary to add the ICACHE_RAM_ATTR macro to ISRs and functions called from there?", https://stackoverflow.com/questions/58113937/esp8266-arduino-why-is-it-necessary-to-add-the-icache-ram-attr-macro-to-isrs-an

 

 

728x90

+ Recent posts