I2Cディスプレイ制御プロジェクトの作成
それでは、C#のプロジェクトを作成しましょう。今回は、仮想ファイルで制御するのではなく、ライブラリを利用します。
前回少し触れましたが、Raspberry Pi(Raspbean OS)では、Wiring PiというGPIOの制御を行うC言語のライブラリがインストールされています。今回は、このライブラリを利用して、I2C通信を行ってみます。
新しいプロジェクト
I2Cディスプレイモジュールを制御するためのプロジェクトを作成します。プロジェクトの作成方法は、前回と同じです。
ただ、Visual Studioのリリースバージョンによっては、連載1回目での設定だけでは、プロジェクトの初期設定が、.NET Core 2.1を使うようになるようです。.NET Core 3.0を使うには、プロジェクトのプロパティを開き、対象のフレームワークから、.NET Core 3.0を選択しておきます。
![.NET Core 3.0を選択](http://cz-cdn.shoeisha.jp/static/images/article/11777/11777_005.png)
まずはVisual Studioで、新しいプロジェクトを作成します。プロジェクトのテンプレートは、コンソールアプリ(.NET Core)を選択します。プロジェクトの構成は、プロジェクト名をOled、ソリューション名をPieCoreとしました。
利用するWiring Piの関数
今回、Wiring Piで提供されている関数のうち、次の関数を利用します。
関数 | 概要 |
---|---|
int wiringPiSetupGpio(void); | Broadcom GPIO番号を利用する |
void pinMode(int pin,int mode); | GPIOピンのmode(INPUT、OUTPUT、PWM_OUTPUT)を設定する |
void digitalWrite(int pin,int value); | GPIOのピンにHIGH(1)かLOW(0)を設定する |
int wiringPiI2CSetup(int devId); | 指定したデバイスIDの初期化 |
int wiringPiI2CWriteReg8(int fd, int reg, int data); | 8ビットのデータ値を指定のレジスタに書き込む |
なお、wiringPiSetupGpio関数は、GPIOのピン番号を、GPIOコネクタ番号ではなく、Broadcom社が定義した番号で指定するように設定します(gpio readallコマンドで表示される表のBCM欄の番号です)。
Wiring Piの関数定義
前述したようにWiring Piは、C言語のライブラリなので、C#からこれらの関数を実行するためには、DllImport属性を使って、次のように、あらかじめ定義しておく必要があります。
using System.Runtime.InteropServices; // Wiring Piラッパークラス class WiringPi { public enum M { INPUT,OUTPUT } // モード public enum V { LOW,HIGH } // 出力値 [DllImport("libwiringPi.so", EntryPoint = "wiringPiSetupGpio")] public static extern int SetupGpio(); [DllImport("libwiringPi.so", EntryPoint = "pinMode")] public static extern void PinMode(int pin,int mode); [DllImport("libwiringPi.so", EntryPoint = "digitalWrite")] public static extern void DigitalWrite(int pin,int value); [DllImport("libwiringPi.so", EntryPoint = "wiringPiI2CSetup")] public static extern int I2CSetup(int devId); [DllImport("libwiringPi.so", EntryPoint = "wiringPiI2CWriteReg8")] public static extern int I2CWriteReg8(int fd, int reg, int data); }
今回は、WiringPiというクラスに定義しました。libwiringPi.soというファイルが、Raspberry PiにインストールされているWiringPiのライブラリです。libwiringPi.so(フルパスは/usr/lib/libwiringPi.so)とは、Linux系OSの共有ライブラリ(.soファイル)で、プログラムから動的に呼び出して使うライブラリです。Windowsでは、DLL(ダイナミックリンクライブラリ)に相当するものです。
C#では、DllImport属性のEntryPointオプションを使って、ライブラリ内の関数名を指定します。なおクラスのメソッド名は、Wiring Piの関数名から少し変更しています。このように、メソッド名は任意に変更することが可能です。
GPIOの制御
このWiringPiというクラスで、GPIOを制御するには、次のようなコードになります。GPIO4の設定を出力にして、値をHIGHに設定しています。
WiringPi.SetupGpio(); // 初期設定 WiringPi.PinMode(4, (int)WiringPi.M.OUTPUT); // モード設定 WiringPi.DigitalWrite(4, (int)WiringPi.V.HIGH); // 値の設定
I2C通信
今回のディスプレイモジュールでは、アプリケーションからデータを送り込むだけで制御できます。そのため、I2C通信といっても、初期化を行うwiringPiI2CSetupと、データを送信するwiringPiI2CWriteReg8という2つの関数を使うだけで、とても簡単です。
ただ、ディスプレイモジュールのコントローラー(SSD1306)に送るデータやコマンドは、やや複雑になります。
SSD1306の制御
SSD1306のデータシート(技術的な資料)は、こちらのPDF資料で公開されています。この資料を参考にして、プログラムを作成します。
SSD1306に送るデータは、大きく2つ、ディスプレイの設定コマンドと、表示用のデータにわかれます。基本的な制御の流れは、初期設定、表示データ送信という順になります。
コマンド送信
OLEDディスプレイ(SSD1306)モジュールを制御するクラスを作成しました。まず、コンストラクタ、各送信メソッドを解説しましょう。
// OLEDディスプレイ(SSD1306)モジュール制御クラス public class OledSsd1306 { // I2Cファイルディスクリプタ(1) int I2cfd { get; } // コマンド送信(2) public void SendCmd(params int[] args) { foreach (int c in args) { WiringPi.I2CWriteReg8(I2cfd, 0x00, c); // コマンドとして1バイト送信(3) } } // データ送信(5) public void SendData(byte b) { WiringPi.I2CWriteReg8(I2cfd, 0x40, b); // データとして1バイト送信 } public OledSsd1306(int fd) { I2cfd = fd; // ディスプレイの初期設定(4) SendCmd(0xA8,64-1); // Set MUX Ratio SendCmd(0xD3,0); // Set Display Offset SendCmd(64); // Set Display Start Line SendCmd(0xA1); // Set Segment re-map SendCmd(0xC8); // Set COM Output Scan Direction SendCmd(0xDA,0x12); // Set COM Pins hardware configuration SendCmd(0x81,0x7F); // Set Contrast Control SendCmd(0xA4); // Disable Entire Display On SendCmd(0xA6); // Set Normal Display SendCmd(0xD5,0x80); // Set Osc Frequency SendCmd(0x8D,0x14); // Enable charge pump regulator SendCmd(0x20,0x00); // Set Memory Addressing Mode(Horizontal Addressing Mode) SendCmd(0x21,0,127); // Set Column Address SendCmd(0x22,0,7); // Set Page Address SendCmd(0xAF); // Display On } }
プロパティとして定義しているI2Cファイルディスクリプタは、wiringPiI2CSetup関数の戻り値を保存するためのものです(1)。実はI2Cデバイスも、アプリケーションからは仮想ファイルとしてアクセスできます。その仮想ファイルをアクセスするためのファイルディスクリプタです。
SendCmdメソッドは、SSD1306にコマンドを送信するメソッドです(2)。SSD1306のコマンドは、1バイトのコマンドコードと、0~2バイトのパラメータになっています。SendCmdメソッドでは、コマンドとパラメータを可変引数として定義しています。
コマンドの送信は、wiringPiI2CWriteReg8という関数を使います。この関数の第1引数は、ファイルディスクリプタです。第2引数と第3引数が送信するデータとなります。SSD1306では、最初にコマンドなのか表示データなのかを区別する値が必要です。コマンドなら、0x00、表示データなら、0x40とします。SendCmdメソッドは、コマンド送信メソッドなので、wiringPiI2CWriteReg8の第2引数には、0x00を指定しています(3)。
コンストラクタ内で、設定コマンドを送信して、OLEDディスプレイの初期化を行っています(4)。
SSD1306の初期設定は、データシートの最後にサンプルのフローチャートが載っています。コンストラクタでは、このサンプルを参考にしてコマンドを送信しています。
ここでの初期化は、ディスプレイの初期設定で、受信データをディスプレイの左上から右に向かって表示する、等の主に画面の描画方法についての設定になります。コマンドの詳細については、データシートを参照してください。
表示データ送信
表示用データ送信のSendDataメソッドでは、1バイトのデータを送信するメソッドです(5)。SSD1306コントローラーでは、1バイトごとにデータを送信するようになっています。その送信データと画面表示の関係は次の図のようになります。
![送信データと画面表示](http://cz-cdn.shoeisha.jp/static/images/article/11777/11777_006.png)
ディスプレイモジュールに送信したデータのビット状態が、そのまま画面のドットのON/OFFとなります。送信データの1バイトは、横1ドット縦8ビット分として表示されます。また1バイトのMSB(上位ビット)が下、LSB(下位ビット)が上となりますので、図の赤い線の領域は、0x21というデータを表示していることになります。
データを連続して送信すると、図の矢印の向きに順に設定されます。今回のディスプレイモジュールは、横128ドット、縦64ドットなので、1画面分のデータは、128*64/8 = 1024バイトとなります。なお表示位置が、右下の最後までくると、また左上からの表示になります(コンストラクタで、そういったモードに設定しているため)。
また、コンストラクタでの初期設定時は、画面表示の開始(原点)は左上からですが、コマンドで設定することによって、表示範囲などを変更することができます。
実行
ディスプレイの初期化までを行うには、以下のようなコードになります。最初のGPIOの出力設定は、ディスプレイモジュールの電源にGPIO4を利用した場合の処理です。ディスプレイモジュールのVCC端子を、Raspberry Piの電源端子に接続した場合は不要です。
// GPIOの初期設定 ~略~ Thread.Sleep(500); // I2C通信の初期化 int fd = WiringPi.I2CSetup(0x3C); // I2C通信の初期化(1) var oled = new OledSsd1306(fd); // ディスプレイの初期化(2)
まず、I2Cデバイスに設定されたアドレスを指定して、I2CSetupメソッドを実行します(1)。そして、戻り値のファイルディスクリプタを使って、OledSsd1306クラスをインスタンス化します(2)。なお、I2CSetupメソッドの戻り値が-1のときはエラーですので、実使用時にはエラー処理を入れたほうがいいでしょう。
早速Raspberry Piで実行してみましょう。
$ dotnet /home/pi/source/repos/PieCore/Oled/bin/Debug/netcoreapp3.0/Oled.dll
次のように、砂嵐のような画面になるはずです。
![起動時の画面表示](http://cz-cdn.shoeisha.jp/static/images/article/11777/11777_007.png)
画面クリア
この表示になっても、壊れているわけではなく、正常な表示です。このディスプレイモジュールの場合、内蔵のグラフィックメモリの内容を表示するようになっていて、リセット状態では、初期化されていないメモリの値をそのまま表示します。
まず画面をクリアする処理を作ってみましょう。クリア用の専用コマンドはありません。そのため、グラフィックメモリを0で初期化する、つまり、0のデータを1024バイト分送信してクリアする必要があります。
// 画面クリア for (int i = 0; i < (128 * 64 / 8); i++) { oled.SendData(0); }
実行すると、すべて消えて元の黒い表示に戻るはずです。
最後に
今回は、ライブラリを使ったディスプレイモジュールのI2C通信を解説しました。次回は、ディスプレイモジュールに直線や文字を表示する方法を解説します。