Font to Binary Converter ツールを開く

Arduino と OLED(SSD1306)で日本語を表示する:美咲フォント 8×8 を配列にして drawBitmap で描く

公開: · 作者: takushio2525

メニューの項目や「こんにちは」のような決まった言葉だけを OLED に出したいなら、日本語フォントを丸ごと入れなくても、使う文字のドットだけを配列で持てば足ります。美咲フォントなら 1 文字 8 バイトなので、5 文字でも 40 バイトです。この記事では、Font to Binary Converter でその配列を作り、Arduino Uno と SSD1306 の OLED に表示するまでを説明します。

こんにちは

この方法が向いている場面

  • 向いている: 表示する文字が前もって決まっているとき(メニュー、状態の表示、あいさつ、単位など)。使う文字の分のデータだけで済むので、フラッシュの小さいマイコンでも収まります。
  • 向いていない: 受け取った文章をそのまま表示するなど、どの文字が来るか分からないとき。この場合は、フォント全体を持つ表示ライブラリを使うほうが確実です。

用意するもの

  • Arduino Uno(ATmega328P)など。この記事のスケッチは、Arduino IDE 同梱の arduino-cli で Uno 向けにコンパイルできることを確かめています
  • SSD1306 を使った 128×64 ドットの OLED モジュール(I2C 接続)。Uno では SDA を A4、SCL を A5 につなぎます
  • Arduino IDE のライブラリマネージャで入れる「Adafruit SSD1306」と「Adafruit GFX Library」(依存する Adafruit BusIO も一緒に入れます)

配列を作る

  1. Font to Binary Converter を開き、「かんたん」モードのまま使います
  2. 「1 変換したい文字」に こんにちは と入力します
  3. 「2 見た目」のフォントで「Misaki Gothic (8x8)」を選びます(ドットサイズも 8 になります)
  4. 「3 出力フォーマット」で「Arduino / C言語」を選び、出力の「コピー」を押します

出力は次のようになります(ツールで実際に出したものです)。

const uint8_t font_data[] PROGMEM = {
  // char: "こ" (8x8)
  // ........
  // .#####..
  // ........
  // ........
  // .#......
  // #.......
  // .######.
  // ........
  0x00, 0x7C, 0x00, 0x00, 0x40, 0x80, 0x7E, 0x00,
  // char: "ん" (8x8)
  // ...#....
  // ...#....
  // ..#.....
  // ..##....
  // .#..#.#.
  // .#..#.#.
  // #....#..
  // ........
  0x10, 0x10, 0x20, 0x30, 0x4A, 0x4A, 0x84, 0x00,
  // char: "に" (8x8)
  // #.......
  // #..###..
  // #.......
  // #.......
  // #..#....
  // #.#.....
  // #..####.
  // ........
  0x80, 0x9C, 0x80, 0x80, 0x90, 0xA0, 0x9E, 0x00,
  // char: "ち" (8x8)
  // ..#.....
  // #######.
  // ..#.....
  // .#.###..
  // .##...#.
  // .....#..
  // ..###...
  // ........
  0x20, 0xFE, 0x20, 0x5C, 0x62, 0x04, 0x38, 0x00,
  // char: "は" (8x8)
  // #...#...
  // #.#####.
  // #...#...
  // #...#...
  // #.###...
  // ##..##..
  // #.###.#.
  // ........
  0x88, 0xBE, 0x88, 0x88, 0xB8, 0xCC, 0xBA, 0x00
};

1 文字ぶんは、コメントのドット絵(# が点灯)と 8 個の数値です。数値 1 つが横 1 行の 8 ドットで、左端のドットが最上位ビット(0x80)に入っています。たとえば「こ」の 2 行目 .#####.. は 2 進で 01111100、16 進で 0x7C です。PROGMEM が付いているので、配列は SRAM ではなくフラッシュメモリに置かれます。

スケッチ

出力を貼り付けたスケッチの全体です。画面の左上から 5 文字を横に並べて描きます。

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// 128×64 ドット・I2C 接続の OLED。リセットピンは使わない(-1)
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// ↓ Font to Binary Converter の出力をそのまま貼る
const uint8_t font_data[] PROGMEM = {
  // char: "こ" (8x8)
  // ........
  // .#####..
  // ........
  // ........
  // .#......
  // #.......
  // .######.
  // ........
  0x00, 0x7C, 0x00, 0x00, 0x40, 0x80, 0x7E, 0x00,
  // char: "ん" (8x8)
  // ...#....
  // ...#....
  // ..#.....
  // ..##....
  // .#..#.#.
  // .#..#.#.
  // #....#..
  // ........
  0x10, 0x10, 0x20, 0x30, 0x4A, 0x4A, 0x84, 0x00,
  // char: "に" (8x8)
  // #.......
  // #..###..
  // #.......
  // #.......
  // #..#....
  // #.#.....
  // #..####.
  // ........
  0x80, 0x9C, 0x80, 0x80, 0x90, 0xA0, 0x9E, 0x00,
  // char: "ち" (8x8)
  // ..#.....
  // #######.
  // ..#.....
  // .#.###..
  // .##...#.
  // .....#..
  // ..###...
  // ........
  0x20, 0xFE, 0x20, 0x5C, 0x62, 0x04, 0x38, 0x00,
  // char: "は" (8x8)
  // #...#...
  // #.#####.
  // #...#...
  // #...#...
  // #.###...
  // ##..##..
  // #.###.#.
  // ........
  0x88, 0xBE, 0x88, 0x88, 0xB8, 0xCC, 0xBA, 0x00
};

const uint8_t CHAR_W = 8;  // 1 文字の幅(ドット)
const uint8_t CHAR_H = 8;  // 1 文字の高さ(ドット)
const uint8_t BYTES_PER_CHAR = (CHAR_W + 7) / 8 * CHAR_H;  // 1 文字のバイト数(8×8 なら 8)
const uint8_t CHAR_COUNT = sizeof(font_data) / BYTES_PER_CHAR;

void setup() {
  // 128×64 のモジュールの多くは I2C アドレス 0x3C(0x3D のものもある)
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    for (;;) {}  // 初期化に失敗したら止める(配線とアドレスを確かめる)
  }
  display.clearDisplay();
  for (uint8_t i = 0; i < CHAR_COUNT; i++) {
    // i 文字目のデータの先頭を渡して、8×8 のビットマップとして描く
    display.drawBitmap(i * CHAR_W, 0, font_data + i * BYTES_PER_CHAR, CHAR_W, CHAR_H, SSD1306_WHITE);
  }
  display.display();  // ここで初めて画面に送られる
}

void loop() {}
  • drawBitmap の 3 番目の引数に、font_data + i * BYTES_PER_CHAR(i 文字目のデータの先頭)を渡しています。const uint8_t の配列を渡すと、drawBitmap はフラッシュから読み出す処理(pgm_read_byte)で読むので、PROGMEM の配列をそのまま渡せます。
  • display.display() を呼ぶまで、画面は変わりません。drawBitmap は画面バッファに描くだけです。
  • Adafruit_SSD1306 は begin() のときに、128×64 なら 1,024 バイトの画面バッファを確保します。Uno の SRAM は 2,048 バイトしかないので、フォントのデータを PROGMEM でフラッシュに置いておくのが大事です。このスケッチをコンパイルすると、グローバル変数は 344 バイトでした(画面バッファは実行時に確保されるので、この数字には入りません)。

なぜこの設定で合うのか

Adafruit_GFX の drawBitmap は、ビットマップを上の行から順に読み、1 行を「(幅 + 7) / 8」バイトとして、各バイトの最上位ビット(0x80)から左→右へ描きます(ライブラリの Adafruit_GFX.cpp の drawBitmap でこの処理を確かめられます)。「Arduino / C言語」の出力は次の設定なので、この読み方とそのまま一致します。

設定値意味
構造ビットパック(行方向)横に並んだ 8 ドットを 1 バイトに詰める
ビット順MSB first左端のドットを最上位ビットに入れる
表記16進数書き方だけの違いで、10 進・2 進にしても値は同じ

幅が 8 の倍数でないとき(たとえば 12 ドット)は 1 行が 2 バイトになり、余ったビットは 0 で埋まります。drawBitmap も 1 行を「(幅 + 7) / 8」バイトとして読むので、このときもそのまま渡せます。スケッチの BYTES_PER_CHAR も同じ式で計算しています。並び方の詳しい説明は「出力の読み方」にまとめています。

文字を大きく表示する

8×8 の文字は、128×64 の画面に 1 行 16 文字入る大きさで、小さく感じることがあります。大きくする方法は 2 つあります。

DotGothic16(16×16)で作り直す

フォントで「DotGothic16 (16x16)」を選ぶと、ドットサイズが 16 になり、1 文字 32 バイトの配列になります。スケッチの CHAR_W と CHAR_H を 16 に変えれば、BYTES_PER_CHAR は自動で 32 になり、同じ drawBitmap で描けます。

8×8 のデータのまま、1 ドットを 2×2 に広げて描く

データを増やさずに大きく見せたいときは、1 ドットを fillRect で 2×2 の四角として描きます。上のスケッチに次の関数を足し、drawBitmap の代わりに drawChar2x(i * 16, 0, font_data + i * 8) のように呼びます。

// 1 ドットを 2×2 に広げて描く(8×8 の文字を 16×16 の大きさで表示する)
void drawChar2x(int16_t x, int16_t y, const uint8_t *glyph) {
  for (uint8_t row = 0; row < 8; row++) {
    uint8_t bits = pgm_read_byte(glyph + row);  // 1 行ぶん(bit7 が左端)
    for (uint8_t col = 0; col < 8; col++) {
      if (bits & (0x80 >> col)) {
        display.fillRect(x + col * 2, y + row * 2, 2, 2, SSD1306_WHITE);
      }
    }
  }
}

この関数は、配列の読み方(1 バイト = 1 行、bit7 が左端)をそのまま書いたものです。3 倍・4 倍にしたいときは、2 のところを変えます。

U8g2 を使う場合

U8g2 ライブラリで描くときは、drawXBMP を使います。drawXBMP は XBM 形式、つまり各バイトの最下位ビットが左端のデータを読みます(u8g2_bitmap.c の u8g2_DrawHXBMP)。そのため、ツールで「詳細」モードに切り替え、「ビット順」を「LSB first」にして出力し直します。

#include <U8g2lib.h>
#include <Wire.h>

// 128×64・I2C 接続の SSD1306。F はバッファを全画面ぶん持つ版
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// ↓ ビット順を LSB first にして出力したもの
const uint8_t font_data[] PROGMEM = {
  // char: "こ" (8x8)
  // ........
  // .#####..
  // ........
  // ........
  // .#......
  // #.......
  // .######.
  // ........
  0x00, 0x3E, 0x00, 0x00, 0x02, 0x01, 0x7E, 0x00,
  // char: "ん" (8x8)
  // ...#....
  // ...#....
  // ..#.....
  // ..##....
  // .#..#.#.
  // .#..#.#.
  // #....#..
  // ........
  0x08, 0x08, 0x04, 0x0C, 0x52, 0x52, 0x21, 0x00,
  // char: "に" (8x8)
  // #.......
  // #..###..
  // #.......
  // #.......
  // #..#....
  // #.#.....
  // #..####.
  // ........
  0x01, 0x39, 0x01, 0x01, 0x09, 0x05, 0x79, 0x00,
  // char: "ち" (8x8)
  // ..#.....
  // #######.
  // ..#.....
  // .#.###..
  // .##...#.
  // .....#..
  // ..###...
  // ........
  0x04, 0x7F, 0x04, 0x3A, 0x46, 0x20, 0x1C, 0x00,
  // char: "は" (8x8)
  // #...#...
  // #.#####.
  // #...#...
  // #...#...
  // #.###...
  // ##..##..
  // #.###.#.
  // ........
  0x11, 0x7D, 0x11, 0x11, 0x1D, 0x33, 0x5D, 0x00
};

void setup() {
  u8g2.begin();
  u8g2.clearBuffer();
  for (uint8_t i = 0; i < sizeof(font_data) / 8; i++) {
    u8g2.drawXBMP(i * 8, 0, 8, 8, font_data + i * 8);
  }
  u8g2.sendBuffer();
}

void loop() {}
  • U8g2 には drawBitmap もありますが、この関数はフラッシュから読み出す処理を通らず、ポインタの先をそのまま読みます。そのため、Uno などの AVR で PROGMEM に置いた配列には使えません。PROGMEM の配列は drawXBMP で描きます。
  • コンストラクタ名に _F_ が付く版は画面バッファ 1,024 バイトを最初から確保するので、このスケッチのグローバル変数は 1,556 バイト(Uno の SRAM の 75%)でした。メモリが足りなくなったら、_1_ や _2_ が付くページバッファ版に替える方法があります(描き方が firstPage() / nextPage() のループに変わります)。

うまく表示されないとき

症状確かめること
何も表示されないI2C アドレス(多くは 0x3C、モジュールによっては 0x3D)、配線(Uno は SDA が A4・SCL が A5)、display.display() の呼び忘れ
文字が左右反転して見えるビット順。drawBitmap は MSB first、drawXBMP は LSB first のデータを読む
文字が斜めにずれて崩れるツールで選んだドットサイズと、スケッチの CHAR_W・CHAR_H が揃っているか
違う文字が出る配列の文字は入力した順に並ぶ。コメントの char: で順番を確かめる
動きが不安定・勝手に再起動するSRAM の不足。配列に PROGMEM が付いているか、ほかに大きな配列を SRAM に置いていないか

美咲フォントについて

美咲フォントは、門真なむ氏が作った 8×8 ドットの日本語ビットマップフォントで、JIS 第一・第二水準の漢字を収めています。一部の記号を除いてすべての文字が 7×7 ドットの範囲に収まっているので、文字を隙間なく並べても字間と行間が空きます(配布元の説明より)。上の出力でも、どの文字も右端の列と一番下の行が空いています。

Font to Binary Converter に同梱しているのは「美咲ゴシック第2」(misaki_gothic_2nd.ttf)で、半角文字は 3×7 ドット、全角の仮名は 7×7 ドットをいっぱいに使う字形です。美咲フォントは作者の定めるライセンスで配布されています。作品や製品に使うときは、配布元のライセンスの説明を確かめてください。

LED マトリクスに出したいときは「8×8 LED マトリクスに文字を出す」を参考にしてください。