一、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)行。

二、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è)備。
-
使用創(chuàng)建一個(gè)命令鏈接
i2c_cmd_link_create()。
然后,用要發(fā)送到從站的一系列數(shù)據(jù)填充它:-
起始位-
i2c_master_start() -
從機(jī)地址-
i2c_master_write_byte()。提供單字節(jié)地址作為此函數(shù)調(diào)用的參數(shù)。 -
數(shù)據(jù)-一個(gè)或多個(gè)字節(jié)作為參數(shù)
i2c_master_write() -
停止位-
i2c_master_stop()
這兩個(gè)函數(shù)
i2c_master_write_byte()和i2c_master_write()具有一個(gè)額外的參數(shù)指定所述主是否應(yīng)當(dāng)確保它已經(jīng)接收到ACK位。 -
起始位-
通過(guò)調(diào)用觸發(fā)I2C控制器執(zhí)行命令鏈接
i2c_master_cmd_begin()。一旦觸發(fā)執(zhí)行,就不能修改命令鏈接。傳輸命令后,通過(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 中的例程修改
SCL 為 GPIO23
SDA 為 GPIO18
#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角度加速度傳感器

