《ESP32调试异常集锦》之移植I2C程序时i2c_master_cmd_begin返回-1
项目场景:
《ESP32从0到1》之I2C通讯(AHT20温湿度传感器)_esp32 i2c-CSDN博客
之前的程序,测试正常。现将其移植到其他的项目中,运行发现导致重启。
问题描述
串口调试信息如下:跟踪程序发现在第二次发送0x71获取状态字的时候导致的重启。
[0;32mI (11320) i2c-simple-example: I2C initialized successfully[0m
[0;32mI (11320) I2C_DEBUG: err: 0x0[0m
[0;32mI (11330) i2c-simple-example: status = 0x18[0m
[0;32mI (11330) i2c-simple-example: read status[0m
[0;32mI (11340) I2C_DEBUG: err: 0xFFFFFFFF[0m
ESP_ERROR_CHECK failed: esp_err_t 0xffffffff (ESP_FAIL) at 0x400d7ff7
file: "./components/aht20/aht20.c" line 77
func: ReadAHT20Data
expression: AHT20_register_read(statuscmd, 1,data, 1)abort() was called at PC 0x40090c53 on core 0Backtrace: 0x40081932:0x3ffcc8f0 0x40090c5d:0x3ffcc910 0x40097042:0x3ffcc930 0x40090c53:0x3ffcc9a0 0x400d7ff7:0x3ffcc9d0 0x400d7b73:0x3ffcca10 0x401bc25e:0x3ffccb00 0x401bc2d9:0x3ffccb50ELF file SHA256: 8cf2ff4afRebooting...
ets Jul 29 2019 12:21:46
因为程序中用了函数ESP_ERROR_CHECK进行了判断,AHT20_register_read返回的并不是0x00所以导致重启。究其原因需要找到为什么AHT20_register_read没有返回0x00.
AHT20_register_read函数如下所示:
static esp_err_t AHT20_register_read(uint8_t* reg_addr,size_t cmdlen, uint8_t *data, size_t len)
{return i2c_master_write_read_device(I2C_MASTER_NUM, AHT20_SENSOR_ADDR, reg_addr, cmdlen, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}
i2c_master_write_read_device函数如下所示(i2c.c中的函数,未作修改):
esp_err_t i2c_master_write_read_device(i2c_port_t i2c_num, uint8_t device_address,const uint8_t* write_buffer, size_t write_size,uint8_t* read_buffer, size_t read_size,TickType_t ticks_to_wait)
{esp_err_t err = ESP_OK;uint8_t buffer[I2C_TRANS_BUF_MINIMUM_SIZE] = { 0 };i2c_cmd_handle_t handle = i2c_cmd_link_create_static(buffer, sizeof(buffer));assert (handle != NULL); err = i2c_master_start(handle);if (err != ESP_OK) {goto end;}err = i2c_master_write_byte(handle, device_address << 1 | I2C_MASTER_WRITE, true);if (err != ESP_OK) {goto end;}err = i2c_master_write(handle, write_buffer, write_size, true);if (err != ESP_OK) {goto end;}err = i2c_master_start(handle);if (err != ESP_OK) {goto end;}err = i2c_master_write_byte(handle, device_address << 1 | I2C_MASTER_READ, true);if (err != ESP_OK) {goto end;}err = i2c_master_read(handle, read_buffer, read_size, I2C_MASTER_LAST_NACK);if (err != ESP_OK) {goto end;}i2c_master_stop(handle);err = i2c_master_cmd_begin(i2c_num, handle, ticks_to_wait);
end:i2c_cmd_link_delete_static(handle);return err;
}
追踪到是i2c_master_cmd_begin出现问题,返回-1,对应到程序中是没有收到ACK.
之前调试成功的程序烧录到当前项目PCBA上,同样的地方出现重启。
之前调试用的ESP32-LyraT-Mini V1.2开发板,烧录之前调试通过的程序,依旧同样地方出现重启。
分析:
1)怀疑是模块问题?更换了新的AHT20模块,问题依旧,可以排除模块问题;
2)之前的开发板和现在自制的PCBA使用相同的模块出现相同的问题,问题应该不是出现在硬件上;
3)程序隐形的问题?
降低频率到200KHz 无作用
CSDN AI针对问题的回复:
问:ESP32调试I2C时i2c_master_write_to_device之后再调用i2c_master_write_read_device会导致i2c_master_cmd_begin返回-1
答:当你在调试ESP32的I2C时,在
i2c_master_write_to_device()
之后接着调用i2c_master_write_read_device()
,如果出现了i2c_master_cmd_begin()
返回-1的问题,可能是以下几个原因:
数据流冲突:这两个函数实际上都在进行I2C传输,如果前一个操作还没有完成,比如缓冲区未清空或者命令还在传输过程中,新的命令会因资源占用而失败。
顺序错误:I2C协议通常是半双工的,这意味着它不会同时进行读取和写入。如果你在设备正忙于接收数据时试图写入,可能会发生冲突。
设备响应超时:如果设备在前一次读写操作中设置了较长的延迟响应时间,或者数据包过大导致处理较慢,可能会影响到后续命令的开始。
错误检测未清除:如果前一次写操作检测到了错误(例如CRC校验错误),ESP32可能会拒绝继续执行直到错误被纠正。
解决策略:
- 确保每个I2C操作之间有适当的延时,让前一个操作有足够的时间完成。
- 检查设备的I2C地址和命令模式,确保它们在写入和读取时是一致的。
- 如果设备支持,可以先发送一个停止条件,等待设备准备好接受下一个命令。
- 使用ESP32提供的异常处理机制,捕获并处理可能出现的错误。
从解决策略上,第一个策略最有可能。检查AHT20资料对照实际程序,发现程序并没有按照资料的要求在启动测量之后等待80ms(之前写程序时预留备注但是因为测试通过,侥幸心理并没有做实际的delay操作)。
修改程序,增加了等待80ms的程序,则AHT20通讯正常,不再返回-1导致重启。
void ReadAHT20Data(float* temp,float* humi)
{uint8_t data[6];uint8_t statuscmd[]={0x71};uint8_t mearcmd[]={0xac,0x33,0x00}; uint16_t timecount=0;uint32_t tmp32; ESP_ERROR_CHECK(i2c_master_init());ESP_LOGI(I2C_TAG, "I2C initialized successfully"); //发送0x71命令,读取状态字ESP_ERROR_CHECK(AHT20_register_read(statuscmd, 1,data, 1)); ESP_LOGI(I2C_TAG, "status = 0x%X", data[0]);//启动测量ESP_ERROR_CHECK(AHT20_register_write(mearcmd, 3)); //delay 80msvTaskDelay(pdMS_TO_TICKS(80));//实际测试至少30msESP_ERROR_CHECK(AHT20_register_read(statuscmd, 1,data, 1)); ESP_LOGI(I2C_TAG, "status = 0x%X", data[0]);while (((data[0]&0x80)==0x80)&&(timecount < I2C_MASTER_TIMEOUT_MS * 1000) ) // 等待BUSY位清空 {usleep(1); data[0]=0;ESP_ERROR_CHECK(AHT20_register_read(statuscmd, 1,data, 1)); ESP_LOGI(I2C_TAG, "status = 0x%X", data[0]); timecount++;} //读取测量数据ESP_ERROR_CHECK(i2c_master_read_from_device(I2C_MASTER_NUM, AHT20_SENSOR_ADDR,data,6,I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS));for(int i=0;i<6;i++){ESP_LOGI(I2C_TAG, "0x%X", data[i]); } ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));//计算温湿度并打印//接收到的6个字节数据,第一个为状态字节,第二第三及第四字节的高四位为湿度,第四字节的低四位和第五第六字节为温度tmp32=0;tmp32 = ((uint32_t)data[1])<<12 | ((uint32_t)data[2])<<4 |(data[3]>>4);*humi=(float)tmp32/ 1048576 *100;//ESP_LOGI(I2C_TAG, "tmp32: 0x%lx,humi: %.2f",tmp32,humi); //tmp32为长整型,打印时需要注意tmp32=0;tmp32 = ((uint32_t)(data[3]&0x0f))<<16 | ((uint32_t)data[4])<<8 |(data[5]);*temp=(float)tmp32/ 1048576 * 200-50;//ESP_LOGI(I2C_TAG, "tmp32: 0x%lx,temp: %.2f",tmp32,temp); ESP_LOGI(I2C_TAG, "I2C de-initialized successfully");
}
解决方案:
按照AHT20资料,在启动测量后增加了等待80ms的程序。
I2C的通信需要按照I2C从机模块的时序流程进行编程。否则程序上会存在隐患,可能会因为从机模块的个体差异导致一些莫名的问题。