c++arduino-esp32

C++ ESP32 randomly stops


I am developing for my ESP32-Wroom microcontroller in Arduino C++. I am relatively new in programming for this board (and C++ in general).

Currently my code works fine, until it doesn't. When I reset the controller, I keep getting different outputs in the Serial Monitor indicating the program simply stopped.

This is my setup:

I have created a TasksQueue class which queues tasks and runs them on a specific core. That way, if I am on core 1 and want to run some code on core 0, I can simply queue it.

This is the class:

class Task
{
public:
    virtual void start() = 0;
    /// @brief Updates the task's state.
    /// @return true if the task is completed, and false otherwise.
    virtual bool update() = 0;
};

class TasksQueue
{
public:
    TasksQueue(TasksQueue const &) = delete;
    void operator=(TasksQueue const &) = delete;

    static TasksQueue &getInstance()
    {
        // Source: https://stackoverflow.com/a/1008289
        static TasksQueue instance;
        return instance;
    }

    void start(BaseType_t coreId = 1 - ARDUINO_RUNNING_CORE)
    {
        xTaskCreateUniversal(taskUpdateHandler, "tasks queue", getArduinoLoopTaskStackSize(), 
this, 1, NULL, coreId);
    }

    void enqueueTask(std::unique_ptr<Task> task);

private:
    std::mutex lock;
    std::queue<std::unique_ptr<Task>> pendingTasksQueue;
    std::vector<std::unique_ptr<Task>> executedTasks;

    TasksQueue() {}

    void processPendingTasks();

    void removeCompletedTasks();

    static void taskUpdateHandler(void *parameter)
    {
        TasksQueue *instance = static_cast<TasksQueue *>(parameter);
        Serial.println(F("Tasks queue running"));
        while (true)
        {
            // Serial.println(F("Hi"));
            instance->processPendingTasks();
            instance->removeCompletedTasks();
            delay(1);
        }
        Serial.println(F("Tasks queue stopped"));
    }
};

And to test it I have the following function:

void debug(void *params)
{
  TasksQueue::getInstance().enqueueTask(std::unique_ptr<Task>(new TemperatureRequestTask(internalTemperatureSensor)));
  vTaskDelete(nullptr);
}

Which I also register using xTaskCreateUniversal, but on the second core.

And, as I mentioned, the output changes every time I reset the controller. Sometimes it runs all the way and sometimes it just stops.

In any case, the while (true) in TasksQueue::taskUpdateHandler function just seems to stop. At some point I don't get any more prints to the serial monitor.

I added prints in TasksQueue::processPendingTasks and in TasksQueue::removeCompletedTasks and at some point they just stop. Sometimes at the beginning sometimes after few seconds.

Interestingly enough, when I add a Serial.print inside the loop it self like so:

static void taskUpdateHandler(void *parameter)
{
    TasksQueue *instance = static_cast<TasksQueue *>(parameter);
    Serial.println(F("Tasks queue running"));
    while (true)
    {
        Serial.println(F("Hi"));
        instance->processPendingTasks();
        instance->removeCompletedTasks();
        delay(1);
    }
    Serial.println(F("Tasks queue stopped"));
}

It suddenly works great every time.

My only guess is that the task is being deleted for some reason when it doesn't have enough load? IDK...

Does anyone know how to fix this issue?

=== EDIT===

I noticed that instead of usingSerial.println(F("Hi")); I can increase the delay in delay(1);. So my guess is sorta right 🤷‍♂️

But I have no clue what value to use for the delay, and why it matters.

=== EDIT 2=== After looking at how the loop function works, since it is also an infinite loop, I was able to improve the code but still a hit or a miss even though it is a copy-paste...

static void taskUpdateHandler(void *parameter)
{
    static uint64_t lastYield = 0;
    TasksQueue *instance = static_cast<TasksQueue *>(parameter);
    Serial.println(F("Tasks queue running"));
    while (true)
    {
        uint64_t now = millis();
        if ((now - lastYield) > 2000)
        {
            lastYield = now;
            vTaskDelay(5); // delay 1 RTOS tick
        }
        esp_task_wdt_reset();
        instance->processPendingTasks();
        instance->removeCompletedTasks();
    }
    Serial.println(F("Tasks queue stopped"));
}

Note: If I use the loop function instead (by making processPendingTasks and removeCompletedTasks public and calling them) the code functions flawlessly. I avoid doing so just because it limits me to run on a specific core.


Solution

  • After trying various things and looking at the source code to see how the loop function works behind the scenes, I finally got to something that seems to work all the time (I'll need to do some more testing).

    Basically the main thing I had to do in my case, is to make sure that there aren't other tasks on that core.
    Since I ran the TasksQueue on the Arduino's loop core it caused some conflict with the loop function.

    So all I had to add was a simple vTaskDelete(NULL); in the end of the setup function to remove the loop function's task.

    Two things I am still not sure about are:

    1. The delay in the TasksQueue::taskUpdateHandler. Currently it is set to delay(1) every iteration, but in the Arduino's loop code it only delays every 2 seconds

    2. Weather or not I should call esp_task_wdt_reset, like in the Arduino's loop. It seems to work fine without it 🤷‍♂️