はじめに
ESP32をI2C通信のスレーブとして動作させるための良いサンプルコードを見つけることができかったため作成しました。
espressifのgithubのコードをベースにして、シンプルなコードに変更したものです。
※espressifのgithubのコードはESP-IDF(≠Arduino)ですので、その環境のお話となります(Arduinoでは使用できないサンプルコードです)
使用したもの
サンプルコード概仕様
- I2CマスターとI2Cスレーブ用に1つずつタスクを動作させる
- I2Cスレーブ用タスク(i2c_test_task_0):2秒間隔でI2C送信を行う
- I2Cマスター用タスク(i2c_test_task_1):1秒間隔でスレーブ用タスクからI2C受信を行う
※ I2C送信するデータの先頭に0x55(SYNC_BYTE)の1バイトを付けています(スレーブがデータ更新したことを判定するため)
ESP32の結線
下記のように結線します。
SDA | SCL | |
---|---|---|
I2C Master | GPIO18 | GPIO19 |
I2C Slave | GPIO25 | GPIO26 |
コード
変更概要
espressifのサンプルプロジェクトのi2c_example_main.cに対して以下の変更を加えます。
- タスク用関数i2c_test_task():削除
- i2c_master_task()とi2c_master_task():新規作成(i2c_test_taskの代わりにタスク用関数として作成する)
- app_main():上記タスク用関数の変更に伴い変更
変更後コード(上記の「変更概要」の部分のみ記載)
# define DELAY_TIME_MASTER_MS 1000
# define DELAY_TIME_SLAVE_MS 2000
# define SYNC_BYTE 0x55
static void i2c_master_task(void *arg)
{
int ret;
uint32_t task_idx = (uint32_t)arg;
uint8_t *data_rd = (uint8_t *)malloc(DATA_LENGTH);
int cnt = 0;
while (1) {
ESP_LOGI(TAG, "TASK[%d](master) test cnt: %d", task_idx, cnt++);
//---------------------------------------------------
xSemaphoreTake(print_mux, portMAX_DELAY);
ret = ESP_OK;
while(ret == ESP_OK) {
ret = i2c_master_read_slave(I2C_MASTER_NUM, data_rd, RW_TEST_LENGTH);
if (ret == ESP_ERR_TIMEOUT) {
ESP_LOGE(TAG, "I2C Timeout");
} else if (ret == ESP_OK) {
if (data_rd[0] == SYNC_BYTE) {
printf("*******************\n");
printf("TASK[%d] MASTER READ FROM SLAVE\n", task_idx);
printf("*******************\n");
printf("====TASK[%d] Master read ====\n", task_idx);
disp_buf(data_rd, RW_TEST_LENGTH);
}
else {
printf("*******************\n");
printf("TASK[%d] MASTER READ FROM SLAVE\n", task_idx);
printf("*******************\n");
printf("NO DATA\n\n");
ret = ESP_FAIL;
}
} else {
ESP_LOGW(TAG, "TASK[%d] %s: Master read slave error, IO not connected...\n",
task_idx, esp_err_to_name(ret));
}
}
xSemaphoreGive(print_mux);
vTaskDelay((DELAY_TIME_MASTER_MS * (task_idx + 1)) / portTICK_RATE_MS);
}
vSemaphoreDelete(print_mux);
vTaskDelete(NULL);
}
static void i2c_slave_task(void *arg)
{
int i = 0;
uint32_t task_idx = (uint32_t)arg;
uint8_t *data = (uint8_t *)malloc(DATA_LENGTH);
int cnt = 0;
while (1) {
ESP_LOGI(TAG, "TASK[%d](slave) test cnt: %d", task_idx, cnt++);
//---------------------------------------------------
data[0] = SYNC_BYTE;
for (i = 1; i < DATA_LENGTH; i++) {
data[i] = i;
}
xSemaphoreTake(print_mux, portMAX_DELAY);
size_t d_size = i2c_slave_write_buffer(I2C_SLAVE_NUM, data, RW_TEST_LENGTH, 1000 / portTICK_RATE_MS);
if (d_size == 0) {
ESP_LOGW(TAG, "i2c slave tx buffer full");
}
else {
printf("slave send size: %d\n", d_size);
}
printf("*******************\n");
printf("TASK[%d] SLAVE SEND TO MASTER\n", task_idx);
printf("*******************\n");
disp_buf(data, d_size);
xSemaphoreGive(print_mux);
vTaskDelay((DELAY_TIME_SLAVE_MS * (task_idx + 1)) / portTICK_RATE_MS);
}
vSemaphoreDelete(print_mux);
vTaskDelete(NULL);
}
void app_main()
{
print_mux = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(i2c_slave_init());
ESP_ERROR_CHECK(i2c_master_init());
xTaskCreate(i2c_slave_task, "i2c_test_task_0", 1024 * 2, (void *)0, 10, NULL);
xTaskCreate(i2c_master_task, "i2c_test_task_1", 1024 * 2, (void *)1, 10, NULL);
}
終わりに
上記のサンプルコードではI2Cスレーブは送信のみでしたが、i2c_slave_read_buffer()といった関数を使用することで、I2Cスレーブで受信することもできます。
また、I2Cスレーブの送信ということは過去の記事のヌンチャク側と同じです・・・ということは、このESP32でヌンチャクと同じようにI2C通信で振る舞うことができれば、某ゲーム機のヌンチャクとして使用することができそうです(ΦωΦ)フフフ…
見ていただいてありがとうございました。
тнайк чoμ_〆(・ω・。)
おまけ
I2Cスレーブの送信を割込み方式で出来たらいいなーというお話
今回作ったサンプルコードはベースコードと同じくポーリングの方式でI2Cの送信を行っていますが、その部分は使いにくく感じます。
そこで、ArduinoのWire.hの割込みハンドラ登録のように使うことができないかと思って少し調べると、「関数i2c_isr_registerを使ったら良いよ」という記事を所々で見つけました。
しかし、i2c_driver_installという初期設定を行う関数内でi2c_isr_handler_defaultというデフォルトのハンドラが既に登録されていたりして、「みだりに登録していいもんじゃないしなー」と少し調べる必要を感じつつも、その手間を現在放棄中です・・・。
更新履歴
- 2019-04-01:新規作成