RaspberryPiで有機ELディスプレイ(1)
SSD1306を動かしてみる

2021年9月8日

どもです。

前回のエントリまで、Arduinoで有機ELディスプレイSSD1306を動かしてきました。
今回は、このSSD1306をRaspberryPi/i2cで動かしてみます。
なお、ネットで調べてみると、pythonで動かしているサイトはたくさんあります。
しかし、C言語/pigpioを使用している例は見つけられませんでした。
それもあり、今回はC言語/pigpioを使用します!

1. 開発環境

今回のエントリは、以下の環境で開発を行っています。

開発/実行環境
項目 内容
OS Windows10 Pro(1909)
CPU i7-8700
メモリ 16GB
IDE Eclipse
Ver.2019-06(4.12.0)
Build id 20190614-1200

また、RaspebrryPi/pigpioの環境は以下の通りです。

RaspberryPi
項目 内容
HW RaspberryPi3 model B
OS Raspbian GNU/Linux
buster
ライブラリ Version 71
(gpioVersion()関数で確認)

2. 配線

まず、RaspberryPiとSSD1306の接続/配線です。
配線は、下図の通りです。



RaspberryPiは、i2cのI/Fが2つ用意されています。
しかし、デフォルトで有効になっているのは、i2c1(BSC1)のみです。
ピン番号は、「2」と「3」です。
このピンを、SSD1306と接続します。

3. i2cの設定

接続が完了したら、次にRaspberryPiのi2cを有効化します。
既に設定済みの場合には、ココはスキップしてください。
i2cの有効化は、以下の手順で実施します。

  1. RaspberryPiのターミナル上で、以下のコマンドを実施

    sudo raspi-config
  2. 表示される画面の「interfacing Options」を選択する。
  3. 「I2C」を選択
  4. 「Would you like the ARM I2C interafaceto be enabled?」と表示されるので、「はい」(または「yes」)を選択する。

以上で、i2cの有効化は完了です。
RaspberryPiの再起動は、必要ありません。

4. i2cでSSD1306の制御

環境が整ったので、実際にSSD1306の制御を行います。
なお、SSD1306の制御に関する情報は、データシートを参照しています。
データシートは、秋月電商さんの購入ページから取得できます。

4.1. データフォーマット

SSD1306とi2cで通信する場合のデータフォーマットは、前述のデータシートのP.20(Figure 8-7)に記載されています。
このデータ構造は、SMBus Version 2.0 の仕様に一致しています。
また、pigpioのi2c用関数も、このバージョンに対応しています。
そのため、新たにこのフォーマットに準拠した通信を行うための関数を新規に実装する必要はありません。

4.1.1. Control byte

通信データ内の「Control byte」です。
「Control byte」は、「Co」ビットと「D/C#」ビット、および「0」6ビットから構成されます。
「Co」ビットと「D/C#」ビットの値と意味は、以下の通りです。

項目 内容
Co 「Continuation bit」継続ビット
0 → 送信される情報は、1バイトの「Data byte」
1 → 送信される情報は、2バイト以上の「Data byte」
D/C# データ/コマンドの選択ビット
0 → 送信される「Data byte」が「コマンド」
1 → 送信される「Data byte」データが「データ」

4.1.2. Data byte

送信するコマンドまたはデータを設定します。
コマンドについては、データシートあるいは以降のページで記載する内容を参照してください。

4.2. SSD1306の設定

4.2.1. 動作設定の手順

SSD1306の動作の設定は、データシートの末尾に実行手順が記載されています。
具体的には、以下の手順/コマンドを実行します。

SSD1306の設定手順
順番 コマンド名前 コマンド
(バイトデータ)
データ 備考
1 Set MUX Ratio A8h 3Fh マルチプレックス比
0~64まで設定可能
デフォルトは「63」
2 Set Display Offset D3h 00h 描画開始位置の縦方向のオフセット
0~63まで設定可能
デフォルトは「0」
3 Set Display Start Line 40h 描画開始行
0~63まで設定可能
デフォルトは「0」
4 Set Segment re-map (A0h/)A1h 描画開始列の位置
A0h→左側(左から右方向に描画)
A1h→右側(右側から左側に描画)
5 Set COM Output Scan Direction (C0h/)C8h 描画開始行の位置
C0h→上側(上から下方向に描画)
C8h→下側(下側から上側に描画)
6 Set COM Pins hardware configuration DAh 12h データシート見ても、よくわからない…。
とりあえず、12hを指定。
7 Set Contrast Control 81h 7Fh 画面の明るさ(コントラスト)
1~256まで設定可能
デフォルトは7Fh
8 Disable Entire Display On A4h 画面表示を再開する
RAMの内容を、ディスプレイに反映する
9 Set Normal Display A6h 画面表示方法の設定
ビットの値が
0:消灯
1:点灯
10 Set Osc Frequency D5h 80h 周波数設定
デフォルトは80h
11 Enable charge pump regulator 8Dh 14h チャージポンプレギュレータの設定
デフォルトは10h
14hに設定する
12 Dispaly ON AFh ディスプレイ設定

4.2.2. 動作設定の実装

前述のシーケンスを実装します。
実装の際には、上記コマンドを1つずつではなく、全て一気に送信します。
具体的は、以下のコードとなります。

int ssd1306_initialize(uint handle)
{
	int ret = 0;
	uint8_t	initCommand[33] = {
			0x00, 0xA8, 0x3F,	//SetMultiplexRatioコマンド - 0x3F(63)を指定
			0x00, 0xD3, 0x00,	//SetDisplayOffsetコマンド - 0x00(0)を指定
			0x80, 0x40,			//SetDisplayStartlineコマンド
			0x80, 0xA1,			//SetSegmentRemapコマンド//画面左上を原点に設定
			0x80, 0xC8,			//SetCOMOutputScanDirectionコマンド上→下方向へ、走査を実施
			0x00, 0xDA, 0x12,	//SetCOMPingsHardwareConfigurationコマンド
			0x00, 0x81, 0x3F,	//SetCntrastControlコマンド - 0x7Fはリセット
			0x80, 0xA4,			//EntireDisplayONコマンド - RAMコンテンツ表示に戻す
			0x80, 0xA6,			//SetNormalDisplayコマンド - ノーマルコマンド
			0x00, 0xD5, 0x80,	//SetOscFrequencyコマンド - 周波数の初期化
			0x00, 0x8D, 0x14,	//EnableChargePumpRegulator - チャージポンプを有効に設定
			0x80, 0xAF,			//Display ON!
	};
	int	i2cWriteRes = i2cWriteDevice(handle, (char*)initCommand, 33);
	if (0 != i2cWriteRes) {
		ELOG("i2cWriteDevice() failed : %d", i2cWriteRes);

		ret = -1;
	} else {
		ILOG("i2cWriteDevice() succeeded");

		ret = 0;
	}
	return ret;
}

上記コードの中で、各コマンドの前に「0x00」または「0x80」を付与しています。
これが「Control byte」です。
また、「i2cWriteDevice()」はpigpioが提供しているI/Fです。
この関数を使用することで、SMBus Version 2.0に準拠してデータが送信されます。

4.2. 描画

SSD1306の設定が完了したので、画面に描画してみます。

4.2.1. 描画の手順

画面の描画の際には、以下のコマンドを実行します。

順番 コマンド名前 コマンド
(バイトデータ)
データ 備考
1 Entire Dispaly ON A5h 画面に表示するデータを格納したRAMのデータの転送を停止する。
2 Set Column Address 21h 0, 127 描画の開始烈と終了列の番号を指定
0~127まで設定可能
3 Set Page Address 22h 0, 7 描画の開始「ページ」と終了「ページ」を指定
0~7まで設定可能
4 データの送信 40h 「Control byte」に0x40を指定
5 Entire Display ON A4h RAMの内容の描画を再開

4.2.2. 描画の実装

実際に描画を行うコードは、以下のようになります。

int ssd1306_initialize(uint handle)
{
	uint8_t	startDraw[] = { 0x80, 0xA5 };	//Entire display ON
	uint8_t	areaCommand[] = {
			0x00, 0x21, 0, 127,
			0x00, 0x22, 0, 7
	};
	uint8_t endDraw[] = { 0x80, 0xA4 };		//Reresume to RAM content display.

	uint8_t	sampleData[1024 + 1];
	sampleData[0] = 0x40;
	for (int index = 1; index < 1024 + 1; index++) {
		sampleData[index] = (uint8_t)(index - 1);
	}
	i2cWriteDevice(handle, (char*)startDraw, 8);
	i2cWriteDevice(handle, (char*)areaCommand, 8);
	i2cWriteDevice(handle, (char*)sampleData, 1024 + 1);
	i2cWriteDevice(handle, (char*)endDraw, 2);
}

上記関数を実行すると、SSD1306の表示は、下図のようになります。



この通り、画面左上を原点として(何かの)画像が描画されていることが分かります。

4.2.3. データと画像

SSD1306に送信したデータと、実際に描画される画像について書きます。
前述のコードでは、1024バイトの配列に、先頭から1バイトずつ0~255までの値を設定しています。
このデータに従って、SSD1306の各ドットの消灯/点灯が制御されます。
つまり、各データのビットが「0」になっていた場合には、対応するドットを消灯し、「1」になっていた場合にはドットを点灯します。
(設定によっては、消灯/点灯を逆にすることも可能です。)
またデータ1バイトが、ページの1列分のデータになります。



SSD1306に送信しているデータ(上述のコードでは「sampleData」)の各ビットが、ページのビット(0~7)に対応しています。
即ち、sampleDataのLSBがページの一番上、MSBがページの一番下のドットにそれぞれ対応しています。
またsampleData[]の各要素がページの列に対応しており、sampleData[0]が一番左の列に対応しており、配列のインデックスが増加するごとに、対応する列も右に移動します。
配列のインデックスが127になった場合には、対応するページが「PAGE 1」に切り換わり、対応する列が「0」に戻ります。
このようにして、128×64の画面に対して画像が描画されます。

5. まとめ

今回は、RaspberryPiでSSD1306への描画に挑戦しました。
とりあえず、基本的な処理と描画の仕組みは分かりました。
しかし、arduinoを使った場合とは異なり、それほど自由に描画はできません。
そのあたりは、今後の課題かなぁ、と考えています。

ではっ!