4x4 Keypad와 OLED display로 소통하기 (with ESP32, Raspberry Pi)

GAON PARK·2023년 11월 23일
0
post-custom-banner

4x4 Keypad & OLED display

버튼이 16개 달린 패드이다. 4x4의 행렬로 어떤 버튼이 눌렸는지를 확인하며, 이번 프로젝트에서는 각 버튼에 문자열을 매핑해서 라즈베리파이에 연결해놓은 OLED display에 표시되도록 했다.

회로 연결

ESP32 & 4x4 Keypad

4x4 keypadesp32
c4gpio16
c3gpio17
c2gpio5
c1gpio18
r1gpio19
r2gpio21
r3gpio22
r4gpio23

라즈베리파이 & OLED display

oledraspberry pi
dingpio10 (spi mosi)
clkgpio11 (spi sclk)
csgpio8 (spi ce0)
d/cgpio26
resgpio27

라즈베리파이 설정

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

Arduino 라이브러리

keypad

![[Pasted image 20231123153007.png]]

ESP32 코드

로타리 앤코더를 연결한 오른쪽 ESP32에 업로드할 코드에 다음 내용을 추가한다.

  • mqtt 송신할 때 lcd 토픽으로 송신한다.
  • 각 키에 매핑해둔 char 값에 따라 라즈베리파이에 문자열을 보내준다.
  • 버튼 자체에 문자열을 매핑하고 싶었는데, char 만 지원하는 듯했다.
  • keypad_check() : 커스텀 객체가 아닌, 기본 Thread 객체를 만들어서 interval 마다 주기적으로 실행할 함수를 정의했다.
  • 계속해서 값을 읽어와야 하기 때문에 다른 것과 마찬가지로 thread로 구현했다.
#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

  • thread로 구현했다.
  • mqtt 통신을 사용하며 lcd를 구독하고, ESP32에서 메시지를 보내면 그 메시지를 그대로 출력한다.
  • oled에 새로운 문자를 표시할 때마다 한 번 클리어 한다.
  • oled가 거꾸로 꽂혀있기 때문에 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()
post-custom-banner

0개의 댓글