버튼이 16개 달린 패드이다. 4x4의 행렬로 어떤 버튼이 눌렸는지를 확인하며, 이번 프로젝트에서는 각 버튼에 문자열을 매핑해서 라즈베리파이에 연결해놓은 OLED display에 표시되도록 했다.
4x4 keypad | esp32 |
---|---|
c4 | gpio16 |
c3 | gpio17 |
c2 | gpio5 |
c1 | gpio18 |
r1 | gpio19 |
r2 | gpio21 |
r3 | gpio22 |
r4 | gpio23 |
oled | raspberry pi |
---|---|
din | gpio10 (spi mosi) |
clk | gpio11 (spi sclk) |
cs | gpio8 (spi ce0) |
d/c | gpio26 |
res | gpio27 |
SPI interface 활성화
sudo raspi-config
# Interface Options -> SPI Enable
pip3 install adafruit-circuitpython-ssd1306
pip3 install pillow
# 제조사에서 제공하는 예제를 실행해보고 싶다면
# wget -O oled_sample.py https://bit.ly/464Nwgh
# oled_sample.py 에서 핀 정보를 수정하고
# python3 oled_sample.py 실행해본다.
# 맑음 폰트 설치 (프로젝트 실행 파일과 같은 폴더 내에 있어야 함)
wget -O malgun.ttf https://bit.ly/3BzjZfq
adafruit-circuitpython-ssd1306 제조사에서 제공하는 예제
=> https://learn.adafruit.com/monochrome-oled-breakouts/python-usage-2
pillow 공식 document
=> https://pillow.readthedocs.io/en/stable/index.html
keypad
![[Pasted image 20231123153007.png]]
로타리 앤코더를 연결한 오른쪽 ESP32에 업로드할 코드에 다음 내용을 추가한다.
lcd
토픽으로 송신한다. keypad_check()
: 커스텀 객체가 아닌, 기본 Thread
객체를 만들어서 interval 마다 주기적으로 실행할 함수를 정의했다. #include <Keypad.h>
// ... EspMQTTClient 코드
// ... MQTT 송신용 tx()
// ... rotary encoder 코드
// ...
#define ROTARY_ENCODER_SW 36
#define ROTARY_ENCODER_DATA 39
#define ROTARY_ENCODER_CLK 34
const byte ROWS = 4; // define row 4
const byte COLS = 4; // define column 4
const char KEYS[ROWS][COLS] = {
{ '0', '1', '2', '3' },
{ '4', '5', '6', '7' },
{ '8', '9', 'A', 'B' },
{ 'C', 'D', 'E', 'F' }
};
// connect row ports of the button to corresponding IO ports on the board
byte ROW_PINS[ROWS] = { 18, 5, 17, 16 };
// connect column ports of the button to corresponding IO ports on the board
byte COL_PINS[COLS] = { 23, 22, 21, 19 };
// call class library performance function of Keypad
Keypad KEYPAD = Keypad(makeKeymap(KEYS), ROW_PINS, COL_PINS, ROWS, COLS);
char *LCD_TOPIC = "lcd";
// 스레드가 설정된 interval에 맞춰 주기적으로 실행할 함수
void keypad_check(void) {
char key = KEYPAD.getKey();
if (key != NO_KEY) {
String data = "";
switch (key) {
case '0':
data = "^~^";
break;
case '1':
data = "ㅇ0ㅇ";
break;
case '2':
data = "ㅠ_ㅜ";
break;
case '3':
data = "ㅡ_ㅡ";
break;
case '4':
data = "안녕?";
break;
case '5':
data = "나 갈게..";
break;
case '6':
data = "비켜";
break;
case '7':
data = "치워줘..";
break;
case '8':
data = "지나갈래..";
break;
case '9':
data = "막지마..";
break;
case 'A':
data = "먹을거..";
break;
case 'B':
data = "고마워!";
break;
case 'C':
data = "뒤에 비켜!";
break;
case 'D':
data = "좋아~!";
break;
case 'E':
data = "따라와라!";
break;
case 'F':
data = "공부해!!";
break;
}
char tx_data[20] = {};
for (int i = 0; i < data.length(); i++) {
tx_data[i] = data[i];
}
Serial.println(tx_data);
tx(LCD_TOPIC, tx_data);
}
}
// create thread
Thread keypad_th = Thread();
// ... thread controller 관련 코드
void setup(void) {
// ... serial monitor begin
// ... mqtt client enable
// ... 로타리 엔코더 핀 설정
// callback thread func
// ... 로타리 엔코더 스레드 callback func
// 키패드 스레드 callback func
keypad_th.onRun(keypad_check);
keypad_th.setInterval(10);
// ... rotary thread add to thread controller
// keypad thread add to thread controller
controller.add(&keypad_th);
}
// ... callback for the Timer
// ... onConnectionEstablished()
void loop() {
controller.run();
client.loop();
}
oled_thread.py
lcd
를 구독하고, ESP32에서 메시지를 보내면 그 메시지를 그대로 출력한다. ROTATE_180
했다. import board
import digitalio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import paho.mqtt.client as mqtt
import socket
import threading
class OLEDThread(threading.Thread):
BROKER_ADDRESS = socket.gethostbyname(socket.gethostname())
font = ImageFont.truetype('malgun.ttf', 25)
WIDTH = 128
HEIGHT = 64
BORDER = 5
def __init__(self):
super().__init__()
self.client = mqtt.Client("lcd_sub")
self.client.connect(self.BROKER_ADDRESS)
self.client.subscribe("lcd")
self.client.on_message = self.on_command
self.oled_reset = digitalio.DigitalInOut(board.D27)
# use for spi
self.spi = board.SPI()
self.oled_cs = digitalio.DigitalInOut(board.D8)
self.oled_dc = digitalio.DigitalInOut(board.D26)
self.oled = adafruit_ssd1306.SSD1306_SPI(self.WIDTH, self.HEIGHT, self.spi, self.oled_dc, self.oled_reset,
self.oled_cs)
self.display("Hello")
def on_command(self, client, userdata, message):
self.display(str(message.payload.decode("utf-8")))
def display(self, data):
# clear
self.oled.fill(0)
self.oled.show()
image = Image.new("1", (self.oled.width, self.oled.height))
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, self.oled.width, self.oled.height), outline=255, fill=255)
draw.rectangle(
(self.BORDER, self.BORDER, self.oled.width - self.BORDER - 1, self.oled.height - self.BORDER - 1),
outline=0,
fill=0
)
draw.text((10, 10), data, font=self.font, fill=1) # 하얀색 폰트인 글씨를 (10,10) 위치에 그리기
# 이미지 자르기
box = (0, 0, self.WIDTH, self.HEIGHT)
region = image.crop(box) # 해당 영역 만큼 이미지 자르기
# 이미지 변형
region = region.transpose(Image.ROTATE_180) # 180도 회전
image.paste(region, box)
# show
self.oled.image(image)
self.oled.show()
def run(self):
self.client.loop_forever()