Harith 75
Harith 75

Reputation: 13

ESP32 Task Watchdog Triggered

I am working on an ESP32 project where I need to read luminosity data in a continuous loop. Despite following various recommendations, including creating a task with tskIDLE_PRIORITY and increasing the watchdog timeout, I am still facing issues with the task watchdog being triggered. Below is a detailed description of my problem, the code, and the steps I have taken so far.

ESP IDF version : 5.2

When running my code, the task watchdog gets triggered with the following message:

I (15255) MONIDAT: Measurement 86800: Luminosity: 3
I (15265) MONIDAT: Measurement 86900: Luminosity: 3
I (15285) MONIDAT: Measurement 87000: Luminosity: 3
I (15305) MONIDAT: Measurement 87100: Luminosity: 3
E (15305) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
E (15305) task_wdt:  - IDLE0 (CPU 0)
E (15305) task_wdt: Tasks currently running:
E (15305) task_wdt: CPU 0: main
E (15305) task_wdt: CPU 1: IDLE1
E (15305) task_wdt: Print CPU 0 (current core) backtrace


Backtrace: 0x4200D673:0x3FC93300 0x4200DA90:0x3FC93320 0x40377175:0x3FC93350 0x4200AF31:0x3FC99090 0x4200B10C:0x3FC990C0 0x4200B1D3:0x3FC99100 0x4200BE12:0x3FC99130 0x42008436:0x3FC991D0 0x4200849B:0x3FC99200 0x420085E9:0x3FC99240 0x4201B877:0x3FC992A0 0x4037A9E1:0x3FC992D0
0x4200d673: task_wdt_timeout_handling at C:/ESP/components/esp_system/task_wdt/task_wdt.c:441
0x4200da90: task_wdt_isr at C:/ESP/components/esp_system/task_wdt/task_wdt.c:515
0x40377175: _xt_lowint1 at C:/ESP/components/xtensa/xtensa_vectors.S:1240
0x4200af31: s_i2c_send_commands at C:/ESP/components/driver/i2c/i2c_master.c:433 (discriminator 1)
0x4200b10c: s_i2c_transaction_start at C:/ESP/components/driver/i2c/i2c_master.c:509
0x4200b1d3: s_i2c_synchronous_transaction at C:/ESP/components/driver/i2c/i2c_master.c:780
0x4200be12: i2c_master_transmit_receive at C:/ESP/components/driver/i2c/i2c_master.c:995
0x42008436: tsl2591_read_register at C:/ESP/examples/get-started/lisi_monidat/main/lisi_monidat.c:34
0x4200849b: measure_task at C:/ESP/examples/get-started/lisi_monidat/main/lisi_monidat.c:55
0x420085e9: app_main at C:/ESP/examples/get-started/lisi_monidat/main/lisi_monidat.c:98
0x4201b877: main_task at C:/ESP/components/freertos/app_startup.c:208
0x4037a9e1: vPortTaskWrapper at C:/ESP/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:134        


I (15375) MONIDAT: Measurement 87200: Luminosity: 3
I (15385) MONIDAT: Measurement 87300: Luminosity: 3
I (15405) MONIDAT: Measurement 87400: Luminosity: 3
I (15425) MONIDAT: Measurement 87500: Luminosity: 3

But then the code conitunue to run it seems that the WDT is not blocking. For example with a WD set to 6sec in the menue config, each 6 sec I will have a WDT. SO my function will have a real time of +-22s of execution but is timed 17second by the elepsed time.

I attempted to create a task with tskIDLE_PRIORITY as recommended here , but it did not resolve the issue.

I then increased the watchdog timeout value from 6 seconds to 20 seconds in menuConfig, which worked temporarily because my loop takes 17.21 seconds. However, when increasing the number of measurements to 200,000, the loop exceeds 20 seconds, causing the watchdog to trigger again.

Adding a delay (e.g., 10ms) avoids triggering the watchdog, but any delay below 10ms results in an infinite loop due to the default tick period of 10ms.

Code Snippet :

void measure_task(i2c_master_dev_handle_t dev_handle)
{
    uint8_t data[2];
    int num_measurements = 100000;
    int64_t start_time = esp_timer_get_time();

    for (int i = 0; i < num_measurements; i++)
    {
        ESP_ERROR_CHECK(tsl2591_read_register(TSL2591_REGISTER_CHAN0_LOW, data, 2,dev_handle));
        uint16_t luminosity = (data[1] << 8) | data[0];
        // Optionally log every 1000th measurement to avoid overwhelming the log
        if (i % 100 == 0) {
        ESP_LOGI(TAG, "Measurement %d: Luminosity: %d", i, luminosity);
        }
    }

    int64_t end_time = esp_timer_get_time();
    float elapsed_time_sec = (end_time - start_time) / 1000000.0;
    ESP_LOGI(TAG, "Completed %d measurements in %.2f seconds good job", num_measurements, elapsed_time_sec);

}

Steps Taken :

  1. Created a Task with tskIDLE_PRIORITY: Followed suggestions to create a task with tskIDLE_PRIORITY, but it did not solve the issue.
  2. Increased Watchdog Timeout: Changed the watchdog timeout from 6 seconds to 20 seconds, which worked temporarily, but increasing the loop count caused the issue to reappear.
  3. Added Delay: Adding a delay (10ms) prevents the watchdog from triggering, but delays below 10ms result in the loop being considered infinite.
  4. Creating a task and pinned the task to core 1
  5. Increasing the priority of the task

Any help or suggestions would be greatly appreciated. Thank you!

Upvotes: 0

Views: 1708

Answers (2)

user4028
user4028

Reputation: 174

As you discovered, using RTOS the shortest delay is vTaskDelay(1) for 1 tick which with a 100 Hz frequency is 10 ms.

Since you just want to tickle the task watchdog you can just use the function esp_task_wdt_reset().

Note that even this won't fix the RTOS problem of not deleting tasks that were self-deleted (i.e. using vTaskDelete(NULL);). Such self-deleted tasks only get actually deleted in the idle task. The idle task also tickles the watchdog timer. So you can accomplish both by using the FreeRTOS idle hook function you can register with the following done once during startup:

esp_register_freertos_idle_hook_for_cpu(idle_hook, (UBaseType_t)xPortGetCoreID();

This "idle_hook" function can be something like the following:

DRAM_ATTR static uint8_t waiting_for_idle = 0;
DRAM_ATTR uint8_t idle_task_was_run = 0;
static bool idle_hook(void)
{
    idle_task_was_run = 1;
    if (waiting_for_idle) {
        waiting_for_idle = 0;  // we only want to notify once
        xTaskNotifyIndexed(mainTaskHandle, TASK_INDEX_IDLE_RUN, 0, eNoAction);
        return false;  // do not idle (i.e. do not wait for interrupt) as we want main task to run
    }

    return true;  // allows idle task to idle (i.e. to wait for interrupt)
}

Then you can call the following "run_idle_task" function whenever you want to run the idle task, but you should only do this when you know nothing else is running in a tight loop. That is, you can call this from your main tight loop.

void run_idle_task(void)
{
    if (xTaskGetCurrentTaskHandle() != mainTaskHandle) {
        printf( "Must call tasknotify_run_idle_task from main task!\r\n");
        vTaskDelay(1);  // hope that a single 1 tick delay will run the idle task
        return;
    }

    waiting_for_idle = 1;  // will have idle_hook() notify main task when it is run

    // The following suspends the main task so that the idle task can run.
    // It waits for the idle task to finish running so releasing memory from any
    // deleted tasks and resetting the (idle) task watchdog timer.
    xTaskNotifyWaitIndexed(TASK_INDEX_IDLE_RUN, 0, 0, NULL, portMAX_DELAY);

    // waiting_for_idle is set to 0 in idle_hook() just prior to the notify
}

In your startup code you need the following:

TaskHandle_t mainTaskHandle = 0;
:
// in your app_main function:
    mainTaskHandle = xTaskGetCurrentTaskHandle();

Upvotes: 0

0___________
0___________

Reputation: 67835

It is not the same as programming in Linux. freeRTOS does not have round robin scheduling by default. Your task does not pass the control to the scheduler (until it ends) and you do not reset the watchdog - thus watchdog kicks off. You deadlocked the freeRTOS

add vTaskDelay(1); or taskYIELD(); in the loop to let other tasks run.

Upvotes: 1

Related Questions