APM32F427 기반 SPI LCD에 LVGL 그래픽 라이브러리 포팅하기

APM32F427 마이크로컨트롤러와 SPI TFT-LCD 통합 가이드

1. 하드웨어 플랫폼 개요

APM32F427은 ARM Cortex-M4F 코어를 기반으로 동작하며, 최대 240MHz의 클록 주파수와 448KB의 내장 SRAM을 제공합니다. 이는 임베디드 GUI 애플리케이션 구현 시 메모리 병목 문제 없이 부드러운 사용자 인터페이스를 구현할 수 있는 이상적인 조건을 제공합니다.

해당 MCU는 RGB 인터페이스(LTDC)를 지원하지 않지만, SPI 및 EMMC-SMC 인터페이스를 통해 외부 디스플레이 드라이버와 연결 가능합니다. 본 문서에서는 ILI9341 칩셋 기반의 240x320 해상도 TFT 모듈과의 연동을 다룹니다.

2. SPI 기반 LCD 제어 회로 구성

사용된 TFT 모듈의 핀맵은 다음과 같습니다:

  • SCK: SPI 클록 신호
  • MOSI: 데이터 출력 (마스터에서 슬레이브로)
  • CS: 칩 셀렉트 (저전압 활성화)
  • DC: 명령/데이터 선택 (0: 명령, 1: 데이터)
  • RESET: 하드웨어 리셋 핀
  • LED: 백라이트 전원 (3.3V 입력)

3. SPI 통신 초기화 및 데이터 전송 함수 구현

SPI1을 사용하여 최대 전송 속도를 확보하기 위해 분주율을 2로 설정하였습니다. 이를 통해 SPI 클럭을 시스템 클럭의 절반으로 동작시키며 고속 데이터 전송을 달성합니다.

void spi_lcd_init(void)
{
    GPIO_Config_T gpio_config;
    SPI_Config_T spi_config;

    // 클록 활성화
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SPI1 | RCM_APB2_PERIPH_SYSCFG);

    // SCK 및 MOSI 핀 AF 설정
    GPIO_ConfigPinAF(LCD_GPIO_PORT, LCD_SCK_PIN_SRC, GPIO_AF_5);
    GPIO_ConfigPinAF(LCD_GPIO_PORT, LCD_MOSI_PIN_SRC, GPIO_AF_5);

    // GPIO 설정
    gpio_config.pin = LCD_SCK_PIN | LCD_MOSI_PIN;
    gpio_config.mode = GPIO_MODE_AF;
    gpio_config.speed = GPIO_SPEED_100MHz;
    gpio_config.otype = GPIO_OTYPE_PP;
    gpio_config.pupd = GPIO_PUPD_NOPULL;
    GPIO_Config(LCD_GPIO_PORT, &gpio_config);

    // SPI 설정
    SPI_ConfigStructInit(&spi_config);
    spi_config.mode = SPI_MODE_MASTER;
    spi_config.baudrateDiv = SPI_BAUDRATE_DIV_2;
    spi_config.direction = SPI_DIRECTION_2LINES_TXONLY;
    spi_config.phase = SPI_CLKPHA_2EDGE;
    spi_config.polarity = SPI_CLKPOL_HIGH;
    spi_config.nss = SPI_NSS_SOFT;
    spi_config.length = SPI_DATA_LENGTH_8B;
    SPI_Config(SPI1, &spi_config);

    SPI_Enable(SPI1);
}

단일 바이트 전송 함수는 전송 완료 대기 후 MOSI 레지스터에 데이터를 기록하고, 수신 버퍼 비어있음 상태를 확인하여 더미 데이터를 소비합니다.

uint8_t spi_send_byte(uint8_t data)
{
    while (!(SPI1->STS & SPI_FLAG_TXBE));
    SPI_I2S_TxData(SPI1, data);

    while (!(SPI1->STS & SPI_FLAG_RXBNE));
    return SPI_I2S_RxData(SPI1);
}

4. 디스플레이 제어 저수준 함수

GPIO 핀을 이용한 제어 신호 관리가 필요합니다. 아래 매크로는 각각 CS, DC, RST 핀의 상태를 조작합니다:

#define LCD_CS_LOW()  (LCD_GPIO_PORT->BOR = LCD_CS_PIN)
#define LCD_CS_HIGH() (LCD_GPIO_PORT->BCR = LCD_CS_PIN)
#define LCD_DC_CMD()  (LCD_GPIO_PORT->BOR = LCD_DC_PIN)
#define LCD_DC_DATA() (LCD_GPIO_PORT->BCR = LCD_DC_PIN)
#define LCD_RST_LOW() (LCD_GPIO_PORT->BOR = LCD_RST_PIN)
#define LCD_RST_HIGH() (LCD_GPIO_PORT->BCR = LCD_RST_PIN)

명령 및 데이터 전송 함수는 다음과 같습니다:

void lcd_write_command(uint8_t cmd)
{
    LCD_CS_LOW();
    LCD_DC_CMD();
    spi_send_byte(cmd);
    LCD_CS_HIGH();
}

void lcd_write_data(uint8_t data)
{
    LCD_CS_LOW();
    LCD_DC_DATA();
    spi_send_byte(data);
    LCD_CS_HIGH();
}

void lcd_write_color_rgb565(uint16_t color)
{
    LCD_CS_LOW();
    LCD_DC_DATA();
    spi_send_byte(color >> 8);
    spi_send_byte(color & 0xFF);
    LCD_CS_HIGH();
}

5. 화면 영역 설정 및 픽셀 그리기

ILI9341 드라이버는 RAM 접근을 위해 X 및 Y 축의 시작/종료 좌표를 설정해야 합니다.

void lcd_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    lcd_write_command(0x2A);
    lcd_write_data(x1 >> 8);
    lcd_write_data(x1 & 0xFF);
    lcd_write_data(x2 >> 8);
    lcd_write_data(x2 & 0xFF);

    lcd_write_command(0x2B);
    lcd_write_data(y1 >> 8);
    lcd_write_data(y1 & 0xFF);
    lcd_write_data(y2 >> 8);
    lcd_write_data(y2 & 0xFF);

    lcd_write_command(0x2C); // Write memory start
}

void lcd_draw_pixel_at(uint16_t x, uint16_t y, uint16_t color)
{
    lcd_set_address_window(x, y, x, y);
    lcd_write_color_rgb565(color);
}

6. LVGL 그래픽 라이브러리 통합

LVGL v9.4 기준으로 포팅을 진행합니다. 공식 저장소 또는 미러 사이트에서 소스 코드를 가져옵니다:

6.1 프로젝트 구조 구성

프로젝트 내 lvgl/ 디렉토리에 다음 폴더를 포함합니다:

  • src/ – 핵심 라이브러리 소스
  • demos/ – 예제 애플리케이션
  • porting/ – 포팅 템플릿 파일

다음 파일들을 프로젝트에 추가합니다:

lv_port_disp_template.c → lv_port_disp.c
lv_port_indev_template.c → lv_port_indev.c
lv_conf_template.h → lv_conf.h

6.2 헤더 검색 경로 등록

컴파일러 설정에 다음 경로를 포함시킵니다:

  • ./lvgl/src
  • ./lvgl
  • ./lvgl/demos
  • ./lvgl/examples/porting

간단한 헤더 포함을 위해 전처리기 정의를 추가합니다:

#define LV_LVGL_H_INCLUDE_SIMPLE

6.3 LVGL 설정 수정

lv_conf.h에서 필요한 기능을 활성화합니다:

#define LV_USE_DEMO_WIDGETS      1
#define LV_USE_DEMO_BENCHMARK    1
#define LV_USE_DEMO_STRESS       1
#define LV_USE_PERF_MONITOR      1
#define LV_USE_MEM_MONITOR       1

6.4 디스플레이 드라이버 연결

포팅 파일에서 실제 하드웨어 제어 함수를 연결합니다:

static void disp_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
{
    uint16_t * buf = (uint16_t *)px_map;
    int32_t x, y;

    for (y = area->y1; y <= area->y2; y++) {
        for (x = area->x1; x <= area->x2; x++) {
            lcd_draw_pixel_at(x, y, *buf++);
        }
    }

    lv_display_flush_ready(disp);
}

디스플레이 해상도는 포팅 코드에서 정의합니다:

#define MY_DISP_HOR_RES  320
#define MY_DISP_VER_RES  240

6.5 시간 기반 타이머 설정

LVGL은 주기적으로 `lv_timer_handler()`를 호출해야 하며, 이를 위해 1ms 주기의 타이머 인터럽트를 구성합니다.

void timer_setup_for_lvgl(void)
{
    TMR_BaseConfig_T tmr_config;

    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_TMR1);
    
    tmr_config.period = 999;
    tmr_config.division = 239;
    tmr_config.countMode = TMR_COUNTER_MODE_UP;
    TMR_ConfigTimeBase(TMR1, &tmr_config);

    TMR_EnableInterrupt(TMR1, TMR_INT_UPDATE);
    NVIC_EnableIRQRequest(TMR1_UP_TMR10_IRQn, 0, 0);
    TMR_Enable(TMR1);
}

void TMR1_UP_TMR10_IRQHandler(void)
{
    if (TMR_ReadIntFlag(TMR1, TMR_INT_UPDATE)) {
        TMR_ClearIntFlag(TMR1, TMR_INT_UPDATE);
        lv_tick_inc(1); // 1ms 경과 알림
    }
}

6.6 메모리 설정 조정

GUI 처리를 위한 스택 및 힙 공간을 확보합니다. startup_apm32f427xx.s에서 다음과 같이 수정:

Stack_Size      EQU     0x00001000  ; 스택 4KB → 16KB
Heap_Size       EQU     0x00000A00  ; 힙 512B → 2.5KB

7. 메인 애플리케이션 실행

초기화 순서는 다음과 같습니다:

int main(void)
{
    SystemInit();
    spi_lcd_init();
    timer_setup_for_lvgl();

    lv_init();
    lv_port_disp_init(); // 디스플레이 등록

    // 원하는 데모 선택
    lv_demo_widgets();
    // lv_demo_benchmark();
    // lv_demo_stress();

    while (1) {
        lv_timer_handler(); // 필수 호출
        delay_ms(5);
    }
}

성능 모니터링을 활성화한 경우, 화면 상단에 FPS 및 메모리 사용량이 실시간 표시됩니다.

8. 결론

APM32F427의 고성능 M4F 코어와 넉넉한 SRAM은 LVGL과 같은 고급 GUI 프레임워크를 안정적으로 구동하기에 매우 적합합니다. SPI 인터페이스를 활용한 TFT 제어는 하드웨어 자원 절약과 함께 충분한 성능을 제공하며, 다양한 데모 애플리케이션을 통해 부드러운 애니메이션과 반응형 UI를 확인할 수 있습니다.

태그: APM32F427 LVGL SPI-TFT ILI9341 Embedded-GUI

6월 1일 22:46에 게시됨