小メモリAVRでも使える!軽量SSD1306ライブラリと5×7自作フォント表示の方法

小メモリAVRでも使える!軽量SSD1306ライブラリと5×7自作フォント表示の方法
スポンサーリンク

小メモリなAVRマイコン(たとえばATtiny402など)で、SSD1306 OLEDディスプレイに文字を表示したい…でもAdafruitのライブラリは容量が大きすぎる!
そんなときに役立つのが、自作の軽量ライブラリです。
今回は、フォントデータを縦ビット構成の5×7ドットで管理し、シンプルなコードでOLED表示を行うプログラムをご紹介します。
I2C通信だけで動作し、追加ライブラリ不要!
しかも、自分でフォントデータを編集してカスタマイズも可能です。

スポンサーリンク

完成スケッチ

まずは動作する全体プログラムを掲載します。OLEDに “HELLO123456789–” や “Hello World!” などが表示されるはずです。

oled_display.ino(完成プログラム本体)

#include <Wire.h>
#include "SimpleFont.h" 
const char* mySt = "Hello World!";
#define OLED_ADDR 0x3C
const uint8_t init_cmds[] = {
  0xAE, 0x20, 0x00, 0xB0, 0xC8, 0x00, 0x10,
  0x40, 0x81, 0x7F, 0xA1, 0xA6, 0xA8, 0x3F,
  0xA4, 0xD3, 0x00, 0xD5, 0xF0, 0xD9, 0x22,
  0xDA, 0x12, 0xDB, 0x20, 0x8D, 0x14, 0xAF
};
void sendCommand(uint8_t cmd) {
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(0x00);
  Wire.write(cmd);
  Wire.endTransmission();
}
void sendData(uint8_t data) {
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(0x40);
  Wire.write(data);
  Wire.endTransmission();
}
void initOLED() {
  Wire.begin();
  delay(100);
  for (uint8_t i = 0; i < sizeof(init_cmds); i++) {
    sendCommand(init_cmds[i]);
  }
}
void setCursor(uint8_t x, uint8_t page) {
  sendCommand(0xB0 + page);
  sendCommand(0x00 + (x & 0x0F));
  sendCommand(0x10 + (x >> 4));
}
void clearDisplay() {
  for (uint8_t page = 0; page < 8; page++) {
    setCursor(0, page);
    for (uint8_t i = 0; i < 128; i++) {
      sendData(0x00);
    }
  }
}
void drawCharVertical(char c, uint8_t x, uint8_t page) {
  uint8_t index;
   if (c >= 'A' && c <= 'Z') {
    index = c - 'A'; // 0〜25
  } else if (c >= '0' && c <= '9') {
    index = 26 + (c - '0'); // 26〜35
  } else if (c >= 'a' && c <= 'z') {
    index = 36 + (c - 'a'); // 36〜61
  } else {
    // 特殊記号追加する
    switch (c) {
      case '+': index = 62; break;
      case '-': index = 63; break;
      case '=': index = 64; break;
      case '%': index = 65; break;
      case '.': index = 66; break;
      default: return; // 未定義文字は無視
    }
  }
  for (int col = 0; col < 5; col++) {
    uint8_t colBits = 0;
    for (int row = 0; row < 7; row++) {
      if (font5x7[index][row] & (1 << (4 - col))) {
        colBits |= (1 << row);
      }
    }
    setCursor(x + col, page);
    sendData(colBits);
  }
  setCursor(x + 5, page);
  sendData(0x00); // スペース
}
void drawStringVertical(const char* str, uint8_t x, uint8_t page) {
  while (*str) {
    drawCharVertical(*str, x, page);
    x += 6; // 5ドット+スペース
    str++;
  }
}
void setup() {
  initOLED();
  clearDisplay();
  drawStringVertical("HELLO123456789--", 0, 2);
   drawStringVertical("Hlloa+-=%.", 0, 4);  // テスト用
   drawStringVertical(mySt, 0, 6);
  //delay(8000);
  //clearDisplay();
}
void loop() {
}

フォントヘッダーファイル(SimpleFont.h)

フォントファイルを別ファイルとして格納するとプログラムは見やすくなります。

#ifndef SIMPLEFONT_H
#define SIMPLEFONT_H
// 5x7 縦向きフォント A-Z, 0-9
const uint8_t font5x7[][7] = {
  // A-Z (26文字)
  { 0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001 },  // A
  { 0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110 },  // B
  { 0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110 },  // C
  { 0b11100, 0b10010, 0b10001, 0b10001, 0b10001, 0b10010, 0b11100 },  // D
  { 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111 },  // E
  { 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000 },  // F
  { 0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01110 },  // G
  { 0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001 },  // H
  { 0b01110, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 },  // I
  { 0b00001, 0b00001, 0b00001, 0b00001, 0b10001, 0b10001, 0b01110 },  // J
  { 0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001 },  // K
  { 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111 },  // L
  { 0b10001, 0b11011, 0b10101, 0b10001, 0b10001, 0b10001, 0b10001 },  // M
  { 0b10001, 0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001 },  // N
  { 0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110 },  // O
  { 0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000 },  // P
  { 0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101 },  // Q
  { 0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001 },  // R
  { 0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110 },  // S
  { 0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100 },  // T
  { 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110 },  // U
  { 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100 },  // V
  { 0b10001, 0b10001, 0b10001, 0b10001, 0b10101, 0b11011, 0b10001 },  // W
  { 0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001 },  // X
  { 0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100 },  // Y
  { 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111 },  // Z
  // 0-9 (10文字)
  { 0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110 },  // 0
  { 0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 },  // 1
  { 0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111 },  // 2
  { 0b11110, 0b00001, 0b00001, 0b01110, 0b00001, 0b00001, 0b11110 },  // 3
  { 0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010 },  // 4
  { 0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110 },  // 5
  { 0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110 },  // 6
  { 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b10000 },  // 7
  { 0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110 },  // 8
  { 0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100 },  // 9
  // a-z (26文字)
  { 0b00000, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111 },  // a
  { 0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b11110 },  // b
  { 0b00000, 0b00000, 0b01110, 0b10001, 0b10000, 0b10001, 0b01110 },  // c
  { 0b00001, 0b00001, 0b01101, 0b10011, 0b10001, 0b10001, 0b01111 },  // d
  { 0b00000, 0b00000, 0b01110, 0b10001, 0b11110, 0b10000, 0b01110 },  // e
  { 0b00110, 0b01001, 0b01000, 0b11100, 0b01000, 0b01000, 0b01000 },  // f
  { 0b00000, 0b01111, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110 },  // g
  { 0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001 },  // h
  { 0b00100, 0b00000, 0b01100, 0b00100, 0b00100, 0b00100, 0b01110 },  // i
  { 0b00010, 0b00000, 0b00110, 0b00010, 0b00010, 0b10010, 0b01100 },  // j
  { 0b10000, 0b10000, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010 },  // k
  { 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 },  // l
  { 0b00000, 0b00000, 0b11010, 0b10101, 0b10101, 0b10101, 0b10101 },  // m
  { 0b00000, 0b00000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001 },  // n
  { 0b00000, 0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110 },  // o
  { 0b00000, 0b00000, 0b11110, 0b10001, 0b11110, 0b10000, 0b10000 },  // p
  { 0b00000, 0b00000, 0b01111, 0b10001, 0b01111, 0b00001, 0b00001 },  // q
  { 0b00000, 0b00000, 0b10110, 0b11001, 0b10000, 0b10000, 0b10000 },  // r
  { 0b00000, 0b00000, 0b01110, 0b10000, 0b01110, 0b00001, 0b11110 },  // s
  { 0b01000, 0b01000, 0b11100, 0b01000, 0b01000, 0b01001, 0b00110 },  // t
  { 0b00000, 0b00000, 0b10001, 0b10001, 0b10001, 0b10011, 0b01101 },  // u
  { 0b00000, 0b00000, 0b10001, 0b10001, 0b01010, 0b01010, 0b00100 },  // v
  { 0b00000, 0b00000, 0b10001, 0b10001, 0b10101, 0b10101, 0b01010 },  // w
  { 0b00000, 0b00000, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001 },  // x
  { 0b00000, 0b00000, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110 },  // y
  { 0b00000, 0b00000, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111 },  // z
  { 0b00000, 0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0b00000 },  // +
  { 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000 },  // -
  { 0b00000, 0b00000, 0b11111, 0b00000, 0b11111, 0b00000, 0b00000 },  // =
  { 0b11000, 0b11001, 0b00010, 0b00100, 0b01000, 0b10011, 0b00011 },  // %
  { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00110, 0b00110 }   // .
};
#endif

特殊記号は + – = % . 5種類に絞りました。簡単に自分で追加することも可能です。

使い方

aruduino IDEを開きます
上記のプログラムardino ideのinfo領域に書き込みます。
次にヘッダーファイルをardino ideに作ります。
これでinfoファイルからヘッダーファイルがincludeされて使えるようになります。
下図で示します。

SSD1306と通信する基本構造

ライブラリと変数定義

#include <Wire.h>
#include "SimpleFont.h"
  • Wire.hはI2C通信に必要。
  • "SimpleFont.h"には、あらかじめ作成した5×7フォントデータ(英大文字・小文字・数字・記号)を配列で定義しています。
const char* mySt = "Hello World!";
#define OLED_ADDR 0x3C
  • myStには表示テスト用の文字列を入れています。
  • OLED_ADDRは一般的なSSD1306のI2Cアドレス0x3Cを定義しています。

OLED初期化とI2C送信関数

const uint8_t init_cmds[] = { ... };
  • SSD1306の初期化に必要な一連のコマンド列です。
void sendCommand(uint8_t cmd)
void sendData(uint8_t data)
  • I2C経由でコマンド・データを送る汎用関数です。
void initOLED()
  • OLED初期化のメイン関数で、init_cmdsを1バイトずつ送信していきます。

カーソル位置設定と画面クリア

void setCursor(uint8_t x, uint8_t page)
  • 表示位置(横ピクセルとページ単位の縦位置)を設定します。
void clearDisplay()
  • 全8ページ(64ピクセル)の画面を0で塗りつぶします。

5×7フォント縦型文字表示

void drawCharVertical(char c, uint8_t x, uint8_t page)
  • 1文字分のデータをfont5x7から取り出し、縦方向にビットを変換して描画。
  • 大文字・小文字・数字・記号などに応じてフォント配列のインデックスを算出。
void drawStringVertical(const char* str, uint8_t x, uint8_t page)
  • 複数文字を6ピクセル間隔で横並びに描画します。

setup()での表示実行

void setup() {
  initOLED();
  clearDisplay();
  drawStringVertical("HELLO123456789--", 0, 2);
  drawStringVertical("Hlloa+-=%.", 0, 4);
  drawStringVertical(mySt, 0, 6);
}
  • 画面初期化後、3段に渡って異なる文字列を表示しています。
  • 2行目:小文字と特殊記号(+ - = % .
  • 3行目:Hello World!のテスト文字列

loop()関数は空

void loop() {}
  • このサンプルでは、表示後にループ処理は必要ないため空にしています。

このプログラムは、小規模・低リソースのマイコンでも使える軽量なOLED表示ロジックとして、教育用・実験用に非常に有効です。次章では、このプログラムに含まれるフォントファイルの作成方法や、SimpleFont.hの中身についても解説していきます。

フォントデータファイル SimpleFont.h の中身と拡張方法

このOLED表示プログラムでは、フォントデータをすべて SimpleFont.h にまとめています。軽量マイコンでも扱えるように、1文字あたり7バイト×5ドットの5×7フォントを縦方向ビット列(バイナリ形式)で定義しています。

ヘッダーファイルの基本構成

#ifndef SIMPLE_FONT_H
#define SIMPLE_FONT_H
#include <stdint.h>
// フォントデータ:5x7ピクセル、縦方向ビット列(各文字7バイト)
const uint8_t font5x7[][7] = {
  // 例:文字 'A'
  {0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001}, // A
  // ... 以下、B, C, D ... Z
  // 数字 0〜9
  // 小文字 a〜z
  // 記号 + - = % .
};
#endif

インデックスの割り当て

インデックスの割り当て

  • AZ:インデックス 025
  • 09:インデックス 2635
  • az:インデックス 3661
  • 特殊記号:
  • '+':62
  • '-':63
  • '=':64
  • '%':65
  • '.':66

プログラムの drawCharVertical() 関数では、文字に応じてこのインデックスを動的に計算しています。

フォントデータの追加・修正方法

フォントデータの追加・修正方法

文字のビットパターンを縦方向で作る
たとえば、文字 '+' を縦5ドット×横7行でデザインする場合:

. . # . .
. . # . .
# # # # #
. . # . .
. . # . .
. . . . .
. . . . .

このパターンを上から1行ずつビット化(#=1, .=0)し、各行を7ビットで表すと:

{0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0b00000, 0b00000}, // +

font5x7 の末尾に追加
たとえば、.(ドット)を追加したい場合:

{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b00100}, // .

フォントデザインのヒント

  • 5×7ドットだと、文字はやや細めになりますが、表示速度・読みやすさ・省メモリのバランスが良好。
  • アスキー文字の中でも頻出のもの(空白、コロン、スラッシュ、@、! など)は追加しておくと便利です。
  • 不要な文字は削除して容量を節約できます。

数値から文字列への変換(itoa()を使う方法)

今回作ったライブラリーは、文字変数。数値変数を表示するのには少し工夫が必要ですメモリーを消費させないためにも使える関数が制限されます。
簡単に説明します。

String myS = "1234abc";  // 場合は
ssd1306_print(0, 3, mys.c_str();  // c_str() 関数で const char* 型に変換する
const char myS1* = "1234abcefg";  //の宣言の時は
ssd1306_print(0, 4, myS1);   //で動きます
int num = 123;  //の時
char buffer[10];  // 数字を格納するための十分な長さのバッファ
itoa(num, buffer, 10);  //itoa 関数で変換する
ssd1306_print(0, 5, buffer);
float num1 = 123.45;  //floatの時
int num = num1;
itoa(num, buffer, 10);
char buf[12];  //フロートタイプはこのように使うことができますがメモリーが多く使われ実用的ではありません
float f = 123.45;
dtostrf(f, 6, 2, buf);  //整数部分6だ小数部分2が見方という指定です
  //アルディーノnanoは問題なく使えますメモリが多いので

のように工夫して使ってください。
下記はコンパイルした時のメモリー使用状況の一例です。
nano

最大30720バイトのフラッシュメモリのうち、スケッチが6286バイト(20%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が615バイト(30%)を使っていて、ローカル変数で1433バイト使うことができます。

ATTyny402

最大4096バイトのフラッシュメモリのうち、スケッチが2120バイト(51%)を使っています。
最大256バイトのRAMのうち、グローバル変数が96バイト(37%)を使っていて、ローカル変数で160バイト使うことができます。

まとめ

このプログラムとフォントライブラリを使えば、外部ライブラリに頼らずにSSD1306ディスプレイに文字列を表示することができます。とくにATtiny402などの小型AVRマイコンでグラフィック表示を行いたい方にとっては、シンプルかつ強力な手段となるでしょう。

次回は、スクロール表示やグラフィック描画など、より高度な機能の追加についてご紹介する予定です!

タイトルとURLをコピーしました