NETMF : I2C の実装方法と I2C 接続キャラクターディスプレイの例

I2C (=Inter-Integrated Circuit) について、.NET Micro Framework で実装する方法を紹介します。
※「I2C とは何?」について本稿では概要を紹介します。詳しい話は検索結果にお任せします。

GPIO だとマイコンボードからキャラクターディスプレイを利用するのに 4ピン(+電源系 2ピン)の接続が必要です。
(8ビットモードだと 8ピン必要)
I2C だと SCL、SDA の 2本の信号線接続(+電源)で済みます。複数の I2C モジュールを接続したい場合でも2本の信号線を共用できます。ピンの消費が少ないのがメリットです。


I2C の概要

I2C は2本の信号線で、マスター(マイコンボード)とスレーブ(モジュール)とが双方向の通信ができます。
通信はすべてマスター主導で行われます。
各スレーブはアドレスを持っているため、マスターからのデータ送受信要求に対して、特定のスレーブだけがその後のデータ送受信を継続します。これによって各スレーブは信号線を共用できるわけです。2本の信号線はプルアップ抵抗を介して VDD (電源) に接続する必要があります。

アドレス、プルアップ抵抗をモジュールに内蔵しているかどうかなどは、各モジュールのデータシートに記載されています。
※スレーブのアドレスは製造元が “独自に” 決めます。アドレスの衝突があり得るため、ジャンパーなどでアドレス切り替えできるモジュールもあります。


.NET Micro Framework の I2C 対応

.NET Micro Framework は組込み用途での利用が一般的なので、ライブラリ内に I2C の機能を持っています。

  • 初期化
    I2C 利用に必要なモジュールのアドレスは、.NET Micro Framework では I2CDevice クラスのインスタンス化のタイミングで指定します。var _i2C = new I2CDevice(new I2CDevice.Configuration(i2CAddress, clockRateKhz));

    Configuration クラスのコンストラクタの i2cAddress 引数がモジュールのアドレス、clockRateKhz が転送速度です。
    (転送速度は 100KHz または 400KHz、さらに速い規格もありますが、評価用途のマイコンボード、モジュールでは 100KHz でも困らないはず)
  • データ送受信
    .NET Micro Framework では送受信したいデータを CreateWriteTransaction または CreateReadTransaction で作成し、これらを I2CTransaction 型の配列に必要なだけ入れます。
    実際の送受信は Execute メソッドで行います。
    データの組み立ては自前で行う必要があるため、ヘルパークラスを用意するとラクにコードが書けます。これについては後述。

I2C  通信(に限りませんが)本当はモジュールのタイミングチャートを見てタイミングを図る必要があるのですが、I2CDevice クラスがそのあたりの面倒を隠ぺいしてくれています。おかげで非常に簡単にデータ送受信ができます。


I2C 接続キャラクターディスプレイのコード例

I2C の例として、キャラクターディスプレイを使ってみます。

GPIO の使い方の例としてもキャラクターディスプレイを使いましたが、I2C 接続の場合でも GPIO 接続ディスプレイの知識がそのまま使えます。
というのは、I2C 接続であっても多くの場合は HD44780 互換のディスプレイを積んでいるからです。

GPIO ではビットごとにピンを割り当てていたものを、I2C ではバイトの形にまとめて送受信するという違いだけです。(つまり、モジュール側でデータを分解してディスプレイの各ピンの制御を行っているということです)


I2CDevice の継承クラス

I2CDevice クラスはデータ送受信そのものの詳細を隠ぺいしてくれますが、データを作るところは対応していません。そこでこんなクラスを用意してみます。

public class I2CDeviceEx
{
    private readonly I2CDevice _i2C;
    private readonly int _timeout;

    private readonly byte[] _adata = new byte[1];   // アドレス指定用のバイトデータ領域
    private readonly byte[] _rdata = new byte[1];   // データ受信用のバイトデータ領域
    private readonly byte[] _wdata = new byte[2];   // データ送信用のバイトデータ領域

    private I2CDevice.I2CTransaction[] _trRegRead;      // データ受信用のトランザクションデータ
    private I2CDevice.I2CTransaction[] _trRegWrite;     // データ送信用のトランザクションデータ

    public I2CDeviceEx(ushort i2CAddress, int clockRateKhz = 100, int timeout = 1000)
    {
        _i2C = new I2CDevice(new I2CDevice.Configuration(i2CAddress, clockRateKhz));
        _timeout = timeout;

    }

    protected byte RegRead(byte reg)
    {
        _adata[0] = reg;
        _trRegRead = new I2CDevice.I2CTransaction[] {
            I2CDevice.CreateWriteTransaction(_adata),
            I2CDevice.CreateReadTransaction(_rdata) };
        _i2C.Execute(_trRegRead, _timeout);
        return _rdata[0];
    }

    protected void RegReads(byte reg, ref byte[] data)
    {
        _adata[0] = reg;
        _trRegRead = new I2CDevice.I2CTransaction[] {
            I2CDevice.CreateWriteTransaction(_adata),
            I2CDevice.CreateReadTransaction(data) };
        _i2C.Execute(_trRegRead, _timeout);
    }

    protected void RegWrite(byte reg, byte val)
    {
        _wdata[0] = reg;
        _wdata[1] = val;
        _trRegWrite = new I2CDevice.I2CTransaction[] { I2CDevice.CreateWriteTransaction(_wdata) };
        _i2C.Execute(_trRegWrite, _timeout);
    }

    protected void RegWriteMask(byte reg, byte val, byte mask)
    {
        var tmp = RegRead(reg);
        _wdata[0] = reg;
        _wdata[1] = (byte)(tmp & ~(int)mask | ((int)val & (int)mask));
        _trRegWrite = new I2CDevice.I2CTransaction[] { I2CDevice.CreateWriteTransaction(_wdata) };
        _i2C.Execute(_trRegWrite, _timeout);
    }
}

キャラクターディスプレイのクラス

I2CDeviceEx クラスでは、I2CDevice 自体を使いやすくするメソッドを実装しました。

各モジュールに対して、今回の例で言うと、キャラクターディスプレイモジュールに対しては、I2CDeviceEx を継承するクラスを用意するとコードの見通しがよくなります。
例えば、
RegWrite((byte)0x80, data);
よりは
WriteCharactor(data);
のほうが読みやすいですね。(”(byte)0x80” ってなんだよという話です)

public class I2CLiquidCrystal : I2CDeviceEx
{
    private bool _displayOn = true;         // ディスプレイをオンにするか
    private bool _cursorOn = false;         // カーソルを表示するかどうか
    private bool _blinkOn = false;          // カーソル位置でブリンクするか

    private readonly int _commandWait = 1;  // コマンド実行後のウェイト

    public I2CLiquidCrystal(ushort acm1602N1Address, int defaultClockRateKhz, int timeout) :
        base(acm1602N1Address, defaultClockRateKhz, timeout)
    {
        Thread.Sleep(1000);

        WriteCommand(0x30, 5);      // 8ビットモードにセット + 5msウェイト
        WriteCommand(0x30);         // 8ビットモードにセット
        WriteCommand(0x30);         // 8ビットモードにセット
        WriteCommand(0x38);         // 行数とフォントの設定
        WriteCommand(0x80);         // 表示オフ

        WriteCommand(0x01);         // 表示クリア
        WriteCommand(0x06);         // カーソルと表示のシフト設定
        WriteCommand(0x0c);         // カーソルとブリンクの表示をオフ

        Thread.Sleep(100);
    }

    public void Print(string msg)
    {
        for (var i = 0; i < msg.Length; i++)
        {
            WriteCharactor((byte)msg[i]);
        }
    }

    public void Clear()
    {
        WriteCommand(0x01, 5);      // Clear Displayはウェイトが必要
    }

    public void Home()
    {
        WriteCommand(0x02, 5);      // Return Homeはウェイトが必要
    }

    public void DisplayOn(bool displayOn)
    {
        ControlDisplay(displayOn, _cursorOn, _blinkOn);
        _displayOn = displayOn;
    }

    public void CursorOn(bool cursorOn)
    {
        ControlDisplay(_displayOn, cursorOn, _blinkOn);
        _cursorOn = cursorOn;
    }

    public void BlinkOn(bool blinkOn)
    {
        ControlDisplay(_displayOn, _cursorOn, blinkOn);
        _blinkOn = blinkOn;
    }

    public void SetCursor(int row, int col)
    {
        var addr = (byte)(((byte)row) << 6) + (byte)col;
        WriteCommand((byte)(0x80 | addr));
    }

    private void ControlDisplay(bool displayOn, bool cursorOn, bool blinkOn)
    {
        var cmd = (byte)0x08;
        if (displayOn)
            cmd |= 0x04;
        if (cursorOn)
            cmd |= 0x02;
        if (blinkOn)
            cmd |= 0x01;

        WriteCommand(cmd, _commandWait);
    }

    public void WriteCharactor(byte data)
    {
        RegWrite((byte)0x80, data);
    }

    public void WriteCommand(byte cmd, int wait = 1)
    {
        var reg = (byte)0x00;
        RegWrite(reg, cmd);
        Thread.Sleep(wait);
    }
}

キャラクターディスプレイはマイコンボードからモジュールへのデータ送信しかありません。出力デバイスなので当然ですね。
制御コマンドの場合は対応する 1バイトのみをトランザクションに入れて Execute を呼び出します。
文字出力の場合は、制御コマンド 0x80 に続いて出力したい文字データをトランザクションに入れます。

モジュールによっては HD44780 拡張コマンドを使用するものがあります。この場合は上記のコードでは動作しません。特に初期化コマンドに差があります。拡張コマンドについては GR ファミリー用のクラスライブラリのソースコードを参考にしてください。


アプリケーションのコード例

キャラクターディスプレイの使用方法の例を兼ねて、アプリケーションのコードも紹介します。

public class Program
{
    private const ushort Acm1602N1Address = 0x50;   // ACM1602N1-FLW-FBWのアドレス
    private const int DefaultClockRateKhz = 100;    // 転送速度 (KHz)
    private const int Timeout = 1000;               // 送受信ごとのタイムアウト

    public static void Main()
    {
        var lcd = new I2CLiquidCrystal(Acm1602N1Address, DefaultClockRateKhz, Timeout);

        lcd.Clear();                        // 画面クリア (クリア後は 1行 1列にカーソル)
        Thread.Sleep(1000);
        lcd.Print("Hello, I2C CLCD!");      // 文字列表示
        lcd.SetCursor(1, 4);                // カーソル移動
        lcd.Print("I2C Demo");
        Thread.Sleep(3000);

        while (true)
        {
            lcd.BlinkOn(true);              // 次の文字出力位置で点滅
            Thread.Sleep(3000);
            lcd.BlinkOn(false);
            lcd.CursorOn(true);
            Thread.Sleep(3000);
            lcd.CursorOn(false);            // カーソル表示
            Thread.Sleep(3000);

            for (var i = 0; i < 3; i++)
            {
                lcd.DisplayOn(false);       // 画面非表示
                Thread.Sleep(1000);
                lcd.DisplayOn(true);        // 画面表示
                Thread.Sleep(1000);
            }

            Thread.Sleep(2000);
        }
    }
}

 


今回のコードは、秋月電子の ACM1602NI-FLW-FBW で動作確認しました。
実際に自分でも動かしてみたい方はパーツを購入した上で以下を参考に回路を組んでみてください。

WP_20151231_13_12_52

回路図はこんな感じ。

2015-12-31 14-37-012015-12-31 14-49-06

 

今回のコードで I2C 接続のキャラクターディスプレイを利用できるようになりますが、毎回似たようなコードを書くのも面倒です。
より手軽に呼び出すために GR ファミリー用のクラスライブラリの利用もアリです。

NETMF の実装方法の紹介まで、かなり間が開きました。
GR-PEACH + I2C モジュールの紹介をしようとしたところで、回路図が書けず(Fritzing で書こうにも当然 GR-PEACH のパーツがない)、そうこうしているうちにハンズオン準備などがあり、先送りしてしまっていました。
Fritzing 用のパーツを作ったことでようやく I2C について書けました。

広告
カテゴリー: .NET Micro Framework, IoT ALGYAN タグ: , パーマリンク

NETMF : I2C の実装方法と I2C 接続キャラクターディスプレイの例 への1件のフィードバック

  1. ピンバック: NETMF : 複数の I2C モジュールを接続(三軸加速度センサー ADXL345+キャラクターディスプレイ) | 技術との戯れ

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中