8×8 LED マトリクス(MAX7219)に文字を表示する:ドットフォントを 1 文字 8 バイトの配列にする
8×8 の LED マトリクスに文字を出すときは、1 文字を「8 行 × 8 ドット」の点灯パターンとして持ちます。横 1 行の 8 個の LED を 1 バイトの 8 ビットに対応させれば、1 文字はちょうど 8 バイトです。この記事では、Font to Binary Converter で美咲フォント 8×8 の配列を作り、MAX7219 を載せた LED マトリクスのモジュールに表示するまでを説明します。
1 文字 = 8 バイトの考え方
「ロ」を例にすると、上の行から順に、点灯する LED を 1、消える LED を 0 にして 8 ビットの数にしたものが 1 行ぶんのデータです。左端の LED を最上位ビット(bit7)に置いています。
| 行 | ドット(# が点灯) | 2 進 | 16 進 |
|---|---|---|---|
| 0 | ........ | 0b00000000 | 0x00 |
| 1 | #######. | 0b11111110 | 0xFE |
| 2 | #.....#. | 0b10000010 | 0x82 |
| 3 | #.....#. | 0b10000010 | 0x82 |
| 4 | #.....#. | 0b10000010 | 0x82 |
| 5 | #.....#. | 0b10000010 | 0x82 |
| 6 | #######. | 0b11111110 | 0xFE |
| 7 | ........ | 0b00000000 | 0x00 |
この 8 個の数を上の行から順に並べたものが、「ロ」1 文字のデータです。
用意するもの
- Arduino Uno など。この記事のスケッチは、Arduino IDE 同梱の arduino-cli で Uno 向けにコンパイルできることを確かめています
- MAX7219 を載せた 8×8 LED マトリクスのモジュール(VCC・GND のほか、DIN・CS・CLK の 3 本で制御します)
- Arduino IDE のライブラリマネージャで入れる「LedControl」
配線はスケッチの LedControl lc(12, 11, 10, 1) に合わせて、DIN を 12 番、CLK を 11 番、CS を 10 番ピンにつなぎます。ほかのピンにつなぐときは、この数字を変えます。
配列を作る
- Font to Binary Converter を開き、「かんたん」モードのまま「1 変換したい文字」に
ハローと入力します - 「2 見た目」のフォントで「Misaki Gothic (8x8)」を選びます(ドットサイズも 8 になります)
- 「3 出力フォーマット」で「Arduino / C言語」を選び、出力をコピーします
const uint8_t font_data[] PROGMEM = {
// char: "ハ" (8x8)
// ..#.#...
// ..#..#..
// ..#..#..
// .#...#..
// .#....#.
// .#....#.
// #.....#.
// ........
0x28, 0x24, 0x24, 0x44, 0x42, 0x42, 0x82, 0x00,
// char: "ロ" (8x8)
// ........
// #######.
// #.....#.
// #.....#.
// #.....#.
// #.....#.
// #######.
// ........
0x00, 0xFE, 0x82, 0x82, 0x82, 0x82, 0xFE, 0x00,
// char: "ー" (8x8)
// ........
// ........
// #.......
// .######.
// ........
// ........
// ........
// ........
0x00, 0x00, 0x80, 0x7E, 0x00, 0x00, 0x00, 0x00
};
1 文字ずつ表示する(setRow)
LedControl の setRow で、1 行ずつ 8 回送ると 1 文字になります。次のスケッチは「ハ」「ロ」「ー」を 0.8 秒ずつ順に表示します。
#include <LedControl.h>
// LedControl(DIN のピン, CLK のピン, CS のピン, つないだ MAX7219 の数)
LedControl lc(12, 11, 10, 1);
// ↓ Font to Binary Converter の出力をそのまま貼る
const uint8_t font_data[] PROGMEM = {
// char: "ハ" (8x8)
// ..#.#...
// ..#..#..
// ..#..#..
// .#...#..
// .#....#.
// .#....#.
// #.....#.
// ........
0x28, 0x24, 0x24, 0x44, 0x42, 0x42, 0x82, 0x00,
// char: "ロ" (8x8)
// ........
// #######.
// #.....#.
// #.....#.
// #.....#.
// #.....#.
// #######.
// ........
0x00, 0xFE, 0x82, 0x82, 0x82, 0x82, 0xFE, 0x00,
// char: "ー" (8x8)
// ........
// ........
// #.......
// .######.
// ........
// ........
// ........
// ........
0x00, 0x00, 0x80, 0x7E, 0x00, 0x00, 0x00, 0x00
};
const uint8_t CHAR_COUNT = sizeof(font_data) / 8; // 8×8 は 1 文字 8 バイト
void setup() {
lc.shutdown(0, false); // 省電力モードを解除する
lc.setIntensity(0, 4); // 明るさ(0〜15)
lc.clearDisplay(0);
}
void loop() {
for (uint8_t c = 0; c < CHAR_COUNT; c++) {
for (uint8_t row = 0; row < 8; row++) {
// 1 バイト = 1 行ぶん。bit7 が 0 列目の LED
lc.setRow(0, row, pgm_read_byte(&font_data[c * 8 + row]));
}
delay(800);
}
}
setRow(0, row, value)は、valueのbit7を 0 列目、bit0を 7 列目の LED に対応させます(LedControl.cpp のsetLedがB10000000 >> columnでビットを選んでいます)。「Arduino / C言語」の出力は左端のドットがbit7なので、そのまま渡せます。- 配列は PROGMEM(フラッシュ)にあるので、
pgm_read_byteで 1 バイトずつ読み出してから渡しています。
向きが合わないとき
モジュールによって、LED の並びと MAX7219 の配線の向きが違います。表示してみて文字の向きがおかしければ、次のように直します。
| 見え方 | 直し方 |
|---|---|
| 左右が逆 | ツールを「詳細」モードにして、「ビット順」を「LSB first」にして出力し直す |
| 上下が逆 | スケッチの setRow(0, row, …) を setRow(0, 7 - row, …) にする |
| 90 度回っている | 「構造」を「ビットパック(列方向)」にして出力し、そのまま setRow に渡す(行と列が入れ替わる)。それでも左右か上下が逆なら、上の 2 つを組み合わせる |
文字を流す(スクロール)
8×8 では一度に 1 文字しか出せないので、長い言葉は横に流すと読みやすくなります。流すときは、1 バイトが縦 1 列になる列方向のデータが扱いやすくなります。列を 1 つずつずらして表示するだけで済むからです。
- かんたんモードで「Arduino / C言語」を選んだあと、画面の右上で「詳細」に切り替えます
- 「構造」を「ビットパック(列方向)」にします(「ビット順」は MSB first のまま)
const uint8_t font_data[] PROGMEM = {
// char: "ハ" (8x8)
// ..#.#...
// ..#..#..
// ..#..#..
// .#...#..
// .#....#.
// .#....#.
// #.....#.
// ........
0x02, 0x1C, 0xE0, 0x00, 0x80, 0x70, 0x0E, 0x00,
// char: "ロ" (8x8)
// ........
// #######.
// #.....#.
// #.....#.
// #.....#.
// #.....#.
// #######.
// ........
0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00,
// char: "ー" (8x8)
// ........
// ........
// #.......
// .######.
// ........
// ........
// ........
// ........
0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00
};
1 文字ぶんの 8 バイトは、左の列から順に並んでいます。各バイトの bit7 が一番上の LED です。このデータを 1 列ずつずらしながら表示するスケッチです。
#include <LedControl.h>
LedControl lc(12, 11, 10, 1);
// ↓ 構造を「ビットパック(列方向)」、ビット順を MSB first にして出力したもの
const uint8_t font_data[] PROGMEM = {
// char: "ハ" (8x8)
// ..#.#...
// ..#..#..
// ..#..#..
// .#...#..
// .#....#.
// .#....#.
// #.....#.
// ........
0x02, 0x1C, 0xE0, 0x00, 0x80, 0x70, 0x0E, 0x00,
// char: "ロ" (8x8)
// ........
// #######.
// #.....#.
// #.....#.
// #.....#.
// #.....#.
// #######.
// ........
0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00,
// char: "ー" (8x8)
// ........
// ........
// #.......
// .######.
// ........
// ........
// ........
// ........
0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00
};
const int16_t COLS = sizeof(font_data); // 1 バイト = 1 列(bit7 が上端)
void setup() {
lc.shutdown(0, false);
lc.setIntensity(0, 4);
lc.clearDisplay(0);
}
void loop() {
// offset 列目からの 8 列を表示し、1 列ずつずらす(右から入って左へ流れる)
for (int16_t offset = -8; offset < COLS; offset++) {
for (uint8_t col = 0; col < 8; col++) {
int16_t i = offset + col;
uint8_t bits = (i >= 0 && i < COLS) ? pgm_read_byte(&font_data[i]) : 0;
lc.setColumn(0, col, bits);
}
delay(80);
}
}
setColumn(0, col, value)は、valueのbit7を 0 行目(一番上)に対応させます(LedControl.cpp のsetColumnがvalue >> (7 - row)で 1 行ずつ取り出しています)。列方向・MSB first の出力は上端がbit7なので、1 バイトをそのまま 1 列として送れます。- 美咲フォントは右端の 1 列が空いているので、文字を詰めて並べて流しても、文字と文字の間が空きます。
offsetを -8 から始めているのは、何も表示していない状態から、文字が右端から入ってくるようにするためです。setColumnは内部で LED を 1 個ずつ設定するので、setRowより MAX7219 へ送る回数が多くなります(1 列で 8 回)。流す速さはdelayで調整します。
モジュールを複数つなぐとき
MAX7219 のモジュールは数珠つなぎにできます。LedControl lc(12, 11, 10, 4) のように最後の引数で台数(LedControl では最大 8 台)を指定し、setRow や setColumn の最初の引数で何台目か(0 から数える)を選びます。流す場合は、4 台なら 32 列ぶんを並べて、同じ考え方で 1 列ずつずらします。
配列の並び方の詳しい説明は「出力の読み方」に、OLED に出す方法は「Arduino と OLED で日本語を表示する」にまとめています。