ESP32 Arduino:BluetoothSerial.h 的 .println() 破坏了其他内核上的硬件定时器中断

问题描述

我使用的是 ESP32 Arduino IDE。我在每个核心都有一个任务。在 core0 任务中,我设置了一个定时器中断,它向任务发出信号(通过 interruptCounter 变量)每 100 us 切换一次引脚。在 core1 上,我有一个任务,它使用 SerialBT.println("1") 函数在蓝牙上发送一些乱码。

我用示波器测量了引脚。如果我删除 SerialBT.println("1") 函数,或使用 Serial.println("1"),或者不在 Arduino IDE 上打开蓝牙串行监视器,它工作正常,我得到一个不错的 5 kHz 方波信号。但是,如果我在与蓝牙关联的端口上打开串行监视器,引脚会以非常随机的间隔切换,因此频率一直在变化。

我尝试了很多东西,但我仍然不知道在这代码一个核心如何影响另一个

编辑:在示波器上找到运行/停止按钮后,我意识到它仍然以常规的 100us 间隔切换,但每切换几次后它通常会忘记切换几毫秒。这些毛刺之间的间隔和脉冲似乎是不规则的。所以问题仍然存在,但我只是添加了此信息。

EDIT1:我还注意到interruptCounter 在这些停止期间像它应该的那样上升。所以它只是核心 0 函数不知何故没有响应。

#include "BluetoothSerial.h"
#include "esp_task_wdt.h"

volatile int interruptCounter = 0;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

BluetoothSerial SerialBT;

//interrupt routine
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&timerMux);
 
}
 
void setup() {
 
  Serial.begin(2000000);
  
  SerialBT.begin("ESP32"); //Bluetooth device name
  Serial.println("The device started,Now you can pair it with bluetooth!");
  
  pinMode(26,OUTPUT);

  disableCore0WDT(); 
  disableCore1WDT();
  
  xTaskCreatePinnedToCore(
    adc_read,/* Function to implement the task */
    "adc_read_task",/* Name of the task */
    1024,/* Stack size in words */
    NULL,/* Task input parameter */
    1,/* Priority of the task */
    NULL,/* Task handle. */
    0);  /* Core where the task should run */

  xTaskCreatePinnedToCore(
    bl_send,/* Function to implement the task */
    "bl_send_task",/* Task input parameter */
    2,/* Task handle. */
    1);  /* Core where the task should run */
}

 
void loop() {vTaskDelete(NULL);  }

static void  adc_read (void *pvParameters )
{

  //launch the interrupt on core 0
  timer = timerBegin(0,80,true);
  timerAttachInterrupt(timer,&onTimer,true);
  timerAlarmWrite(timer,100,true);
  timerAlarmEnable(timer);
  
  for(;;) {

    if (interruptCounter > 0) {     
      portENTER_CRITICAL(&timerMux);
      interruptCounter=0;
      portEXIT_CRITICAL(&timerMux);
              
      digitalWrite(26,!digitalRead(26));
    }  
  }
}

static void bl_send (void *pvParameters )
{
    for( ;; )
    {
      SerialBT.println("1");
    }
}

解决方法

首先,ESP 有其他事情要做,主要是运行 WiFi 和蓝牙堆栈(传统上在核心 0 上运行,但我不确定这些天它是否具有确定性)。您的任务是与在那里运行的一堆其他实时关键代码共享 CPU。这使您无法保证应用程序代码何时执行。如果您将任务的优先级设置为高于所有其他任务(包括 WiFi 和 BT 堆栈),那么您就有机会了。请注意,在这种情况下,您不能运行繁忙循环或阻塞 I/O 操作,因为它们会使其他任务饿死。

其次,即使您提高了任务的优先级,您也需要告诉 FreeRTOS 在 ISR 终止后立即通知您的任务。递增计数器(并在循环中对其进行轮询)不是任务通知机制。使用信号量,这正是它的用途。结束 ISR 时不要忘记调用具有相关值的 portYIELD_FROM_ISR 以触发即时任务切换。

第三,ESP32 中的 ISR 延迟有点不确定,显然您需要至少考虑 2 us。