You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

调试过程记录

  1. 最初尝试修改Arduino代码失败,切换到ESP-IDF的I2C驱动后出现ACK错误。
  2. 曾怀疑连续写字节时多次请求ACK导致问题,改为单次写入两个寄存器地址:
const uint8_t REGISTER[] = {REGISTER0, REGISTER1};
r |= i2c_master_write(handle, (byte*)&REGISTER, 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
}
  1. 最终发现:快速循环读取时正常,间隔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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:01:45