STM32 general purpose timer as a trigger for multiple time dependant events
Imagine, you're building a TDMA application with an STM32 controller and you're solving the problem of precise timing for multiple events.
TDMA stands for Time Division Multiple Access. It is a channel access method that allows multiple users to share a common data channel (e.g. the same frequency) by dividing the signal into different time slots.
In the followong playground TDMA system with three time slots, the master transmits in slot #0 and it receives from three other users in slots #1, #2. The transmit function is called at time T0=0, and the receive functions are called at times T1, and T2, respectively. The later times are all based on the master clock T0, but they may be slightly adjusted to compensate for the delays of the remote devices.
A neat way to solve this timing issue is to use a timer (e.g., TIM2) with multiple channels to generate all the required times. The timer overflow event can be used as the master clock, which triggers the transmit function. Two output compare interrupts can be used to trigger the receive functions. The times of the receive functions can be easily adjusted by changing the channels' OCx values.
The following picture shows the timing we're trying to acheive:
The orange dot is the time when the master transmits. The red and blue are the two receving time slots.
To begin, we need to configure the timer. We enable the timer by selecting the internal clock source and setting the total TDMA period between consecutive master transmissions. In this example it is set to 1 sec.
Next, we configure the Channels 1 and 2 as Output Compare No Output. We select the mode "Active on Match" and CH polatity to Low. Other important changes are highlighted.
We also have to enable the timer interrupt.
Importantly, we wanted to use the overflow event as the trigger to start transmission. However, as we configured the timer in OC mode, the overflow interrupt is not set. We need to do it manually in the code.
Here are the changes to our main:
/* USER CODE BEGIN 2 */
HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_2);
// When starting OC mode, the overflow interrupt is not enabled by default.
// So we do it manually using "Update Event" trigger.
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
/* USER CODE END 2 */
and these:
/* USER CODE BEGIN WHILE */
while (1)
{
// Some delay. We don't want to call ITM data transfer too often
for (int i=0; i<1000; i++)
asm("nop");
// Read counter to a variable. This operation triggers the ITM data transfer
// that you will see on the SWV Data Trace plot
cnt = htim2.Instance->CNT;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
Also we need the inteerupt handlers. You may choose to register your own handlers, but personally I prefer using the weakly default handlers provided ny HAL:
/* USER CODE BEGIN 4 */
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1){
// call Receive1() function
a = 1;
}
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2){
// call Receive2() function
b = 1;
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
// call Transmit() function
a=0;
b=0;
}
/* USER CODE END 4 */
The variables cnt, a and b I'm using to plot the timer internals using ITM mechanism via SWV.
One last note. Be careful when setting the Pulse value of the OC channel to small numbers. A very short pulse (e.g., value=1) may result in unpredictable behavior.
The full code is available here:
https://github.com/aupilot/stm32-timer-multiple-irqs
Please read my other posts. Your comments are welcome and appreciated!
Comments
Post a Comment