Both sides previous revisionPrevious revisionNext revision | Previous revision |
en:iot-open:practical:hardware:sut:esp32:adv1_1 [2024/03/25 20:08] – [Steps] pczekalski | en:iot-open:practical:hardware:sut:esp32:adv1_1 [2024/03/26 08:46] (current) – [Steps] pczekalski |
---|
====== ADV1: Using timers to execute code asynchronously ====== | ====== ADV1: Using timers to execute code asynchronously ====== |
It is advised to use timers that periodically execute a function to handle repeating tasks. Hardware timers work parallel to the CPU and consume few CPU resources. ESP32-S3 has 4 hardware timers, but each timer can execute multiple handlers. | It is advised to use timers that periodically execute a function to handle repeating tasks. Hardware timers work parallel to the CPU and consume few CPU resources. ESP32-S3 has 4 hardware timers, but each timer can execute multiple handlers. You can think about these handlers as they are interrupted handling functions, but instead of externally triggered interrupts, those are initiated internally by the hardware timer.\\ |
The idea of using the timer is to encapsulate a piece of compact code that can be run virtually asynchronously and executed is a precisely-defined time manner. In this scenario, we use a timer to update the LCD screen periodically. We choose a dummy example where Timer 1 is used to increment a value of the ''byte'' type, and Timer 2 reads this value and writes it on the LCD. Naturally, a collision may occur whenever two processes are to access a single memory block (variable). It is critical when both processes (tasks, asynchronous functions) are writing to it. However, in our example, the handler executed by Timer 1 only writes to the variable, while Timer 2 only reads from it. In this scenario, there is no need to use mutexes (semaphores). A scenario with detailed description of when to use mutexes is beyond the scope of this example. | The idea of using the timer is to encapsulate a piece of compact code that can be run virtually asynchronously and executed is a precisely-defined time manner. In this scenario, we use a timer to update the LCD screen periodically. We choose a dummy example where Timer 1 is used to increment a value of the ''byte'' type, and Timer 2 reads this value and writes it on the LCD. Naturally, a collision may occur whenever two processes are to access a single memory block (variable). It is critical when both processes (tasks, asynchronous functions) are writing to it. However, in our example, the handler executed by Timer 1 only writes to the variable, while Timer 2 only reads from it. In this scenario, there is no need to use mutexes (semaphores). A scenario with a detailed description of when to use mutexes is beyond the scope of this example. |
| |
| |
<note tip>If you do need to use floating point calculations in ISR or timer functions, you can use ''double'' as this type is not hardware accelerated in ESP32s and is software implemented. Note, however, that there is a significant performance drop between ''float'' (faster) and ''double'' (slower) calculations.</note> | <note tip>If you do need to use floating point calculations in ISR or timer functions, you can use ''double'' as this type is not hardware accelerated in ESP32s and is software implemented. Note, however, that there is a significant performance drop between ''float'' (faster) and ''double'' (slower) calculations.</note> |
| |
| <note important>Timer handlers are marked with ''IRAM_ATTR'' to be kept in RAM rather than flash. It is because of the performance. Interrupt and Timer functions should be kept as simple as possible. A watchdog exception can be thrown if more complex tasks are to be done and handler execution takes too long, particularly when the previous execution is not finished before the next one starts. This sort of problem may require fine-tuning the timer frequency and eventually changing the algorithm to set up only a flag in the handler that is later detected and handled in the ''loop()'' function.</note> |
=== Step 6 === | === Step 6 === |
Configure timers, start LCD and enable timers. Note the correct order: LCD have to be ready when the timer calls ''LCD.write(...);'': | Configure timers, start LCD and enable timers. Note the correct order: LCD have to be ready when the timer calls ''LCD.write(...);'': |
| |
=== Step 7 === | === Step 7 === |
This way, a main loop is empty: everything runs asynchronously, thanks to the timers. | This way, a main loop is empty: everything runs asynchronously, thanks to the timers. As suggested above, when timer handlers require long or unpredictable execution time (e.g. external communication, waiting for the reply), handlers should set a flag that is read in the main loop to execute the appropriate task and then clear the flag. |
<code c> | <code c> |
void loop() | void loop() |