I2C (Arduino IDE + ESP ボードマネージャ)

はじめに

教育ボードに搭載されている液晶ディスプレイ (LCD) とリアルタイムクロック (RTC) を使う.

プログラムの書き方

Arduino IDE のWire Library を参照して欲しい.

I2C で LCD や RTC と通信する際は, read や write の引数を通信相手先の都合に合わせる必要がある. そのためには各機器のデータシートを確認せねばならない.

プログラムの分割

教育ボードに搭載された LCD を扱うためのプログラムのソースは長いので, ファイルの分割を行う.

右上の ▼ アイコンを押せば, タブとして新たなファイルが追加される. 新たなタブのファイル名に拡張子を付けずに保存すると, 拡張子は .ino となる. .ino の場合には, #include は特に必要無い. .ino 同士は一緒にコンパイルされる.

プログラムの作成

LCD に最初の 10 秒間 "Hello!! from ESP" と表示させ, その後に時刻を表示させるようにする.

lcd.ino

LCD 用のプログラム. データシートとプログラム中の注釈を参照のこと.

1  /*
2   * LCD AQM0802A-RN-GBW
3   * 
4   * データシート: http://akizukidenshi.com/download/ds/xiamen/AQM0802.pdf
5   */
6 
7  #define lcd_address 0x3e      //LCD AQM0802 の I2C アドレスの指定
8 
9 
10 int lcd_cmd(byte cmd) {
11     Wire.beginTransmission(lcd_address);   //スレーブアドレスの指定. LCDのI2Cアドレス(7ビット) を指定し, LCDとの通信を開始. Wire.beginTransmission関数の引数は7ビットなので引数にRTCのI2Cアドレスを与えれば良い.
12     Wire.write(0x00);                      //コントロールバイトの指定. コマンドを送る1つ送る場合は 0x00. 
13     Wire.write(cmd);                       //コマンド (8bit)
14     return Wire.endTransmission();         //送信終了
15 }
16  
17 int lcd_data(byte data) {
18     Wire.beginTransmission(lcd_address);   //スレーブアドレスの指定. LCDのI2Cアドレス(7ビット)を指定し, RTCとの通信を開始. 
19     Wire.write(0x40);                      //液晶に表示させるデータを 1 つ送る場合は 0x40. 
20     Wire.write(data);                      //データバイト. 英数字の場合はアスキーコードに同じ. 
21     return Wire.endTransmission();         //送信終了
22 }
23 
24 void lcd_clear() {
25     lcd_cmd(0x01);                         //Clear Display
26 }
27  
28 void lcd_home0() {
29     lcd_cmd(0x02);                         //Return Home
30 }
31 
32 void lcd_home1() {
33     lcd_cursor(0,1);                  //2行目の先頭にカーソル移動(詳細はlcd_cursor関数のコメント参照)
34 }
35 
36 void lcd_cursor(int x, int y){
37     //カーソル移動.コマンド8ビットのうち,8ビット目は常に1, 残り7ビットは液晶のDDRAMアドレス. 
38     //DDRアドレスは, 1行目は 0x00 ~ 0x07, 2行目は 0x40~0x47. 
39     //0x80とDDRAMアドレスの OR を取ることでコマンドを生成している.
40     byte ca = (x + y * 0x40) | (0x80); 
41     lcd_cmd(ca);
42 }
43 
44 void lcd_init() {
45     //初期化はデータシートの「初期設定例」をコピー.
46     delay(200);
47     lcd_cmd(0x38); delay(1);        //Function set
48     lcd_cmd(0x39); delay(1);        //Function set
49     lcd_cmd(0x14); delay(1);        //Internal OSC frequency
50     lcd_cmd(0x70); delay(1);        //Control set
51     lcd_cmd(0x56); delay(1);        //Power/ICON/Contrast control
52     lcd_cmd(0x6C); delay(300);      //Follower control
53     lcd_cmd(0x38); delay(1);        //Function set
54     lcd_cmd(0x0C); delay(1);        //Display ON
55     lcd_cmd(0x01); delay(1);        //Clear Display
56 }
57  
57  
58 void lcd_print( String pdata) {
59     //引数で与えられた文字列の表示
60     int n = pdata.length(); 
61     for (int i = 0; i < n; i = i + 1) {
62         lcd_data(pdata.charAt(i));        //英数字を書くためのデータ = ascii 
63     delay(1);
64     }
65 }

rtc.ino

RTC 用のプログラム. データシートとプログラム中の注釈を参照のこと. このプログラム中で初期時刻を指定している.

1  /*
2  * リアルタイムクロック EPSON RX-8035 SA
3  * 
4  * データシート:http://akizukidenshi.com/download/ds/seikoepson/rx-8035_am.pdf
5  * 
6  * 時刻の表示方法 : 
7  */
8 
9  # define rtc_address 0x32   //リアルタイムクロック EPSON RX-8035 の I2C アドレスの指定
10 
11 void rtc_init(){
12   // RTC初期化.
13   Wire.beginTransmission(rtc_address);  //スレーブアドレスの指定. RTCのI2Cアドレス(7ビット)を指定し, RTCとの通信を開始. Wire.beginTransmission関数の引数は7ビットなので引数にRTCのI2Cアドレスを与えれば良い.
14   Wire.write(0xE0);                     //コントロールバイトの指定. 8ビットのうち,上位4ビットは書き込み開始アドレスの指定(レジスタアドレスFhへの書き込み), 下位4ビットは転送モードの指定(0hはwriteモード)を意味する.  データシート参照. 
15   Wire.write(0x00);                     //送信データの指定. レジスタEhの全てのビットをゼロクリアすれば良い. 
16   Wire.write(0x00);                     //送信データの指定. レジスタFhの全てのビットをゼロクリアすれば良い. (レジスタはオートインクリメントされている)
17   Wire.endTransmission();               //送信終了
18   delay(1);
19 }
20 
21 void rtc_set(){
22   // RTC TEST時間書き込み
23   Wire.beginTransmission(rtc_address);  //スレーブアドレスの指定. RTCのI2Cアドレス(7ビット)を指定し, RTCとの通信を開始. Wire.beginTransmission関数の引数は7ビットなので引数にRTCのI2Cアドレスを与えれば良い.
24   Wire.write(0x00);                     //コントロールバイトの指定. 8ビットのうち,上位4ビットは書き込み開始アドレスの指定(レジスタアドレス0hへの書き込み), 下位4ビットは転送モードの指定(0hはwriteモード)を意味する. データシート参照. 
25   Wire.write(0x50);                     //sec. レジスタアドレス0h への書き込み.  
26   Wire.write(0x59);                     //min. レジスタアドレスはオートインクリメントされるので, 書き込み開始レジスタアドレスは 1h となる. 
27   Wire.write(0x23 | 0x80);              //hour. レジスタアドレスはオートインクリメントされるので, 書き込み開始レジスタアドレス 2h となる.  //24時間モードにするために8ビット目を1にする必要があるため, 0x80 との OR を取る.
28   Wire.write(0x01);                     //week //今は使わないので値は何でも良い. 1=月曜.
29   Wire.write(0x31);                     //day. レジスタアドレスはオートインクリメントされるので, 書き込み開始レジスタアドレス 4h となる. 
30   Wire.write(0x12);                     //month. レジスタアドレスはオートインクリメントされるので, 書き込み開始レジスタアドレス 5h となる. 
31   Wire.write(0x19);                     //year. レジスタアドレスはオートインクリメントされるので, 書き込み開始レジスタアドレス 6h となる. 
32   Wire.endTransmission();               //送信終了
33   delay(1);
34 
35   // RTC初期化. 時刻設定のあとに PON のビットを0にする必要がある.
36   Wire.beginTransmission(rtc_address);  //スレーブアドレスの指定. RTCのI2Cアドレスを指定し, RTCと通信開始を宣言. 
37   Wire.write(0xF0);                     //コントロールバイトの指定. レジスタアドレスFhへの書き込みを宣言.  
38   Wire.write(0x00);                     // PON Clear. 全部のビットをゼロにしておくで問題ない. 
39   Wire.endTransmission();               //送信終了
40   delay(1);
41 }
42 
43 void rtc_get(struct tm *tt){         //引数 tt は時刻を保管するのに適当な構造体.
44   Wire.requestFrom(rtc_address, 8);  //スレーブアドレスの指定と読み込みバイト数. RTCのアドレスを指定し, RTCから8バイト分を読み込むことを宣言. レジスタの先頭アドレスから読み込む場合はこの関数で十分 (= コントロールバイトの指定は不要) のようだ. 
45   Wire.read();                       //1バイト目は必要ないので捨てる. 理由を理解できてない....
46   tt->tm_sec = Wire.read();          //2バイト目 (レジスタアドレス0h)は秒. 
47   tt->tm_min = Wire.read();          //3バイト目 (レジスタアドレス1h)は分. 
48   tt->tm_hour = Wire.read() & 0x3f;  //4バイト目 (レジスタアドレス2h)は時. 時を表すのに使うのは6ビット分なので, 0x3fとANDを取れば必要なビット分だけ得られる (8ビット目は 24時間モードか12時間モードを表す) 
49   Wire.read();                       //5バイト目 (レジスタアドレス3h)は曜日. 0は日曜, 6 は土曜. 必要ないので捨てる.
50   tt->tm_mday = Wire.read();         //6バイト目 (レジスタアドレス4h)は日.
51   tt->tm_mon = Wire.read();          //7バイト目 (レジスタアドレス5h)は月.
52   tt->tm_year = Wire.read();         //8バイト目 (レジスタアドレス6h)は年-2000
53 }

メイン関数

1  #include<Wire.h>
2 
3  const int PIN_SDA = 21;  //I2CのGPIO(SDA)ピン
4  const int PIN_SCL = 22;  //I2CのGPIO(SCL)ピン
5  struct tm tt;            //時刻保管用の構造体 
6  char Time0[10];
7  char Time1[10];
8     
9  void setup() {
10  //I2C初期化
11   Wire.begin(PIN_SDA, PIN_SCL);
12 
13   //LCD 初期化 
14   lcd_init();
15 
16   // 1 行目にカーソル移動 (1文字 字下げ)
17   lcd_cursor(1, 0);
18   // 1 行目の先頭に移動
19   //lcd_home0();
20 
21   // 1 行目に表示
22   lcd_print("Hello!!");
23 
24   // 2 行目にカーソル移動 (2文字 字下げ)
25   //lcd_cursor(2, 1);
26   // 2 行目の先頭に移動
27   lcd_home1();
28     
29   // 2 行目に表示
30   lcd_print("from ESP");
31 
32   //10秒待つ 
33   delay(10000);
34 
35   //RTC 初期化
36   rtc_set();
37   rtc_set();
38 }
39 
40 void loop() {
41   //RTCより時刻取得
42   rtc_get(&tt);
43 
44   //時刻を文字列に
45   sprintf(Time0, "%02x-%02x-%02x", tt.tm_year, tt.tm_mon, tt.tm_mday);
46   sprintf(Time1, "%02x:%02x:%02x", tt.tm_hour, tt.tm_min, tt.tm_sec); 
47 
48   //時刻表示
49   lcd_home0();
50   lcd_print( Time0 );
51   lcd_home1();
52   lcd_print( Time1 );
53   
54   //1秒待つ 
55   delay(1000);
56 }

課題

スイッチの ON (HIGH) と OFF (LOW) を液晶ディスプレイ (LCD) に表示しなさい. 以下のように LCD に表示すること.

  • すべてのスイッチが OFF の時

    SW:1234
       LLLL
  • スイッチ 1 のみ ON の時

    SW:1234
       HLLL
  • スイッチ 1, 3, 4 が ON の時

    SW:1234
       HLHH