今回は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 B | Raspberry Pi 1, 2, 4, zero, picoでも問題ありません |
SSD1306 | I2C接続のタイプ |
今回はRaspberry Pi 3 model Bを使用しますが、基本的にどのモデルでも動作可能と思われます。
(ただし、本環境以外は動作未確認)
SSD1306はI2C接続タイプを使用します。SPI接続タイプも販売されていますのでご注意ください。
接続
接続図とピンアサインは以下の通りです。
SSD1306 | Raspberry Pi |
---|---|
VCC | 1番ピン(3.3V) |
GND | 6番ピン(GND) |
SCL | 5番ピン(SCL) |
SDA | 3番ピン(SDA) |
Raspberry Piの設定
SSD1306とI2Cで通信しますので、Raspberry PiでI2Cを使えるように設定しておきます。
以下の記事から設定を行ってください。
また、記事で紹介されているデバイスドライバを使用しますので、使い方もチェックしてください。
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設定が必要な場合はコンマ区切りで表現) |
---|---|---|---|
1 | Set MUX Ratio | 使用する行数。 default値0x3F=64行 | 0xA8, 0x3F |
2 | Set Display Offset | 画面垂直方向のオフセット(ずらし) オフセットは設けないので0に設定 | 0xD3, 0x00 |
3 | Set Display Start Line | 画面スタートライン 全領域使用するので0x40を指定(0x40~0x7F) | 0x40 |
4 | Set Segment Re-map | 水平方向の反転 デフォルトは右下スタートだが、今回は左上スタートにするので反転有効=0xA1 | 0xA1 |
5 | Set COM Output Scan Direction | 垂直方向の反転 デフォルトは右下スタートだが、今回は左上スタートにするので反転有効=0xC8 | 0xC8 |
6 | Set COM Pins Hardware Configuration | COM signals pinの設定のようだが値を変えても挙動に変化無し。デフォルト値0x12でOK | 0xDA, 0x12 |
7 | Set Contrast Control | 256階調のコントラスト(明るさ)設定 デフォルトは0x7F。最大は0xFF。お好みでOK | 0x81, 0xFF |
8 | Disable Entire Display On | 0xA4で画面メモリの内容を画面表示 0xA5でテスト用(と思われる)の全画面表示機能なので使用しない | 0xA4 |
9 | Set Normal Display | 白黒反転設定 通常は1が発光だが、その逆の設定も可能 今回は通常設定 | 0xA6 |
10 | Set Osc Frequency | ディスプレイのクロック設定 デフォルト値0x80でOK | 0xD5, 0x80 |
11 | Set Addressing Mode | アドレスモードの設定 0x00:水平方向アドレッシング←今回はこれ 0x01:垂直方向アドレッシング 0x10:ページアドレッシング(default) | 0x20, 0x00 |
12 | Enable Charge Pump Regulator | ディスプレイをONにするにはチャージポンプレギュレータをONにしておく必要があるので設定します | 0x8D, 0x14 |
13 | Display On | 0xAEは画面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の通信プロトコル
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 | 型定義などの共通定義 |
I2cCtl | I2Cデバイスドライバ制御モジュール |
Ssd1306 | SSD1306制御モジュール |
先程は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の使い方が目的のため)。
参考サイトを掲載しておきますので、ご参照頂けたらと思います。
最後に
いかがでしたでしょうか。
SSD1306の使い方はイメージできたでしょうか。
個人的には初期化がポイントかなと思います。
初期化さえ押さえておけば基本的な使い方は簡単ですので問題ないかと思います。
今回は紹介しませんでしたが、画面の特定の位置だけ書き換えたい場合はページアドレッシングモードを使ってみるのが良さそうです。
是非皆さんもいろいろ試してみてください。
それでは。
コメント