Sim's blog

電子工作はじめてみました

NetduinoでI2C

2010-09-05 22:00:22 | Netduino
NetduinoでキャラクタLCDの続きになります。

秋月のリアルタイムクロックモジュールRTC-8564NBを使って液晶時計を作ってみました。


RTC8564-NBはI2Cで制御してやる必要があります。以前、RTC-8564NBを使ってみるという記事でAVRから制御したことがあります。

.NET Micro FrameworkにはI2Cのクラスがあるので、これを使えばI2Cで通信ができます(APIリファレンス)。とはいっても、使い方が分かるまで結構紆余曲折でした。

Microsoft.SPOT.HardwareというNamespaceの下にはI2C関連の5つのクラスがあります。
- I2CDevice I2Cデバイスのインスタンスを作るためのクラス
- I2CDevice.Configuration I2Cインタフェースの通信速度等の設定を行うクラス
- I2CDevice.I2CReadTransaction I2Cデバイスからの読み込みを行うトランザクション
- I2CDevice.I2CTransaction I2Cデバイスとのトランザクション
- I2CDevice.I2CWriteTransaction I2Cデバイスへの書き込みを行うトランザクション

まずは、インスタンスを作ります。I2CDeviceクラスとI2CDevice.Configurationクラスを使って、次のように初期化します。

 rtc8564 = new I2CDevice(new I2CDevice.Configuration(0x51, clockRate));

ここで0x51というのはRTC-8564NBのデバイスのアドレスです。clockRateはKHz単位の速度です。今回はint clockRate = 100;としているので100KHzで通信することになります。

実際の通信はrtc8564.Execute(トランザクションの配列, タイムアウト);のようにして行います。タイムアウトはミリ秒単位で指定します。さて、トランザクションって何でしょう?というところで出てくるのが残りの3つのクラスです。

何バイトか読む、もしくは書くというのが一つのトランザクションです。これを束ねて何バイト書いてから、何バイト読むみたいな一連の流れをトランザクションの配列で指定してやります。I2Cはマルチマスタが可能なので、通信している途中に別のマスタが割り込んでこないように、ある程度の読み書きをまとめてする必要があります。そのために、こんな面倒なことをします。

実際のトランザクションの例です。まずはトランザクションの配列を作ります。

 I2CDevice.I2CTransaction[] actions = new I2CDevice.I2CTransation[2];

ここでは2つの要素が含まれる配列actionsを作っています。次に配列の要素を初期化してやります。

 byte[] addr = new byte[1]{ 0x02 };
 byte[] data = new byte[7];

 actions[0] = I2CDevice.CreateWriteTransaction(addr);
 actions[1] = I2CDevice.CreateReadTransaction(data)

actions[0]はI2Cデバイスへの書き込みトランザクションです。書き込む内容はaddrという1バイトの配列です。書き込む長さは配列の長さです。
書き込む内容が変わるときはaddr[0]の内容を書き換えます。書き換えるバイト数が変わるときには別の配列を用意してやる必要があります(結構面倒)。例えばaddr = new byte[2]みたいなことをします。
actions[1]はI2Cデバイスから読み出すトランザクションです。読み込む長さは配列の長さになります。上の例だとdataの長さである7バイトを読み込みます。配列の一部だけ読み込むといった動作はできません。

今回書いたコードです。
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace NetduinoI2C
{
    // RTC8564クラスのサンプル by Sim
    public class Program
    {
        static CLCD lcd;
        static string[] WeekDay = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

        public static void Main()
        {
            // write your code here
            lcd = new CLCD(Pins.GPIO_PIN_D12, Pins.GPIO_PIN_D11, Pins.GPIO_PIN_D5, Pins.GPIO_PIN_D4, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);

            // RTCの宣言(通信速度kbps, タイムアウト ミリ秒)
            RTC8564 rtc = new RTC8564(100, 500);

            // 時間の設定 BCDで設定する(stop()してから設定する)
            rtc.year  = 0x10; // 年の下位2桁
            rtc.month = 0x09; // 月
            rtc.day   = 0x02; // 日
            rtc.wday  = 4;    // 曜日 0:日 1:月 2:火 3:水 4:木 5:金 6:土
            rtc.hour  = 0x01; // 時
            rtc.min   = 0x46; // 分
            rtc.sec   = 0x30; // 秒
            rtc.write();

            // start()で開始
            rtc.start();

            // 1HzのCLKOUTの立ち上がりで割り込みがかかるようにする
            InterruptPort clkout = new InterruptPort(Pins.GPIO_PIN_D7, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
            // 割り込み処理ルーチンの設定
            clkout.OnInterrupt += new NativeEventHandler(delegate(uint data1, uint data2, DateTime time)
            {
                // 割り込み処理ルーチン。D7の立ち上がりで呼び出される。匿名メソッド

                // RTC8564NBから時刻を読み出す
                rtc.read();

                // 表示
                // 1行目
                lcd.home();
                hex2(0x20);
                hex2(rtc.year);
                lcd.write('/');
                hex2(rtc.month);
                lcd.write('/');
                hex2(rtc.day);
                lcd.write(' ');
                lcd.puts(WeekDay[rtc.wday]);

                // 2行目
                lcd.setCursor(0, 1);
                hex2(rtc.hour);
                lcd.write(':');
                hex2(rtc.min);
                lcd.write(':');
                hex2(rtc.sec);
            });

            // 無限ループ
            Thread.Sleep(Timeout.Infinite);
        }

        // 16進2桁出力
        static char[] hexchar = new char[16] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        static public void hex2(byte x)
        {
            lcd.write(hexchar[x >> 4]);
            lcd.write(hexchar[x & 15]);
        }
    }

    // RTC8564NBドライバ by Sim : EPSON TOYOCOM I2C bus real time clock module
    // 秋月電子 通販コード I-00233 (http://akizukidenshi.com/catalog/g/gI-00233/)
    //
    // 接続例
    // 1pin CLKOE  - open          内部でpull-upされている
    // 2pin CLKOUT - Netduino:D7   1Hzの矩形波出力
    // 3pin INT    - open          未使用
    // 4pin GND    - Netduino:GND
    // 5pin SDA    - Netduino:A4   2.2kΩでpull-up
    // 6pin SCL    - Netduino:A5   2.2kΩでpull-up
    // 7pin NC     - open
    // 8pin Vdd    - Netduino:3.3V パスコンはモジュール上に実装されているので外部には不要

    public class RTC8564 : IDisposable
    {
        I2CDevice rtc8564;

        public byte year, month, day, wday, hour, min, sec;
        public int timeout;

        // トランザクション用
        private byte[] cmd  = new byte[2];
        private byte[] addr = new byte[1] { 0x02 }; // 秒から7バイト読み始める
        private byte[] data = new byte[7];
        private I2CDevice.I2CTransaction[] wr = new I2CDevice.I2CTransaction[1];
        private I2CDevice.I2CTransaction[] rd = new I2CDevice.I2CTransaction[2];

        // コンストラクタ。初期化する(newしたときに呼ばれる)
        // 時刻は未設定、停止状態になる
        public RTC8564(int clockRate, int timeout)
        {
            this.timeout = timeout;

            // define transaction
            wr[0] = I2CDevice.CreateWriteTransaction(cmd);  // for sendcmd()
            rd[0] = I2CDevice.CreateWriteTransaction(addr); // for read()
            rd[1] = I2CDevice.CreateReadTransaction(data);  // for read()

            rtc8564 = new I2CDevice(new I2CDevice.Configuration(0x51, clockRate));

            stop();
            sendcmd(0x01, 0x00); // Control 2 : 0x00
            sendcmd(0x0e, 0x00); // Timer Control : 0x00
            sendcmd(0x0d, 0x83); // CLKOUT frequency : 0x83 (1Hz)
        }

        // 動作開始
        public void start()
        {
            sendcmd(0x00, 0x00); // Control 1 : 0x00
        }

        // 動作停止
        public void stop()
        {
            sendcmd(0x00, 0x20); // Control 1 : 0x20
        }

        // RTC8564にコマンドを送る
        private void sendcmd(byte address, byte data)
        {
            cmd[0] = address;
            cmd[1] = data;
            rtc8564.Execute(wr, timeout);
        }

        // 時刻の読み出し
        public void read()
        {
            rtc8564.Execute(rd, timeout);
            sec   = (byte)(data[0] & 0x7f);
            min   = (byte)(data[1] & 0x7f);
            hour  = (byte)(data[2] & 0x3f);
            day   = (byte)(data[3] & 0x3f);
            wday  = (byte)(data[4] & 0x07);
            month = (byte)(data[5] & 0x1f);
            year  = data[6];
        }

        // 時刻の設定
        public void write()
        {
            sendcmd(0x02, sec);
            sendcmd(0x03, min);
            sendcmd(0x04, hour);
            sendcmd(0x05, day);
            sendcmd(0x06, wday);
            sendcmd(0x07, month);
            sendcmd(0x08, year);
        }

        // デストラクタ
        public void Dispose()
        {
            rtc8564.Dispose();
        }
    }

    // Character LCD Arduino like API by Sim
    public class CLCD
    {
        // 前回と同じなので省略
    }

}

RTC-8564NBにはCLKOUTという1Hz出力端子があります。今回はCLKOUTの立ち上がりで割り込みをかけて、1秒毎に時刻を読み込んで表示するようにしました。
割り込み処理ルーチンは匿名メソッドを使ってMainの中に直接書きました。
A5がSCL、A4がSDAというのはArduinoとピンコンパチです。
トランザクションの配列を事前に用意しておくのが結構面倒です。

最新の画像もっと見る

5 コメント

コメント日が  古い順  |   新しい順
複数のI2CDevice (なま)
2012-09-25 19:32:47
こんにちは、はじめまして。
最近netduinoで遊んでましてSimさんのブログはいろいろと参考にさせて頂いてます。ありがとうございます。

netduinoでI2Cを使っていてひとつわからない事があるので質問させて頂きます。

I2Cのデバイスに秋月気圧計とストロベリーリナックスの液晶をつないでいます。
どちらかのデバイスひとつを繋いだ時は問題なく動いているのですが、I2Cバスに2つのデバイスを接続した時、2つ目のI2CDeviceのnewで例外発生してしまっています。
.NET Frameworkのdllで以下のような例外です。

An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.SPOT.Hardware.dll

もしかしてこのdll内で複数インスタンス生成に対応していないのでは??などと疑っているのですが、Simさんなにかご存知ないでしょうか?

もしなにかご存知でしたら教えていただきたく宜しくお願い致します。



返信する
Unknown (なま)
2012-09-25 19:42:47
連投すいません、このへんに解がありそうです。
がんばってみます。

http://forums.netduino.com/index.php?/topic/2054-using-multiple-i2c-devices-i2cdevice/
返信する
re:Unknown (Sim)
2012-09-29 04:35:02
こんにちは、なまさん

手元では試せないのですが、こちらの情報が使えないでしょうか?
http://www.tinyclr.com/forum/topic?id=686

I2CDeviceのnewは1回だけで、Configメンバーに2回代入するという方法で複数デバイスを実現しているようです。
返信する
Unknown (なま)
2012-10-03 15:34:29
Simさん、ありがとうございます。
すいません、解決しててこちらに報告するの忘れてました。まさにSimさんの言うとおり、I2CDeviceのnewは一回で、Configを切り替えて使う方法で出来ました。上記フォーラム内でSharedI2CDeviceクラスを公開されててそれを使っています。

ありがとうございました。
これからもブログ楽しみにしています。
返信する
re:Unknown (Sim)
2012-10-08 20:57:49
こんにちは、なまさん

こちらこそ勉強になりました。ありがとうございます。
返信する

コメントを投稿