安価なToF測距モジュールMTOF171000C0と安価なWi-FiモジュールESP-WROOM-02でI2C通信

## 目的
ある物体までの距離が変化したことを検知して物体の状態を把握できる小型IoTデバイスを作りたいと思い、これを実現するためToF測距モジュールMTOF171000C0とWi-FiモジュールESP-WROOM-02を使用してI2C通信を行います。
MTOF171000C0は安価かつ測距範囲が2cm～120cmのため近距離の物体検知に向いています。
ESP-WROOM-02は新モデルのESP32-WROOM-32よりも機能が限られますが、やはり安価で手に入るのでコスト面で優秀です。

## 準備
今回はESP8266 RTOS SDKと用意されているサンプルプログラムを使用します。下記公式ページを参考に開発環境を導入します。

- [ESP8266 RTOS SDK Programming Guide](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/get-started/index.html)

ESP-WROOM-02にプログラムを書き込むための回路は下記ページを参考にブレッドボード上で製作しました。
[秋月電子のDIP化キット](http://akizukidenshi.com/catalog/g/gK-09758/)を使用しています。

- [ESP-WROOM-02のライターを作る - キノコの自省録](http://kinokorori.hatenablog.com/entry/2017/09/06/000000)
- [8か月以上の長期間動作を確認！ 超省エネIoTセンサESP32-WROOM-32 | ボクにもわかる電子工作のブログ](https://bokunimo.net/blog/esp/21/)

上記回路はIO0をオープンにするだけでそのまま動作確認用の回路になります。
MTOF171000C0の接続方法は[アプリケーションノート](http://www.socle-tech.com/doc/IC%20Channel%20Product/ToF%20Module%20MTOF171000C0%20Application%20Notes.pdf)を参考に下記の通り接続します。
アプリケーションノートではモジュール選択ピンとしてRXDが指定されていますが、今回はモジュールを一つしか接続しないのでGNDに接続でOKです。

| MTOF171000C0 | ESP-WROOM-02 |
|:-:|:-:|
| VDD（赤） | 3V3 |
| GND（黒） | GND |
| TXD（白） | GND |
| RXD（茶） | GND |
| SCL（緑） | IO14 |
| SDA（黃） | IO2 |

完成形です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305999/21987e4c-ba4f-3410-e775-0acc43187eed.png)

## プログラム
SDK内に用意されているサンプルを修正していきます。
下記コマンドを実行してI2C通信のサンプルをコピーします。

```
$ cd ~/esp
$ cp -r $IDF_PATH/examples/peripherals/i2c .
```

i2c/main/user_main.cを開いて修正します。
以下、ソースコード全文です。

```c:user_main.c
/* I2C example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "esp_log.h"
#include "esp_system.h"
#include "esp_err.h"

#include "driver/i2c.h"

static const char *TAG = "main";

/**
 * TEST CODE BRIEF
 *
 * This example will show you how to use I2C module by running two tasks on i2c bus:
 *
 * - read external i2c sensor, here we use a ToF sensor for instance.
 * - Use one I2C port(master mode) to read or write the other I2C port(slave mode) on one ESP8266 chip.
 *
 * Pin assignment:
 *
 * - master:
 *    GPIO2 is assigned as the data signal of i2c master port
 *    GPIO14 is assigned as the clock signal of i2c master port
 *
 * Connection:
 *
 * - connect sda/scl of sensor with GPIO2/GPIO14
 * - no need to add external pull-up resistors, driver will enable internal pull-up resistors.
 *
 * Test items:
 *
 * - read the sensor data, if connected.
 */

#define I2C_EXAMPLE_MASTER_SCL_IO           14               /*!< gpio number for I2C master clock */
#define I2C_EXAMPLE_MASTER_SDA_IO           2                /*!< gpio number for I2C master data  */
#define I2C_EXAMPLE_MASTER_NUM              I2C_NUM_0        /*!< I2C port number for master dev */
#define I2C_EXAMPLE_MASTER_TX_BUF_DISABLE   0                /*!< I2C master do not need buffer */
#define I2C_EXAMPLE_MASTER_RX_BUF_DISABLE   0                /*!< I2C master do not need buffer */

#define TOF_SENSOR_ADDR                     0x52             /*!< slave address for ToF sensor */
#define WRITE_BIT                           I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT                            I2C_MASTER_READ  /*!< I2C master read */
#define ACK_CHECK_EN                        0x1              /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS                       0x0              /*!< I2C master will not check ack from slave */
#define ACK_VAL                             0x0              /*!< I2C ack value */
#define NACK_VAL                            0x1              /*!< I2C nack value */
#define LAST_NACK_VAL                       0x2              /*!< I2C last_nack value */

/**
 * Define the ToF Sensor register address:
 */
#define READ_DISTANCE   0xD3
#define RESET_SENSOR    0xF5

/**
 * @brief i2c master initialization
 */
static esp_err_t i2c_example_master_init()
{
    int i2c_master_port = I2C_EXAMPLE_MASTER_NUM;
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = I2C_EXAMPLE_MASTER_SDA_IO;
    conf.sda_pullup_en = 1;
    conf.scl_io_num = I2C_EXAMPLE_MASTER_SCL_IO;
    conf.scl_pullup_en = 1;
    conf.clk_stretch_tick = 300; // 300 ticks, Clock stretch is about 210us, you can make changes according to the actual situation.
    ESP_ERROR_CHECK(i2c_driver_install(i2c_master_port, conf.mode));
    ESP_ERROR_CHECK(i2c_param_config(i2c_master_port, &conf));
    return ESP_OK;
}

/**
 * @brief test code to write tofsnsr
 *
 * 1. send data
 * ___________________________________________________________________________________________________
 * | start | slave_addr + wr_bit + ack | write reg_address + ack | write data_len byte + ack  | stop |
 * --------|---------------------------|-------------------------|----------------------------|------|
 *
 * @param i2c_num I2C port number
 * @param reg_address slave reg address
 * @param data data to send
 * @param data_len data length
 *
 * @return
 *     - ESP_OK Success
 *     - ESP_ERR_INVALID_ARG Parameter error
 *     - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
 *     - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
 *     - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
 */
static esp_err_t i2c_example_master_tofsnsr_write(i2c_port_t i2c_num, uint8_t reg_address, uint8_t *data, size_t data_len)
{
    int ret;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, TOF_SENSOR_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, reg_address, ACK_CHECK_EN);
    i2c_master_write(cmd, data, data_len, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

/**
 * @brief test code to read tofsnsr
 *
 * 1. send reg address
 * ______________________________________________________________________
 * | start | slave_addr + wr_bit + ack | write reg_address + ack | stop |
 * --------|---------------------------|-------------------------|------|
 *
 * 2. read data
 * ___________________________________________________________________________________
 * | start | slave_addr + wr_bit + ack | read data_len byte + ack(last nack)  | stop |
 * --------|---------------------------|--------------------------------------|------|
 *
 * @param i2c_num I2C port number
 * @param reg_address slave reg address
 * @param data data to read
 * @param data_len data length
 *
 * @return
 *     - ESP_OK Success
 *     - ESP_ERR_INVALID_ARG Parameter error
 *     - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
 *     - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
 *     - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
 */
static esp_err_t i2c_example_master_tofsnsr_read(i2c_port_t i2c_num, uint8_t reg_address, uint8_t *data, size_t data_len)
{
    int ret;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, TOF_SENSOR_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, reg_address, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    if (ret != ESP_OK) {
        return ret;
    }

    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, TOF_SENSOR_ADDR << 1 | READ_BIT, ACK_CHECK_EN);
    i2c_master_read(cmd, data, data_len, LAST_NACK_VAL);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    return ret;
}

static esp_err_t i2c_example_master_tofsnsr_init(i2c_port_t i2c_num)
{
    uint8_t cmd_data;
    vTaskDelay(100 / portTICK_RATE_MS);
    i2c_example_master_init();
    cmd_data = 0x00;    // Reset ToF Sensor
    ESP_ERROR_CHECK(i2c_example_master_tofsnsr_write(i2c_num, RESET_SENSOR, &cmd_data, 1));
    return ESP_OK;
}

static void i2c_task_example(void *arg)
{
    uint8_t sensor_data[2];
    uint16_t distance;
    int ret;

    i2c_example_master_tofsnsr_init(I2C_EXAMPLE_MASTER_NUM);

    while (1) {

        memset(sensor_data, 0, 2);
        ret = i2c_example_master_tofsnsr_read(I2C_EXAMPLE_MASTER_NUM, READ_DISTANCE, sensor_data, 2);

        if (ret == ESP_OK) {
            distance = sensor_data[0] << 8 | sensor_data[1];
            ESP_LOGI(TAG, "DISTANCE[mm]: %d\n", distance);

        } else {
            ESP_LOGE(TAG, "No ack, sensor not connected...skip...\n");
        }

        vTaskDelay(1000 / portTICK_RATE_MS);
    }

    i2c_driver_delete(I2C_EXAMPLE_MASTER_NUM);
}

void app_main(void)
{
    //start i2c task
    xTaskCreate(i2c_task_example, "i2c_task_example", 2048, NULL, 10, NULL);
}
```

変更・追加する箇所はマクロ定義部分とi2c_task_example関数内くらいです。
SCL/SDAのIOポート番号、ToFモジュールのID、距離情報の格納レジスタ、リセットレジスタを指定します。
変数名は適宜変更してください。

```c
#define I2C_EXAMPLE_MASTER_SCL_IO           14               /*!< gpio number for I2C master clock */
#define I2C_EXAMPLE_MASTER_SDA_IO           2                /*!< gpio number for I2C master data  */

#define TOF_SENSOR_ADDR                     0x52             /*!< slave address for ToF sensor */

/**
 * Define the ToF Sensor register address:
 */
#define READ_DISTANCE   0xD3
#define RESET_SENSOR    0xF5

```

```c

static void i2c_task_example(void *arg)
{
    uint8_t sensor_data[2];
    uint16_t distance;
    int ret;

    i2c_example_master_tofsnsr_init(I2C_EXAMPLE_MASTER_NUM);

    while (1) {

        memset(sensor_data, 0, 2);
        ret = i2c_example_master_tofsnsr_read(I2C_EXAMPLE_MASTER_NUM, READ_DISTANCE, sensor_data, 2);

        if (ret == ESP_OK) {
            distance = sensor_data[0] << 8 | sensor_data[1];
            ESP_LOGI(TAG, "DISTANCE[mm]: %d\n", distance);

        } else {
            ESP_LOGE(TAG, "No ack, sensor not connected...skip...\n");
        }

        vTaskDelay(1000 / portTICK_RATE_MS);
    }

    i2c_driver_delete(I2C_EXAMPLE_MASTER_NUM);
}
```

## 書き込み・動作確認
書き込み用回路に電源を供給し、下記コマンドでプログラムを書き込みます。

```
$ cd ~/esp/i2c
$ make flash
```

書き込みが完了したら電源供給をOFFにしてGNDにつながっているIO0をオープンにし（内部プルアップによりHIGHレベルになります）、再度電源を供給して下記コマンドを実行します。

```
$ make monitor
```

実行結果が表示されます。
8888となっているのは距離が測定できない場合に出力される値です。
精度ですが、150mmを測定した時に誤差が最大±5mm程度ですので物体検知用途であれば問題ないかと思います。

```
Toolchain path: /mingw32/bin/xtensa-lx106-elf-gcc
Toolchain version: crosstool-ng-1.22.0-100-ge567ec7b
Compiler version: 5.2.0
Python requirements from C:/msys32/home/xxxxx/esp/ESP8266_RTOS_SDK/requirements.txt are satisfied.
MONITOR
--- idf_monitor on COM3 74880 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
I (13344) main: DISTANCE[mm]: 38

I (14344) main: DISTANCE[mm]: 38

I (15344) main: DISTANCE[mm]: 38

I (16344) main: DISTANCE[mm]: 38

I (17344) main: DISTANCE[mm]: 42

I (18344) main: DISTANCE[mm]: 8888

I (19344) main: DISTANCE[mm]: 8888

I (20345) main: DISTANCE[mm]: 188

```