esp32

4. i2c_simple_main.c 분석

2426 2022. 8. 2. 20:56

먼저 i2c에 대해 알아보자.

 

SCL, SDA 두 개의 선을 이용한다.

SCL은 클럭을 동기화하기 위한 선이다.

SDA는 데이터를 송수신 하기 위한 선이다.

하나의 선으로 통신하므로 직렬 통신이며,

하나의 선으로 송수신 모두 가능하므로 반이중 방식이다.

또한 클럭을 위한 선이 있으므로 동기화 방식을 따른다.

 

  UART I2C SPI
동기/비동기 비동기 동기 동기
관계 수 1:1 N:N 1:N
선 수 2 (TX, RX) 2 (SDA, SCL) 4 (SCK, MISO, MOSI, SS)
이중통신 전이중 반이중 전이중
전송 거리 UART 대비 짧음 UART 대비 짧음
전송 속도 느림 SPI 대비 느림 빠름

출처: https://hydroponicglass.tistory.com/224

 

위 표를 보면 주요 직렬 통신의 특징이 나오는데

i2c는 spi(플래쉬 메모리 연결에 사용) 대비 느리기 때문에 주로 하드웨어를 제어하는 용도로 사용된다고 한다.

 

이번 포스팅은 결과를 로그를 통해서 볼 수 있지만 mpu9250센서가 없어 대부분 실행되지 않을 것이므로 분석에 집중하겠다.

 

아래 i2c_simple_main.c 코드를 보자.

/* i2c - Simple example

   Simple I2C example that shows how to initialize I2C
   as well as reading and writing from and to registers for a sensor connected over I2C.

   The sensor used in this example is a MPU9250 inertial measurement unit.

   For other examples please check:
   https://github.com/espressif/esp-idf/tree/master/examples

   See README.md file to get detailed usage of this 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 "esp_log.h"
#include "driver/i2c.h"

static const char *TAG = "i2c-simple-example";

#define I2C_MASTER_SCL_IO           CONFIG_I2C_MASTER_SCL      /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO           CONFIG_I2C_MASTER_SDA      /*!< GPIO number used for I2C master data  */
#define I2C_MASTER_NUM              0                          /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
#define I2C_MASTER_FREQ_HZ          400000                     /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
#define I2C_MASTER_TIMEOUT_MS       1000

#define MPU9250_SENSOR_ADDR                 0x68        /*!< Slave address of the MPU9250 sensor */
#define MPU9250_WHO_AM_I_REG_ADDR           0x75        /*!< Register addresses of the "who am I" register */

#define MPU9250_PWR_MGMT_1_REG_ADDR         0x6B        /*!< Register addresses of the power managment register */
#define MPU9250_RESET_BIT                   7

/**
 * @brief Read a sequence of bytes from a MPU9250 sensor registers
 */
static esp_err_t mpu9250_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
    return i2c_master_write_read_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, &reg_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
}

/**
 * @brief Write a byte to a MPU9250 sensor register
 */
static esp_err_t mpu9250_register_write_byte(uint8_t reg_addr, uint8_t data)
{
    int ret;
    uint8_t write_buf[2] = {reg_addr, data};

    ret = i2c_master_write_to_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);

    return ret;
}

/**
 * @brief i2c master initialization
 */
static esp_err_t i2c_master_init(void)
{
    int i2c_master_port = I2C_MASTER_NUM;

    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };

    i2c_param_config(i2c_master_port, &conf);

    return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}


void app_main(void)
{
    uint8_t data[2];
    ESP_ERROR_CHECK(i2c_master_init());
    ESP_LOGI(TAG, "I2C initialized successfully");

    /* Read the MPU9250 WHO_AM_I register, on power up the register should have the value 0x71 */
    ESP_ERROR_CHECK(mpu9250_register_read(MPU9250_WHO_AM_I_REG_ADDR, data, 1));
    ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]);

    /* Demonstrate writing by reseting the MPU9250 */
    ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_PWR_MGMT_1_REG_ADDR, 1 << MPU9250_RESET_BIT));

    ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));
    ESP_LOGI(TAG, "I2C unitialized successfully");
}

 

헤더파일 아래에 로그에 사용하기 위한 태그를 설정하고

 

sdkconfig.h

 

기본으로 설정된 SDA, SCL를 불러온다.

아두이노 ide에서는 SDA = GPIO21, SCL = GPIO22 라고한다.

 

i2c.h

 

이 개발 키트가 가질 수 있는 i2c 마스터의 수는 2이고 이 프로그램에서 사용하는 i2c의 번호는 0으로 설정되었다.

 

i2c_simple_main.c

 

클럭주파수를 400,000로 설정하였다.

송수신 버퍼는 설정하지 않았다.

생각해 보면 이 프로그램은 1대1 통신이므로 버퍼를 쓸 필요가 없을 것 같다.

 

i2c_simple_main.c
i2c_simple_main.c

 

main의 line 85에 있는 초기화 함수를 보자.

i2c 번호를 변수에 받고 

어떤 모드로 설정할지 정한다. 함수 이름이 master_init 이므로 마스터 모드를 사용한다.

sda,scl의 gpio번호는 정의된 매크로를 사용하며, 

여기서 i2c의 중요한 개념인 pull up이란게 나온다.

플로팅의 개념부터 알고 가자.

 

출처: https://blog.naver.com/jamduino/220820935325
출처: https://blog.naver.com/jamduino/220820935325

 

이것을 왜 알아야 하나? 라고 한다면

현재 사용중인 esp32 wroom 모델은 풀업 저항이 내부에 달려있어서 PULLUP_ENABLE 과 같은 코드로 해결 가능하지만 풀업 저항이 없는 mpu의 경우에는 직접 저항을 달아줘야 제대로 통신이 동작할 수 있기 때문이다.

 

다시 돌아와서

main의 line 71,72는 sda, scl의 내부 풀업 저항을 사용하겠다는 뜻이다.

 

i2c.h

 

또한 마스터 모드이므로 마스터의 클락 속도를 설정한다. (단, 1Mhz 미만으로 설정해야함.)

 

esp_err_t i2c_param_config(i2c_port_t i2c_num, const i2c_config_t *i2c_conf)
{
    i2c_clock_source_t src_clk = I2C_CLK_SRC_DEFAULT;
    esp_err_t ret = ESP_OK;

    ESP_RETURN_ON_FALSE(i2c_conf != NULL, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_ADDR_ERROR_STR);
    ESP_RETURN_ON_FALSE(i2c_conf->mode < I2C_MODE_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_MODE_ERR_STR);

    if (i2c_conf->mode == I2C_MODE_MASTER) {
        src_clk = s_get_clk_src(i2c_conf->clk_flags, i2c_conf->master.clk_speed);
    }
#if SOC_I2C_SUPPORT_SLAVE
    else {
    #if SOC_I2C_SUPPORT_REF_TICK
        /* On ESP32-S2, APB clock shall always be used in slave mode as the
         * other one, I2C_CLK_SRC_REF_TICK, is too slow, even for sampling a
         * 100KHz SCL. */
        src_clk = I2C_CLK_SRC_APB;
    #else
        src_clk = s_get_clk_src(i2c_conf->clk_flags, i2c_conf->slave.maximum_speed);
    #endif // CONFIG_IDF_TARGET_ESP32S2
    }
#endif // SOC_I2C_SUPPORT_SLAVE
    ESP_RETURN_ON_FALSE(src_clk != I2C_CLOCK_INVALID, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_CLK_FLAG_ERR_STR);

    ret = i2c_set_pin(i2c_num, i2c_conf->sda_io_num, i2c_conf->scl_io_num,
                      i2c_conf->sda_pullup_en, i2c_conf->scl_pullup_en, i2c_conf->mode);
    if (ret != ESP_OK) {
        return ret;
    }
    i2c_hw_enable(i2c_num);
    I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock));
    i2c_hal_disable_intr_mask(&(i2c_context[i2c_num].hal), I2C_LL_INTR_MASK);
    i2c_hal_clr_intsts_mask(&(i2c_context[i2c_num].hal), I2C_LL_INTR_MASK);
#if SOC_I2C_SUPPORT_SLAVE
    if (i2c_conf->mode == I2C_MODE_SLAVE) {  //slave mode
        i2c_hal_slave_init(&(i2c_context[i2c_num].hal), i2c_num);
        i2c_hal_set_source_clk(&(i2c_context[i2c_num].hal), src_clk);
        i2c_hal_set_slave_addr(&(i2c_context[i2c_num].hal), i2c_conf->slave.slave_addr, i2c_conf->slave.addr_10bit_en);
        i2c_hal_set_rxfifo_full_thr(&(i2c_context[i2c_num].hal), I2C_FIFO_FULL_THRESH_VAL);
        i2c_hal_set_txfifo_empty_thr(&(i2c_context[i2c_num].hal), I2C_FIFO_EMPTY_THRESH_VAL);
        //set timing for data
        i2c_hal_set_sda_timing(&(i2c_context[i2c_num].hal), I2C_SLAVE_SDA_SAMPLE_DEFAULT, I2C_SLAVE_SDA_HOLD_DEFAULT);
        i2c_hal_set_tout(&(i2c_context[i2c_num].hal), I2C_SLAVE_TIMEOUT_DEFAULT);
        i2c_hal_enable_slave_rx_it(&(i2c_context[i2c_num].hal));
    } else
#endif // SOC_I2C_SUPPORT_SLAVE
    {
        i2c_hal_master_init(&(i2c_context[i2c_num].hal), i2c_num);
        //Default, we enable hardware filter
        i2c_hal_set_filter(&(i2c_context[i2c_num].hal), I2C_FILTER_CYC_NUM_DEF);
        i2c_hal_set_bus_timing(&(i2c_context[i2c_num].hal), i2c_conf->master.clk_speed, src_clk, s_get_src_clk_freq(src_clk));
    }
    i2c_hal_update_config(&(i2c_context[i2c_num].hal));
    I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock));
    return ESP_OK;
}

 

i2c_param_config 함수의 내부를 보자.

이 함수는 i2c의 마스터 번호와 설정을 위한 i2c_config_t 구조체를 인수로 받는다.

 

i2c.h

 

기본 클럭 소스를 4로 설정하면 APB가 i2c 소스 클럭이 된다.

 

또한 i2c_param_config 함수에서는 설정을 위해 받은 i2c_config 가 설정이 되어있는지(NULL이 아닌지) i2c_config->mode가 slave 또는 master 둘중 하나인지 검사한다.

 

esp_err_t i2c_set_pin(i2c_port_t i2c_num, int sda_io_num, int scl_io_num, bool sda_pullup_en, bool scl_pullup_en, i2c_mode_t mode)
{
    ESP_RETURN_ON_FALSE(( i2c_num < I2C_NUM_MAX ), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR);
    ESP_RETURN_ON_FALSE(((sda_io_num < 0) || ((GPIO_IS_VALID_OUTPUT_GPIO(sda_io_num)))), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_SDA_IO_ERR_STR);
    ESP_RETURN_ON_FALSE(scl_io_num < 0 ||
#if SOC_I2C_SUPPORT_SLAVE
              (GPIO_IS_VALID_GPIO(scl_io_num) && mode == I2C_MODE_SLAVE) ||
#endif // SOC_I2C_SUPPORT_SLAVE
              (GPIO_IS_VALID_OUTPUT_GPIO(scl_io_num)),
              ESP_ERR_INVALID_ARG, I2C_TAG,
              I2C_SCL_IO_ERR_STR);
    ESP_RETURN_ON_FALSE(sda_io_num < 0 ||
              (sda_pullup_en == GPIO_PULLUP_ENABLE && GPIO_IS_VALID_OUTPUT_GPIO(sda_io_num)) ||
              sda_pullup_en == GPIO_PULLUP_DISABLE, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_GPIO_PULLUP_ERR_STR);
    ESP_RETURN_ON_FALSE(scl_io_num < 0 ||
              (scl_pullup_en == GPIO_PULLUP_ENABLE && GPIO_IS_VALID_OUTPUT_GPIO(scl_io_num)) ||
              scl_pullup_en == GPIO_PULLUP_DISABLE, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_GPIO_PULLUP_ERR_STR);
    ESP_RETURN_ON_FALSE((sda_io_num != scl_io_num), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_SCL_SDA_EQUAL_ERR_STR);

    int sda_in_sig, sda_out_sig, scl_in_sig, scl_out_sig;
    sda_out_sig = i2c_periph_signal[i2c_num].sda_out_sig;
    sda_in_sig = i2c_periph_signal[i2c_num].sda_in_sig;
    scl_out_sig = i2c_periph_signal[i2c_num].scl_out_sig;
    scl_in_sig = i2c_periph_signal[i2c_num].scl_in_sig;
    if (sda_io_num >= 0) {
        gpio_set_level(sda_io_num, I2C_IO_INIT_LEVEL);
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[sda_io_num], PIN_FUNC_GPIO);
        gpio_set_direction(sda_io_num, GPIO_MODE_INPUT_OUTPUT_OD);

        if (sda_pullup_en == GPIO_PULLUP_ENABLE) {
            gpio_set_pull_mode(sda_io_num, GPIO_PULLUP_ONLY);
        } else {
            gpio_set_pull_mode(sda_io_num, GPIO_FLOATING);
        }
        esp_rom_gpio_connect_out_signal(sda_io_num, sda_out_sig, 0, 0);
        esp_rom_gpio_connect_in_signal(sda_io_num, sda_in_sig, 0);
    }
    if (scl_io_num >= 0) {
        if (mode == I2C_MODE_MASTER) {
            gpio_set_level(scl_io_num, I2C_IO_INIT_LEVEL);
            gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[scl_io_num], PIN_FUNC_GPIO);
            gpio_set_direction(scl_io_num, GPIO_MODE_INPUT_OUTPUT_OD);
            esp_rom_gpio_connect_out_signal(scl_io_num, scl_out_sig, 0, 0);
        } else {
            gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[scl_io_num], PIN_FUNC_GPIO);
            gpio_set_direction(scl_io_num, GPIO_MODE_INPUT);
        }
        esp_rom_gpio_connect_in_signal(scl_io_num, scl_in_sig, 0);
        if (scl_pullup_en == GPIO_PULLUP_ENABLE) {
            gpio_set_pull_mode(scl_io_num, GPIO_PULLUP_ONLY);
        } else {
            gpio_set_pull_mode(scl_io_num, GPIO_FLOATING);
        }
    }
#if !SOC_I2C_SUPPORT_HW_CLR_BUS
    i2c_context[i2c_num].scl_io_num = scl_io_num;
    i2c_context[i2c_num].sda_io_num = sda_io_num;
#endif
    return ESP_OK;
}

 

i2c_param_config 함수의 중간쯤 나오는 i2c_set_pin이라는 함수 이다. 

sda, scl, pull-up 등을 ESP_RETURN_ON_FALSE 함수를 통해 적절히 검사한다.

 

출처:&nbsp;https://en.wikipedia.org/wiki/I&sup2;C

 

여기서 가장 중요한 통신하기 위한 준비가 필요하다.

위 그림에서 볼 수 있듯 시작하기 전에 SDA, SCL은 High 인 상태이다.

 

if (sda_io_num >= 0) {
        gpio_set_level(sda_io_num, I2C_IO_INIT_LEVEL);
if (scl_io_num >= 0) {
        if (mode == I2C_MODE_MASTER) {
            gpio_set_level(scl_io_num, I2C_IO_INIT_LEVEL);

 

High로 만들어 주기위해 1(High)로 설정한다.