ESP32在ISR中运行I2C持续崩溃,求可行实现方案
ESP32定时器ISR中执行I2C采样实现严格10Hz频率的可行方案
核心结论
你的需求完全可以实现,问题出在当前使用的Arduino I2C库(Wire)和ADS1115封装库无法在ISR上下文安全运行,只要改用底层硬件操作方式即可避开限制。
为什么当前代码会复位
你在ISR中调用的adc.setCompareChannels()内部依赖Arduino Wire库的阻塞式I2C写入,而Wire库使用了FreeRTOS的互斥锁、任务调度相关API——这些操作在ISR上下文是严格禁止的,会触发系统断言或死锁,最终导致ESP32复位。
可行实现方案
1. 抛弃高层封装库,直接操作I2C硬件寄存器
ESP32的I2C外设支持直接寄存器操作,完全可以在ISR中快速完成单帧I2C读写,无需依赖FreeRTOS或阻塞API:
- 提前在初始化阶段完成I2C外设的GPIO、波特率、时序参数配置
- 在ISR中直接操作I2C的
DATA、ADDR、CTRL等寄存器,发送ADS1115的配置命令和读取转换结果 - 所有ISR中调用的操作函数必须添加
IRAM_ATTR属性,确保代码在IRAM中运行(避免Flash访问导致的阻塞)
示例核心逻辑代码:
#include "driver/i2c.h" #define I2C_MASTER_NUM I2C_NUM_0 #define ADS1115_ADDR 0x48 #define ADS1115_REG_CONFIG 0x01 #define ADS1115_REG_CONVERSION 0x00 // ADS1115配置位宏定义 #define ADS1115_COMP_0_GND 0x4000 #define ADS1115_SINGLE 0x8000 #define ADS1115_475_SPS 0x00E0 #define ADS1115_RANGE_2048 0x0000 IRAM_ATTR void i2c_write_reg(uint8_t reg, uint16_t data) { // 直接操作I2C寄存器发送写命令(简化版,实际需添加错误处理) i2c_master_start(I2C_MASTER_NUM); i2c_master_write_byte(I2C_MASTER_NUM, (ADS1115_ADDR << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(I2C_MASTER_NUM, reg, true); i2c_master_write_byte(I2C_MASTER_NUM, (data >> 8) & 0xFF, true); i2c_master_write_byte(I2C_MASTER_NUM, data & 0xFF, true); i2c_master_stop(I2C_MASTER_NUM); } IRAM_ATTR uint16_t i2c_read_reg(uint8_t reg) { // 直接操作I2C寄存器发送读命令(简化版) i2c_master_start(I2C_MASTER_NUM); i2c_master_write_byte(I2C_MASTER_NUM, (ADS1115_ADDR << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(I2C_MASTER_NUM, reg, true); i2c_master_start(I2C_MASTER_NUM); i2c_master_write_byte(I2C_MASTER_NUM, (ADS1115_ADDR << 1) | I2C_MASTER_READ, true); uint8_t high = i2c_master_read_byte(I2C_MASTER_NUM, I2C_MASTER_ACK); uint8_t low = i2c_master_read_byte(I2C_MASTER_NUM, I2C_MASTER_NACK); i2c_master_stop(I2C_MASTER_NUM); return (high << 8) | low; } IRAM_ATTR bool TimerHandler0(void * timerNo) { static bool toggle0 = false; digitalWrite(LED_BUILTIN, toggle0); toggle0 = !toggle0; static uint8_t current_channel = 0; // 切换通道并启动转换 uint16_t config = (current_channel ? 0x5000 : ADS1115_COMP_0_GND) | ADS1115_SINGLE | ADS1115_475_SPS | ADS1115_RANGE_2048; i2c_write_reg(ADS1115_REG_CONFIG, config); // 读取上一次转换结果(转换时间约2.1ms,远小于100ms间隔) static uint16_t adc_vals[2]; if (toggle0) { adc_vals[current_channel] = i2c_read_reg(ADS1115_REG_CONVERSION); current_channel = !current_channel; // 将采样值存入FIR滤波器缓冲区 } return true; }
2. 优化采样流程,减少ISR内操作次数
- 将ADS1115配置为连续转换模式,只需在ISR中读取转换结果,切换通道的操作合并到前一次ISR的末尾,减少单次ISR的操作量
- 利用ADS1115的转换完成引脚(ALERT/RDY)作为辅助校验,但定时器触发更能保证10Hz的严格采样间隔
3. 确保ISR优先级高于其他任务
- 配置定时器中断的优先级高于TFT显示任务的优先级(ESP32中断优先级范围0-15,数值越小优先级越高,建议设置为3-5)
- 禁止TFT显示任务抢占定时器ISR,保证采样时刻不受显示操作干扰
关键注意事项
- 所有ISR中执行的代码必须是无阻塞、无调度、无互斥锁操作的,仅允许寄存器级操作或原子操作
- 避免在ISR中执行耗时过长的逻辑,I2C单帧操作耗时通常在微秒级,不会影响10Hz的采样间隔
- 若使用ESP-IDF开发,需确保I2C驱动配置为非阻塞/中断模式,而非默认的阻塞模式
内容的提问来源于stack exchange,提问作者Wojciech
相关产品推荐
相关产品推荐

