the code is We should first know the embassy_executor::InterruptExecutorInterrupt mode executorAs it goes to suggest thread-mode Executor, Let’s seeBut how does the timer work?then let’s dive into the work flow of timer and its interrupt.init partTrouble shootingit seems the feature change doesn’t work, tim hz doesn’t match description:Solultion Process:
the code is
We should first know the embassy_executor::InterruptExecutor
Because we use it to construct the High and Med task, It is important to dig into it.
from embassy api:
Interrupt mode executor
This executor runs tasks in interrupt mode. The interrupt handler is set up to poll tasks, and when a task is woken the interrupt is pended from software.
I suspect this in cortex-M4 of stm32 would be (notice that require Privilege):
about the privilege control:
you will find our example or say in the embassy-stm32 design, it runs in privilege mode for task
This allows running async tasks at a priority higher than thread mode. One use case is to leave thread mode free for non-async tasks. Another use case is to run multiple executors: one in thread mode for low priority tasks and another in interrupt mode for higher priority tasks. Higher priority tasks will preempt lower priority ones.
It is even possible to run multiple interrupt mode executors at different priorities, by assigning different priorities to the interrupts. For an example on how to do this, See the ‘multiprio’ example for ‘embassy-nrf’.
To use it, you have to pick an interrupt that won’t be used by the hardware. Some chips reserve some interrupts for this purpose, sometimes named “software interrupts” (SWI). If this is not the case, you may use an interrupt from any unused peripheral.
It is somewhat more complex to use, it’s recommended to use the thread-mode [
Executor
] instead, if it works for your use case.As it goes to suggest thread-mode Executor, Let’s see
Thread mode executor, using WFE/SEV.
This is the simplest and most common kind of executor. It runs on thread mode (at the lowest priority level), and uses the
WFE
(short for “Wait For Event”) ARM instruction to sleep when it has no more work to do. When a task is woken, a SEV
instruction is executed, to make the WFE
exit from sleep and poll the task.This executor allows for ultra low power consumption for chips where
WFE
triggers low-power sleep without extra steps. If your chip requires extra steps, you may use raw::Executor
directly to program custom behavior.But how does the timer work?
first you should refer to Trouble shooting to set a proper timer.
then let’s dive into the work flow of timer and its interrupt.
init part
first see the Upcounting mode
In upcounting mode, the counter counts from 0 to the auto-reload value (content of the
TIMx_ARR register), then restarts from 0 and generates a counter overflow event.
An Update event can be generated at each counter overflow or by setting the UG bit in the
TIMx_EGR register (by software or by using the slave mode controller).
The UEV event can be disabled by software by setting the UDIS bit in TIMx_CR1 register.
This is to avoid updating the shadow registers while writing new values in the preload
registers. Then no update event occurs until the UDIS bit has been written to 0. However,
the counter restarts from 0, as well as the counter of the prescaler (but the prescale rate
does not change). In addition, if the URS bit (update request selection) in TIMx_CR1
register is set, setting the UG bit generates an update event UEV but without setting the UIF
flag (thus no interrupt or DMA request is sent). This is to avoid generating both update and
capture interrupts when clearing the counter on the capture event.
this action corresponding to the code :
When an update event occurs, all the registers are updated and the update flag (UIF bit in
TIMx_SR register) is set (depending on the URS bit):
- The buffer of the prescaler is reloaded with the preload value (content of the TIMx_PSC register)
- The auto-reload shadow register is updated with the preload value (TIMx_ARR)
And next see the embassy-stm32 set the overflow and half-overflow interrupts:
according to the manual:
and the pac code :
we can figure that the code in init set the UIE and CC1IE to 1 (enable them)
see the following line seems to unpend the interrupt and enable it.
this setting is to set the nvic to enable the interrup.
Trouble shooting
it seems the feature change doesn’t work, tim hz doesn’t match
description:
I config the time tree to my board(As it has outer HSE with 8Mhz,I can use PLL to use a 84Mhz max sysclk and AHB), but the tim seems wrong. The change setting in
embassy-time = { version = "0.3.0", path = "../../embassy-time" , features = ["tick-hz-48_000_000","defmt"] }
doesn’t work.tick-hz-48_000_000
It does set the register but our frequency setting is too high so that it can’t be precise.
Solultion Process:
According to my analysis, I need to config the tim manually.
first of all: reference the fucking manual!
from—
STM32F401xB/C and STM32F401xD/E advanced Arm®-based 32-bit MCUs - Reference manual
from the manual above, we can see TIM3 is in APB1 and according our config in:
we use 84Mhz ‘s for AHB, And timer also use 84Mhz(haven’t been divided yet)
And dive into
I find embassy_stm32 config the timer in:
So let’s debug into it and see why our config didn’t take effect:
It get the correct APB1 timer frequency : 84Mhz
Found that my timer frequency setting is so high that it can’t be set a proper prescaler.
So if we change it to 100_000hz, the timer is good.
And by investigating the timer_driver code, I found that the init part only do enable the UIE and Ch1 compare/capture to set the global time, which the code called period, this seems a global timer.
But when we use Timer::after_millis(3000).await; actually we are not using the period as time counting, but we use the capture and compare time to set the alarming point time. The detail is too much about this procedure, I’ll leave it and discuss it in a single blog.