ESP32學(xué)習(xí)筆記(17)——I2C接口使用

一、I2C簡(jiǎn)介

I2C(Inter-Integrated Circuit ,內(nèi)部集成電路) 總線是一種由飛利浦 Philip 公司開(kāi)發(fā)的串行總線。是兩條串行的總線,它由一根數(shù)據(jù)線(SDA)和一根 時(shí)鐘線(SCL)組成。兩條線都需要上拉電阻。I2C 總線上可以接多個(gè) I2C 設(shè)備,每個(gè)器件都有一個(gè)唯一的地址識(shí)別。同一時(shí)間只能有一個(gè)主設(shè)備,其他為從設(shè)備。通常 MCU 作為主設(shè)備控制,外設(shè)作為從設(shè)備。

ESP32有兩個(gè)I2C控制器(也稱為端口),負(fù)責(zé)處理I2C總線上的通信。每個(gè)I2C控制器都可以作為主機(jī)或從機(jī)運(yùn)行。

ESP-IDF 編程指南——I2C

二、API說(shuō)明

以下 I2C 接口位于 driver/include/driver/i2c.h。

2.1 i2c_param_config

2.2 i2c_driver_install

2.3 i2c_cmd_link_create

2.4 i2c_master_start

2.5 i2c_master_write_byte

2.6 i2c_master_write

2.7 i2c_master_read_byte

2.8 i2c_master_read

2.9 i2c_master_stop

2.10 i2c_master_cmd_begin

2.11 i2c_cmd_link_delete

三、編程流程

3.1 設(shè)置通信參數(shù)

要建立I2C通信,請(qǐng)先配置驅(qū)動(dòng)程序。這是通過(guò)設(shè)置結(jié)構(gòu)的參數(shù)來(lái)完成的i2c_config_t

  • 設(shè)置I2C操作模式-從機(jī)或主機(jī)i2c_mode_t

  • 配置通訊引腳

    • 為SDA和SCL信號(hào)分配GPIO引腳
    • 設(shè)置是否啟用ESP32的內(nèi)部上拉電路
  • (僅限主機(jī))設(shè)置I2C時(shí)鐘速度

  • (僅限從機(jī))配置以下內(nèi)容

    • 是否啟用10位地址模式
    • 定義從機(jī)地址

之后,初始化給定I2C端口的配置。為此,調(diào)用函數(shù)i2c_param_config()并將端口號(hào)和結(jié)構(gòu)傳遞給該函數(shù)i2c_config_t。

配置示例(主機(jī)):

int i2c_master_port = 0;
i2c_config_t conf = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = I2C_MASTER_SDA_IO,         // select GPIO specific to your project
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_io_num = I2C_MASTER_SCL_IO,         // select GPIO specific to your project
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .master.clk_speed = I2C_MASTER_FREQ_HZ,  // select frequency specific to your project
    // .clk_flags = 0,          /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */
};

配置示例(從機(jī)):

int i2c_slave_port = I2C_SLAVE_NUM;
i2c_config_t conf_slave = {
    .sda_io_num = I2C_SLAVE_SDA_IO,          // select GPIO specific to your project
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_io_num = I2C_SLAVE_SCL_IO,          // select GPIO specific to your project
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .mode = I2C_MODE_SLAVE,
    .slave.addr_10bit_en = 0,
    .slave.slave_addr = ESP_SLAVE_ADDR,      // address of your project
};

3.2 驅(qū)動(dòng)程序安裝

配置I2C驅(qū)動(dòng)程序后,通過(guò)i2c_driver_install()使用以下參數(shù)調(diào)用該函數(shù)來(lái)安裝它:

  • 端口號(hào),來(lái)自的兩個(gè)端口號(hào)之一 i2c_port_t

  • 主機(jī)或從機(jī),選自 i2c_mode_t

  • (僅限從機(jī))為發(fā)送和接收數(shù)據(jù)分配的緩沖區(qū)大小。由于I2C是以主機(jī)為中心的總線,因此,只有在主機(jī)請(qǐng)求時(shí),數(shù)據(jù)才能從從機(jī)傳輸?shù)街鳈C(jī)。因此,從設(shè)備通常將具有一個(gè)發(fā)送緩沖區(qū),從設(shè)備在其中寫(xiě)入應(yīng)用程序數(shù)據(jù)。數(shù)據(jù)保留在發(fā)送緩沖區(qū)中,由主機(jī)自行決定是否由主機(jī)讀取。

  • 用于分配中斷的標(biāo)志(請(qǐng)參閱esp_hw_support / include / esp_intr_alloc.h中的ESP_INTR_FLAG_ *值)

3.3 運(yùn)行I2C通信

安裝I2C驅(qū)動(dòng)程序后,ESP32已準(zhǔn)備好與其他I2C設(shè)備通信。

ESP32的I2C控制器作為主設(shè)備負(fù)責(zé)與I2C從設(shè)備建立通信,并發(fā)送命令以觸發(fā)從設(shè)備采取行動(dòng),例如進(jìn)行測(cè)量并將讀數(shù)發(fā)送回主設(shè)備。

為了更好地進(jìn)行流程組織,驅(qū)動(dòng)程序提供了一個(gè)稱為“命令鏈接”的容器,該容器應(yīng)填充一系列命令,然后傳遞給I2C控制器以執(zhí)行。

3.3.1 作為主機(jī)通信

  • 主機(jī)寫(xiě)
    下面的示例顯示了如何為I2C主設(shè)備構(gòu)建命令鏈接,以將n個(gè)字節(jié)發(fā)送給從設(shè)備。
  1. 使用創(chuàng)建一個(gè)命令鏈接i2c_cmd_link_create()。
    然后,用要發(fā)送到從站的一系列數(shù)據(jù)填充它:

    1. 起始位-i2c_master_start()
    2. 從機(jī)地址- i2c_master_write_byte()。提供單字節(jié)地址作為此函數(shù)調(diào)用的參數(shù)。
    3. 數(shù)據(jù)-一個(gè)或多個(gè)字節(jié)作為參數(shù)i2c_master_write()
    4. 停止位-i2c_master_stop()

    這兩個(gè)函數(shù)i2c_master_write_byte()i2c_master_write()具有一個(gè)額外的參數(shù)指定所述主是否應(yīng)當(dāng)確保它已經(jīng)接收到ACK位。

  2. 通過(guò)調(diào)用觸發(fā)I2C控制器執(zhí)行命令鏈接i2c_master_cmd_begin()。一旦觸發(fā)執(zhí)行,就不能修改命令鏈接。

  3. 傳輸命令后,通過(guò)調(diào)用釋放命令鏈接使用的資源i2c_cmd_link_delete()。

  • 主機(jī)讀
    下面的示例顯示了如何為I2C主機(jī)構(gòu)建命令鏈接以從從機(jī)讀取n個(gè)字節(jié)。


    與寫(xiě)入數(shù)據(jù)相比,第4步中的命令鏈接不是使用i2c_master_write...函數(shù)而是使用i2c_master_read_byte()i2c_master_read()。同樣,配置步驟5中的最后一次讀取,以便主機(jī)不提供ACK位。

  • 寫(xiě)或讀指示
    發(fā)送從機(jī)地址后(請(qǐng)參見(jiàn)上圖兩個(gè)步驟中的步驟3),主機(jī)會(huì)和從機(jī)進(jìn)行寫(xiě)入或讀取。

有關(guān)主機(jī)實(shí)際操作的信息隱藏在從機(jī)地址的最低有效位中。

因此,主機(jī)發(fā)送的用于將數(shù)據(jù)寫(xiě)入從機(jī)的命令鏈接包含該地址,如下所示:(ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE, ACK_EN);

同樣,從從站讀取的命令鏈接如下所示:

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ, ACK_EN);

3.3.2 作為從機(jī)通信

安裝I2C驅(qū)動(dòng)程序后,ESP32已準(zhǔn)備好與其他I2C設(shè)備通信。

該API為從機(jī)提供以下功能

  • i2c_slave_read_buffer()
    當(dāng)主機(jī)將數(shù)據(jù)寫(xiě)入從機(jī)時(shí),從機(jī)將自動(dòng)將其存儲(chǔ)在接收緩沖區(qū)中。這允許從機(jī)應(yīng)用程序i2c_slave_read_buffer()自行決定調(diào)用該函數(shù)。如果接收緩沖區(qū)中沒(méi)有數(shù)據(jù),此函數(shù)還具有一個(gè)參數(shù),用于指定塊時(shí)間。這將允許從機(jī)應(yīng)用程序在指定的超時(shí)時(shí)間內(nèi)等待數(shù)據(jù)到達(dá)緩沖區(qū)。

  • i2c_slave_write_buffer()
    發(fā)送緩沖區(qū)用于存儲(chǔ)從設(shè)備要以FIFO順序發(fā)送給主設(shè)備的所有數(shù)據(jù)。數(shù)據(jù)將一直保留在那里,直到主設(shè)備請(qǐng)求為止。該函數(shù)i2c_slave_write_buffer()具有一個(gè)參數(shù),用于指定發(fā)送緩沖區(qū)已滿時(shí)的阻止時(shí)間。這將允許從機(jī)應(yīng)用程序在指定的超時(shí)時(shí)間內(nèi)等待發(fā)送緩沖區(qū)中足夠的可用空間。

四、ESP32作為主機(jī)與BH1750光照強(qiáng)度傳感器通信

根據(jù) esp-idf\examples\peripherals\i2c\i2c_self_test 中的例程修改
SCLGPIO23
SDAGPIO18

#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"

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

#define I2C_MASTER_SCL_IO GPIO_NUM_23           /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO GPIO_NUM_18           /*!< gpio number for I2C master data  */
#define I2C_MASTER_NUM 1                        /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ 100000               /*!< 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 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 BH1750_SLAVE_ADDR   0x23 // 從機(jī)地址
#define BH1750_PWR_DOWN     0x00 // 關(guān)閉模塊
#define BH1750_PWR_ON       0x01 // 打開(kāi)模塊等待測(cè)量指令
#define BH1750_RST          0x07 // 重置數(shù)據(jù)寄存器值在PowerOn模式下有效
#define BH1750_CON_H        0x10 // 連續(xù)高分辨率模式,1lx,120ms
#define BH1750_CON_H2       0x11 // 連續(xù)高分辨率模式,0.5lx,120ms
#define BH1750_CON_L        0x13 // 連續(xù)低分辨率模式,4lx,16ms
#define BH1750_ONE_H        0x20 // 一次高分辨率模式,1lx,120ms,測(cè)量后模塊轉(zhuǎn)到PowerDown模式
#define BH1750_ONE_H2       0x21 // 一次高分辨率模式,0.5lx,120ms,測(cè)量后模塊轉(zhuǎn)到PowerDown模式
#define BH1750_ONE_L        0x23 // 一次低分辨率模式,4lx,16ms,測(cè)量后模塊轉(zhuǎn)到PowerDown模式

SemaphoreHandle_t print_mux = NULL;

/**
 @brief I2C驅(qū)動(dòng)初始化
 @param 無(wú)
 @return 無(wú)
*/
int I2C_Init(void)
{
    int i2c_master_port = I2C_MASTER_NUM;
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = I2C_MASTER_SDA_IO;
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = I2C_MASTER_SCL_IO;
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.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);
}

/**
 @brief I2C寫(xiě)數(shù)據(jù)函數(shù)
 @param slaveAddr -[in] 從設(shè)備地址
 @param regAddr -[in] 寄存器地址
 @param pData -[in] 寫(xiě)入數(shù)據(jù)
 @param dataLen -[in] 寫(xiě)入數(shù)據(jù)長(zhǎng)度
 @return 錯(cuò)誤碼
*/
int I2C_WriteData(uint8_t slaveAddr, uint8_t regAddr, uint8_t *pData, uint16_t dataLen)
{
    int ret;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (slaveAddr << 1) | WRITE_BIT, ACK_CHECK_EN);
    if(NULL != regAddr)
    {
        i2c_master_write_byte(cmd, regAddr, ACK_CHECK_EN);
    }
    i2c_master_write(cmd, pData, dataLen, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(1, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

/**
 @brief I2C讀數(shù)據(jù)函數(shù)
 @param slaveAddr -[in] 從設(shè)備地址
 @param regAddr -[in] 寄存器地址
 @param pData -[in] 讀出數(shù)據(jù)
 @param dataLen -[in] 讀出數(shù)據(jù)長(zhǎng)度
 @return 錯(cuò)誤碼
*/
int I2C_ReadData(uint8_t slaveAddr, uint8_t regAddr, uint8_t *pData, uint16_t dataLen)
{
    int ret;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (slaveAddr << 1) | READ_BIT, ACK_CHECK_EN);
    if(NULL != regAddr)
    {
        i2c_master_write_byte(cmd, regAddr, ACK_CHECK_EN);
    }
    i2c_master_read(cmd, pData, dataLen, ACK_VAL);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(1, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}

static void i2c_test_task(void *arg)
{
    int ret;
    uint8_t sensor_data[2] = {0};
    uint8_t sensor_data_h, sensor_data_l;
    int cnt = 0;
    uint8_t data;

    while (1) {
        ESP_LOGI(TAG, "test cnt: %d", cnt++);
        data = BH1750_PWR_ON;              // 發(fā)送啟動(dòng)命令
        I2C_WriteData(BH1750_SLAVE_ADDR, NULL, &data, 1);
        data = BH1750_ONE_L;               // 設(shè)置一次低分辨率模式,測(cè)量后模塊轉(zhuǎn)到PowerDown模式
        I2C_WriteData(BH1750_SLAVE_ADDR, NULL, &data, 1);
        vTaskDelay(30 / portTICK_RATE_MS); // 設(shè)置完成后要有一段延遲
        ret = I2C_ReadData(BH1750_SLAVE_ADDR, NULL, sensor_data, 2);
        sensor_data_h = sensor_data[0];
        sensor_data_l = sensor_data[1];
        // ret = i2c_master_sensor_test(I2C_MASTER_NUM, &sensor_data_h, &sensor_data_l);
        xSemaphoreTake(print_mux, portMAX_DELAY);
        if (ret == ESP_ERR_TIMEOUT) {
            ESP_LOGE(TAG, "I2C Timeout");
        } else if (ret == ESP_OK) {
            printf("*******************\n");
            printf("MASTER READ SENSOR( BH1750 )\n");
            printf("*******************\n");
            printf("data_h: %02x\n", sensor_data_h);
            printf("data_l: %02x\n", sensor_data_l);
            printf("sensor val: %.02f [Lux]\n", (sensor_data_h << 8 | sensor_data_l) / 1.2);
        } else {
            ESP_LOGW(TAG, "%s: No ack, sensor not connected...skip...", esp_err_to_name(ret));
        }
        xSemaphoreGive(print_mux);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
    vSemaphoreDelete(print_mux);
    vTaskDelete(NULL);
}

void app_main(void)
{
    print_mux = xSemaphoreCreateMutex();
    ESP_ERROR_CHECK(I2C_Init());
    xTaskCreate(i2c_test_task, "i2c_test_task_0", 1024 * 2, NULL, 10, NULL);
}

查看打印:



? 由 Leung 寫(xiě)于 2021 年 5 月 13 日

? 參考:第十二章 ESP32讀取SHT30的溫濕度(IIC)
    ESP32 開(kāi)發(fā)筆記(三)源碼示例 10_IIC_ADXL345 使用IIC總線實(shí)現(xiàn)讀取ADXL345角度加速度傳感器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容