Raspberry Pi で I2C OLEDディスプレイを使う
0.96インチのOLEDディスプレイ(以下、0.96インチOLED)を以前から使ってみたいと思っていました。今までにM5Stack系では簡単に使えることが分かっていましたが、Raspberry Piの場合はPythonなら良いけれど、C言語の場合は結構面倒であるらしくて敬遠していました。
しばらくどうしようかと思案していましたが、ChatGPTなら、どういう答えを出すか興味があり、やってみました。すると、それまで考えていた方法よりも簡潔でスマートなコードを出力してきました。さて、本当に動作するのでしょうか。
1.OLEDの組み立て
0.96インチOLEDはI2Cで制御します。製品にはピンヘッダが用意されていますので、OLED基板にハンダ付けします。ピンは4つだけなので、簡単です。
Raspberry Pi 4 Model Bと接続します。SDAとSCLを接続し、3.3V電源とGNDも接続します。これだけで接続は終わりです。
2.Raspberry Piの設定
I2Cを有効にする
0.96インチOLEDはI2Cで制御するので、Raspberry Piの「設定」でI2Cを有効化する必要があります。
I2Cが有効であることは、以下のコマンドで確認します。
2cdetect -y 1
こちらで、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
実行形式ができました。さっそく実行してみます。
表示されました!。ChatGPT恐るべしです。でも、よく見ると初期化時の背景が残ったままになっています。初期化コマンドを調べないといけませんね。(今は、初期化コマンドの仕様に疑問がある状態です)
それと、行の指定が無効になってしまいます。いろいろ試しましたが、1行目だけの表示になり、最後の桁の文字が表示されたら、最初に戻って表示される現象が起きました。
やはり、ChatGPTの出力はあくまでも雛形であり、それをフラッシュアップする必要がありそうです。
※以降、追記予定 いくつかRaspberry Pi用のライブラリを見つけてはいますが、完成度が良くないです。
このライブラリに関しても、delay関数をsleep関数に置き換えたり、swap_values関数をssd1306_swap関数に置き換えたりなどの修正が必要です。また、動作してくれない関数もあります。さらに、漢字を使うとなると、大幅に手をいれなければなりません。漢字を表示する例もあるけれど、LovyanGFXライブラリのような便利なものが、まだ見つかっていません。
4. 0.96インチOLEDを使うならM5Stackが良さそう!
実はATOM Liteを使って0.96インチOLEDの表示を行っているのですが、ライブラリが充実していて、図形や日本語表示も簡単に実現できます。そのため、Raspberry Piでないと駄目という条件でなければ、M5Stack系で0.96インチOLEDを扱った方が良いと思います。
いずれ、ATOM Lite を使った0.96インチOLEDの作成例をご紹介したいと思います。