I2C (ESP-IDF 環境 + C 言語)

はじめに

教育ボードに搭載されているリアルタイムクロック (RTC) を使う.

プログラムの書き方.

I2C のプログラムの書き方は, ESP-IDF Programming GuideAPI ReferenceI2C Driver を参照して欲しい.

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

I2C のテスト

全部のセンサーについてアドレスの取得を確認.

$ cp -r esp-idf/examples/peripherals/i2c/i2c_tools ./

$ cd i2c_tools

$ make menuconfig

$ make

$ make flash monitor   (Ctrl-] で終了)

  esp32> i2cconfig --port=0 --sda=21 --scl=22 --freq=100000

  esp32> i2cdetect
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- 32 -- -- -- -- -- -- -- -- -- -- -- 3e -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

出力結果より, 0x3e (LCD) と 0x30 (RTC) が接続されていることがわかる.

プログラムの作成

標準出力に 1 秒間隔で時刻を表示するプログラムを作成する.

準備

まずは適当な資源一式のディレクトリをコピーして名前を変えておく. ここでは hello_world のディクトリをコピーすることにした.

$ cp -r hello_world i2c

$ cd i2c

$ mv main/hello_world_main.c main/i2c_main.c

プログラム (i2c_main.c) の作成

RTC に時刻を設定し,時間を標準出力に出力するようにしてみる.以下のサンプルは <URL:i2c-rtc.c> でダウンロードできる.

1  #include <stdio.h>
2  #include <time.h>
3  #include "driver/i2c.h"
4  #include "esp_err.h"
5  #include "esp_log.h"
6  #include "freertos/task.h"
7  
8  #define SDA_PIN GPIO_NUM_21 
9  #define SCL_PIN GPIO_NUM_22
10 #define rtc_address 0x32     //I2Cアドレスの指定
11 
12 //I2C初期化
13 void i2c_init(){
14   i2c_port_t port = 0;
15   uint32_t speed = 400 * 1000;  //スピードはデフォルト値
16   
17   i2c_config_t config = {
18     .mode = I2C_MODE_MASTER,  //マスターの設定
19     .scl_io_num = SCL_PIN,    //SCLのGPIO番号の設定
20     .sda_io_num = SDA_PIN,    //SDAのGPIO番号の設定
21     .scl_pullup_en = true,    //プルアップする
22     .sda_pullup_en = true,    //プルアップする
23     .master.clk_speed = speed,  //スピードの設定
24   };
25    
26   i2c_param_config(port, &config); //configの読み込み
27   i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0);
28 }
29 
30 //RTC初期化
31 void rtc2_init(){
32   i2c_port_t port = 0;
33   i2c_cmd_handle_t cmd = i2c_cmd_link_create();  
34   
35   i2c_master_start(cmd);
36   i2c_master_write_byte(cmd, rtc_address << 1 | I2C_MASTER_WRITE, I2C_MASTER_ACK);  //上位 7 ビットが I2C アドレス, 下位 1 ビットが書き込み (I2C_MASTER_WRITE) か読み込み (I2C_MASTER_READ) を示す.
37   i2c_master_write_byte(cmd, 0xE0, I2C_MASTER_ACK); //アドレス Eh の指定. 前半4bitがE、後半4bitが0なので、0xE0
38   i2c_master_write_byte(cmd, 0x00, I2C_MASTER_ACK); //アドレス Eh への書き込み
39   i2c_master_write_byte(cmd, 0x00, I2C_MASTER_ACK); //自動インクリメントなので、Fh への書き込み
40   i2c_master_stop(cmd);
41   i2c_master_cmd_begin(port, cmd, 1 / portTICK_RATE_MS);
42   i2c_cmd_link_delete(cmd);
43   
44   vTaskDelay(100/ portTICK_PERIOD_MS); //0.1秒待つ
45 }
46 
47 //時刻セット. 
48 void rtc2_set(){
49   i2c_port_t port = 0;
50   i2c_cmd_handle_t cmd = i2c_cmd_link_create();  
51 
52   //時刻を設定する。アドレス 0h から順に秒・分・時・week・日・月・年(下2桁)を入れる
53   //ここで,2020-11-30 23:59:30 にセットせよ.但し,week は 1 にセットすれば良い.
54   //
55   // ............
56   // 
57 
58   vTaskDelay(100/ portTICK_PERIOD_MS);  
59 
60   //書き込み後に、アドレスFhをゼロクリアする
61   // 
62   // ............
63   // 
64 
65   vTaskDelay(100/ portTICK_PERIOD_MS);  
66 }
67 
68 
69 //時刻の呼び出し. "...." の部分は自分で直すこと.
70 void rtc2_get(struct tm *tt){
71   uint8_t data_rd[8];
72   i2c_port_t port = 0;
73 
74   i2c_cmd_handle_t  cmd = i2c_cmd_link_create();
75   i2c_master_start(cmd); 
76   i2c_master_write_byte(cmd, ..............., I2C_MASTER_ACK);  //読み込みは? rtc2_init を参照.
77   i2c_master_read_byte(cmd, &data_rd[0], I2C_MASTER_ACK); //1byte目はackあり
78   i2c_master_read_byte(cmd, &data_rd[1], I2C_MASTER_ACK); //2byte目はackあり
79   i2c_master_read_byte(cmd, &data_rd[2], I2C_MASTER_ACK); //3byte目はackあり
80   i2c_master_read_byte(cmd, &data_rd[3], I2C_MASTER_ACK); //4byte目はackあり
81   i2c_master_read_byte(cmd, &data_rd[4], I2C_MASTER_ACK); //5byte目はackあり
82   i2c_master_read_byte(cmd, &data_rd[5], I2C_MASTER_ACK); //6byte目はackあり
83   i2c_master_read_byte(cmd, &data_rd[6], I2C_MASTER_ACK); //7byte目はackあり
84   i2c_master_read_byte(cmd, &data_rd[7], I2C_MASTER_NACK); //読み取りの最後は NACK
85   i2c_master_stop(cmd);
86   i2c_master_cmd_begin(port, cmd, 1 / portTICK_RATE_MS);
87   i2c_cmd_link_delete(cmd);
88 
89   tt->tm_year = data_rd[7];
90   tt->tm_mon  = data_rd[6];
91   tt->tm_mday = data_rd[5];
92   tt->tm_hour = ......... ;  //24時間モードの場合は?
93   tt->tm_min  = data_rd[2];
94   tt->tm_sec  = data_rd[1];
95 }
96 
97 //メインプログラム
98 void app_main() {
99
100   struct tm tt;  //時刻を入れる構造体
101
102   //I2C初期化
103   i2c_init();
104
105   //RTC初期化
106   rtc2_init();
107 
108   //時刻の設定. この関数を自分で作ること.
109   rtc2_set();
110 
111   while(1){
112     rtc2_get( &tt ); //時刻取得. 要修正.
113  
114     printf("20%02x-%02x-%02x   ", tt.tm_year, tt.tm_mon, tt.tm_mday);
115     printf("%02x:%02x:%02x \n", tt.tm_hour, tt.tm_min, tt.tm_sec);
116 
117     vTaskDelay(1000 / portTICK_PERIOD_MS);
118   }
119 
120 }

コンパイルと実行

$ make

$ make flash monitor

課題

上記プログラムを作成せよ.余裕があれば,時刻に連動して LED を点灯させたり,LCD に時刻を表示させてみよ.