.NET対応組み込みデバイス「Netduino」入門(5)
Netduinoシリアル通信(I2C)でLCD表示
シリアル通信(I2C)をさらに学ぼう。I2C通信で液晶ディスプレイ(LCD)に文字を出力するサンプルを作成する。
前回は、温度センサーからI2C通信で温度を取得する方法を取り上げた。今回は、I2C通信で液晶ディスプレイ(LCD)に文字を出力する方法を取り上げる。前回と組み合わせれば、温度センサーの値をLCDに表示することも可能になる。
AQM0802Aモジュールについて
今回のサンプルでは、I2C対応機器としてAQM0802A-RN-GBW液晶+ブレッドボード用ピッチ変換基盤というLCDモジュール(図1)を使用する。
このAQM0802Aモジュールは、LCDとピッチ変換基盤の間で9本の足のはんだ付けが必要で、さらに、ピッチ変換基盤に5本足のはんだ付けが必要だ。
はんだ付けを少しでも減らしたいのであれば、AQM0802Aとピッチ変換基盤の間がはんだ付け済みで、あとは5本足のはんだ付けだけでよいLCDモジュールというのもある。今回の記事とはピン配置が若干違うが、同じプログラムで動作するので、費用と手間とのバランスでどちらかを選択するのがいいだろう。
Netduino+AQM0802Aモジュールのハード構成
AQM0802Aモジュールの物理仕様
AQM0802Aモジュールには5つの足と2つのジャンパーがある(図2)。
ジャンパーをそれぞれはんだ付けすることで、SCLやSDAのプルアップ抵抗を有効にできるが、別途、ブレッドボード上にプルアップ抵抗を設置するのではんだ付けは行わない。
今回使用したピッチ変換基盤は、ブレッドボードのピン間隔に合わせるだけではなく、+5VとGND間に挟むパスコン、VOUT、CAP1N、CAP1Pに必要なコンデンサーなども基板上に組み込んである。よって、Netduinoとの間に新たにコンデンサーを挟み込む必要はない。
AQM0802Aモジュール接続回路図
I2Cはシリアル通信なので、流れるデータはOnとOffのビットデータとなる。Onのときには5V、Offのときには0Vになる。Netduinoで使用するときはプルアップ抵抗を入れることで、OnとOffの電圧値がより安定する。
AQM0802Aモジュール自体でもプルアップ抵抗を有効にできるが、ジャンパーをはんだ付けしてしまうとプルアップ抵抗が不要なときに対処できないので、ADT7410モジュールの外側で明示的にプルアップ抵抗を回路に組み込むことにした。
プルアップ抵抗値は10kΩを使用する。AQM0802Aの3番ピン(=SCL
)と4番ピン(=SDA
)を、Netduinoを接続するラインから分岐するように抵抗を挟んで+5V
ラインに接続する。
ブレッドボード実装
回路図(図3)を基にしてブレッドボードに部品を配置する(図4)。
Netduinoからの5V
ラインは赤いラインを通ってブレッドボードの一番下のプラス電源ライン、GND
からの線も同様に青いラインを通って下から二番目のGNDラインに接続する。
A11
とE11
は内部的に接続されているので、A11
をプラス電源ラインに接続することで、AQM0802AモジュールのVDD
に+5Vを供給する。同様にAQM0802AモジュールのRESET
も+5Vに接続する。GND
もGNDラインからA15
、そしてE15
への内部結線を経由してAQM0802AモジュールのGND
に接続する。
I2C特有のSCL
とSDA
の2つの信号線は、SCLがB13
、SDAがC14
でNetduinoに接続している。また、プルアップ用の10KΩ抵抗がA13
とA14
からプラス電源ラインの間を接続している。
実際に接続すると、ブレッドボード上のAQM0802Aモジュールは次のようになる。
Netduino+AQM0802Aモジュールのプログラミング
回路が完成したら、アプリのプログラミングを始める。
最初に行う手順は、Visual Studioの[新しいプロジェクト]ダイアログで[Micro Framework]カテゴリの[Netduino Plus 2 Application]テンプレートを選択して、新規にプロジェクトを作成することだ(本稿の例では、プロジェクト名はVB.NET用は「AQM0802LcdVB」、C#用は「AQM0802LcdCS」とした)。
送信データの理解
AQM0802Aモジュールは、液晶表示のコントローラーとしてST7032iというLCDコントロールICを使用している。
ST7032iでは、ST7032i側への書き込みはできるが読み出しができず、ビジーフラグの確認もできない。従って書き込み後は、ST7032データシートPDF(英語)に記載された待ち時間以上の時間が経過するのを待って、次の書き込みを行う必要がある。
送信データのフォーマットは、データシートに全て記載があるが、かなり読み解きづらい。
まずI2Cスレーブアドレスが「0x7c(2進数で1111 1100)」とあるが、これはRWビットという内部的に使われる1bitを先頭に含んでいるので、プログラムとして指定するアドレスは残り7bitの「0x3c(2進数で111 1110)」となる(余談となるが筆者は、このアドレス指定の差にはまって、画面に表示できるまでかなり時間がかかってしまった)。
ST7032iではコマンドとデータという概念があり、その識別はRSビットにより区別する。また書き込みしかできないのにRWビットという常に「0」を指定するRead(読み込み)/Write(書き込み)区別ビットがある。データシートには、「この2bit(=RSビットとRWビット)+8bit」でさまざまなコマンドの説明がされている(図6)。
そのため「10bitデータが基本か」と思ったら、「1byte(=データ連続送信用のCOビット+RSビット+RWビットを含んだ制御バイト)+1byte(=8bit)」の2bytesデータが基本となっているので、注意してほしい。
実際の送信では、この2bytesに先立ってスレーブアドレスを指定することになるが、このあたりは.NET Micro FrameworkのI2Cライブラリが適切に処理してくれるので、プログラムを組みときにはあまり意識しなくてよい。その他にもデータシートにあるSTARTビットやSTOPビット、ACK受信などについても、プログラムコードとしては登場しない要素なので、どのように処理するかを気にしないでよい。
1. コマンド送信
コマンド送信は、Coビット=0、RSビット=0、RWビット=0の制御バイト(1byte)とコマンド(1byte)からなる。つまり「0x00+コマンドバイト」だ(図7)。
コマンド送信が必要な局面の大半は初期化コマンドだ。必要な初期化コマンドについてもデータシートに記載がある。固定で次のような初期化コマンドを送るといいだろう。
- 0x38: 画面サイズ指定
- 0x39: 拡張コマンド設定
- 0x14: Internal OSC Frequency
- 0x70:コントラスト
- 0x52: 5V指定
- 0x6c: Followerコントロール
- 0x38: 拡張コマンド設定完了
- 0x0c: 表示オン
- 0x06: Entry mode set
- 0x01: 画面クリア
2. データ送信(継続あり)
コマンドではなく文字コードをデータとして連続して送信したいときは1文字ずつに分解して、1文字ごとに制御バイトを付けた2bytesデータ(図8)にして送信する。このとき、最終文字以外についてはCoビットを「1」とする。RSビットも「1」となるので、データフォーマットは「0xC0+表示文字コード」になる。NetduinoのI2Cライブラリ的には、1つのI2CWriteTransactionに対して連続して2bytesデータを送信する流れになる。
3. データ送信(継続なし)
1文字しか送信しない場合、もしくは文字列の最後の1文字を送信するときはCoビットを「0」に設定する。RSビットは「1」となるので、データフォーマットは「0x40+表示文字コード」になる。
表示文字コード
表示文字コードもデータシートに記載がある。
0x20~0x7dに割り当てられた英数字はUTF-8コードと同じである。よって、英数字およびほとんどの記号はUTF-8のバイト文字として送信できる。その他については変換テーブルなどを作って対応する。
それでは以上を踏まえてプログラムを作成していこう。
I2C用クラス作成
前回作成したI2C用クラスファイル(Visual Basicなら「I2CLib.vb」ファイル、C#なら「I2CLib.cs」ファイル)を、今回のプロジェクトに取り込む。
ソリューションエクスプローラーでプロジェクト名を右クリックして(表示されるコンテキストメニューの)[追加]-[既存の項目]でダイアログを表示して「I2CLib」クラスファイルをプロジェクトに追加する。
AQM0802用クラス作成
I2CLib
クラスを継承してAQM0802Lib
クラスを作ってAQM0802A特有の部分を隠ぺいしてから使う。AQM0802Lib
クラスは次のようなコードになる。
Private Class AQM0802Lib
Inherits I2CLib
Private Const LCD_CLEARDISPLAY As Byte = &H1
' ……中略……
Private Encoding As System.Text.Encoding = System.Text.Encoding.UTF8 ' ……1
Public Sub New()
MyBase.New(&H3E, 100, 500) ' ……2
Thread.Sleep(40) ' 40ms待つ
' 画面サイズ指定
Me.WriteCommand(LCD_FUNCTIONSET Or LCD_8BITMODE Or LCD_2LINE) ' ……3
' 拡張コマンド設定開始
Me.WriteCommand(LCD_FUNCTIONSET Or LCD_8BITMODE Or LCD_2LINE Or ISBit) ' ……4
' AQM0802用固定設定
Me.WriteCommand(&H14) ' Internal OSC Frequency
Me.WriteCommand(&H70) ' Contrast set
Me.WriteCommand(&H52) ' Power/ICON/Contrast control(5V用) ' ……5
Me.WriteCommand(&H6C) ' Follower control
' 拡張コマンド設定終了
Thread.Sleep(200) ' ……6
Me.WriteCommand(LCD_FUNCTIONSET Or LCD_8BITMODE Or LCD_2LINE)
'
Me.WriteCommand(LCD_DISPLAYCONTROL Or LCD_DISPLAYON) ' DISPLAY ON
Me.WriteCommand(&H6) ' Entry mode set
Me.ClearDisplay()
End Sub
Private Sub WriteCommand(value As Byte)
Me.WriteToRegister(LCD_Command, value)
Thread.Sleep(1) ' ……7
End Sub
Public Sub ClearDisplay()
Me.WriteCommand(LCD_CLEARDISPLAY) ' DISPLAY CLRAR
Thread.Sleep(2) ' ……8
End Sub
Public Sub Locate(x As Byte, y As Byte)
Me.WriteCommand(LCD_SETDDRAMADDR Or CType(CursorOffset, Byte) * y + x) ' ……9
End Sub
Public Sub WriteMessage(msg As String)
Dim byteArray() As Byte = Encoding.GetBytes(msg) ' ……10
For index As Integer = 0 To msg.Length - 2
Me.WriteToRegister(LCD_DataContinue, byteArray(index)) ' ……11
Next
Me.WriteToRegister(LCD_DataLast, byteArray(msg.Length - 1)) ' ……12
Thread.Sleep(1)
End Sub
End Class
|
private class AQM0802Lib : I2CLib
{
private const byte LCD_CLEARDISPLAY = 0x1;
// ……中略……
private System.Text.Encoding Encoding = System.Text.Encoding.UTF8; // ……1
public AQM0802Lib()
: base(0x3e, 100, 500) // ……2
{
Thread.Sleep(40); // 40ms待つ
// 画面サイズ指定
this.WriteCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE); // ……3
// 拡張コマンド設定開始
this.WriteCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | ISBit); // ……4
// AQM0802用固定設定
this.WriteCommand(0x14); // Internal OSC Frequency
this.WriteCommand(0x70); // Contrast set
this.WriteCommand(0x52); // Power/ICON/Contrast control(5V用) // ……5
this.WriteCommand(0x6C); // Follower control
// 拡張コマンド設定終了
Thread.Sleep(200); // ……6
this.WriteCommand(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE);
//
this.WriteCommand(LCD_DISPLAYCONTROL | LCD_DISPLAYON); // DISPLAY ON
this.WriteCommand(0x6); // Entry mode set
this.ClearDisplay();
}
private void WriteCommand(byte value)
{
this.WriteToRegister(LCD_Command, value);
Thread.Sleep(1); // ……7
}
public void ClearDisplay()
{
this.WriteCommand(LCD_CLEARDISPLAY); // DISPLAY CLRAR
Thread.Sleep(2); // ……8
}
public void Locate(byte x, byte y)
{
this.WriteCommand((byte)(LCD_SETDDRAMADDR + CursorOffset * y + x)); // ……9
}
public void WriteMessage(string msg)
{
byte[] byteArray = Encoding.GetBytes(msg); // ……10
for (int index = 0; index <= msg.Length - 2; index++)
{
this.WriteToRegister(LCD_DataContinue, byteArray[index]); // ……11
}
this.WriteToRegister(LCD_DataLast, byteArray[msg.Length - 1]); // ……12
Thread.Sleep(1);
}
}
|
Visual BasicはModule1
モジュール内に、C#はProgram
クラス内に、上記のクラスを追記する。
- 1文字列をバイト配列に変換する。
- 2I2Cスレーブアドレスとして「0x3e」を指定。
- 32行表示指定(=
LCD_2LINE
)で標準モード(=LCD_FUNCTIONSET
+LCD_8BITMODE
)を設定。これは「0x38(2進数で0011 1000)」となり、「画面サイズ指定」コマンドを意味する。図6で示した表の「Function Set」という行を読めば意味が分かる。 - 4標準モード+IS(Instruction Select: 拡張コマンド選択)ビット(=
ISBit
)Onで、拡張モード指定の開始を指示(=「0x39(2進数で0011 1001)」となり、「拡張コマンド設定」コマンドを意味する)。なお、IS(拡張コマンド選択)で指定できる拡張コマンドについては、ST7032データシートPDF(英語)の「IS : normal/extension instruction select」という節の中を参照してほしい。 - 5データシートでは3.3V用に「0x56」になっているが、「0x04」(=ブースター回路On/Off)を「0」(off)にすることで、入力電圧5Vをそのまま使用する。
- 6Follower Controlの設定には「200ms」以上待つ。
- 7コマンドの設定には26.3μs以上必要だが、.NET Micro Frameworkの待ち合わせ時間指定は1ms単位なので切り上げて「1ms」待つ。
- 8画面クリアの処理時間として「2ms」待ち。
- 9画面上の文字の表示位置を先頭に指定。
- 10文字列をバイト配列に変換。
- 11文字列の最後の2文字までを継続データ用の制御データ(=
LCD_DataContinue
)を付与して1文字ずつ送信。 - 12文字列の最後の1文字は、最終データ用の制御データ(=
LCD_DataLast
)を付与して送信。
あとは、Main
メソッドで上記のAQM0802Lib
クラスのインスタンスを生成して、Locate
メソッドで文字の表示位置を指定して、WriteMessage
メソッドを呼び出して文字列でメッセージを書き込めばよい(本稿のサンプルでは、1行目に「Hello」、2行目に「現在の時間」を書き込んでいる)。その実装方法は本論ではないので、サンプルコードを参照してほしい。
アプリ実行
アプリを実行すると1秒ごとに起動してからの時間を更新表示する。
【動画】Netduino plus 2+LCD
まとめ
NetduinoからLCDが使えるようになると、Debug.Print
メソッドを使ってVisual Studioの[出力]ウィンドウで動作を確認しなくてもよくなる。NetduinoはVisual Studioと接続しなくても動作するので、動作状況や測定結果などをLCDに表示できるようにすると、PCと接続せずに動作できるようになる。
次回は、前回の温度センサーと今回のLCDを同時に使用して。複数の機器をI2Cで接続する方法を取り上げる。
※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
3. Netduinoアナログ入力の基本(温度センサー活用)
Lチカができたら、アナログ入力を使ってみよう。温度センサーの値をNetduinoで取得するサンプルを作成し、アナログ入力の基礎を説明する。
6. Netduinoシリアル通信(I2C)で複数機器接続
シリアル通信(I2C)で2つ以上の機器を同時に使用するサンプルを作成する。温度センサーから室温を取得して液晶ディスプレイ(LCD)にリアルタイム表示してみよう。