CodeZine(コードジン)

特集ページ一覧

C#でラズパイに接続したディスプレイを制御してみよう

C#ではじめるラズパイIoTプログラミング 第3回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2019/10/16 11:00
目次

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を選択
.NET Core 3.0を選択

 まずはVisual Studioで、新しいプロジェクトを作成します。プロジェクトのテンプレートは、コンソールアプリ(.NET Core)を選択します。プロジェクトの構成は、プロジェクト名をOled、ソリューション名をPieCoreとしました。

利用するWiring Piの関数

 今回、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属性を使って、次のように、あらかじめ定義しておく必要があります。

[リスト1]Program.csの一部
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に設定しています。

[リスト2]Program.csの一部
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)モジュールを制御するクラスを作成しました。まず、コンストラクタ、各送信メソッドを解説しましょう。

[リスト3]Program.csの一部
// 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バイトごとにデータを送信するようになっています。その送信データと画面表示の関係は次の図のようになります。

送信データと画面表示
送信データと画面表示

 ディスプレイモジュールに送信したデータのビット状態が、そのまま画面のドットのON/OFFとなります。送信データの1バイトは、横1ドット縦8ビット分として表示されます。また1バイトのMSB(上位ビット)が下、LSB(下位ビット)が上となりますので、図の赤い線の領域は、0x21というデータを表示していることになります。

 データを連続して送信すると、図の矢印の向きに順に設定されます。今回のディスプレイモジュールは、横128ドット、縦64ドットなので、1画面分のデータは、128*64/8 = 1024バイトとなります。なお表示位置が、右下の最後までくると、また左上からの表示になります(コンストラクタで、そういったモードに設定しているため)。

 また、コンストラクタでの初期設定時は、画面表示の開始(原点)は左上からですが、コマンドで設定することによって、表示範囲などを変更することができます。

実行

 ディスプレイの初期化までを行うには、以下のようなコードになります。最初のGPIOの出力設定は、ディスプレイモジュールの電源にGPIO4を利用した場合の処理です。ディスプレイモジュールのVCC端子を、Raspberry Piの電源端子に接続した場合は不要です。

[リスト4]Program.csの一部
// 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

 次のように、砂嵐のような画面になるはずです。

起動時の画面表示
起動時の画面表示

画面クリア

 この表示になっても、壊れているわけではなく、正常な表示です。このディスプレイモジュールの場合、内蔵のグラフィックメモリの内容を表示するようになっていて、リセット状態では、初期化されていないメモリの値をそのまま表示します。

 まず画面をクリアする処理を作ってみましょう。クリア用の専用コマンドはありません。そのため、グラフィックメモリを0で初期化する、つまり、0のデータを1024バイト分送信してクリアする必要があります。

[リスト5]Program.csの一部
// 画面クリア
for (int i = 0; i < (128 * 64 / 8); i++)
{
    oled.SendData(0);
}

 実行すると、すべて消えて元の黒い表示に戻るはずです。

最後に

 今回は、ライブラリを使ったディスプレイモジュールのI2C通信を解説しました。次回は、ディスプレイモジュールに直線や文字を表示する方法を解説します。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:C#ではじめるラズパイIoTプログラミング

著者プロフィール

  • WINGSプロジェクト 高江 賢(タカエ ケン)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5