GD32/STM32 : WS2812B using Timer + DMA

Spi not good enough for  you?

 The easy way to use WS2812B programmable RGB leds is using the SPI MOSI pin.

That works fine most of the times with a couple of gotchas :

  • The SPI divider is not very flexible on the GD32/STM32 chips, you can only divide by a power of 2. So if your APB clock is not "near"  enought the WS2812B 400 us tick you are in trouble or must use complicated schemes.
  • Using a SPI "locks" the alternate functions of ~ 4 pins. So you can't use them for PMW or any other alternate functions, even if you dont actually use them.
  • The SPI pin is the one getting killed most often on my boards. I have a few MCUs that are working fine except the spi is fried. 
  • Also the 2nd /3rd SPI are using APB1, so not ok in terms of clock.
In short, Spi0 @ 72 Mhz is ok, other setup is to be looked closely at.

What's the other option ?


The other option is to use PWM + DMA.
We program a 1.2 us PWM mode 0 on a pin.
We change the duty at every cycle to create 0s and 1s.
For example, If the PWM is using a reload value of  100, we'll use 33 for a zero and 66 for a one as compare value for the PWM.
So we'll fill a 16 bits array of 33 and 66 to create the desired waveform, and transfer a new compare value at each PWM cycle.


DMA half transfer for the win

The main idea is as follows :
  • Our internal buffer is worth 2 leds, i.e. 48 bits => 48 x 16 bits =96 bytes 
  • We program a circular DMA transfer from our internal buffer to the CHCVs timer compare register
  • We enable the "half" and "full" transfer interrupts
  • When one occurs, it means the other half is being transfered, so we can fill the current half with the next LED value
  • When the last has been written we must wait 2 dma half cycles for it to be fully transferred

Pros & cons

Pros :
  • We can use any PWM capable pin linked to a DMA capable timer
  • We lock down only one PIN alternate function
  • The state at rest of the pin is zero
  • The timing is much more flexible as we can divide the APB clock by any number between 0 and 65535
  • The translation between "flat" value and PWM value is trivial
Cons:
  • Each led is 24 bits, each bits is converted to a 16 bits PWM compare value. That means that if we precompute everything, each Led will consume 48 bytes.
  • If we do it on the fly, we only consume 96 bytes whatever the  # of leds *BUT* we have a dma interrupt every 33 us. The computation takes about  4us (~ 2 us on a G32F303 @ 72 Mhz).
  • We probably lock a full timer, it's not easy to share different channels of a given timer
Possible improvement:
Using 8 bits->16 bits dma should work most of the times, that would divide by 2 the needed amount of data when precalculated. 


Comments

Popular posts from this blog

Component tester with STM32 : Part 1 ADC, Resistor

Fixing the INA3221

INA3221, weird wiring