Raspberry Pi で I2C OLEDディスプレイを使う

2023年5月19日

oled1306-1

0.96インチOLEDディスプレイ(以下、0.96インチOLED)を以前から使ってみたいと思っていました。今までにM5Stack系では簡単に使えることが分かっていましたが、Raspberry Piの場合はPythonなら良いけれど、C言語の場合は結構面倒であるらしくて敬遠していました。

しばらくどうしようかと思案していましたが、ChatGPTなら、どういう答えを出すか興味があり、やってみました。すると、それまで考えていた方法よりも簡潔でスマートなコードを出力してきました。さて、本当に動作するのでしょうか。

1.OLEDの組み立て

0.96インチOLEDI2Cで制御します。製品にはピンヘッダが用意されていますので、OLED基板ハンダ付けします。ピンは4つだけなので、簡単です。

Raspberry Pi 4 Model Bと接続します。SDASCLを接続し、3.3V電源GNDも接続します。これだけで接続は終わりです。

2.Raspberry Piの設定

I2Cを有効にする

oled1306-2

0.96インチOLEDI2Cで制御するので、Raspberry Piの「設定」でI2C有効化する必要があります。

I2Cが有効であることは、以下のコマンドで確認します。

2cdetect -y 1
oled1306-3

こちらで、3Cの表示がされていればOKです。コマンドが見つからない場合は、以下のツールをインストールしてみてください。

sudo apt-get install i2c-tools
sudo apt-get install libi2c-dev

wiringPiをインストール

必要なライブラリは、wiringPiです。Raspberry Piを使っている人であれば、既にインストールしている場合も多いでしょう。wiringPiのインストール方法は、以下の記事で紹介しています。

3.プログラムコード

ChatGPTから提示されたソースをそのまま使います。

#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>

#define SSD1306_I2C_ADDR 0x3C // SSD1306のI2Cアドレス

// コマンドを送信する関数
void sendCommand(int fd, unsigned char command) {
    wiringPiI2CWriteReg8(fd, 0x00, command);
}

// 初期化する関数
void initialize(int fd) {
    sendCommand(fd, 0xAE); // ディスプレイOFF
    sendCommand(fd, 0x20); // ページアドレッシングモード
    sendCommand(fd, 0x00); // 水平アドレッシングモード
    sendCommand(fd, 0x10); // シフトオフセット
    sendCommand(fd, 0xB0); // ページスタートアドレス
    sendCommand(fd, 0xC8); // COMスキャン方向
    sendCommand(fd, 0x00); // 低アドレスカラム
    sendCommand(fd, 0x10); // 高アドレスカラム
    sendCommand(fd, 0x40); // 開始行アドレス
    sendCommand(fd, 0x81); // コントラストコントロール
    sendCommand(fd, 0xFF); // コントラスト設定
    sendCommand(fd, 0xA1); // セグメントリマップ
    sendCommand(fd, 0xA6); // 通常表示
    sendCommand(fd, 0xA8); // マルチプレクス率
    sendCommand(fd, 0x3F); // マルチプレクス率設定
    sendCommand(fd, 0xA4); // 全画面表示
    sendCommand(fd, 0xD3); // 表示オフセット
    sendCommand(fd, 0x00); // 表示オフセット設定
    sendCommand(fd, 0xD5); // ディスプレイクロック設定
    sendCommand(fd, 0xF0); // ディスプレイクロック設定
    sendCommand(fd, 0xD9); // プリチャージ期間設定
    sendCommand(fd, 0x22); // プリチャージ期間設定
    sendCommand(fd, 0xDA); // COMピン設定
    sendCommand(fd, 0x12); // COMピン設定
    sendCommand(fd, 0xDB); // VCOMHレベル設定
    sendCommand(fd, 0x20); // VCOMHレベル設定
    sendCommand(fd, 0x8D); // 充電ポンプ設定
    sendCommand(fd, 0x14); // 充電
    sendCommand(fd, 0xAF); // ディスプレイON
}

// テキストを表示する関数
void displayText(int fd, const char* text, int row, int col) {
    int i;
    int page = row / 8; // テキストの表示位置のページ
    int start_col = col * 6; // テキストの表示位置の開始カラム

    sendCommand(fd, 0xB0 + page); // ページアドレス指定

    for (i = 0; text[i] != '\0'; i++) {
        int ascii = (int)text[i];
        int fontIndex;
        if (ascii < 32 || ascii > 127) {
            fontIndex = 0; // 非表示文字の場合は空白文字を表示
        } else {
            fontIndex = ascii - 32;
        }

        int j;
        for (j = 0; j < 6; j++) {
            int fontData = Font6x8[fontIndex][j];
            wiringPiI2CWriteReg8(fd, 0x40, fontData);
        }
        wiringPiI2CWriteReg8(fd, 0x40, 0x00); // 文字の間に1ピクセルの空白を入れる
    }
}

int main() {
    int fd;

    // I2Cデバイスをオープン
    if ((fd = wiringPiI2CSetup(SSD1306_I2C_ADDR)) < 0) {
        fprintf(stderr, "Failed to open I2C device\n");
        return 1;
    }

    // 初期化
    initialize(fd);

    // テキスト表示
    const char* text = "Hello, OLED!";
    displayText(fd, text, 0, 0); // 0行目の0カラムから表示

    // クリーンアップ
    close(fd);

    return 0;
}

かなりそれらしく、ソースが提示されています。しかし、「Font6x8」配列がありません。これは文字データですが、ChatGPTも作成例は出してきましたが文字データの完成形ではなかったので、検索して探しました。

#include <stdint.h>

// ----------------------------------------------------------------------------

/* Standard ASCII 6x8 font */
const unsigned char Font6x8[][6] = {
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // sp
  {0x00, 0x00, 0x00, 0x2f, 0x00, 0x00}, // !
  {0x00, 0x00, 0x07, 0x00, 0x07, 0x00}, // "
  {0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14}, // #
  {0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12}, // $
  {0x00, 0x62, 0x64, 0x08, 0x13, 0x23}, // %
  {0x00, 0x36, 0x49, 0x55, 0x22, 0x50}, // &
  {0x00, 0x00, 0x05, 0x03, 0x00, 0x00}, // '
  {0x00, 0x00, 0x1c, 0x22, 0x41, 0x00}, // (
  {0x00, 0x00, 0x41, 0x22, 0x1c, 0x00}, // )
  {0x00, 0x14, 0x08, 0x3E, 0x08, 0x14}, // *
  {0x00, 0x08, 0x08, 0x3E, 0x08, 0x08}, // +
  {0x00, 0x00, 0x00, 0xA0, 0x60, 0x00}, // ,
  {0x00, 0x08, 0x08, 0x08, 0x08, 0x08}, // -
  {0x00, 0x00, 0x60, 0x60, 0x00, 0x00}, // .
  {0x00, 0x20, 0x10, 0x08, 0x04, 0x02}, // /
  {0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0
  {0x00, 0x00, 0x42, 0x7F, 0x40, 0x00}, // 1
  {0x00, 0x42, 0x61, 0x51, 0x49, 0x46}, // 2
  {0x00, 0x21, 0x41, 0x45, 0x4B, 0x31}, // 3
  {0x00, 0x18, 0x14, 0x12, 0x7F, 0x10}, // 4
  {0x00, 0x27, 0x45, 0x45, 0x45, 0x39}, // 5
  {0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6
  {0x00, 0x01, 0x71, 0x09, 0x05, 0x03}, // 7
  {0x00, 0x36, 0x49, 0x49, 0x49, 0x36}, // 8
  {0x00, 0x06, 0x49, 0x49, 0x29, 0x1E}, // 9
  {0x00, 0x00, 0x36, 0x36, 0x00, 0x00}, // :
  {0x00, 0x00, 0x56, 0x36, 0x00, 0x00}, // ;
  {0x00, 0x08, 0x14, 0x22, 0x41, 0x00}, // <
  {0x00, 0x14, 0x14, 0x14, 0x14, 0x14}, // =
  {0x00, 0x00, 0x41, 0x22, 0x14, 0x08}, // >
  {0x00, 0x02, 0x01, 0x51, 0x09, 0x06}, // ?
  {0x00, 0x32, 0x49, 0x59, 0x51, 0x3E}, // @
  {0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C}, // A
  {0x00, 0x7F, 0x49, 0x49, 0x49, 0x36}, // B
  {0x00, 0x3E, 0x41, 0x41, 0x41, 0x22}, // C
  {0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C}, // D
  {0x00, 0x7F, 0x49, 0x49, 0x49, 0x41}, // E
  {0x00, 0x7F, 0x09, 0x09, 0x09, 0x01}, // F
  {0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A}, // G
  {0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F}, // H
  {0x00, 0x00, 0x41, 0x7F, 0x41, 0x00}, // I
  {0x00, 0x20, 0x40, 0x41, 0x3F, 0x01}, // J
  {0x00, 0x7F, 0x08, 0x14, 0x22, 0x41}, // K
  {0x00, 0x7F, 0x40, 0x40, 0x40, 0x40}, // L
  {0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M
  {0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F}, // N
  {0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E}, // O
  {0x00, 0x7F, 0x09, 0x09, 0x09, 0x06}, // P
  {0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q
  {0x00, 0x7F, 0x09, 0x19, 0x29, 0x46}, // R
  {0x00, 0x46, 0x49, 0x49, 0x49, 0x31}, // S
  {0x00, 0x01, 0x01, 0x7F, 0x01, 0x01}, // T
  {0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F}, // U
  {0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F}, // V
  {0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F}, // W
  {0x00, 0x63, 0x14, 0x08, 0x14, 0x63}, // X
  {0x00, 0x07, 0x08, 0x70, 0x08, 0x07}, // Y
  {0x00, 0x61, 0x51, 0x49, 0x45, 0x43}, // Z
  {0x00, 0x00, 0x7F, 0x41, 0x41, 0x00}, // [
  {0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55}, // 55
  {0x00, 0x00, 0x41, 0x41, 0x7F, 0x00}, // ]
  {0x00, 0x04, 0x02, 0x01, 0x02, 0x04}, // ^
  {0x00, 0x40, 0x40, 0x40, 0x40, 0x40}, // _
  {0x00, 0x00, 0x01, 0x02, 0x04, 0x00}, // '
  {0x00, 0x20, 0x54, 0x54, 0x54, 0x78}, // a
  {0x00, 0x7F, 0x48, 0x44, 0x44, 0x38}, // b
  {0x00, 0x38, 0x44, 0x44, 0x44, 0x20}, // c
  {0x00, 0x38, 0x44, 0x44, 0x48, 0x7F}, // d
  {0x00, 0x38, 0x54, 0x54, 0x54, 0x18}, // e
  {0x00, 0x08, 0x7E, 0x09, 0x01, 0x02}, // f
  {0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C}, // g
  {0x00, 0x7F, 0x08, 0x04, 0x04, 0x78}, // h
  {0x00, 0x00, 0x44, 0x7D, 0x40, 0x00}, // i
  {0x00, 0x40, 0x80, 0x84, 0x7D, 0x00}, // j
  {0x00, 0x7F, 0x10, 0x28, 0x44, 0x00}, // k
  {0x00, 0x00, 0x41, 0x7F, 0x40, 0x00}, // l
  {0x00, 0x7C, 0x04, 0x18, 0x04, 0x78}, // m
  {0x00, 0x7C, 0x08, 0x04, 0x04, 0x78}, // n
  {0x00, 0x38, 0x44, 0x44, 0x44, 0x38}, // o
  {0x00, 0xFC, 0x24, 0x24, 0x24, 0x18}, // p
  {0x00, 0x18, 0x24, 0x24, 0x18, 0xFC}, // q
  {0x00, 0x7C, 0x08, 0x04, 0x04, 0x08}, // r
  {0x00, 0x48, 0x54, 0x54, 0x54, 0x20}, // s
  {0x00, 0x04, 0x3F, 0x44, 0x40, 0x20}, // t
  {0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C}, // u
  {0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C}, // v
  {0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C}, // w
  {0x00, 0x44, 0x28, 0x10, 0x28, 0x44}, // x
  {0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C}, // y
  {0x00, 0x44, 0x64, 0x54, 0x4C, 0x44}, // z
  {0x14, 0x14, 0x14, 0x14, 0x14, 0x14} // horiz lines

};

Arduino用にSSD1306用の文字フォントデータがあったので、それをRaspberry Pi用に修正しました。半角英数字しかありませんが、これで表示してみます。

コンパイル&リンクします。

gcc -o oled1306 oled1306.c -lwiringPi

実行形式ができました。さっそく実行してみます。

oled1306-4

表示されました!。ChatGPT恐るべしです。でも、よく見ると初期化時の背景が残ったままになっています。初期化コマンドを調べないといけませんね。(今は、初期化コマンドの仕様に疑問がある状態です)

それと、行の指定無効になってしまいます。いろいろ試しましたが、1行目だけの表示になり、最後の桁の文字が表示されたら、最初に戻って表示される現象が起きました。

やはり、ChatGPTの出力はあくまでも雛形であり、それをフラッシュアップする必要がありそうです。

※以降、追記予定 いくつかRaspberry Pi用のライブラリを見つけてはいますが、完成度が良くないです。

このライブラリに関しても、delay関数をsleep関数に置き換えたり、swap_values関数をssd1306_swap関数に置き換えたりなどの修正が必要です。また、動作してくれない関数もあります。さらに、漢字を使うとなると、大幅に手をいれなければなりません。漢字を表示する例もあるけれど、LovyanGFXライブラリのような便利なものが、まだ見つかっていません。

4. 0.96インチOLEDを使うならM5Stackが良さそう!

oled1306-5

実はATOM Liteを使って0.96インチOLEDの表示を行っているのですが、ライブラリが充実していて、図形日本語表示も簡単に実現できます。そのため、Raspberry Piでないと駄目という条件でなければ、M5Stack系で0.96インチOLEDを扱った方が良いと思います。

いずれ、ATOM Lite を使った0.96インチOLEDの作成例をご紹介したいと思います。