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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:53:19