I2C (Arduino IDE + ESP ボードマネージャ)
はじめに
教育ボードに搭載されている液晶ディスプレイ (LCD) とリアルタイムクロック (RTC) を使う.
プログラムの書き方
Arduino IDE のWire Library を参照して欲しい.
I2C で LCD や RTC と通信する際は, read や write の引数を通信相手先の都合に合わせる必要がある. そのためには各機器のデータシートを確認せねばならない.
- 液晶ディスプレイ(LCD) AQM0802A-RN-GBW
- リアルタイムクロック (RTC) RC-8035SA 日本語, 英語
プログラムの分割
教育ボードに搭載された 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