OLEDに文字以外を表示する

Draw a picture on DIY keyboard based on QMK
自作キーボードのファームウェアによく採用されているQMKにはSSD1306コントローラーによる有機LED(OLED)をサポートしており、標準APIによるアルファベット文字列を画面に表示することができます。ここではそれらのような文字以外を表示するテクニックを紹介します。

OLEDは横1ピクセルにつき、縦8バイト分を1バイトにまとめた値を128ピクセル分並べ、それを4回繰り返すことで128x32ピクセルの画像として表示することができます。
[データシートより抜粋]

このように配列には癖があるため、ビットマップ画像をそのまま流用することはできないため、これらのコントローラーに最適化した画像データを作成する必要があります。

有志が開発した「QMK Logo Editor」を使うと、既存のpng画像を簡単に変換することができます。このプログラムはJavaScriptのみで動くので、JavaScriptが動くブラウザがあれば、Githubのサイトからソースコードをダウンロードすることで、オフラインでも使用出来ます。
タブにある「Examples」から「Drivers/oled」を選択すれば、標準フォントのイメージが表示されます。ブラウザで直接編集することもできますが、「Download Image」で画像ファイルとしていったん書き出してから、ペイントソフトで修正した方が良いでしょう。

赤枠の領域を書き換えれば、ロゴデザインを変更できます。
画像を置き換えたら、「export」に表示されているfont変数を任意のファイル名で保存し、キーボードファームウェアの"config.h"に次の宣言を追加します。
config.h
// 新しいfont変数が収納されたファイル
#define OLED_FONT_H "customfont.h"
次にブラウザで表示しているページにある「Logo」タブを開き、「Export」内のテキストをコピーします。
これを「oled_task_user」関数に記述します。通常は「keymap.c」に新しくこの関数を追加します。
renderlogo.c
void oled_task_user(void) {
    if (!is_keyboard_master()) {
        render_logo();
    }
}

より大きなサイズの画像を表示したい場合はRawモードを使います。このタブを有効にした状態で画像をアップロードすると、バイナリデータを直接出力するためのコードが生成されます。こちらも「oled_task_user」関数に記述します。

リアルタイムに描画したい場合は、「oled_write_pixel(x位置,y位置,trueで描画・falseで消去)」関数を使います。

より複雑な処理を必要とするのであれば、このように描画バッファーを直接書き換える手もあります。
direct.c
extern uint8_t oled_buffer[OLED_MATRIX_SIZE];
extern OLED_BLOCK_TYPE oled_dirty;  // 値を-1にすると、すべての領域が更新対象になる

void setpixel(int x, int y, bool on){
    uint16_t pos = x + (y / 8) * 128;
    if(on){
        oled_buffer[pos] |= (1 << (y % 8));
    }else{
        oled_buffer[pos] &= ~((1 << (y % 8)));
    }
}
直接描画で注意したいことは、マイコンでの描画バッファの更新がI2CによるOLEDへの転送よりも総じて早いため、頻繁に書き換えていると転送が間に合わず、意図する画像が表示されません。
これは、oled_task_userが一定回数呼ばれるごとに描画をするなど、フレームレートを下げることで解決します。

こちらはリアルタイムアニメーションを実行するプログラム例です。
pong.c
void drawball(int px, int py, bool on){
    for(int x = 0; x < 2; x++){
        for(int y = 0; y < 2; y++){
            oled_write_pixel(px + x, py + y, on);
        }
    }
}

void oled_task_user(void) {
    static int frame = 0;
    static int px = 0, py = 0, pxm = 1, pym = 1;

    frame++;
    if(frame >= 100){
        frame = 0;

        drawball(px, py, false);

        px += pxm;
        if(px <= 0) {
            pxm = 1;
        }else if(px >= OLED_DISPLAY_WIDTH - 2){
            pxm = -1;
        }

        py += pym;
        if(py <= 0) {
            pym = 1;
        }else if(py >= OLED_DISPLAY_HEIGHT - 2){
            pym = -1;
        }

        drawball(px, py, true);
    }
}
マイコンやファームウェアの仕様により、描画実行されるタイミングがまちまちなので、描画の間隔は適時調整してください。また、有機LEDは性質上、同じ箇所を点灯し続けると画面が焼き付くことがあるので、適度に画面を消すなどの対策も必要です。

2020/08/22