Raspberry PiからSSD1306に画像を表示しよう[SSD1306の使い方]

今回はOLEDディスプレイのSSD1306の制御方法を紹介したいと思います。

OLEDは画素自体が発行するディスプレイであり、黒と白のコントラストがはっきりしているのが特徴です。
電卓で使われているような液晶表示機(LCD)より視認性もよく、電子工作で利用すると映えるのでオススメ部品です。

流通価格は500~1000円程度(2021年9月現在)のため、気軽に利用できるのもメリットです。

SSD1306は横128pix、縦64pixのディスプレイであり、I2CまたはSPIでマイコンと接続して利用します。

ArduinoやESP32等のArduino IDEが利用できるマイコンであればC言語のadafruitのライブラリが利用できますが、Raspberry Piは対応していないようなのでC言語で制御するには独自で実装する必要があります。

そこで今回はRaspberry PiからC言語でSSD1306を制御する方法をご紹介します。

題材として、トップ画像のような128x64pixの花火の画像を表示させてみたいと思います。

それでは本編いってみましょう。

目次

環境

部品備考
Raspberry Pi 3 model BRaspberry Pi 1, 2, 4, zero, picoでも問題ありません
SSD1306I2C接続のタイプ

今回はRaspberry Pi 3 model Bを使用しますが、基本的にどのモデルでも動作可能と思われます。
(ただし、本環境以外は動作未確認)

SSD1306はI2C接続タイプを使用します。SPI接続タイプも販売されていますのでご注意ください。

接続

接続図とピンアサインは以下の通りです。

SSD1306Raspberry Pi
VCC 1番ピン(3.3V)
GND 6番ピン(GND)
SCL 5番ピン(SCL)
SDA 3番ピン(SDA)
ピンアサイン

Raspberry Piの設定

SSD1306とI2Cで通信しますので、Raspberry PiでI2Cを使えるように設定しておきます。
以下の記事から設定を行ってください。

また、記事で紹介されているデバイスドライバを使用しますので、使い方もチェックしてください。

あわせて読みたい
Raspberry PiでI2Cを利用する方法 [i2c-dev][C言語] 今回はRaspberry PiにてC言語でI2Cを利用する方法をご紹介したいと思います。 Raspberry PiでI2Cを使う手段としてはpigpioやWiringPiといった手段が存在しますが、今回...

SSD1306の使い方

SSD1306の概要

今回は下記の画像の向きでSSD1306を使いたいと思います。
データシートを元に解説していきます。

SSD1306には128x64pixの画面メモリが備えられており、画面メモリを書き換えてあげることで表示を変更することができます。
画面メモリは以下の構成になっています。

左上の1列x8行の塊が画面メモリの1byteにあたります。メモリサイズは128x(64/8)=1KBです。

1byte中のLSB側(下位ビット)が上側、MSB(上位ビット)が下側の画素に相当します。
例えば、1byte目を0x0F、2byte目を0xF0をwriteすると以下のような表示になります。

以上のように、発光させたい画素位置のビットを1に、黒くしたい画素位置を0にすることで所望の画面を表示することができます。

SSD1306の初期設定

datasheetで紹介されている初期化シーケンスをベースに、一部カスタマイズしたシーケンスを紹介します。

カスタマイズしているのは11番目の「Set Addressing Mode」になります。
デフォルトはページアドレッシングモードというページを指定してメモリを書き換えるモードですが、今回は画面左上から右下まで順番に書き換える水平方向アドレッシングモードに設定します。

順番操作説明設定値
(複数byte設定が必要な場合はコンマ区切りで表現)
1Set MUX Ratio使用する行数。
default値0x3F=64行
0xA8, 0x3F
2Set Display Offset画面垂直方向のオフセット(ずらし)
オフセットは設けないので0に設定
0xD3, 0x00
3Set Display Start Line画面スタートライン
全領域使用するので0x40を指定(0x40~0x7F)
0x40
4Set Segment Re-map水平方向の反転
デフォルトは右下スタートだが、今回は左上スタートにするので反転有効=0xA1
0xA1
5Set COM Output Scan Direction垂直方向の反転
デフォルトは右下スタートだが、今回は左上スタートにするので反転有効=0xC8
0xC8
6Set COM Pins Hardware ConfigurationCOM signals pinの設定のようだが値を変えても挙動に変化無し。デフォルト値0x12でOK0xDA, 0x12
7Set Contrast Control256階調のコントラスト(明るさ)設定
デフォルトは0x7F。最大は0xFF。お好みでOK
0x81, 0xFF
8Disable Entire Display On0xA4で画面メモリの内容を画面表示
0xA5でテスト用(と思われる)の全画面表示機能なので使用しない
0xA4
9Set Normal Display白黒反転設定
通常は1が発光だが、その逆の設定も可能
今回は通常設定
0xA6
10Set Osc Frequencyディスプレイのクロック設定
デフォルト値0x80でOK
0xD5, 0x80
11Set Addressing Modeアドレスモードの設定
0x00:水平方向アドレッシング←今回はこれ
0x01:垂直方向アドレッシング
0x10:ページアドレッシング(default)
0x20, 0x00
12Enable Charge Pump RegulatorディスプレイをONにするにはチャージポンプレギュレータをONにしておく必要があるので設定します0x8D, 0x14
13Display On0xAEは画面OFF
0xAFで画面ONです
0xAF

上記の初期化シーケンスを行うソースコードです。

/* SSD1306初期設定値 */
#define SSD1306_CONFIG_MUX_RATIO_CMD (0xA8)
#define SSD1306_CONFIG_MUX_RATIO_A (0x3F)
#define SSD1306_CONFIG_DISPLAY_OFFSET_CMD (0xD3)
#define SSD1306_CONFIG_DISPLAY_OFFSET_A (0x0)
#define SSD1306_CONFIG_DISPLAY_START_LINE (0x40)
#define SSD1306_CONFIG_SEGMENT_REMAP (0xA1)
#define SSD1306_CONFIG_COM_OUT_DIRECTION (0xC8)
#define SSD1306_CONFIG_COM_PIN_CONFIG_CMD (0xDA)
#define SSD1306_CONFIG_COM_PIN_CONFIG_A (0x12)
#define SSD1306_CONFIG_CONTRAST_CMD (0x81)
#define SSD1306_CONFIG_CONTRAST_A (0x7F)
#define SSD1306_CONFIG_ENTIRE_DISPLAY_ON (0xA4)
#define SSD1306_CONFIG_DISPLAY_PIX_MODE (0xA6)
#define SSD1306_CONFIG_DISPLAY_FREQ_CMD (0xD5)
#define SSD1306_CONFIG_DISPLAY_FREQ_A (0xF0)
#define SSD1306_CONFIG_ADDRESSING_MODE_CMD (0x20)
#define SSD1306_CONFIG_ADDRESSING_MODE_A (0x0)
#define SSD1306_CONFIG_CHARGE_PUMP_CMD (0x8D)
#define SSD1306_CONFIG_CHARGE_PUMP_A (0x14)
#define SSD1306_CONFIG_DISPLAY_ON_OFF (0xAF)

#define SSD1306_CTRL_BYTE_CMD_SINGLE (0b00000000)  //1つのコマンドセットのみ
#define SSD1306_CTRL_BYTE_CMD_STREAM (0b10000000)  //コマンドの後ろに続けて複数のコントロールバイト&コマンドor描画データを場合(本モジュールでは使用しない)
#define SSD1306_CTRL_BYTE_DATA_SINGLE (0b01000000) //描画データのWriteのみ
#define SSD1306_CTRL_BYTE_DATA_STREAM (0b11000000) //描画データの後ろに続けて複数のコントロールバイト&コマンドor描画データを場合(本モジュールでは使用しない)

/* 初期設定コマンドセット */
static const U08 U08_SSD1306_Init_Config[] = {
    SSD1306_CONFIG_MUX_RATIO_CMD,
    SSD1306_CONFIG_MUX_RATIO_A,
    SSD1306_CONFIG_DISPLAY_OFFSET_CMD,
    SSD1306_CONFIG_DISPLAY_OFFSET_A,
    SSD1306_CONFIG_DISPLAY_START_LINE,
    SSD1306_CONFIG_SEGMENT_REMAP,
    SSD1306_CONFIG_COM_OUT_DIRECTION,
    SSD1306_CONFIG_COM_PIN_CONFIG_CMD,
    SSD1306_CONFIG_COM_PIN_CONFIG_A,
    SSD1306_CONFIG_CONTRAST_CMD,
    SSD1306_CONFIG_CONTRAST_A,
    SSD1306_CONFIG_ENTIRE_DISPLAY_ON,
    SSD1306_CONFIG_DISPLAY_PIX_MODE,
    SSD1306_CONFIG_DISPLAY_FREQ_CMD,
    SSD1306_CONFIG_DISPLAY_FREQ_A,
    SSD1306_CONFIG_ADDRESSING_MODE_CMD,
    SSD1306_CONFIG_ADDRESSING_MODE_A,
    SSD1306_CONFIG_CHARGE_PUMP_CMD,
    SSD1306_CONFIG_CHARGE_PUMP_A,
    SSD1306_CONFIG_DISPLAY_ON_OFF,
};

/* -----------------------------------------------------------------------------
 Function   : Ssd1306_Init
 Memo       : SSD1306初期化
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
BOOL Ssd1306_Init()
{
    BOOL status = OK;
    U08 cmd_buf[BUFSIZ];

    /* 動作設定 */
    cmd_buf[0] = SSD1306_CTRL_BYTE_CMD_SINGLE;
    U08 *p = &cmd_buf[1];
    for(U32 i=0; i < NUM_OF_SSD1306_CONFIG; i++)
    {
        *(p++) = U08_SSD1306_Init_Config[i];
    }
    Ssd1306_Write(cmd_buf, NUM_OF_SSD1306_CONFIG + 1);

    /* 画面初期化(黒塗り) */
    U08_Draw_Canvas_Payload[0] = SSD1306_CTRL_BYTE_DATA_SINGLE;
    Ssd1306_Write(U08_Draw_Canvas_Payload, SSD1306_DRAW_CANVAS_PAYLOAD_SIZE);

    return status;
}

設定値はdefineで定義し、設定シーケンスを予めテーブルで作っておきます(29~50行目のU08_SSD1306_Init_Config)。

65~69行目で予め作成したテーブルを用いて初期化シーケンスを流しています。

73行目ではオールゼロの画面メモリを送信して画面をゼロ初期化しています。

63行目と72行目でコントロールバイトという情報を付与していますが、次セクションで解説します。

SSD1306の通信プロトコル

datasheet I2C-bus Write data

SSD1306との通信プロトコルを解説します。

データを送る際は、必ず先頭1byteにコントロールバイトと呼ばれる情報を付与し、その後ろに各種コマンドまたは描画データを付与する構成でデータを送信します。

コントロールバイトにはコマンドか表示データかを区別するSelection bitと、複数のコントロールバイトを送信するか否かのContinuation bitが含まれます。

コマンドを送信する場合はSelection bitは0、描画データの場合は1にします。

コマンドを送った後に続けて描画データを送る場合はContinuation bitを1にします。
今回提供するソースコードでは続けて送ることはないので、常にContinuation bitは0にします。

初期化シーケンスでご説明した63行目と72行目はコントロールバイトを意味しており、
どちらもContinuation bitは0、63行目のSelection bitは0、72行目のSelection bitは1となっています。

cmd_buf[0] = SSD1306_CTRL_BYTE_CMD_SINGLE;   //0b00000000
~
U08_Draw_Canvas_Payload[0] = SSD1306_CTRL_BYTE_DATA_SINGLE;   //0b01000000

動作確認

さて、ここまで解説続きでしたので、まずは動かしてみましょう。

ソースコードはgithubからダウンロードしてください。

後述の画像表示をする際にjpg画像を扱うため、ライブラリをインストールしておきます。

sudo apt install libjpeg-dev

githubから落としたファイル一式をRaspberry Piに移動し、makeコマンドでビルドします。

$ make
gcc -Wall -Wextra  -o obj/./I2cCtl.o -c I2cCtl.c
gcc -Wall -Wextra  -o obj/./main.o -c main.c
gcc -Wall -Wextra  -o obj/./Ssd1306.o -c Ssd1306.c
gcc -o ./a.out obj/./I2cCtl.o obj/./main.o obj/./Ssd1306.o /usr/lib/arm-linux-gnueabihf/libjpeg.so

a.outという実行体が生成されますので、実行します。

$ ./a.out

下記のように表示されれば動作OKです。

ソースコードを見ていきましょう。
このテスト表示はmain.cのDrawTest関数で生成しています。

/* -----------------------------------------------------------------------------
 Function   : DrawTest
 Memo       : 
 Date       : 2021.09.25
------------------------------------------------------------------------------*/
void DrawTest()
{
	U08 *canvas = Ssd1306_Get_Draw_Canvas();
	canvas[0] = 0x0F;
	canvas[1] = 0x0F;
	canvas[2] = 0xF0;
	canvas[3] = 0xF0;
	canvas[4] = 0x0F;
	canvas[5] = 0x0F;
	canvas[6] = 0xF0;
	canvas[7] = 0xF0;
	Ssd1306_Update_Frame();
}

9~16行目で描画データの先頭8byteの値を指定しています。
「SSD1306の概要」で説明した画面メモリを思い返せば、期待通り表示されていることが確認できます。

canvasをいじって好きな絵を書いてみてください。

DrawTest関数で呼ばれているSSD1306制御モジュールの中身を見ていきます。

/* -----------------------------------------------------------------------------
 Function   : Ssd1306_Get_Draw_Canvas
 Memo       : キャンバスアドレス取得
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
U08* Ssd1306_Get_Draw_Canvas()
{
    return U08p_Draw_Canvas;
}
/* -----------------------------------------------------------------------------
 Function   : Ssd1306_Update_Frame
 Memo       : 描画バッファをSSD1306に送信して画面更新
 Date       : 2021.08.28
------------------------------------------------------------------------------*/
void Ssd1306_Update_Frame()
{
    U08_Draw_Canvas_Payload[0] = SSD1306_CTRL_BYTE_DATA_SINGLE;
    Ssd1306_Write(U08_Draw_Canvas_Payload, SSD1306_DRAW_CANVAS_PAYLOAD_SIZE);
}

Ssd1306_Get_Draw_Canvas()ではSsd1306.cで管理しているキャンバスのアドレスを返すだけのGetterです。
Ssd1306.cでは予めコントロールバイト+キャンバスを1つの配列(U08_Draw_Canvas_Payload)として確保しており、キャンバスが開始するアドレスがU08p_Draw_Canvasになっています。

Ssd1306_Update_Frame()では、コントロールバイトの設定を行った後、U08_Draw_Canvas_Payloadをまるごと送信します。

画像の表示

それでは改めて先程ダウンロードしたソースコードの説明をします。

各モジュールの説明は以下の通りです。

モジュール説明
main画像の読み込みや画面の作成等
common型定義などの共通定義
I2cCtlI2Cデバイスドライバ制御モジュール
Ssd1306SSD1306制御モジュール

先程はmain.cのDrawTest関数を実行していましたが、今度はDisplayImgを実行しますので、コメントアウトを入れ替えてください。

/* -----------------------------------------------------------------------------
 Function   : main
 Memo       : 
 Date       : 2021.09.25
------------------------------------------------------------------------------*/
int main()
{
	BOOL ret;

	/* 初期化 */
	ret = I2cCtl_Init();
	if (ret == NG)
	{
		printf("I2cCtl_Init Error.\n");
		return -1;
	}
	ret = Ssd1306_Init();
	if (ret == NG)
	{
		printf("Ssd1306_Init Error.\n");
		return -1;
	}

	/* 描画 */
	// DrawTest();
	DisplayImg();

	return 0;
}

makeして実行します

$ make
$ ./a.out

花火の画像が表示されればOKです。

プロジェクトに同梱されているtest.jpgを表示していますので、128x64pixの画像を用意して頂ければ差し替えて表示可能です。

画像の表示アルゴリズム

SSD1306の表示は0か1かのバイナリ形式なので、画像を表示する場合は2値化する必要があります。

2値化のロジックは微分ヒストグラム法や大津の2値化など、様々なアルゴリズムがありますが、今回は簡易化のため輝度の中央値を閾値としています。

一連の処理は次の通りです。

「Read jpg Image」ではlibjpegを使用して画像を読み込みます。

「Binarization」にて2値化を行います。画像の輝度値から中央値を探して閾値とし、輝度が閾値より大きければ1、小さければ0とします。

最後に「Convert SSD1306 Format」で先程2値化したデータを元に、描画データを生成します。

本記事ではlibjpeg等の詳細は解説しません(SSD1306の使い方が目的のため)。
参考サイトを掲載しておきますので、ご参照頂けたらと思います。

気が向いたら書くやつ
libjpegによるC言語での画像入出力 - 気が向いたら書くやつ C言語で画像を処理してみたいと思い、JPEG画像のエンコード/デコード実装として使われているライブラリ、libjpegを試してみました。 2019/3/7:内容を整理。 実行環境 イン...

最後に

いかがでしたでしょうか。

SSD1306の使い方はイメージできたでしょうか。
個人的には初期化がポイントかなと思います。

初期化さえ押さえておけば基本的な使い方は簡単ですので問題ないかと思います。

今回は紹介しませんでしたが、画面の特定の位置だけ書き換えたい場合はページアドレッシングモードを使ってみるのが良さそうです。

是非皆さんもいろいろ試してみてください。

それでは。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次