The challenge with the selection of a microcontroller for a project is: which one has the required number of UART, I2C, SPI? Combine this with the desired package (48pins, 64pins? LQFN?), the needed FLASH and RAM size and then even the hundreds of available microcontroller shrink to a handful only. And many times I need to make compromises: such as I need two hardware I2C, but the microcontroller matching all my other needs has only one I2C hardware. So I might end up with bit-banging the slower I2C bus. Doable, but not ideal.
What is cool that some of the newer NXP Kinetis microcontroller come with an interesting hardware: FlexIO. A peripheral hardware which allows me to implement a custom protocol, including driving WS2812B (Adafruit NeoPixel) LEDs with a FRDM-KL43Z board:
WS2812B Serial Protocol
The WS2812B RGB LEDs need a very specific protocol to shift the data bits: A zero bit is encoded with 0.35μs HIGH followed by 0.9μs LOW. A one bit is encoded with 0.9μs high and and 0.35μs LOW:
Bit-banging this is very ugly and requires precise timing on the assembly level. Both UART and SPI *could* be tweaked to match the protocol, but are both ugly to implement. The most elegant way I have found so far is using DMA (see https://mcuoneclipse.com/2015/08/05/tutorial-adafruit-ws2812b-neopixels-with-the-freescale-frdm-k64f-board-part-5-dma/).
Newer NXP Kinetis devices have a FlexIO peripheral present. Basically it is a number of shift registers and timers, with input and output signals:
The number of timers, shift registers and pins depend on the implementation on the microcontroller. For example the FlexIO on the KL43Z has
- 4 32bit shifters
- 4 16bit timers
- 8 input/output pins
How it works
The logic to drive the WS2812B is in the file WS2812.c on GitHub. The code has lots of comments added so should be easy to read and understand. Below are the main concept points how it works:
- One FlexIO shifter is configured to work in SPI master mode with CPHA=1. It is important that the SPI CLK and MOSI lines are changing from level 0 to 1 a exactly the same time. If CPOL=1 or CPOL=0 does not matter for the WS2812B protocol. The SPI data rate must match the WS2812B protocol (800 kHz).
- On FlexIO timer is used to control the shifter. It uses the internal 8 MHz IRC (Internal Reference Clock) as clock source.
- One FlexIO timer is used as stream generator for the ‘0’ bits. The timer is configured to send the ‘0’ waveform (0.35 μs high, 0.9 μs low), and is enabled with the SPI CLK rising edge. The timer gets disabled at the end of a SPI CLK period. As long as there is data coming from the SPI this timer keeps running to generate the ‘0’ bits.
- Another FlexIO timer is used for the stream of ‘1’ bits. It gets enabled by the rising edge of the SPI MOSI line and keeps running until disabled by a falling edge of the SPI MOSI line. This means the SPI MOSI line must be LOW at the start of a new communication cycle.
- The last FlexIO timer is set to run for a 50 μs period for latching the data values into the WS2812B at the end of the data transmission. This timer is enabled/reset by a rising edge of the SPI CLK line gets disabled when reaching the final count. This means as long as there is SPI CLK activity, this timer will get reset, and only after the SPI CLK would not be toggling for > 50 μs the timer will reach its final count, sets its flag and signal to the rest of the system the ‘latch’ code.
The important trick (and what does not seem to be documented in the FlexIO documentation) is that both the ‘1’ and ‘0’ timer output pins are on the same (!!!) pin. The FlexIO hardware is OR’ing the two signals to generate the correct signal for the WS2812B!
The above concept can be easily applied for multiple WS2812B LED stripes, but I have used it for now with a single one only.
The project is created for GNU/gcc for ARM, using NXP Kinetis Design Studio v3.x and uses the Kinetis SDK v2.x for the FRDM-KL43Z. Below are the steps to create a project (steps can be used for other boards/targets):
- If not already installed: download and install the Kinetis SDK v2.x for your board, see “First NXP Kinetis SDK Release: SDK V2.0 with Online On-Demand Package Builder“
- Start the project wizard with the menu File > New Kinetis SDK v2.x project
- Select your board/device
- Finish the wizard to create the project
- From my project on GitHub, copy Application.c, Application.h, WS2812.c and WS2812.h to your project.
- Call APP_Run() from your main() routine
I used a logic analyzer to inspect and verify the signals. For this, several signals are routed to pins so I can monitor them with a logic analyzer:
The following shows the protocol with the pins PTD6, PTD4, PTD2, PTD3, PTE20 and PTE21:
The followinig shows the details. The line with PTD6 is the data stream with the ‘1’ and ‘0’ bits sent to the strip:
I would like to thank Zeljko Stefanovic for the help and material he has provided me. His very kind support and example project was a huge help for me to get the WS2812B LEDs running with FlexIO.
The project used in this article is available on GitHub (NXP Kinetis SDK V2.0 with NXP Kinetis Design Studio V3.2.0).
- Emulating I2S with FlexIO: http://cache.freescale.com/files/microcontrollers/doc/app_note/AN4955.pdf
- Emulating UART with FlexIO: http://cache.nxp.com/files/32bit/doc/app_note/AN5034.pdf
- Emulating I2C Bus Master with FlexIO: http://cache.nxp.com/files/32bit/doc/app_note/AN5133.pdf?fpsp=1&WT_TYPE=Application%20Notes&WT_VENDOR=FREESCALE&WT_FILE_FORMAT=pdf&WT_ASSET=Documentation&fileExt=.pdf
- Generating PWM with FlexIO: http://cache.nxp.com/files/32bit/doc/app_note/AN5209.pdf?fpsp=1&WT_TYPE=Application%20Notes&WT_VENDOR=FREESCALE&WT_FILE_FORMAT=pdf&WT_ASSET=Documentation&fileExt=.pdf
- Emulating IRDA with FlexIO: http://cache.nxp.com/files/32bit/doc/app_note/AN5116.pdf?fpsp=1&WT_TYPE=Application%20Notes&WT_VENDOR=FREESCALE&WT_FILE_FORMAT=pdf&WT_ASSET=Documentation&fileExt=.pdf
- Tutorial with FRDM-K64F to drive Adafruit WS2812B NeoPixels: https://mcuoneclipse.com/2015/08/01/tutorial-adafruit-ws2812b-neopixels-with-the-freescale-frdm-k64f-board-part-1-hardware/
- Using DMA for WS281B: https://mcuoneclipse.com/2015/08/05/tutorial-adafruit-ws2812b-neopixels-with-the-freescale-frdm-k64f-board-part-5-dma/
- NeoPixels with FRDM-KL25Z board: https://mcuoneclipse.com/2014/07/13/first-adafruit-neopixel-blinks-with-the-frdm-board/