아두이노 LCD 한글 폰트를 RP2350 Touch LCD 3.5의 ST7796 화면에 적용했습니다. 한글·영문·숫자를 20px, 28px, 64px로 나누어 출력했고, 실제 화면에서 글자 크기와 정렬까지 확인했습니다.

RP2350 Touch LCD 3.5에서 한글·영문·숫자 폰트를 출력한 결과

RP2350 Touch LCD 3.5에서 한글·영문·숫자 폰트를 출력한 결과

RP2350 Touch LCD 3.5 폰트 적용 결과

RP2350 Touch LCD 3.5 폰트는 화면에서 맡는 역할에 따라 크기를 나누었습니다.

크기적용 위치화면에서 확인한 내용
20px상태 문구, 작은 안내 문구한글·영문 혼합 문장을 한 화면에 배치
28px화면 제목, 주요 버튼제목과 주요 조작 문구를 선명하게 표시
64px시간, 단계 등 큰 숫자03:00, 12처럼 멀리서 볼 값을 크게 표시

하나의 크기를 모든 문구에 적용하지 않고, 작은 설명은 20px, 제목과 주요 버튼은 28px, 숫자는 64px로 구분했습니다. 큰 폰트에는 필요한 숫자와 :만 넣어 관리했습니다.

TFT LCD에서 비교한 한글·영문 폰트

TFT LCD 한글 출력은 PC 화면에서 고르는 것만으로 끝내지 않고 실제 LCD에서 비교했습니다. NanumSquareNeo, NanumSquareRound, NanumGothic, D2Coding을 같은 문구와 크기로 출력해 한글·영문·숫자의 형태를 확인했습니다.

RP2350 Touch LCD 3.5에서 네 가지 폰트를 같은 조건으로 비교한 화면

RP2350 Touch LCD 3.5에서 네 가지 폰트를 같은 조건으로 비교한 화면

최종 UI에는 NanumSquareNeo를 적용했습니다. 아래 화면처럼 한글, 영문, 숫자가 함께 있는 경우에도 글자의 높이와 간격을 실제 장치에서 확인할 수 있습니다.

NanumSquareNeo 28px로 한글·영문·숫자를 함께 출력한 실제 화면

NanumSquareNeo 28px로 한글·영문·숫자를 함께 출력한 실제 화면

TTF 파일을 TFT LCD에 그대로 넣으면 안 되는 이유

TTF 비트맵 폰트 변환이 필요한 이유는 RP2350이 TTF 원본을 화면용 픽셀로 직접 바꾸는 구조가 아니기 때문입니다. TTF 파일만 Arduino 프로젝트에 복사해도 ST7796 화면에는 글자가 나오지 않습니다.

실제 TTF 파일, UIFont.cpp의 비트맵 데이터, LCD 출력 결과를 연결한 과정

실제 TTF 파일, UIFont.cpp의 비트맵 데이터, LCD 출력 결과를 연결한 과정

실제 작업 순서는 다음 세 단계입니다.

  1. PC에서 사용할 TTF 원본을 선택합니다.
  2. 화면에 필요한 글자를 픽셀 데이터로 바꾸어 UIFont.cpp에 저장합니다.
  3. RP2350이 그 데이터를 읽어 ST7796 LCD에 그립니다.

아래는 변환 전 TTF 원본 파일과 변환 후 코드의 실제 모습입니다.

변환에 사용할 NanumSquare 계열 TTF 원본 파일

변환에 사용할 NanumSquare 계열 TTF 원본 파일

NanumSquareNeo20Bitmap 배열로 생성된 LCD용 픽셀 데이터

NanumSquareNeo20Bitmap 배열로 생성된 LCD용 픽셀 데이터

여기서 비트맵 폰트는 글자 모양을 LCD에 찍을 픽셀 값으로 바꾼 데이터입니다. RP2350은 TTF를 해석하는 대신, 이미 변환된 배열을 읽어 화면에 바로 그립니다.

필요한 글자와 폰트 크기만 변환합니다

임베디드 한글 폰트는 모든 글자를 한꺼번에 넣기보다 화면에서 사용할 글자와 크기만 준비하는 편이 관리하기 쉽습니다.

화면 문구를 먼저 정하고 필요한 글자만 비트맵 폰트로 만드는 방법

화면 문구를 먼저 정하고 필요한 글자만 비트맵 폰트로 만드는 방법

이번 프로젝트에서는 20px와 28px에 UI용 한글·영문을 넣고, 64px에는 큰 숫자 표시에 필요한 문자만 넣었습니다. 화면 문구가 바뀌면 새 글자를 목록에 추가한 뒤 폰트를 다시 생성합니다.

UI_GLYPHS = "...시간모드속도단계설정시작정지..."
python tools/generate_ui_font.py

생성된 UIFont.hUIFont.cpp를 프로젝트에 포함하면 됩니다. TTF 파일은 변환할 때 사용하고, 실제 LCD 출력에는 변환된 비트맵 데이터가 사용됩니다.

LCD 한글이 물음표로 표시되는 이유

LCD 한글이 ?로 표시되면 해당 글자가 비트맵 폰트에 들어 있는지 먼저 확인해야 합니다. 아래 화면의 속?는 코드에 속도를 입력했지만, 변환된 폰트 데이터에 가 빠져 있었던 경우입니다.

비트맵 폰트에 없는 '도'가 물음표로 표시된 실제 화면

비트맵 폰트에 없는 ?로 표시된 실제 화면

해결 방법은 간단합니다.

  1. 사용할 문구를 먼저 확정합니다.
  2. 빠진 글자를 폰트 생성 목록에 추가합니다.
  3. UIFont.hUIFont.cpp를 다시 생성합니다.
  4. 다시 빌드해 실제 LCD에서 확인합니다.

폰트 데이터에 준비된 글자 하나를 개발 과정에서는 glyph(글리프) 라고 부릅니다. 어렵게 생각할 필요 없이, LCD에 미리 넣어둔 글자 한 개의 그림 데이터라고 보면 됩니다.

생성된 폰트를 화면에 그리는 예제

생성된 UIFont를 다른 화면이나 다른 Arduino 프로젝트에 적용할 때는 UIFont.h, UIFont.cpp, ST7796Display.h, ST7796Display.cpp를 함께 포함합니다. 화면 코드는 drawFontText() 또는 drawFontTextCentered()로 그리고, 한 화면을 모두 그린 뒤 마지막에 present()를 한 번만 호출합니다.

#include "ST7796Display.h"
#include "UIFont.h"

static constexpr uint16_t BUTTON_GREEN = 0x07E0;
static constexpr uint16_t BUTTON_RED = 0xF800;
static constexpr uint16_t BUTTON_BLUE = 0x025F;

void drawExampleButton(int16_t x, int16_t y, int16_t w, int16_t h,
                       const char *label, uint16_t fill, uint16_t textColor) {
  ST7796Display::fillRect(x, y, w, h, fill);
  ST7796Display::drawRect(x, y, w, h, ST7796Display::WHITE);

  const int16_t textY = static_cast<int16_t>(
      y + (h - UIFont::NanumSquareNeo20.lineHeight) / 2);
  ST7796Display::drawFontTextCentered(x, textY, w, label,
                                      UIFont::NanumSquareNeo20,
                                      textColor, fill);
}

void drawFontExampleScreen() {
  if (!ST7796Display::begin()) {
    return;
  }

  ST7796Display::fillScreen(ST7796Display::BLACK);

  // 제목: 28px 폰트
  ST7796Display::drawFontTextCentered(0, 10, ST7796Display::width(),
                                      "시간 설정", UIFont::NanumSquareNeo28,
                                      ST7796Display::WHITE,
                                      ST7796Display::BLACK);

  // 작은 라벨과 한글/영문 혼합 문장: 20px 폰트
  ST7796Display::drawFontTextCentered(0, 48, ST7796Display::width(),
                                      "EN 20: Time Mode Speed Stage Back",
                                      UIFont::NanumSquareNeo20,
                                      ST7796Display::CYAN,
                                      ST7796Display::BLACK);
  ST7796Display::fillRect(24, 82, 432, 1, ST7796Display::GRAY);

  drawExampleButton(36, 110, 120, 58, "시작", BUTTON_GREEN,
                    ST7796Display::BLACK);
  drawExampleButton(180, 110, 120, 58, "정지", BUTTON_RED,
                    ST7796Display::WHITE);
  drawExampleButton(324, 110, 120, 58, "뒤로", BUTTON_BLUE,
                    ST7796Display::WHITE);

  ST7796Display::drawFontText(36, 198, "한글 20: 시간 모드 속도 단계 설정",
                              UIFont::NanumSquareNeo20,
                              ST7796Display::WHITE,
                              ST7796Display::BLACK);
  ST7796Display::drawFontText(36, 228, "MIX 28: Mode Speed / 속도",
                              UIFont::NanumSquareNeo28,
                              ST7796Display::YELLOW,
                              ST7796Display::BLACK);
  ST7796Display::drawFontText(36, 268, "03:00", UIFont::NanumSquareNeo64,
                              ST7796Display::WHITE,
                              ST7796Display::BLACK);

  // 화면을 모두 그린 뒤 한 번만 전송해 중간 과정이 보이지 않게 합니다.
  ST7796Display::present();
}

void setup() {
  Serial.begin(115200);
  drawFontExampleScreen();
}

void loop() {
}

긴 문구를 버튼이나 좁은 박스에 넣을 때는 fontTextWidth()로 폭을 먼저 확인합니다. 폭이 박스보다 크면 더 짧은 문구로 바꾸거나, 28px 대신 20px를 쓰거나, 박스를 넓히거나, 두 줄로 나눕니다.

const int16_t w = ST7796Display::fontTextWidth("시간 설정", UIFont::NanumSquareNeo28);

TFT LCD 폰트 적용 정리

TFT LCD 한글 출력은 TTF 파일을 프로젝트에 복사하는 것으로 끝나지 않습니다. 사용할 한글·영문·숫자와 크기를 먼저 정하고, LCD가 읽을 수 있는 비트맵 데이터로 변환해야 합니다.

이번 RP2350 Touch LCD 3.5에서는 다음 기준으로 정리했습니다.

  • 20px: 상태와 안내 문구
  • 28px: 제목과 주요 버튼
  • 64px: 시간과 단계 숫자
  • 새 문구 추가: 글자 목록 수정 후 폰트 재생성
  • 최종 확인: 실제 ST7796 화면에서 ?, 잘림, 정렬 확인

임베디드 한글 폰트는 화면에서 필요한 글자와 크기만 비트맵으로 관리하는 것이 핵심입니다.

문의