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

ESP32中ISR工作机制及任务切换异常问题咨询

ESP32 Li-Fi项目ISR任务切换异常问题与疑问解答

我正在做一个基于ESP32的Li-Fi通信项目,用FreeRTOS创建了三个任务:

  • 信号接收任务Rx1:负责采集光电信号并转成二进制字符串
  • 信号处理任务Rx2:解析Rx1采集的二进制数据并输出字符
  • 信号发送任务Tx:将串口输入的字符串转成二进制,控制LED发送光信号

需求是:默认状态下Rx1、Rx2激活,Tx挂起;按下按键后通过ISR切换为Tx激活,Rx1、Rx2挂起。但目前用ISR进行任务切换时出现异常,同时有两个疑问:

  1. ESP32中ISR(中断服务程序)的工作机制是什么?
  2. 能否在ISR内部直接挂起或恢复FreeRTOS任务?

项目代码

struct Button {
  const uint8_t pin;
  volatile uint8_t numberKeyPresses;
};

#define buttonpin 15
#define ledpin 2
#define photopin 34
#define threshold 100
#define buffersize 16000
#define interval 1 //interval in milliseconds

TaskHandle_t Rx1_handle;
TaskHandle_t Rx2_handle;
TaskHandle_t Tx_handle;

Button button1 = {buttonpin, 0};
volatile bool pressed = false;

void IRAM_ATTR isr() {
  static uint32_t lastPressTime = 0; // Store the last press time to debounce the button
  uint32_t currentTime = millis(); // Get the current time

  // Debounce logic to avoid multiple triggers in quick succession
  if (currentTime - lastPressTime > 400) {  // 200ms debounce time
    lastPressTime = currentTime;
    
    // Increment the press count (wrap around after 2 presses, for example)
    button1.numberKeyPresses = (button1.numberKeyPresses + 1) % 2;  // 2 total presses before wrapping around

    // Perform actions based on the number of presses
    switch (button1.numberKeyPresses) {
      case 0: // First press (or after 5 presses)

        vTaskResume(Rx1_handle);
        vTaskResume(Rx2_handle);
        vTaskSuspend(Tx_handle);

        // xTaskCreatePinnedToCore(Rx1, "Signal Recieving", 100000, NULL, 1, &Rx1_handle, 1);
        // xTaskCreatePinnedToCore(Rx2, "Signal Processing", 10000, NULL, 1, &Rx2_handle, 0);
        // vTaskDelete(Tx_handle);
        break;

      case 1: // Second press
        // Your custom logic for 2nd press, e.g., alternate functionality
        vTaskSuspend(Rx1_handle);
        vTaskSuspend(Rx2_handle);
        vTaskResume(Tx_handle);

        // vTaskDelete(Rx1_handle);
        // vTaskDelete(Rx2_handle);
        // xTaskCreatePinnedToCore(Tx, "Signal Transmitting", 10000, NULL, 1, &Tx_handle, 1);
        break;

      default:
        break;
    }
  }  
}


void setup() {
  Serial.begin(115200);

  pinMode(ledpin, OUTPUT);

  // Testing if LED is Working
  digitalWrite(ledpin, HIGH);
  delay(500);
  digitalWrite(ledpin, LOW);

  pinMode(buttonpin, INPUT_PULLUP);
  attachInterrupt(button1.pin, isr, RISING);

  xTaskCreatePinnedToCore(
    Rx1,         // Function to implement the task
    "Signal Recieving",       // Name of the task
    10000,           // Stack size in words
    NULL,            // Task input parameter
    1,               // Priority of the task
    &Rx1_handle,            // Task handle
    0               // Core where the task should run (Core 0)
  );

  xTaskCreatePinnedToCore(
    Rx2,         // Function to implement the task
    "Signal Recieving",       // Name of the task
    10000,           // Stack size in words
    NULL,            // Task input parameter
    1,               // Priority of the task
    &Rx2_handle,            // Task handle
    1                // Core where the task should run (Core 0)
  );

  xTaskCreatePinnedToCore(
    Tx,         // Function to implement the task
    "Signal Transmitting",       // Name of the task
    10000,           // Stack size in words
    NULL,            // Task input parameter
    1,               // Priority of the task
    &Tx_handle,            // Task handle
    1                // Core where the task should run (Core 0)
  );

  vTaskResume(Rx1_handle);
  vTaskResume(Rx2_handle);
  vTaskSuspend(Tx_handle);
}

String binData_rx = "";

char binaryToChar(String binary) {
  int value = 0;
  for (int i = 0; i < 8; i++) {
    value <<= 1;  // Shift left to make space for the next bit
    if (binary.charAt(i) == '1') {
      value = value | 1;  // Set the least significant bit if it's '1'
    }
  }
  return (char)value;  // Return the resulting character
}

void processData() {
  int indx = binData_rx.indexOf("00000010");
  // Serial.println(indx);
  binData_rx.remove(0, indx+8);
  while(binData_rx.length()) {
    String subData = binData_rx.substring(0, 8);
    binData_rx.remove(0, 8);
    if(subData == "00000011") break;
    char ch = binaryToChar(subData);
    // processedData += ch;
    Serial.print(ch);
    vTaskDelay(interval * 8 / portTICK_PERIOD_MS); //in milliseconds
    // vTaskDelay(interval * 8 ); // in microseconds
  }
  binData_rx.clear();
  Serial.println("");
}

void Rx1(void *parameter) { // Recieves Signal
  Serial.println("Signal Recieving");
  for(;;) {
    int value = analogRead(photopin);
    if(value > threshold) binData_rx += '0';
    else binData_rx += '1';

    vTaskDelay(interval / portTICK_PERIOD_MS); // in milli seconds
    // vTaskDelay(interval); // in micro seconds
  }
}

void Rx2(void *parameter) { // Processes the Signal
  Serial.println("Singal Processing");
  for(;;) {
    if(binData_rx.length() > buffersize) {
    binData_rx.remove(0, 15900);
    }
    else if(binData_rx.indexOf("00000010") > -1) {
      binData_rx.remove(0, binData_rx.indexOf("00000010"));
      vTaskDelay(interval * 10 / portTICK_PERIOD_MS); // in milli seconds
      // vTaskDelay(interval * 10); // in micro seconds

      processData();
    }

    vTaskDelay(500 / portTICK_PERIOD_MS); // in milli seconds
    // vTaskDelay(500 * 1000); // in micro seconds
  }
}

// Tx part

String stringToBinary(String str) {
  String output = "";
  for (int i = 0; i < str.length(); i++) {
    char c = str.charAt(i);
    // Print the binary representation of each character
    for (int j = 7; j >= 0; j--) {
      
      output += String((c >> j) & 1);
    }
    // output += " ";
  }
  return output;
}

void Tx(void *parameter) {
  Serial.println("Signal Transmitting");
  for(;;) {
    if(Serial.available() > 0) {

      String input = Serial.readStringUntil('\n');
      String binData_tx = "00000010"+stringToBinary(input)+"0000001100000011";
      //Serial.println(2);
      Serial.print( binData_tx);

      for(int i=0; i<binData_tx.length(); i++) {
        if(binData_tx.charAt(i) == '1') {
          digitalWrite(ledpin, HIGH);
        
          //delayMicroseconds(interval);
        } 
        else {
          digitalWrite(ledpin, LOW);
          //delayMicroseconds(interval);
        }

        // digitalWrite(ledPin, (binData[i] == '1') ? HIGH : LOW);
        delay(interval); // delay of 200ms
      }

      binData_tx.clear();
      Serial.println();

      digitalWrite(ledpin, LOW);
    }
  }
}

void loop() {

}

问题解答

一、ESP32 ISR工作机制

ESP32是双核Xtensa LX6处理器,每个核心拥有独立的中断控制器,ISR核心特性如下:

  1. 高优先级执行:ISR会打断当前运行的任务,优先执行,完成后才回到被打断的上下文。
  2. 受限运行环境:ISR不能调用阻塞函数(如delay()、vTaskDelay())或动态内存分配操作,因为这些操作依赖FreeRTOS调度器,而ISR运行时调度器处于暂停状态。
  3. IRAM存储要求:ISR函数必须用IRAM_ATTR修饰,将代码放在指令RAM中,避免从SPI Flash加载代码导致延迟或异常。
  4. 支持中断嵌套:高优先级ISR可以打断低优先级ISR,具体优先级可通过API配置。

二、ISR内部能否挂起/恢复FreeRTOS任务?

可以,但需遵循FreeRTOS的中断安全规则:

  • 挂起任务:vTaskSuspend()本身是中断安全的,它仅修改任务状态,不会触发调度,可直接在ISR中调用。
  • 恢复任务:必须使用中断安全版本xTaskResumeFromISR(),因为恢复任务可能触发上下文切换,需要通过该API通知调度器,并在必要时调用portYIELD_FROM_ISR()主动触发调度。

三、代码异常原因及修复

1. 核心问题

  • 直接在ISR中调用非中断安全的vTaskResume(),可能导致调度器状态异常。
  • 使用millis()获取时间,依赖系统定时器中断,在ISR中调用可能导致时间计数不准确。

2. 修复后的ISR代码

void IRAM_ATTR isr() {
  static TickType_t lastPressTick = 0;
  TickType_t currentTick = xTaskGetTickCountFromISR();

  // 防抖:400ms转换为系统tick数
  if (currentTick - lastPressTick > 400 / portTICK_PERIOD_MS) {
    lastPressTick = currentTick;
    
    button1.numberKeyPresses = (button1.numberKeyPresses + 1) % 2;

    switch (button1.numberKeyPresses) {
      case 0:
        vTaskResume(Rx1_handle);
        vTaskResume(Rx2_handle);
        vTaskSuspend(Tx_handle);
        break;

      case 1:
        vTaskSuspend(Rx1_handle);
        vTaskSuspend(Rx2_handle);
        // 使用中断安全版本恢复任务
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xTaskResumeFromISR(Tx_handle, &xHigherPriorityTaskWoken);
        // 若需要切换上下文,通知调度器
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        break;

      default:
        break;
    }
  }  
}

3. 额外优化建议

  • 共享资源保护:binData_rx是Rx1和Rx2的共享变量,需用互斥锁(xSemaphoreHandle)保护,避免并发访问导致数据损坏。
  • 替换阻塞函数:Tx任务中的delay()应改为vTaskDelay(),符合FreeRTOS任务调度逻辑。
  • 调整任务优先级:给Rx1设置更高优先级,确保信号采集不丢失;Tx任务优先级可与Rx1相同或略低。

内容的提问来源于stack exchange,提问作者curious_boy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 00:42:05