Today, Sibille and I ran the final tests for the led-driving PCBs software using Vlaya’s SPI generation code. We wanted to make sure that if the main board sends an entire SPI frame (24 LED configuration instructions and a “TOP” command), the CPU managed to configure all the LEDs and turn them on. It does 🙂
On the nucleo devboard, we can observe 20 out of 24 timers. Have a look at the outputs we measured with the logic analyser :
Here is a link to the processor Reference manual. We will briefly explain our code, and give you the main pages of the RM we used. If you want to know more about the algorithm or have any question, feel free to comment !
- At the beginning of the main function, the CPU initializes and configures the clocks, GPIOs, SPI bus and timers.
- Then it goes to sleep and only wakes up for interrupts. To do so, we use the WFI instruction after setting the SLEEPONEXIT bit in the appropriate register to go directly back to sleep after handling an interrupt. (cf Programming Manual pp. 42, 104 and 136)
- Any SPI reception triggers an interrupt which wakes up the CPU to run the SPI IRQ Handler.
- If the received data corresponds to a LED configuration, the SPI handler configures the appropriate LED accordingly (writing to the concerned CCR register, cf RM pp. 368 and following).
- If it is a “TOP” command, it configures the number of PWM repetitions by setting the repetition counter register (RCR) of TIM1 (cf RM p. 317) and turns on the LEDs by enabling the timers counters (cf RM p. 415).
- After a given number of PWM repetitions (encoded in the TOP command) a second interruption is triggered using the TIM1 repetition counter update event. It turns the LEDs off with the procedure described below.
We set the duty cycle width to zero (taken into account on the next cycle) and enable One-pulse mode to freeze the timers also at the next PWM cycle, just after the outputs go low from the zero-width duty cycle.
If we simply forced the outputs low (there are several ways to do this), we would be likely to stop the PWM short in the middle of a cycle, resulting in the last cycle having a shorter width.
What we want is for the output to be driven low only after the current PWM cycle is over. To this end, we can simply write 0 to the preload register where the duty cycle is stored (CCR register). This new value is only taken into account at the end of the current cycle (to do this the preloader must be enabled in the initial configuration, cf RM 13.3.10, p. 328).
Except now we have another problem : with this method we never stopped the PWM (we merely set a duty cycle width of zero). So the next time we configure a LED color we’ll write some new value in the duty cycle register and at the following cycle the LED will turn on – before we ever fire the LEDs.
To avoid this we’d have to freeze the timer, so that it stops counting through PWM cycles. Except that if we freeze the timer, we’ll have to drive the outputs low immediately and not at the end of the cycle (which would never come, since the timer is frozen). Seems like an inextricable situation :/
We could simply stop the timer at the time of configuring the color, just before writing to the CCR register. However this is a bit clunky because any timer can have up to four channels which are configured separately, so we’d end up stopping the same timer four times. More importantly we want the color configuration procedure to be as short as possible, so as to minimize the risk of taking too long in the SPI interrupt handler and missing the next transmission.
But there is a better solution : what we actually want when turning off the LEDs is for the timer to freeze at the next cycle, just after accepting the new zero-width duty cycle. This can be done by enabling One-pulse mode (cf RM 13.3.15, p. 338) : what it does is precisely to freeze the timer at the end of the current cycle (this is normally used to generate a single pulse of desired width after a desired delay, but it works for us). Now the next time we’ll configure a LED color, it won’t immediately be taken into account since the counter is frozen. All that’s left to do is to remember to restart the timer in the fire command.
That’s all for now. Next we’ll load the code from the Flash (for now it’s directly loaded to RAM by the debugger). Also, we’ll adapt the code to the top_pcb (same processor but routed differently and drives one additional LED).
We’ll keep you posted 🙂