여기까지가 사실상 예제는 내부 인터페이스는 마지막인거 같고, 굴러다니는 모듈이 있으면 하나씩 붙혀서 사용할 수 있도록 계속 업데이트를 할 예정이다. 일반적으로 임베디드에서 사용하는 대부분의 내부 인터페이스인 GPIO, PWM, I2C, SPI 정도만 해봤으면 어느정도는 해본걸 테고, 이후에는 이러한 디바이스를 쓰기 위한 세부적인 내용을 다뤄볼지 아니면, 어차피 취미니까 이런저런걸 만드는 정도로 진행할지는 필자의 여유시간에 따라 달라지게 될 것 같다.
암만 찾아도 LCD가 나오지 않아서, 알리에서 또 2만원치 구매했는데... 구매하고 포기하고 있으니, 굴러나왔다. 하하핫.. 뭐 사놓으면 다음에 뭐 쓰던지 하겠지 싶어 그냥 놔뒀다.
필자가 OLED를 쓰려고 하는 이유는 단순하다. 일반적인 TFT-LCD의 경우에 사이즈가 조금 커지면 대부분 5V의 전압을 요구하는데 반해서, ESP32의 캐리어를 구매해서 외부 전원을 사용하지 않는한, 최대 4.5V (정품기준)만 공급할 수 있기 때문에, 어쩔수 없이 3.3V로 동작 가능한 LCD를 찾다보니, OLED가 딱이었다. 필자의 기억으로는 그냥 LCD도 하나 있었던거 같은데, 케이블만 보이고 모듈은 보이지 않았다. 뭐... 어쩔수 없고.. 어차피 사용방법은 같으니까.
SPI의 통신 방식은 나중에 별도로 작성하도록 하고 필자가 사용하려는 Waveshare 1.5" OLED의 경우 다음의 시퀀스로 초기화를 해주어야 한다. 아래의 커맨드는 SSD1351 데이터시트 32페이지 Command Table을 확인하면된다.
1) Command Lock: 0xFD, 0x12 (Optional), 0xFD, 0xB1 (Unlock)
2) Display Off: 0xAE
3) Set Clock Div: 0xB3, 0xF1
4) Set MUX Ratio: 0xCA, 0x7F
5) Set Display Offset: 0xA2, 0x00
6) Set Display Start Line: 0xA1, 0x00
7) Set Re-map / Color Depth: 0xA0, 0x74 (depends on layout)
8) Set Column Address: 0x15, 0x00, 0x7F
9) Set Row Address: 0x75, 0x00, 0x7F
10) Set GPIO: 0xB5, 0x00
11) Set VSL: 0xB4, ... (often skipped)
12) Set Precharge: 0xB1, 0x32
13) Set VCOMH: 0xBE, 0x05
14) Set Display Mode: 0xA6 (Normal)
15) Set VDD: 0xAB, 0x01 (VDD Internal)
16) Display ON: 0xAF
99) VSL / Precharge 2 는 생략가능 (미세조정등이 필요하거나 할 경우 사용)
1. 연결
연결은 OLED 모듈의 핀과 다음과 같은 순서로 연결 하였다. 핀 정보는 가지고 있는 ESP32에 따라서 변경할 수 있다.
2. 소스코드
소스코드는 아래와 같다.
#include <stdio.h>
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// 핀 정의 (보드에 따라 수정 필요)
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 18
#define PIN_NUM_CS 5
#define PIN_NUM_DC 21
#define PIN_NUM_RST 22
#define OLED_WIDTH 128
#define OLED_HEIGHT 128
static const char *TAG = "SSD1351";
// SPI 핸들
spi_device_handle_t spi;
// 명령 전송
void ssd1351_cmd(uint8_t cmd) {
spi_transaction_t t = {
.length = 8,
.tx_buffer = &cmd
};
gpio_set_level(PIN_NUM_DC, 0); // 명령 모드
spi_device_transmit(spi, &t);
}
// 데이터 전송
void ssd1351_data(const uint8_t *data, int len) {
if (len == 0) return;
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data
};
gpio_set_level(PIN_NUM_DC, 1); // 데이터 모드
spi_device_transmit(spi, &t);
}
// 디스플레이 초기화 시퀀스
void ssd1351_init() {
ESP_LOGI(TAG, "Initializing display");
gpio_set_level(PIN_NUM_RST, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(PIN_NUM_RST, 1);
vTaskDelay(pdMS_TO_TICKS(100));
ssd1351_cmd(0xFD); ssd1351_data((uint8_t[]){0x12}, 1); // Command Lock
ssd1351_cmd(0xFD); ssd1351_data((uint8_t[]){0xB1}, 1); // Command Lock
ssd1351_cmd(0xAE); // Display Off
ssd1351_cmd(0xB3); ssd1351_data((uint8_t[]){0xF1}, 1); // Clock
ssd1351_cmd(0xCA); ssd1351_data((uint8_t[]){0x7F}, 1); // MUX Ratio
ssd1351_cmd(0xA0); ssd1351_data((uint8_t[]){0x74}, 1); // Remap
ssd1351_cmd(0x15); ssd1351_data((uint8_t[]){0x00, 0x7F}, 2); // Column
ssd1351_cmd(0x75); ssd1351_data((uint8_t[]){0x00, 0x7F}, 2); // Row
ssd1351_cmd(0xA1); ssd1351_data((uint8_t[]){0x00}, 1); // Start Line
ssd1351_cmd(0xA2); ssd1351_data((uint8_t[]){0x00}, 1); // Display Offset
ssd1351_cmd(0xB5); ssd1351_data((uint8_t[]){0x00}, 1); // GPIO
ssd1351_cmd(0xAB); ssd1351_data((uint8_t[]){0x01}, 1); // VDD Internal
ssd1351_cmd(0xB1); ssd1351_data((uint8_t[]){0x32}, 1); // Phase
ssd1351_cmd(0xBE); ssd1351_data((uint8_t[]){0x05}, 1); // VCOMH
ssd1351_cmd(0xA6); // Normal Display
ssd1351_cmd(0xAF); // Display On
}
// 윈도우 설정
void ssd1351_set_window(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
ssd1351_cmd(0x15); ssd1351_data((uint8_t[]){x0, x1}, 2); // Column
ssd1351_cmd(0x75); ssd1351_data((uint8_t[]){y0, y1}, 2); // Row
ssd1351_cmd(0x5C); // Write RAM
}
// 컬러바 그리기
void draw_colorbar() {
uint16_t r_color = 0xF800; // Red
uint16_t g_color = 0x07E0; // Green
uint16_t b_color = 0x001F; // Blue
int bar_height = OLED_HEIGHT / 3;
uint8_t buf[OLED_WIDTH * 2]; // 1줄 = 128픽셀 = 256바이트 (RGB565)
for (int i = 0; i < OLED_WIDTH; ++i) {
buf[i * 2 + 0] = r_color >> 8;
buf[i * 2 + 1] = r_color & 0xFF;
}
ssd1351_set_window(0, 0, OLED_WIDTH - 1, bar_height - 1);
for (int y = 0; y < bar_height; ++y)
ssd1351_data(buf, sizeof(buf));
for (int i = 0; i < OLED_WIDTH; ++i) {
buf[i * 2 + 0] = g_color >> 8;
buf[i * 2 + 1] = g_color & 0xFF;
}
ssd1351_set_window(0, bar_height, OLED_WIDTH - 1, bar_height * 2 - 1);
for (int y = 0; y < bar_height; ++y)
ssd1351_data(buf, sizeof(buf));
for (int i = 0; i < OLED_WIDTH; ++i) {
buf[i * 2 + 0] = b_color >> 8;
buf[i * 2 + 1] = b_color & 0xFF;
}
ssd1351_set_window(0, bar_height * 2, OLED_WIDTH - 1, OLED_HEIGHT - 1);
for (int y = 0; y < bar_height; ++y)
ssd1351_data(buf, sizeof(buf));
}
// SPI 초기화
void spi_init() {
spi_bus_config_t buscfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = -1,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 20 * 1000 * 1000,
.mode = 0,
.spics_io_num = PIN_NUM_CS,
.queue_size = 7,
};
gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
spi_bus_add_device(SPI2_HOST, &devcfg, &spi);
}
void app_main(void) {
spi_init();
ssd1351_init();
draw_colorbar();
}
3. 실행결과
실행결과는 3줄의 컬러바가 출력된다.
최근댓글