RS's Travel & Electronic

u8glib : OLED에 한글 비트맵 출력 본문

시퀀스와 아두이노

u8glib : OLED에 한글 비트맵 출력

RS 2021. 7. 15. 23:35
SMALL

비트맵 이미지 출력하기의 요령으로 출력하는 방법입니다.

우노급 아두이노에서 한글 폰트를 구현하는 것은 용량 부담이 큽니다. 구조도 복잡하고요.

시제품을 제작하려는데 화면출력코드 만으로 용량을 다 잡아먹어버리면 곤란합니다.

 

OLED에 한글 인터페이스를 구현하려고 할 때 의외로 많은 글자가 필요하지 않기도 합니다.

제가 소형 가전 시제품의 사용자 인터페이스를 OLED로 만드는데 한글표시를 해야 될 경우 사용한 방법을 제가 예전에 의뢰받아서 제작했던 물건의 예를 들어 설명하고자 합니다.

 

의뢰 내역은,


약품을 분사하는 장치입니다. 최대 10번의 분사 횟수가 주어집니다. 분사할 때마다 리미트 스위치가 한번 눌리게 되고, 눌리면 횟수에서 1이 감소합니다. 화면에는 분사 횟수의 잔여량이 표시되어야 합니다. 단, 영문이 아니라 한글이어야 합니다.  


로 축약할 수 있습니다. 당시 제작 조건 에는 블루투스 기능 같은 그 외의 요소들도 있었지만, 이번에 설명할 것은 한글 출력의 설명이니까 다른 부분은 넘기기로 하죠.

 

 

 

그럼, 먼저 출력할 문자들을 도트 찍기로 만들어놓을 필요가 있습니다.

 

구글에 "도트 찍는 프로그램"이라고 치면 여러 프로그램을 사용할 수 있지만, 제 경우는 다 귀찮아서ㅋ 그냥 윈도 그림판으로 찍었습니다. 실제로 그림판 하나면 한글 찍는 거 정도는 문제없습니다.

 

도트를 찍는 게 귀찮아서 텍스트를 작게..

예를 들어 약 7pt정도 크기로 그림판에 쓰고, 그걸 그대로 써볼까 생각하기도 했습니다.

'톳' , '호빗' , '팰' 이런 좀 획수가 많은 글자들은 여지없이 뭉개져서 나와버립니다.

 

 

이런 식으로 요.. 헥스데이터를 뽑아서 출력해봐야 뭐라 쓰여있는지 읽기 힘들 겁니다.

그렇다고 크기를 크게 쓰면 화면이 작아서 감당이 안됩니다.

작고 복잡할 글자도 표현하려면 역시 직접 도트를 찍어야 되겠다고 생각했습니다.

필요한 글자는 '버' '튼' '누' '금' '남' '음' '회'입니다.

 

 

 

그림판에서 새 파일을 시작한 뒤에 크기 조정을 누르고, 픽셀 단위로 가로 16, 세로 15로 선택합니다.

그런 다음 출력하고 싶은 글자를 도구 탭의 연필을 선택하고 굵기를 최소(한 픽셀짜리)로 선택한 다음 글자를 "그립니다"

 

더 작게 하고 싶다면 가로 8, 세로 10 정도로 해도 너무 복잡한 글자.. 뭐 가령 뷁 이라던가 ㅋ

그런 게 아니면 쓸 수는 있지만 정작 출력했을 때는 잘 안보일 겁니다. 한글 도트를 찍을 때는 16,15를 추천합니다.

 

도트를 찍을때 글자가 좀 못생겨도 됩니다ㅋ 뜻만 제대로 통할 거 같게 찍으시면 돼요.

찍을 땐 못생겼어도 나중에 출력된 걸 보면 그럴싸하거든요.

 

이전 포스팅에서처럼 만들어진 도트 파일을 한 개 한 개 헥스파일로 변환해줍니다.

변환툴은 여기에도 올려놓겠습니다. 변환 옵션은 W/B 1bit입니다.

 

ezBMP.exe
0.13MB

 

변환된 파일을 C 컴파일러로 열어서 배열을 긁어서 복사하고 아두이노 IDE 안에 복사한 뒤 아두이노 코딩을 시작하겠습니다.

 

 

#include "U8glib.h"
#include <avr/pgmspace.h>


#define LIMITSW     6
#define RESETSW     7
#define THISTIME    5

const unsigned char IMG_BU[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x08,0x10,0x88,
0x10,0x88,0x1f,0x88,0x10,0xf8,0x10,0x88,
0x0f,0x08,0x00,0x08,0x00,0x08,0x00,0x08,
0x00,0x00,0x00,0x00,0x00,0x00
}; 

const unsigned char IMG_TUN[] PROGMEM = {
0x00,0x00,0x0f,0xe0,0x08,0x00,0x0f,0xc0,
0x08,0x00,0x0f,0xe0,0x00,0x00,0x00,0x00,
0x3f,0xfc,0x00,0x00,0x08,0x00,0x08,0x00,
0x08,0x00,0x07,0xf0,0x00,0x00
};

const unsigned char IMG_NU[] PROGMEM = {
0x00,0x00,0x00,0x00,0x08,0x00,0x08,0x00,
0x08,0x00,0x08,0x00,0x07,0xf0,0x00,0x00,
0x00,0x00,0x7f,0xfc,0x00,0x80,0x00,0x80,
0x00,0x80,0x00,0x80,0x00,0x00
}; 

const unsigned char IMG_RUM[] PROGMEM = {
0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x10,
0x07,0xe0,0x08,0x00,0x07,0xf0,0x00,0x00,
0x7f,0xfe,0x00,0x00,0x0f,0xf0,0x08,0x10,
0x08,0x10,0x0f,0xf0,0x00,0x00
}; 

const unsigned char IMG_NAM[] PROGMEM = {
0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x38,0x1f,0xa0,0x00,0x20,
0x00,0x00,0x1f,0xe0,0x10,0x20,0x10,0x20,
0x1f,0xe0,0x00,0x00,0x00,0x00
}; 

const unsigned char IMG_UM[] PROGMEM = {
0x00,0x00,0x07,0xf0,0x08,0x08,0x08,0x08,
0x07,0xf0,0x00,0x00,0x00,0x00,0x7f,0xfe,
0x00,0x00,0x00,0x00,0x07,0xf0,0x04,0x10,
0x04,0x10,0x07,0xf0,0x00,0x00
}; 

const unsigned char IMG_HOI[] PROGMEM = {
0x00,0x00,0x00,0x00,0x04,0x10,0x04,0x10,
0x1f,0x10,0x0e,0x10,0x11,0x10,0x0e,0x10,
0x04,0x10,0x04,0x10,0x1f,0xd0,0x00,0x10,
0x00,0x10,0x00,0x10,0x00,0x00
}; 


U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);   // I2C / TWI 

char spray_count_str [3];
int spray_count = 20;


void oled_Drawing ()
{
  sprintf(spray_count_str, "%d", spray_count);
  u8g.firstPage();
  do  {
    u8g.drawBitmapP (30, 1, 2, 15, IMG_BU); 
    u8g.drawBitmapP (46, 1, 2, 15, IMG_TUN); 
    u8g.drawBitmapP (62, 1, 2, 15, IMG_NU); 
    u8g.drawBitmapP (78, 1, 2, 15, IMG_RUM); 
    u8g.drawBitmapP (8, 30, 2, 15, IMG_NAM);
    u8g.drawBitmapP (24, 30, 2, 15, IMG_UM);
    u8g.setFont(u8g_font_gdb25);
    if ( spray_count >= 10 ) u8g.drawStr( 52, 50, spray_count_str);
    else u8g.drawStr( 60, 50, spray_count_str);
    u8g.drawBitmapP (100, 30, 2, 15, IMG_HOI);
  } while( u8g.nextPage() );
}


void minusBT()
{
  static boolean minusBTStat = false;
  static boolean laminusBTStat = true;
  static unsigned long laminusBTTime = 0;
  
  boolean readingStat = digitalRead(LIMITSW);
  if (readingStat != laminusBTStat) laminusBTTime = millis(); 
  if ( (millis() - laminusBTTime) > THISTIME ) {
    if (readingStat != minusBTStat) {
      minusBTStat = readingStat;
      if (minusBTStat == false) {  
        if (spray_count > 0) spray_count--;
        oled_Drawing ();
      }
    }
  }
  laminusBTStat = readingStat;
}


void resetBT()
{
  static boolean resetBTStat = false;
  static boolean laresetBTStat = true;
  static unsigned long laresetBTTime = 0;
  
  boolean readingStat = digitalRead(RESETSW);
  if (readingStat != laresetBTStat) laresetBTTime = millis(); 
  if ( (millis() - laresetBTTime) > THISTIME ) {
    if (readingStat != resetBTStat) {
      resetBTStat = readingStat;
      if (resetBTStat == false) {  
        spray_count = 20;
        oled_Drawing ();
      }
    }
  }
  laresetBTStat = readingStat;
}

void setup() {
  pinMode (RESETSW, INPUT_PULLUP);
  pinMode (LIMITSW, INPUT_PULLUP);

  oled_Drawing ();
}

void loop() {
  minusBT();
  resetBT();
}

 

 

용량은 이 정도 차지했습니다.

아마 글자를 16x15가 아니라 8x10 정도로 했으면 훨씬 용량을 덜 먹었을 겁니다.

 

 

생각보다는 깨끗하게 나오죠.

도트로 찍어놓고 봤을 땐 별로였는데요.

 

 

 

이 경우 버튼 누름은 한 개의 비트맵으로 만드는 게 더 나았을 겁니다.

그럼 drawBitmap도 한 번만 호출하면 되니까요.

하지만 보통 타이틀에 해당하는 부분은 왜 보통 "버 튼 누 름" 이런 식으로 사이 띄기를 하잖아요.

그럴 때도 한 개의 비트맵으로 만든다면, 각 글자 사이의 공백마저 데이터로 취급돼서 비트맵 헥스데이터의 용량이 더 커져버립니다.

 

그래서 저는 이런 식으로 한글을 출력할 경우, 가급적 한 글자씩 비트맵을 만듭니다.

한번 써먹은 글자를 다른 데서 써먹을 수도 있으니까요.

"버튼 누름"을 한 개의 비트맵으로 만들면 나중에 다른 부분에서 "버"자 글자만 사용하는 건 불가능하죠?

 

한글뿐만 아니라 u8 glib에서 지원하지 않는 기호 같은 것을 출력할 때도 이런 방법을 사용합니다.

섭씨온도 기호(°C) 같은 경우가 해당되겠죠.

LIST
Comments