Page 1 of 1

how to effectively use ESP timer interrupt callback.

Posted: Tue Nov 01, 2022 4:41 pm
by Swagger
I was working on a project where I should send some data to a cloud infrastructure at a specific frequency.

So I was trying to figure out an apt method to do so.

methods am trying :
1) We could create a task and set up a function to send the data to every specific interval and use task delay for the same.
2) We could create a timer interrupt that will trigger on every interval as set to the timer And will be pushed to call back.

Now I believe timer interrupt will be a better option as we will not need the task to be running all the time.

Now, if we use a timer, we can use a callback function to get triggered every interval.


But upon looking at the esp32 documentation for timer callback:

"ESP_TIMER_TASK. Timer callbacks are dispatched from a high-priority esp_timer task. Because all the callbacks are dispatched from the same task, it is recommended to only do the minimal possible amount of work from the callback itself, posting an event to a lower-priority task using a queue instead.
"
Link: https://docs.espressif.com/projects/esp ... timer.html


So here arise my problems. Should I use a Queue to be sent from the callback function to a low-priority task which will, in turn, process the data as per my requirement, or should I use a binary semaphore to notify the task?

If I am using the Queue, the task will be in waiting, and not sure whether that will be using my resources. If I am using a binary semaphore to notify the task, I believe the task will be in a suspended state till it gets the semaphore. (As per my understanding) And I believe a suspended task will be only using lesser resources.

So if anyone can point out the best method for me, it will be appreciated. Also, any other alternative way which you believe will be the right path to follow? As resource management is a concern for me.

Thanks in advance.

Re: how to effectively use ESP timer interrupt callback.

Posted: Tue Nov 01, 2022 10:01 pm
by davidzuhn
My thoughts are that the HW timers should be used when dealing with either very short repeat cycles or when dealing with specific hardware peripherals. Things like "update the cloud every so often" don't need to resolution available with the HW timers (the latency with the network will throw off the timing more than the OS issues).

I have network update tasks which run several times a second (LAN, not cloud), and I have a task which runs that spends almost all of its time in a vTaskDelay call:

Code: Select all

while (true) {
    vTaskDelay(pdMS_TO_TICKS(1000)/ N);   // N is measured in Hz
    // gather the current state
    // format for the network protocol (JSON usually, but sometimes protobuf)
    // send data to the network handler task
}
I don't find the overhead of a separate task onerous, and the simplicity makes this pretty simple to come back to and understand several months later. Since I have several things which can generate an event on the network, there is a single task that handles all of that I/O, and I copy the outbound data to that task as an event. This leaves very little opportunity for this timer task to have to deal with I/O errors or other conditions which can lead to a need for an increased stack size.

Yes, there's a task "running", but it's not consuming CPU in a busy wait, it's held by FreeRTOS in the vTaskDelay.

I also use a pattern (like Arduino) where my app_main will run setup() and then go into a forever loop. This loop will increment a counter every second (and not really do anything else except manage the pilot light and perhaps some other very low level debugging support) and then post an event to a primary controller which can then decide what to do (read this item every n second, do that thing every 60, etc.). I'll do both of these in just about any of my larger programs for embedded systems.

Code: Select all

void
loop()
{
    const int ms_between_ticks = 50;
    constexpr int ticks_per_second = 1000 / ms_between_ticks;

    constexpr int ticks_per_second = 1000 / ms_between_ticks;

    static uint64_t counter = 0;
    static TickType_t lastWakeTime = xTaskGetTickCount();

    vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(ms_between_ticks));

    mainController.postOneSystemTick();

    if ((++counter % ticks_per_second) == 0) {
        mainController.postOneSecondTick();
    }

#if CONFIG_BKT_RUN_HEAP_INTEGRITY_CHECK
    if ((++counter % (CONFIG_BKT_HEAP_INTEGRITY_CHECK_INTERVAL * ticks_per_second)) == 0) {
        ESP_LOGI(TAG, "checking heap integrity");
        if (!heap_caps_check_integrity_all(true)) {
            ESP_LOGE(TAG, "***** heap integrity failure");
            // DO SOMETHING FUNKY HERE....
            hw.pilotLight.setOnOffTime(100, 100);

        }
    }
#endif

#if CONFIG_BKT_PRINT_HEAP_INFO
    if ((++counter % (CONFIG_BKT_PRINT_HEAP_INFO_INTERVAL * ticks_per_second)) == 0) {
        ESP_LOGI(TAG, "heap info");
        heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);
        show_heap_info();
    }
#endif
}



extern "C"
void
app_main()
{
    ESP_LOGE(TAG, "start of app_main");

    setup();

    while(true) {
        loop();
    }

    ESP_LOGE(TAG, "this should never happen");
    // NEVER EXIT
}
Everything else that needs to run is done in another task that's started in setup().

This is absolutely not the only way to do things, but it does work well for my purposes.


david zuhn