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