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:
bikeNomad
Date:
Thu Jun 11 15:24:41 2015 +0000
Revision:
1:86a910560879
Parent:
0:a8535703f23b
Child:
3:df4319053bfa
changed WS2811 to template class to allow for lower memory usage.

Who changed what in which revision?

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