.NET対応組み込みデバイス「Netduino」入門(4)
Netduinoシリアル通信(I2C)の基本
アナログ入力の次はシリアル通信(I2C)の基礎を習得しよう。I2C対応の温度センサーの値をNetduinoで取得するサンプルを作成する。
前回で、アナログ入力の基本を解説した。
今回は、Netduinoのもう一つの入出力機能であるI2C通信について取り上げる。I2Cは周辺機器との間で特定バイト長のバイナリ値としてデータをやりとりする。
I2Cとは
I2CはInter-Integrated Circuitの略であり、アイ・スクエア・シーまたはアイ・ツー・シーと呼ばれるシリアルバス規格だ。
I2Cで使われるのはSDA(シリアルデータ)とSCL(シリアルクロック)の2つであり、この2つに複数の入力機器(センサーなど)と出力機器(モーターや液晶パネル)をつなげることができる。
各機器(I2Cではノードと呼ぶ)の識別は、個々のノードに割り当てたアドレスにより行う。アドレスは、7bitのアドレス空間すなわち128個のアドレスから16個の予約アドレスを除いた112ノードを同時に接続できる。
しかしながらアドレス空間の割り当て機関などは存在しないため、各メーカーが独自に使用アドレスを決定し、アドレスがバッティングしたときのためにディップスイッチやジャンパー接続によりアドレスを変更できるようにしている場合が多い。そのため112ノードを接続しようとするとかなり綿密に機器を選択するなどの工夫が必要になる。
NetduinoのI2C
Netduinoのボードの左上にI2C関連のピンがある(図1)。SC
がSCL(シリアルクロック)ピンで、SD
がSDA(シリアルデータ)ピンだ。
ADT7410温度センサーモジュールについて
今回のサンプルでは、I2C対応機器としてADT7410温度センサーモジュールという温度センサー(図2)を使って室温を測定する。
このADT7410モジュールは、購入後に基盤とピンを合計4カ所はんだ付けしなければならない。本連載では極力はんだ付けはしたくなかったのだが、手ごろな価格でピンがはんだ付け済みのものを探したが見つけることができず、比較的はんだ付け箇所が少なくて作業しやすいものを選択したので、この点は了承してほしい(※はんだごてが手元にない場合は、「Amazon.co.jp: goot電子工作用はんだこてセット X-2000E」などを購入すると、全てそろっているので便利だ)。
多少はんだ付けの手間はあるが、ADT7410温度センサーモジュールは、温度センサーとしてADT7410を採用し、パスコン(図3のC1)やプルアップ抵抗(図3のR1やR2)など必要な周辺部品がすでに組み込まれている(図3)。
ADT7410の性能としては、測定レンジは-55~150℃、測定誤差の最大値は±0.5℃である。また、消費電力は最大210μA(25℃時)だ。
なお、測定した温度は、ADコンバーターで13bit(デフォルト値)のデジタル値に自動変換される。よって1bitあたり0.0625℃(デフォルトの13bitモード時)の分解能がある。前回使ったLM16CIZからの入力が0~3.3Vを1024段階で取得するアナログ入力の制限から3mV=0.3℃の分解能であったのと比較すると、ADコンバーターでの丸め誤差も少なく正確な温度測定が期待できる。
Netduino+ADT7410モジュールのハード構成
ADT7410モジュールの物理仕様
ADT7410モジュールには4つの足と4つのジャンパーがある(図4)。
ジャンパーJ3
とジャンパーJ4
をそれぞれはんだ付けすることでアドレスを変更できるが、今回ははんだ付けせずアドレスは「0x48」を用いる。
ジャンパーJ1
とジャンパーJ2
をそれぞれはんだ付けすることでSCL
やSDA
をNetduinoで使うときに必要なプルアップ抵抗を有効にできる。今回は、ここもはんだ付けせず別途ブレッドボード上にプルアップ抵抗を使用する。基板上のプルアップ抵抗を使わないのは、はんだ付けしてしまうとプルアップしたくないときに対応できないからであり、それ以上に深い理由はない。
ADT7410モジュールとNetduinoを接続するときには、図4の上から、Netduinoの5V
ピン、SC
ピン、SD
ピン、GND
に接続すればよい。
ADT7410モジュール接続回路図
I2Cはシリアル通信なので、流れるデータはOnとOffのビットデータとなる。Onのときには5V、Offのときには0Vになる。Netduinoで使用するときはプルアップ抵抗を入れることでOnとOffの電圧値がより安定する。
ADT7410モジュール自体でもプルアップ抵抗を有効にできるが、ジャンパーをはんだ付けしてしまうとプルアップ抵抗が不要なときに対処できないので、ADT7410モジュールの外側で明示的にプルアップ抵抗を回路に組み込むことにした。
プルアップ抵抗値は、ADT7410モジュールでも採用している10kΩを使用する。ADT7410の2番ピン(=SDA
)と3番ピン(=SCL
)を、Netduinoを接続するラインから分岐するように抵抗を挟んで+5V
ラインに接続する。
ブレッドボード実装
回路図を基にしてブレッドボードに部品を配置する。
Netduinoからの5V
ラインは赤いラインを通ってブレッドボードの一番下のプラス電源ライン、GND
からの線も同様に青いラインを通って下から二番目のGNDラインに接続する。
A10
とE10
は内部的に接続されているのでADT7410モジュールのVDD
に+5Vを供給する。GNDもA7
からE7
の内部結線を経由してADT7410モジュールのGND
に接続する。
I2C特有のSCL
とSDA
の2つの信号線は、SCLがC8
、SDAがB9
でNetduinoに接続している。また、プルアップ用の10KΩ抵抗がA8
とA9
からプラス電源ラインの間を接続している。
実際に接続すると次のようになる。
Netduino+ADT7410モジュールのプログラミング
回路が完成したら、アプリのプログラミングを始める。
最初に行う手順は、Visual Studioの[新しいプロジェクト]ダイアログで[Micro Framework]カテゴリの[Netduino Plus 2 Application]テンプレートを選択して、新規にプロジェクトを作成することだ(本稿の例では、プロジェクト名はVB.NET用は「ADT7410TemperatureVB」、C#用は「ADT7410TemperatureCS」とした)。
センサー値の理解
ADT7410はレジスタと呼ばれる1byte(=8bit)の領域を介して値のやりとりを行う。レジスタは全部で13個ある(表1)。
レジスタアドレス | レジスタ名 | 内容 |
---|---|---|
0x00 | Temperature value most significant byte | 温度値(上位) |
0x01 | Temperature value least significant byte | 温度値(下位) |
0x02 | Status | ステータス |
0x03 | Configuration | 設定 |
0x04 | THIGH setpoint most significant byte | 高温限界(上位) |
0x05 | THIGH setpoint least significant byte | 高温限界(下位) |
0x06 | TLOW setpoint most significant byte | 低温限界(上位) |
0x07 | TLOW setpoint least significant byte | 低温限界(下位) |
0x08 | TCRIT setpoint most significant byte | クリティカル限界(上位) |
0x09 | TCRIT setpoint least significant byte | クリティカル限界(下位) |
0x0A | THYST setpoint | |
0x0B | ID | |
0x2F | Software reset | ソフトウェアリセット |
温度データは0x00
と0x01
の2つのレジスタ、つまり2byte値で取得できる。2byteの中のデータフォーマットには、13bitモードと16bitモードの2つのデータフォーマットがある。
どちらのモードでも「0℃=0」(13bitモード時は3ビット右シフトが必要)となる。初期設定は13bitモードだが、この連載では設定変更して16bitモードで使用する。16bitモードにするには、後述する設定レジスタ(0x03
)に「0x80」を指定して設定変更を行う。
16bitモードでの測定値を実際の気温に変換するには、以下のように変換を行う。
また、最上位bitが符号なので、「0x8000」(=32768)以上の場合はマイナス気温を表す。この場合、測定値から「65536」(=32768×2)を減算してから「128」で除算する。
初期設定では高温限界が64℃、低温限界が10℃、クリティカル限界が147℃に設定されている。それぞれ限界値を越えると、INT端子やCT端子がアクティブ(0V→3.3Vまたは3.3V→0Vが流れる)になる。今回使用したADT7410モジュールではINT端子やCT端子が未使用になっているが、使用可能なモジュールを利用してINT端子やCT端子を監視することで、限界超えのときにアラートを上げるような回路も組むことが可能だ。
ADT7410の設定
ADT7410の設定初期は以下の通りである。この設定は、設定レジスタ(0x03
)により変更できる。
13bitモードと16bitモードの違いは測定値のところで説明した。16bitモードにしたいときは「0x80」を設定すればよい。
動作モードもADT7410の特徴的な機能だ。具体的には以下の動作モードがある。
- ノーマルモード: ノーマルモードでは連続して温度を計測する。計測した温度をAD変換するのに要する時間は240msなので500ms間隔以上でループしながらレジスタ
0x00
と0x01
を読み取れば温度測定値を得られる。ノーマルモードの場合、常に210μAの電流が流れるため700μWの消費電力となる。 - ワンショットモード: 測定時だけ測定用に回路に通電し、測定が終わると必要最小限の消費電力に落とすモードだ。このモードに設定したら最低でも240msの待ち合わせ後にレジスタからデータを取得する。再度測定するにはもう一度、ワンショットモードに設定レジスタを設定する。測定するときにだけ210μAの電流が流れるがそれ以外のときは2μAまで落ちる。2μAの場合の消費電力は7μWと100分の1になるため測定間隔を調整することで電池稼働時などの稼働時間を長くできる。
- 1SPSモード: 1SPS(1サンプル/秒)モードは1秒間に1回だけAD変換を行うモードである。1秒以上の間隔での測定でよければ、ノーマルモードではなく1SPSモードにすることで46μAの電流しか必要でなくなり消費電力も180μW程度に抑えられる。
I2C用クラス作成
I2Cを使う場合、単にピンの値を取得するのではなく、レジスタの指定や指定バイト数を読み取るなど、I2Cの約束事に合わせたコードが必要になってくる。そこで今後の再利用も考えて、まずはI2C用クラスを作成しておく。
ソリューションエクスプローラーでプロジェクト名を右クリックして(表示されるコンテキストメニューの)[追加]-[クラス]でダイアログを表示して「I2CLib」という名前で新規にクラスファイルをプロジェクトに追加する。クラスファイルには次のコードを記述する。
Imports Microsoft.SPOT.Hardware
Friend Class I2CLib
Implements IDisposable
Private I2c As I2CDevice
Private Timeout As Integer
Private I2cAddr As UShort
Public Sub New(addr As UShort, clockRate As Integer, timeout As Integer)
Me.I2cAddr = addr
Me.Timeout = timeout
Me.I2c = New I2CDevice(New I2CDevice.Configuration(addr, clockRate)) ' ……1
End Sub
Private Sub Write(buffer As Byte())
Dim tx() = New I2CDevice.I2CWriteTransaction() {I2CDevice.CreateWriteTransaction(buffer)}
Dim result = Me.I2c.Execute(tx, Me.Timeout)
' ……中略……
End Sub
Private Sub Read(buffer As Byte())
Dim tx() = New I2CDevice.I2CReadTransaction() {I2CDevice.CreateReadTransaction(buffer)}
Dim result = Me.I2c.Execute(tx, Me.Timeout)
' ……中略……
End Sub
Protected Sub WriteToRegister(register As Byte, value As Byte) ' ……2
Me.Write(New Byte() {register, value})
End Sub
Protected Function ReadToRegister(register As Byte, length As UShort) As Byte() ' ……3
Dim buffer(length - 1) As Byte
Me.Write(New Byte() {register})
Me.Read(buffer)
Return buffer
End Function
#Region "IDisposable Support"
' ……中略……
#End Region
End Class
|
using Microsoft.SPOT.Hardware;
using System;
namespace ADT7410TemperatureCS
{
internal class I2CLib : IDisposable
{
private I2CDevice I2c;
private int Timeout;
private ushort I2cAddr;
public I2CLib(ushort addr, int clockRate, int timeout)
{
this.I2cAddr = addr;
this.Timeout = timeout;
this.I2c = new I2CDevice(new I2CDevice.Configuration(addr, clockRate)); // ……1
}
private void Write(byte[] buffer)
{
var tx = new I2CDevice.I2CWriteTransaction[] { I2CDevice.CreateWriteTransaction(buffer) };
var result = this.I2c.Execute(tx, this.Timeout);
// ……中略……
}
private void Read(byte[] buffer)
{
var tx = new I2CDevice.I2CReadTransaction[] {I2CDevice.CreateReadTransaction(buffer)};
var result = this.I2c.Execute(tx, this.Timeout);
// ……中略……
}
protected void WriteToRegister(byte register, byte value) // ……2
{
this.Write(new Byte[] { register, value });
}
protected byte[] ReadToRegister(byte register, int length) // ……3
{
var buffer = new byte[length];
this.Write(new byte[] { register });
this.Read(buffer);
return buffer;
}
#region "IDisposable Support"
// ……中略……
#endregion
}
}
|
※ここではポイントだけ掲載している。省略されている箇所は、サンプルコードをダウンロードして、該当ファイルを参照してほしい。
- 1I2Cデバイスのアドレスとクロックレートを指定。
- 2レジスタへの出力(I2Cデバイスへの出力)は、レジスタアドレスと値を連続して出力する。
- 3レジスタからの入力(I2Cデバイスからの入力)は、レジスタアドレスを出力後に値を入力する。
ADT7410用クラス作成
上記のI2CLib
クラスをそのまま使ってもよいが、I2CLib
クラスを継承してADT7410Lib
クラスを作ってADT7410特有の部分を隠ぺいしてから使うことにする。具体的には次のようなコードになる。
Private Class ADT7410Lib
Inherits I2CLib
Private Enum Registers As Byte
TEMP_MSB = &H0
TEMP_LSB = &H1
CONFIGRATION = &H3
End Enum
Public Sub New()
MyBase.New(&H48, 100, 500) ' ……1
Me.WriteToRegister(Registers.CONFIGRATION, &H80) '16bitモード指定 ……2
End Sub
Public Function ReadTemperatre() As Single
Dim data() As Byte = Me.ReadToRegister(Registers.TEMP_MSB, 2) ' ……3
Dim val As Long = CType(data(0), Long) * 256 + CType(data(1), Long)
If (val >= 32768) Then
val = val - 65536 '負数計算
End If
Return CType(val, Single) / 128.0F ' ……4
End Function
End Class
|
private class ADT7410Lib : I2CLib
{
private enum Registers : byte
{
TEMP_MSB = 0x0,
TEMP_LSB = 0x1,
CONFIGRATION = 0x3
}
public ADT7410Lib()
: base(0x48, 100, 500) // ……1
{
this.WriteToRegister((byte)Registers.CONFIGRATION, 0x80); //16bitモード指定 ……2
}
public Single ReadTemperature()
{
byte[] data = this.ReadToRegister((byte)Registers.TEMP_MSB, 2); // ……3
long val = (long)data[0] * 256 + (long)data[1];
if (val >= 32768)
{
val = val - 65536; //負数計算
}
return (Single)val / 128.0F; // ……4
}
}
|
Visual BasicはModule1
モジュール内に、C#はProgram
クラス内に、上記のクラスを追記する。
- 1I2Cデバイスのアドレス(0x48)とクロックレート(100kHz)を指定。
- 2設定レジスタに「0x80」を指定して16bitモードに設定。
- 3測定値レジスタ
0x00
と0x01
の2バイトを取得。 - 4「128」で除算し気温を取得。
あとは、Main
メソッドで上記のADT7410Lib
クラスのインスタンスを生成して、ReadTemperatre
メソッドを呼び出して気温の値を取得すればよい。その実装方法は本論ではないので、サンプルコードを参照してほしい。
アプリ実行
以上でサンプルアプリは完成だ。実際に動かしてみると、Visual Studioの[出力]ウィンドウに気温が表示される。出力値を前回のLM61の値と一緒にグラフ化してみる。
ADT7410での測定値の方が±0.1℃の範囲内に揺れが収まっている。
まとめ
I2Cで接続する場合、プログラムコード自体はアナログ入力よりも複雑だが今回のようにI2Cクラスやセンサー固有クラスを作ることでその部分は解消できる。
そうなってくると、I2Cは測定精度もよく値の扱い方もPCなどでの通信に近いものがあるのでなじみやすい。アドレス競合があるので同種センサーを大量に接続するとかは難しいが、複数機器が電源込みで4本の接続線でNetduinoにつながるのは魅力的である。
次回はADT7410で接続したものをブレッドボード上に配置した液晶画面に表示する方法を紹介する。使用する部品としてはAQM0802Aを予定している。
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
2. Netduino、まずは基本のLチカ
初めてのNetduinoアプリ開発。まずは基本の基本であるオンボードのLチカから。次にブレッドボード上の外部LEDのLチカも行う。また、ファームウェアの更新方法も説明する。
3. Netduinoアナログ入力の基本(温度センサー活用)
Lチカができたら、アナログ入力を使ってみよう。温度センサーの値をNetduinoで取得するサンプルを作成し、アナログ入力の基礎を説明する。