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

ESP32 FreeRTOS双任务异常:sendData持续运行receiveData极少执行求助

ESP32 FreeRTOS蓝牙回显任务异常排查

我在ESP32上用FreeRTOS开发蓝牙回显功能,创建了两个任务:

  • receiveData任务:从蓝牙模块接收数据并存入队列
  • sendData任务:从队列读取数据回传给发送端

但出现异常:sendData任务持续高频运行,receiveData任务几乎不执行。通过在任务循环中添加Serial.println("test")调试发现,sendData中的调试语句被无限调用,receiveData中的仅被调用少数几次,功能完全不符合预期。


代码文件

main.cpp

#include <Arduino.h>

#include "constants.h"

#include "bluetooth.h"

// utility objects
Bluetooth bluetooth;  /*this is the object throught which we can control the bluetooth*/


// task handlers
TaskHandle_t xHandle_bluetoothSendData = NULL;    /*this is the task handler for the bleutooth send data task*/
TaskHandle_t xHandle_bluetoothReceiveData = NULL; /*this is the task handler for the bleutooth receive data task*/


// Queue handlers
QueueHandle_t xQueue_bluetoothSendData = NULL;    /*this is the queue used for bluetooth send data queue task*/
QueueHandle_t xQueue_bluetoothRcvData = NULL;     /*this is the queue used for bluetooth receive data queue task*/


// task queues contents structures
typedef struct
{
  char data[BLUETOOTH_MSG_SEND_LEN];
}bluetoothSendMsg_t;

typedef struct
{
  char data[BLUETOOTH_MSG_RCV_LEN];
}bluetoothRcvMsg_t;



/**
 * @brief this is the task used for sending data from esp32
*/
void sendData(void* pvParameters)
{
  /*struct for sending data*/
  bluetoothSendMsg_t *sendMessage;

  while (1)
  {
    /*get what's in the queue*/
    if(xQueue_bluetoothSendData != NULL && xQueueReceive(xQueue_bluetoothSendData, &sendMessage, (TickType_t) 0) == pdPASS)
    {
      Serial.print(sendMessage->data);  
      bluetooth.sendData(sendMessage->data);      /*sending the message received back to receiver*/ 
    }
  }
}

/**
 * @brief this is the task used for reveiving data by esp32
*/
void receiveData(void* pvParameters)
{
  /*for sending and receiving structs*/
  bluetoothRcvMsg_t receivedMsg;
  bluetoothSendMsg_t *messageTobeSend;

  while (1)
  {

    /*receiving message form the receiver from the receiver and put it in a queue to be send back*/
    strcpy(receivedMsg.data, bluetooth.getData(BLUETOOTH_MSG_RCV_LEN, 0).c_str());

    Serial.print("test");

    /*for some reason we received an empty message*/
    if(receivedMsg.data[0] != '\0')
    {
      messageTobeSend = (bluetoothSendMsg_t*) pvPortMalloc(sizeof(bluetoothSendMsg_t)); /*make a place in the heap for this new message*/
      strcpy(messageTobeSend->data, receivedMsg.data);  /*copying received message to another struct that will be send back*/
      Serial.print(messageTobeSend->data);
      xQueueSend(xQueue_bluetoothSendData, (void*) &messageTobeSend, (TickType_t) 0); // send back to the queue , non blocking if the queue is full
      receivedMsg.data[0] = '\0';
    }

  }
  
}

void setup() {
  // put your setup code here, to run once:
  bluetooth.init(); /*initialization for the code*/

  /*for debugging purposes*/
  Serial.begin(115200); 
  while(!Serial);

  /*create needed queues for inter-task communication*/
  xQueue_bluetoothSendData = xQueueCreate(BLUETOOTH_SEND_DATA_QUEUE_LEN, sizeof(bluetoothSendMsg_t*));
  xQueue_bluetoothRcvData = xQueueCreate(BLUETOOTH_RCV_DATA_QUEUE_LEN, sizeof(bluetoothRcvMsg_t*));
  
  /*create needed tasks*/
  xTaskCreatePinnedToCore(sendData, "Send data to bluetooth", BLUETOOTH_STACK_SEND_DATA, nullptr, tskIDLE_PRIORITY , &xHandle_bluetoothSendData, tskNO_AFFINITY);
  xTaskCreatePinnedToCore(receiveData, "receive dat from the bluetooth", BLUETOOTH_STACK_RCV_DATA, nullptr, tskIDLE_PRIORITY , &xHandle_bluetoothReceiveData, tskNO_AFFINITY);


} 

void loop() {
}

constants.h

#ifndef CONSTANTS_H_
#define CONSTANTS_H_


// related to the bluetooth
#define BLUETOOTH_STACK_SEND_DATA       1024      // used by freeRTOS to create task for sending data
#define BLUETOOTH_MSG_SEND_LEN          5       // used by queues for sending data to the task
#define BLUETOOTH_SEND_DATA_QUEUE_LEN   10      // used by queues to create the length of the queue

#define BLUETOOTH_STACK_RCV_DATA       1024      // used by freeRTOS to create task for sending data
#define BLUETOOTH_MSG_RCV_LEN          5       // used by queues for sending data to the task
#define BLUETOOTH_RCV_DATA_QUEUE_LEN   10      // used by queues to create the length of the queue


#endif

bluetooth.h

#ifndef BLUETOOTH_H_
#define BLUETOOTH_H_

#include "BluetoothSerial.h"

class Bluetooth
{
private:

    /*this is object used to access bluetooth utils*/
    BluetoothSerial SerialBT;

public:

    /*this is the name of the esp32 when being searched for*/
    const String device_name = "ESP32-Carcotech";

    /*this is the Pin number for connection with ESP for legacy devices*/
    const char *pin = "1234";

    /*constructor*/
    Bluetooth();
  
    /*destructor*/
    ~Bluetooth();

    /**
     * @brief used to receive a string from the bluetooth
     * @param dataLen: is the length of the expected data to receive from the bluetooth module
     * @param timeOutMS: is the timeOut to wait for if not all data received : put 0 to wait infinitely if didn't till recevie same dataLen
    */
    String getData(int dataLen, uint32_t timeOutMS);

    /**
     * @brief used to send data over the bluetooth module
     * @param data: this is the data to send
    */
    void sendData(String data);


    /**
     * @brief this is the initialization function, for some reason putting the intialization code in the 
     * constructor results in watchdog timer to reset the esp32 over and over and over
    */
    void init();

};

#endif

bluetooth.cpp

#include "bluetooth.h"

Bluetooth::Bluetooth()
{


}

String Bluetooth::getData(int dataLen, uint32_t timeOutMS)
{

    // this is a counter for how many data we received
    int receivedCounter = 0;

    // this is the buffer to receive in
    String data;

    // setting the timeOut
    SerialBT.setTimeout(timeOutMS);    

    // check if we should wait indefinitely or not
    if(timeOutMS == 0)
    {   

        while(SerialBT.available())
        {
            if(receivedCounter == dataLen)
                break;
            
            data += (char)SerialBT.read();
            receivedCounter++;
        }
        
    }
    else
    {

        while(SerialBT.available())
        {
            int temp = SerialBT.read();

            if(receivedCounter == dataLen || temp == -1 )
                break;
            
            data += (char)SerialBT.read();
            receivedCounter++;
        }

    }

    return data + "\0";
}

void Bluetooth::sendData(String data)
{
    SerialBT.write((const uint8_t *)data.c_str(), strlen(data.c_str()));
}

void Bluetooth::init()
{
    
    // initialization of the bluetooth
    SerialBT.begin(device_name);

    // telling the bluetooth to use that pin for connection
    SerialBT.setPin(pin);
}


Bluetooth::~Bluetooth()
{
}

问题排查与解决

核心问题分析

  1. sendData任务空循环抢占CPU:使用xQueueReceive(..., (TickType_t)0)非阻塞读取,队列空时立刻返回并进入下一次循环,持续占用CPU时间片。两个任务优先级均为tskIDLE_PRIORITY,导致receiveData任务无法获得执行机会。
  2. 蓝牙接收逻辑无效:getData函数中,timeOutMS=0时仅检查一次SerialBT.available(),无数据时直接返回空字符串,receiveData任务循环执行但实际没读到有效数据。
  3. 内存泄漏:sendData任务取出队列中的堆内存后未释放,长期运行会耗尽堆空间。
  4. 字符串越界风险:BLUETOOTH_MSG_SEND_LEN和BLUETOOTH_MSG_RCV_LEN设为5,但字符串需要末尾\0,strcpy时若源字符串长度超过限制会导致内存越界。

修正方案

1. 改造sendData任务为阻塞式读取

将队列读取超时改为portMAX_DELAY,队列空时任务进入阻塞状态,主动让出CPU:

void sendData(void* pvParameters)
{
    bluetoothSendMsg_t *sendMessage;

    while (1)
    {
        if(xQueue_bluetoothSendData != NULL && xQueueReceive(xQueue_bluetoothSendData, &sendMessage, portMAX_DELAY) == pdPASS)
        {
            Serial.print(sendMessage->data);  
            bluetooth.sendData(sendMessage->data);
            vPortFree(sendMessage); // 释放堆内存,避免泄漏
        }
    }
}

2. 修复蓝牙接收逻辑

修改getData函数,timeOutMS=0时循环等待直到收到指定长度数据,同时修复原代码中重复读取的bug:

String Bluetooth::getData(int dataLen, uint32_t timeOutMS)
{
    int receivedCounter = 0;
    String data;
    SerialBT.setTimeout(timeOutMS);    

    if(timeOutMS == 0)
    {   
        // 循环等待足够长度的数据
        while(receivedCounter < dataLen)
        {
            if(SerialBT.available())
            {
                data += (char)SerialBT.read();
                receivedCounter++;
            }
            vTaskDelay(pdMS_TO_TICKS(1)); // 短延时让出CPU
        }
    }
    else
    {
        while(receivedCounter < dataLen)
        {
            int temp = SerialBT.read();
            if(temp == -1) break; // 超时或无数据
            
            data += (char)temp;
            receivedCounter++;
        }
    }

    return data;
}

同时优化receiveData任务,添加延时并使用strncpy避免越界:

void receiveData(void* pvParameters)
{
    bluetoothRcvMsg_t receivedMsg;
    bluetoothSendMsg_t *messageTobeSend;

    while (1)
    {
        memset(receivedMsg.data, 0, sizeof(receivedMsg.data));
        String tempData = bluetooth.getData(BLUETOOTH_MSG_RCV_LEN - 1, 0); // 预留\0位置
        strncpy(receivedMsg.data, tempData.c_str(), sizeof(receivedMsg.data)-1);

        Serial.println("test");

        if(receivedMsg.data[0] != '\0')
        {
            messageTobeSend = (bluetoothSendMsg_t*) pvPortMalloc(sizeof(bluetoothSendMsg_t));
            memset(messageTobeSend->data, 0, sizeof(messageTobeSend->data));
            strncpy(messageTobeSend->data, receivedMsg.data, sizeof(messageTobeSend->data)-1);
            
            Serial.println(messageTobeSend->data);
            xQueueSend(xQueue_bluetoothSendData, &messageTobeSend, portMAX_DELAY); // 阻塞式发送避免丢数据
        }

        vTaskDelay(pdMS_TO_TICKS(1)); // 让出CPU
    }
}

3. 可选:调整任务优先级

若需receiveData任务优先执行,可给它设置更高优先级:

xTaskCreatePinnedToCore(sendData, "Send data to bluetooth", BLUETOOTH_STACK_SEND_DATA, nullptr, tskIDLE_PRIORITY , &xHandle_bluetoothSendData, tskNO_AFFINITY);
xTaskCreatePinnedToCore(receiveData, "receive dat from the bluetooth", BLUETOOTH_STACK_RCV_DATA, nullptr, tskIDLE_PRIORITY + 1 , &xHandle_bluetoothReceiveData, tskNO_AFFINITY);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 19:55:02