Library allowing up to 16 strings of 60 WS2811 or WS2812 LEDs to be driven from a single FRDM-KL25Z board. Uses hardware DMA to do a full 800 KHz rate without much CPU burden.

Dependents:   Multi_WS2811_test

After being frustrated by the SPI system's performance, I ended up using an approach inspired by Paul Stoffregen's OctoWS2811. This uses 3 of the 4 DMA channels triggered by the TPM0 timer PWM and overflow events.

This design will allow for up to 16 strings of up to 60 (limited by RAM space) WS2811/WS2812 LEDs to be driven on a single port. Adding more strings takes the same time to DMA, because the bits are output in parallel.

Here is my test program:

Import programMulti_WS2811_test

Test program for my Multi_WS2811 library that started out as a fork of heroic/WS2811. My library uses hardware DMA on the FRDM-KL25Z to drive up to 16 strings of WS2811 or WS2812 LEDs in parallel.

Here's 60 LEDs on a single string, at 10% brightness: https://www.icloud.com/sharedalbum/#B015oqs3qeGdFY

Note though that the 3.3V output from the FRDM-KL25Z's GPIO pins is OUT OF SPEC for driving the 5V WS2812 inputs, which require 3.5V for a logic HIGH signal. It only works on my board if I don't connect my scope or logic analyzer to the output pin. I recommend that you add a 5V buffer to the outputs to properly drive the LED strings. I added a CD4504 to do the 3.3 to 5V translation (mostly because I had one). You could use (say) a 74HCT244 to do 8 strings.

Each LED in a string takes 24/800e3 seconds to DMA, so if MAX_LEDS_PER_STRING is set to 60, then it takes 1.8 msec to actually do the DMA, plus 64 usec of guard time, or 1.87 msec per frame (538 frames/second). Of course, actually composing the frame will take most of the time in a real program.

The way I have my code set up, I can use up to 8 pins on PORTD. However, changing the defines at the top of WS2811.cpp will change the selected port.

Alternatively, you could use another port to get more strings. Watch out for pin mux conflicts, though.

Here are your choices:

  • PORTE: 15 total: PTE0-PTE5, PTE20-PTE25, PTE29-PTE31
  • PORTD: 8 total: PTD0-PTD7
  • PORTC: 16 total: PTC0-PTC13, PTC16-17
  • PORTB: 16 total: PTB0-PTB11, PTB16-19
  • PORTA: 15 total: PTA0-PTA5, PTA12-PTA20

Here is how the DMA channels are interleaved:

/media/uploads/bikeNomad/ws2812.png

The way I have it set up to generate the three phases of the required waveform is this:

I have timer TPM0 set up to generate events at overflow (OVF), at 250 nsec (CH0), and at 650 nsec (CH1). At 1250 nsec it resets to 0.

At timer count = 0, DMA0 fires, because it's triggered by TPM0's overflow (OVF) event. This results in the data lines being driven to a constant "1" level, as the data that DMA0 is programmed to transfer is a single, all-1's word. (This is the easiest way to explain what is happening; this is the way I'd wanted it to work, but I had to use as much precious RAM as for the RGB data to hold 1's to get it to work).

At 250 nsec, DMA1 fires, because it's triggered by TPM0's CH0 compare event. This drives either a 0 or 1 level to the pins, because DMA1 is programmed to transfer our data bytes to the pins.

At 650 nsec, DMA2 fires, because it's triggered by TPM0's CH1 compare event. This results in the data lines being driven to a constant "0" level, as the data that DMA2 is programmed to transfer is a single, all-0's word.

At 1250 nsec, the timer resets to 0, and the whole cycle repeats.

Because this library uses three of timer TPM0's six channels (and sets TPM0 to 800kHz), you will need to select TPM1 or TPM2 output pins if you want to use PwmOut pins in your program (for instance, for RC servos, which want a 50Hz frequency). If you just want to change discrete LED brightnesses, you can use TPM0's CH3, CH4, or CH5 pins. Just make sure that you set up your PwmOut instance at the same frequency.

Here is a table showing the assignment of timer resources to PwmOut capable pins in the FRDM-KL25Z:

KL25Z pinArduino nameTimerChannel
PTA3TPM0CH0
PTC1A5TPM0CH0
PTD0D10TPM0CH0
PTE24TPM0CH0
PTA4D4TPM0CH1
PTC2A4TPM0CH1
PTD1D13/LED_BLUETPM0CH1
PTE25TPM0CH1
PTA5D5TPM0CH2
PTC3TPM0CH2
PTD2D11TPM0CH2
PTE29TPM0CH2
PTC4TPM0CH3
PTD3D12TPM0CH3
PTE30TPM0CH3
PTC8D6TPM0CH4
PTD4D2TPM0CH4
PTE31TPM0CH4
PTA0TPM0CH5
PTC9D7TPM0CH5
PTD5D9TPM0CH5
PTE26TPM0CH5
PTA12D3TPM1CH0
PTB0A0TPM1CH0
PTE20TPM1CH0
PTA13D8TPM1CH1
PTB1A1TPM1CH1
PTE21TPM1CH1
PTA1D0/USBRXTPM2CH0
PTB18LED_REDTPM2CH0
PTB2A2TPM2CH0
PTE22TPM2CH0
PTA2D1/USBTXTPM2CH1
PTB19LED_GREENTPM2CH1
PTB3A3TPM2CH1
PTE23TPM2CH1
Committer:
Ned Konz
Date:
Sat Jun 13 00:18:32 2015 -0700
Revision:
5:2c3b76ea0b40
Parent:
4:990838718b51
Worked out flapping and color animation

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Ned Konz 3:df4319053bfa 1 //! @file WS2811.h
Ned Konz 3:df4319053bfa 2 // Mbed library to control WS2801-based RGB LED Strips
Ned Konz 3:df4319053bfa 3 // some portions (c) 2011 Jelmer Tiete
Ned Konz 3:df4319053bfa 4 // This library is ported from the Arduino implementation of Adafruit Industries
Ned Konz 3:df4319053bfa 5 // found at: http://github.com/adafruit/LPD8806
Ned Konz 3:df4319053bfa 6 // and their strips: http://www.adafruit.com/products/306
Ned Konz 3:df4319053bfa 7 // Released under the MIT License: http://mbed.org/license/mit
Ned Konz 3:df4319053bfa 8 //
Ned Konz 3:df4319053bfa 9 /*****************************************************************************/
Ned Konz 3:df4319053bfa 10
Ned Konz 3:df4319053bfa 11 // Heavily modified by Jas Strong, 2012-10-04
Ned Konz 3:df4319053bfa 12 // Changed to use a virtual base class and to use software SPI.
Ned Konz 3:df4319053bfa 13 //
Ned Konz 3:df4319053bfa 14 // Modified by Ned Konz, December 2013.
Ned Konz 3:df4319053bfa 15 // Using three-phase DMA ala Paul Stoffegren's version.
Ned Konz 3:df4319053bfa 16 // Example:
Ned Konz 3:df4319053bfa 17 // @code
Ned Konz 3:df4319053bfa 18 // #include <mbed.h>
Ned Konz 3:df4319053bfa 19 // // In one file that includes this one,
Ned Konz 3:df4319053bfa 20 // // #define INSTANTIATE_TEMPLATES as non-zero before including this file:
Ned Konz 3:df4319053bfa 21 // #define INSTANTIATE_TEMPLATES 1
Ned Konz 3:df4319053bfa 22 // #include "WS2811.h"
Ned Konz 3:df4319053bfa 23 // // Then declare a template class with the maximum number of LEDs per strip that you will need:
Ned Konz 3:df4319053bfa 24 // unsigned const maxLEDs = 30;
Ned Konz 3:df4319053bfa 25 // template class WS2811<maxLEDs>;
Ned Konz 3:df4319053bfa 26 // // You can reduce typing using a typedef:
Ned Konz 3:df4319053bfa 27 // typedef WS2811<maxLEDs> MyWS2811;
Ned Konz 3:df4319053bfa 28 // // Later, define instances of this template class, each with up to the maximum number of LEDs:
Ned Konz 3:df4319053bfa 29 // MyWS2811 lightStrip1(nLEDs, DATA_OUT_PIN1);
Ned Konz 3:df4319053bfa 30 // MyWS2811 lightStrip2(nLEDs, DATA_OUT_PIN2);
Ned Konz 3:df4319053bfa 31 // @endcode
Ned Konz 3:df4319053bfa 32
Ned Konz 3:df4319053bfa 33 #ifndef MBED_WS2811_H
Ned Konz 3:df4319053bfa 34 #define MBED_WS2811_H
Ned Konz 3:df4319053bfa 35
Ned Konz 3:df4319053bfa 36 #include "LedStrip.h"
Ned Konz 3:df4319053bfa 37
Ned Konz 3:df4319053bfa 38 //
Ned Konz 3:df4319053bfa 39 // Configuration
Ned Konz 3:df4319053bfa 40 //
Ned Konz 3:df4319053bfa 41
Ned Konz 3:df4319053bfa 42 #ifndef WS2811_IO_PORT
Ned Konz 3:df4319053bfa 43 #define WS2811_IO_PORT PORTD
Ned Konz 3:df4319053bfa 44 #endif
Ned Konz 3:df4319053bfa 45
Ned Konz 3:df4319053bfa 46 #ifndef WS2811_IO_GPIO
Ned Konz 3:df4319053bfa 47 #define WS2811_IO_GPIO PTD
Ned Konz 3:df4319053bfa 48 #endif
Ned Konz 3:df4319053bfa 49
Ned Konz 3:df4319053bfa 50 // define WS2811_DEBUG_PIN to identify a pin in WS2811_IOPORT used for debug output
Ned Konz 3:df4319053bfa 51 // #define WS2811_DEBUG_PIN 4 /* PTD4 debugOut */
Ned Konz 3:df4319053bfa 52
Ned Konz 3:df4319053bfa 53 // Define WS2811_MONITOR_TPM0_PWM as non-zero to monitor PWM timing on PTD0 and PTD1
Ned Konz 3:df4319053bfa 54 // PTD0 TPM0/CH0 PWM_1 J2/06
Ned Konz 3:df4319053bfa 55 // PTD1 TPM0/CH1 PWM_2 J2/12 (also LED_BLUE)
Ned Konz 3:df4319053bfa 56 #define WS2811_MONITOR_TPM0_PWM 0
Ned Konz 3:df4319053bfa 57
Ned Konz 3:df4319053bfa 58 extern "C" void DMA0_IRQHandler();
Ned Konz 3:df4319053bfa 59 extern "C" void TPM0_IRQHandler();
Ned Konz 3:df4319053bfa 60
Ned Konz 3:df4319053bfa 61 template <unsigned MAX_LEDS_PER_STRIP>
Ned Konz 3:df4319053bfa 62 class WS2811 : public LedStrip
Ned Konz 3:df4319053bfa 63 {
Ned Konz 5:2c3b76ea0b40 64 public:
Ned Konz 3:df4319053bfa 65 WS2811(unsigned n, unsigned pinNumber)
Ned Konz 3:df4319053bfa 66 : LedStrip(n)
Ned Konz 3:df4319053bfa 67 , pinMask(1U << pinNumber)
Ned Konz 3:df4319053bfa 68 {
Ned Konz 3:df4319053bfa 69 enabledPins |= pinMask;
Ned Konz 5:2c3b76ea0b40 70 initialized = false;
Ned Konz 3:df4319053bfa 71 }
Ned Konz 3:df4319053bfa 72
Ned Konz 3:df4319053bfa 73 virtual void show()
Ned Konz 3:df4319053bfa 74 {
Ned Konz 3:df4319053bfa 75 uint16_t i, n = numPixels(); // 3 bytes per LED
Ned Konz 3:df4319053bfa 76 uint8_t *p = pixels;
Ned Konz 5:2c3b76ea0b40 77
Ned Konz 5:2c3b76ea0b40 78 for (i=0; i<n; i++ )
Ned Konz 5:2c3b76ea0b40 79 {
Ned Konz 3:df4319053bfa 80 writePixel(i, p);
Ned Konz 3:df4319053bfa 81 p += 3;
Ned Konz 3:df4319053bfa 82 }
Ned Konz 3:df4319053bfa 83 }
Ned Konz 5:2c3b76ea0b40 84
Ned Konz 3:df4319053bfa 85 virtual void begin()
Ned Konz 3:df4319053bfa 86 {
Ned Konz 3:df4319053bfa 87 blank();
Ned Konz 3:df4319053bfa 88 show();
Ned Konz 3:df4319053bfa 89 }
Ned Konz 3:df4319053bfa 90
Ned Konz 3:df4319053bfa 91 virtual void blank()
Ned Konz 3:df4319053bfa 92 {
Ned Konz 3:df4319053bfa 93 std::memset(pixels, 0x00, numPixelBytes());
Ned Konz 5:2c3b76ea0b40 94
Ned Konz 3:df4319053bfa 95 std::memset(dmaData.dmaWords, 0x00, sizeof(dmaData.dmaWords));
Ned Konz 3:df4319053bfa 96 }
Ned Konz 3:df4319053bfa 97
Ned Konz 3:df4319053bfa 98 static void startDMA();
Ned Konz 3:df4319053bfa 99 static unsigned maxLEDsPerStrip() { return MAX_LEDS_PER_STRIP; }
Ned Konz 3:df4319053bfa 100 static void wait_for_dma_done();
Ned Konz 3:df4319053bfa 101
Ned Konz 3:df4319053bfa 102 private:
Ned Konz 3:df4319053bfa 103 uint32_t pinMask;
Ned Konz 3:df4319053bfa 104
Ned Konz 3:df4319053bfa 105 void writePixel(unsigned n, uint8_t *p)
Ned Konz 3:df4319053bfa 106 {
Ned Konz 3:df4319053bfa 107 uint32_t *dest = dmaData.dmaWords + n * BITS_PER_RGB;
Ned Konz 3:df4319053bfa 108 writeByte(*p++, pinMask, dest + 0); // G
Ned Konz 3:df4319053bfa 109 writeByte(*p++, pinMask, dest + 8); // R
Ned Konz 3:df4319053bfa 110 writeByte(*p, pinMask, dest + 16); // B
Ned Konz 3:df4319053bfa 111 }
Ned Konz 3:df4319053bfa 112
Ned Konz 3:df4319053bfa 113 // Class Static:
Ned Konz 3:df4319053bfa 114
Ned Konz 3:df4319053bfa 115 static bool initialized;
Ned Konz 3:df4319053bfa 116 static uint32_t enabledPins;
Ned Konz 3:df4319053bfa 117
Ned Konz 3:df4319053bfa 118 static void writeByte(uint8_t byte, uint32_t mask, uint32_t *dest)
Ned Konz 3:df4319053bfa 119 {
Ned Konz 5:2c3b76ea0b40 120 for (uint8_t bm = 0x80; bm; bm >>= 1)
Ned Konz 5:2c3b76ea0b40 121 {
Ned Konz 3:df4319053bfa 122 // MSBit first
Ned Konz 3:df4319053bfa 123 if (byte & bm)
Ned Konz 3:df4319053bfa 124 *dest |= mask;
Ned Konz 3:df4319053bfa 125 else
Ned Konz 3:df4319053bfa 126 *dest &= ~mask;
Ned Konz 3:df4319053bfa 127 dest++;
Ned Konz 3:df4319053bfa 128 }
Ned Konz 3:df4319053bfa 129 }
Ned Konz 3:df4319053bfa 130
Ned Konz 3:df4319053bfa 131 static void hw_init();
Ned Konz 3:df4319053bfa 132 static void io_init();
Ned Konz 3:df4319053bfa 133 static void clock_init();
Ned Konz 3:df4319053bfa 134 static void dma_init();
Ned Konz 3:df4319053bfa 135 static void tpm_init();
Ned Konz 3:df4319053bfa 136 static void dma_data_init();
Ned Konz 5:2c3b76ea0b40 137
Ned Konz 3:df4319053bfa 138 friend void TPM0_IRQHandler();
Ned Konz 5:2c3b76ea0b40 139
Ned Konz 5:2c3b76ea0b40 140 static const unsigned DMA_LEADING_ZEROS = 2;
Ned Konz 5:2c3b76ea0b40 141 static const unsigned BITS_PER_RGB = 24;
Ned Konz 3:df4319053bfa 142 static const unsigned DMA_TRAILING_ZEROS = 1;
Ned Konz 3:df4319053bfa 143
Ned Konz 5:2c3b76ea0b40 144 struct DMALayout
Ned Konz 5:2c3b76ea0b40 145 {
Ned Konz 3:df4319053bfa 146 uint32_t start_t1_low[ DMA_LEADING_ZEROS ];
Ned Konz 3:df4319053bfa 147 uint32_t dmaWords[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
Ned Konz 3:df4319053bfa 148 uint32_t trailing_zeros_1[ DMA_TRAILING_ZEROS ];
Ned Konz 5:2c3b76ea0b40 149
Ned Konz 3:df4319053bfa 150 uint32_t start_t0_high[ DMA_LEADING_ZEROS - 1 ];
Ned Konz 3:df4319053bfa 151 uint32_t allOnes[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
Ned Konz 3:df4319053bfa 152 uint32_t trailing_zeros_2[ DMA_TRAILING_ZEROS + 1 ];
Ned Konz 3:df4319053bfa 153 };
Ned Konz 5:2c3b76ea0b40 154
Ned Konz 3:df4319053bfa 155 static DMALayout dmaData;
Ned Konz 3:df4319053bfa 156 };
Ned Konz 3:df4319053bfa 157
Ned Konz 3:df4319053bfa 158 #endif
Ned Konz 3:df4319053bfa 159
Ned Konz 3:df4319053bfa 160 #if INSTANTIATE_TEMPLATES
Ned Konz 3:df4319053bfa 161 #include "WS2811.cpp"
Ned Konz 3:df4319053bfa 162 #endif
Ned Konz 3:df4319053bfa 163