일반적으로 임베디드에서 많이 사용하는 그래픽 LCD들은 몇 가지 종류가 있는데 SPI, LVDS, MIPI-DSI, HDMI 등이 그러하다. 최근 고성능 장치에서는 DSI등을 제공하기도 하는데, ESP32와 같은 저속 MCU에서는 일반적으로 SPI LCD를 주로 사용한다. 그래서 국내에서 구매할랬더니, 너무 비싸서 알리익스프레스에서 2개를 구매했다. 

     

    우선 하나는 라즈베리파이에서 테스트를 했고, 동일한 칩을 사용한 다른 LCD는 ESP32에서 테스트 하기 위해서 구매한 것이다. 라즈베리용은 해상도는 좀 낮지만 크기가 큰걸 선택했다. 해당 제품이 5V가 기준인거 같아서 말이다. 전에도 언급했지만 ESP32의 경우 별도로 전원을 설계하지 않으면 4.5V정도 밖에 나오지 않기 때문에 조금 투박하고 큰 LCD는 라즈베리파이에 주고, 작고 귀여운 LCD는 ESP32에 적용한 것이다. 둘다 SPI니까... 뭐.. 괜찮겠지.. 하고 말이다.

     

     

    아랬족이 ESP32용으로 설정한건데... 나중에 보니 LCD 컨트롤러가 다른거였다,,, 뭐 어잿거나 그래서 간단하게 칼라바를 만들어 동작을 확ㅇ니해 보도록 하자. 그런데 이러한 LCD 나 카메라등은 MCU에서 제어하기가 쉽지가 않다. 데이터 시트가 너무 복잡하기 때문이다. 하지만 걱정하지 않아도 된다. 이런부분은 chatgpt를 이용하면 편리하다는 거슬~ 알고있잖은가. 원래는 데이터 시트를 확인해가면서 이것저것 건드리면서 해야되는데... 무섭다..

     

     

    1. 시스템 연결

      이제 그림은 그릴 필요는 없을 것 같고.I/O 정보만 정리하고 가도록 하자. 아래는 구매한 제품이고, 컨트롤러는 ST7789이다. SPI 인터페이스를 사용하기 때문에 인터페이스는 아래와 같다. 게다가 출력장치이므로 입력핀 MISO는 생략되었다. 

      

     

      - GND는             ESP32 - GND핀

      - VCC는             ESP32 - 3.3V핀

      - CLK(SCL)       ESP32 - 18번 핀

      - MOSI(SDA)    ESP32 - 11번 핀

      - RST는             ESP32 - 15번 핀

      - DC는               ESP32 - 5번 핀

      - CS는               ESP32 - 4번 핀

      - BL은                ESP32 - 3.3V핀

     

     

    2. 소스코드

      소스코드는 아래와 같다. LCD 컨트롤러의 데이터시트는 alldatasheet에 들어가서 다운로드 받도록 하고, 기본 동작 설명은 주석으로 대체 하도록 한다. 

    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "driver/spi_master.h"
    #include "driver/gpio.h"
    #include "esp_log.h"
    #include <string.h>
    
    #define LCD_HOST SPI2_HOST
    
    #define PIN_NUM_MOSI 11     // MOSI (SDA)
    #define PIN_NUM_CLK  18     // CLK (SCL)
    #define PIN_NUM_RST  15     // RST
    #define PIN_NUM_DC   5      // DC
    #define PIN_NUM_CS   4      // CS
    
    #define LCD_WIDTH  240      // 가로 해상도 
    #define LCD_HEIGHT 240      // 세로 해상도
    
    // 색상 설정을 위한 매크로 
    #define RGB565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))
    
    static const char *TAG = "ST7789";
    
    spi_device_handle_t spi;    // Soft SPI 핸들 
    
    void lcd_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 lcd_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 lcd_data_byte(uint8_t data) 
    {
        lcd_data(&data, 1);
    }
    
    void lcd_reset() 
    {
        gpio_set_level(PIN_NUM_RST, 0);   // RST를 0으로 설정
        vTaskDelay(pdMS_TO_TICKS(50));
        gpio_set_level(PIN_NUM_RST, 1);   // RST를 1로 설정
        vTaskDelay(pdMS_TO_TICKS(50));
    }
    
    void lcd_init() 
    {
        ESP_LOGI(TAG, "Initializing SPI and LCD");
    
        spi_bus_config_t buscfg = {
            .mosi_io_num = PIN_NUM_MOSI,
            .miso_io_num = -1,            // LCD이므로 입력은 필요없음.
            .sclk_io_num = PIN_NUM_CLK,
            .quadwp_io_num = -1,
            .quadhd_io_num = -1,
        };
        spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
    
        spi_device_interface_config_t devcfg = 
        {
            .clock_speed_hz = 40 * 1000 * 1000,      // ST7789 는 최대 62.5Mhz를 지원
            .mode = 0,                               // ST7789 일반적으로 mode 0 또는 3 사용
            .spics_io_num = PIN_NUM_CS,              // CS핀 설정
            .queue_size = 7,                         // 큐 크기 (동시에 처리가능한 전송 카운트)
        };
        spi_bus_add_device(LCD_HOST, &devcfg, &spi);
    
        gpio_config_t io_conf = 
        {
            .pin_bit_mask = (1ULL << PIN_NUM_DC) | (1ULL << PIN_NUM_RST),   // DC와 RST GPIO 등록
            .mode = GPIO_MODE_OUTPUT,
        };
        gpio_config(&io_conf);
    
        gpio_set_level(PIN_NUM_DC, 1);     // DC를 HIGH로 설정
        gpio_set_level(PIN_NUM_RST, 1);    // RST를 HIGH로 설정
    
        lcd_reset();                       // RST를 흔들어줌
    
        // ST7789 초기화 시퀀스 - Porch 설정
        lcd_cmd(0x36); lcd_data_byte(0x70);            // 메모리 엑세스 컨트롤 (MY=1,MX=1,MV=0,ML=0,RGB=1 -> XY 180도 반전, RGB순서)
        lcd_cmd(0x3A); lcd_data_byte(0x05);            // 픽셀 포맷을 설정 (16bit/pixel - RGB565)
        // 프레임 간격 및 Back Porch 설정 : Front Porch = 12, Back Porch = 12, RTNB=0 (Unknown), VFP=0x33, VBP=0x33
        lcd_cmd(0xB2); uint8_t b2[] = {0x0C, 0x0C, 0x00, 0x33, 0x33}; lcd_data(b2, 5);
        lcd_cmd(0xB7); lcd_data_byte(0x35);            // Gate Control : 전압레벨 조정 (VGH=13.26V, VGL=-10.43V)
        lcd_cmd(0xBB); lcd_data_byte(0x19);            // 공통전압레벨 설정 : 0x19=1.325V 
        lcd_cmd(0xC0); lcd_data_byte(0x2C);            // AVDD(Positive Power Voltage) 설정 = 0x2c -> 4.6V (3.3v에서도 잘 동작)
        lcd_cmd(0xC2); lcd_data_byte(0x01);            // 디지털 전압 조정 활성화 0x01 -> VRH 설정 활성화
        lcd_cmd(0xC3); lcd_data_byte(0x12);            // VHR 설정 : 0x12--> 4.6V
        lcd_cmd(0xC4); lcd_data_byte(0x20);            // VDV 설정 : VCOM DC 저ㅗㄴ압 설정 Fine-tune -> 0x20 = 0.8v
        lcd_cmd(0xC6); lcd_data_byte(0x0F);            // 프레임 레이트 컨트롤 (노멀모드) : 0x0f -> 약 60Hz
        // 전원제어2 : AVDD, AVCL(0xA4) - 활성화, AVEE(0xA1) - Source/Gate Timing 조정 
        lcd_cmd(0xD0); lcd_data_byte(0xA4); lcd_data_byte(0xA1);   
        // Positive Gamma Cureve(E0), Negative Gamma Curve(E1)
        lcd_cmd(0xE0); uint8_t e0[] = {0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23};
        lcd_data(e0, sizeof(e0));                      // 휘도, 대비, 색 표현관련 설정 적용
        lcd_cmd(0xE1); uint8_t e1[] = {0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23};
        lcd_data(e1, sizeof(e1));                      // 패널 특성에 따라 조정 필요
    
        lcd_cmd(0x21);                                 // Inversion ON -> 픽셀 색상 반전 활성화 (화면 깜빡임이나 특정 색이 보정됨)
        lcd_cmd(0x11);                                 // Sleep Out -> 슬립모드에서 정상모드로 복귀
        vTaskDelay(pdMS_TO_TICKS(120));                // 설정 지연시간 (데이터시트 명시)
        lcd_cmd(0x29);                                 // 화면표시 시작
    }
    
    // 영상 출력을 위한 영역 설정
    void lcd_set_addr_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) 
    {
        uint8_t data[4];
    
        lcd_cmd(0x2A);        // 컬럼 주소 설정 (가로)
        data[0] = x0 >> 8; data[1] = x0 & 0xFF;
        data[2] = x1 >> 8; data[3] = x1 & 0xFF;
        lcd_data(data, 4);
    
        lcd_cmd(0x2B);        // 페이지 어드레스 설정(세로)
        data[0] = y0 >> 8; data[1] = y0 & 0xFF;
        data[2] = y1 >> 8; data[3] = y1 & 0xFF;
        lcd_data(data, 4);
    
        lcd_cmd(0x2C);        // 메모리 기록 가능 설정
    }
    
    // 사각형 그리기 함수
    void lcd_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) 
    {
        lcd_set_addr_window(x, y, x + w - 1, y + h - 1);
    
        uint32_t size = w * h;
        uint8_t hi = color >> 8;
        uint8_t lo = color & 0xFF;
        const int buf_len = 512;
        uint8_t buffer[buf_len];
        for (int i = 0; i < buf_len; i += 2) 
        {
            buffer[i] = hi;
            buffer[i + 1] = lo;
        }
    
        while (size > 0) 
        {
            int to_send = size > (buf_len / 2) ? (buf_len / 2) : size;
            lcd_data(buffer, to_send * 2);
            size -= to_send;
        }
    }
    
    
    
    void app_main() {
        lcd_init();
    
        ESP_LOGI(TAG, "Drawing color bars...");
        uint16_t colors[] = 
        {
            RGB565(255, 0, 0),     // Red
            RGB565(0, 255, 0),     // Green
            RGB565(0, 0, 255),     // Blue
            RGB565(255, 255, 0),   // Yellow
            RGB565(0, 255, 255),   // Cyan
            RGB565(255, 0, 255),   // Magenta
            RGB565(255, 255, 255), // White
            RGB565(0, 0, 0),       // Black
        };
    
        int bar_height = LCD_HEIGHT / 8;
        for (int i = 0; i < 8; ++i) 
        {
            lcd_fill_rect(0, i * bar_height, LCD_WIDTH, bar_height, colors[i]);
        }
    
        ESP_LOGI(TAG, "Done.");
    }

     

     

    3. 결과확인

      컬러바 출력소스에 대해서 빌드하고 결과를 확인하면 다음과 같다. 

     

     

    이렇게 출력하고 나니까 뭔가 남는게 없는거 같은데.. ESP32 I2C하고 SPI는 별도로 뭔가를 해서 설명이 필요한것 같으니, ESP32 2개를 이용해서 실제 데이터가 어떻게 가는지 정리해서 올리도록 하겠다. 이번주는 휴일이 몇일 있어서 정리하기 딱 좋은 날이긴 한데.. 

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기