ESP32通过I2C读取M117B传感器报错:从设备未ACK响应
ESP32驱动M117温度传感器I2C无ACK问题排查与解决
问题背景
自定义PCB上的ESP32通过I2C连接M117温度传感器,传感器地址为0x45,I2C扫描可检测到设备存在,但使用ESP-IDF的driver/i2c.h驱动时,i2c_master_cmd_begin()抛出错误:Sending command error, slave doesn't ACK the transfer。
初始代码
#include <driver/i2c.h> // GPIO number used for I2C master clock #define I2C_MASTER_SCL_IO GPIO_NUM_22 // GPIO number used for I2C master data #define I2C_MASTER_SDA_IO GPIO_NUM_21 #define I2C_MASTER_NUM (i2c_port_t)0 /* I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */ #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 I2C_MASTER_TIMEOUT_MS 1000 #define SENSOR_ADDR 0x45 /* Slave address of the Temp sensor */ // Register to read temperature from M117B Mysentech #define REGISTER0 0xCC #define REGISTER1 0x44 /** * i2c master initialization */ static esp_err_t i2cMasterInit() { i2c_config_t conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_MASTER_SDA_IO, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = I2C_MASTER_SCL_IO, .scl_pullup_en = GPIO_PULLUP_ENABLE, {{.clk_speed = I2C_MASTER_FREQ_HZ}} }; esp_err_t r = ESP_OK; r |= i2c_param_config(I2C_MASTER_NUM, &conf); // config I2C r |= i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); // Intall I2C if (r) thermometer.error("i2cMasterInit() result to %d", r); else thermometer.success("i2cMasterInit()"); return r; } /** Reads the M117 temperature */ static esp_err_t m117ReadTemp(uint8_t *raw, size_t len) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); esp_err_t r = ESP_OK; // == Create the I2C command // = Start write to register r |= i2c_master_start(cmd); logErrorIfAny("m117ReadTemp()|i2c_master_start()", r); r |= i2c_master_write_byte(cmd, SENSOR_ADDR << 1 | I2C_MASTER_WRITE, true); r |= i2c_master_write_byte(cmd, REGISTER0, true); logErrorIfAny("m117ReadTemp()|i2c_master_write_byte()", r); r |= i2c_master_write_byte(cmd, REGISTER1, true); logErrorIfAny("m117ReadTemp()|i2c_master_write_byte()", r); // = Start read from register r |= i2c_master_start(cmd); logErrorIfAny("m117ReadTemp()|i2c_master_start()", r); r |= i2c_master_write_byte(cmd, SENSOR_ADDR << 1 | I2C_MASTER_READ, true); r |= i2c_master_read(cmd, raw, len, I2C_MASTER_LAST_NACK); logErrorIfAny("m117ReadTemp()|i2c_master_read()", r); // = Stop r |= i2c_master_stop(cmd); logErrorIfAny("m117ReadTemp()|i2c_master_stop()", r); // == Send the command r |= i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); logErrorIfAny("m117ReadTemp()|i2c_master_cmd_begin()", r); i2c_cmd_link_delete(cmd); logErrorIfAny("m117ReadTemp():", r); return r; }
调试过程记录
- 最初尝试修改Arduino代码失败,切换到ESP-IDF的I2C驱动后出现ACK错误。
- 曾怀疑连续写字节时多次请求ACK导致问题,改为单次写入两个寄存器地址:
const uint8_t REGISTER[] = {REGISTER0, REGISTER1}; r |= i2c_master_write(handle, (byte*)®ISTER, sizeof REGISTER, true);
但错误依旧。
3. 修改为拆分读写流程,写入寄存器后发送停止信号,延迟后再发起读操作,错误仍出现在i2c_master_cmd_begin():
/// Reads the M117 temperature static float m117ReadTemp(bool &ready) { ready = false; esp_err_t r = ESP_OK; float temperature = 0; i2c_cmd_handle_t handle = i2c_cmd_link_create(); uint8_t REGISTER[] = {REGISTER0, REGISTER1}; // == Create the I2C command // = Start write to register r |= i2c_master_start(handle); r |= i2c_master_write_byte(handle, SENSOR_ADDR << 1 | I2C_MASTER_WRITE, true); r |= i2c_master_write(handle, REGISTER, sizeof REGISTER, true); // = Stop r |= i2c_master_stop(handle); delay(15); // Makes no difference uint8_t data[2] = {0, 0}; // = Start read from register r |= i2c_master_start(handle); r |= i2c_master_write_byte(handle, SENSOR_ADDR << 1 | I2C_MASTER_READ, true); r |= i2c_master_read(handle, data, sizeof data, I2C_MASTER_LAST_NACK); // = Stop r |= i2c_master_stop(handle); delay(15); // Makes no difference // == Send the command r |= i2c_master_cmd_begin(I2C_MASTER_NUM, handle, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); // ====> THIS IS WHERE THE ERROR POPS FROM <=== logErrorIfAny("m117ReadTemp()|i2c_master_cmd_begin()", r); // == Clean up i2c_cmd_link_delete(handle); // blah blah }
- 最终发现:快速循环读取时正常,间隔10秒以上读取时,第一次正常,后续全部出现ACK错误。
解决方案建议
1. 处理传感器低功耗休眠
M117闲置一段时间后会进入低功耗模式,此时需要特定唤醒时序。每次读取前先发送空的I2C起始+停止信号唤醒传感器:
// 唤醒传感器 i2c_cmd_handle_t wake_cmd = i2c_cmd_link_create(); i2c_master_start(wake_cmd); i2c_master_write_byte(wake_cmd, SENSOR_ADDR << 1 | I2C_MASTER_WRITE, false); // 不等待ACK i2c_master_stop(wake_cmd); i2c_master_cmd_begin(I2C_MASTER_NUM, wake_cmd, 100 / portTICK_RATE_MS); i2c_cmd_link_delete(wake_cmd); vTaskDelay(10 / portTICK_RATE_MS); // 给传感器唤醒缓冲时间
2. 修正I2C命令链路使用方式
ESP-IDF的i2c_cmd_link对应单次I2C传输序列,拆分的读写操作需要分别创建链路并执行:
static float m117ReadTemp(bool &ready) { ready = false; esp_err_t r = ESP_OK; float temperature = 0; uint8_t data[2] = {0, 0}; // 第一步:发送寄存器指令 i2c_cmd_handle_t write_cmd = i2c_cmd_link_create(); r |= i2c_master_start(write_cmd); r |= i2c_master_write_byte(write_cmd, SENSOR_ADDR << 1 | I2C_MASTER_WRITE, true); r |= i2c_master_write(write_cmd, (uint8_t[]){0xCC, 0x44}, 2, true); r |= i2c_master_stop(write_cmd); r |= i2c_master_cmd_begin(I2C_MASTER_NUM, write_cmd, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); i2c_cmd_link_delete(write_cmd); if (r != ESP_OK) { logErrorIfAny("Write register failed", r); return temperature; } vTaskDelay(10 / portTICK_RATE_MS); // 等待传感器完成温度转换 // 第二步:读取温度数据 i2c_cmd_handle_t read_cmd = i2c_cmd_link_create(); r |= i2c_master_start(read_cmd); r |= i2c_master_write_byte(read_cmd, SENSOR_ADDR << 1 | I2C_MASTER_READ, true); r |= i2c_master_read(read_cmd, data, 2, I2C_MASTER_LAST_NACK); r |= i2c_master_stop(read_cmd); r |= i2c_master_cmd_begin(I2C_MASTER_NUM, read_cmd, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS); i2c_cmd_link_delete(read_cmd); if (r == ESP_OK) { ready = true; // 按M117协议转换温度值 int16_t raw_temp = (data[0] << 8) | data[1]; temperature = raw_temp / 256.0f; } else { logErrorIfAny("Read data failed", r); } return temperature; }
3. 检查I2C总线硬件上拉
自定义PCB若缺少4.7kΩ左右的I2C上拉电阻,闲置后总线电平会不稳定,导致传感器无法响应。确认SDA/SCL引脚的硬件上拉,若硬件无配置,ESP32内部上拉强度有限,建议补充外部上拉电阻。
4. 增加重试机制
读取失败时,尝试重复操作或重新初始化I2C总线:
#define MAX_RETRY 3 esp_err_t r; int retry = 0; do { // 执行读写操作逻辑 r = ...; retry++; if (r != ESP_OK) vTaskDelay(50 / portTICK_RATE_MS); } while (r != ESP_OK && retry < MAX_RETRY);
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

