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:
Sat Jan 04 00:40:08 2014 +0000
Revision:
0:a8535703f23b
Child:
1:86a910560879
Initial revision of library.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
bikeNomad 0:a8535703f23b 1 // 800 KHz WS2811 driver driving potentially many LED strings.
bikeNomad 0:a8535703f23b 2 // Uses 3-phase DMA
bikeNomad 0:a8535703f23b 3 // 16K SRAM less stack, etc.
bikeNomad 0:a8535703f23b 4 //
bikeNomad 0:a8535703f23b 5 // Per LED: 3 bytes (malloc'd) for RGB data
bikeNomad 0:a8535703f23b 6 //
bikeNomad 0:a8535703f23b 7 // Per LED strip / per LED
bikeNomad 0:a8535703f23b 8 // 96 bytes (static) for bit data
bikeNomad 0:a8535703f23b 9 // + 96 bytes (static) for ones data
bikeNomad 0:a8535703f23b 10 // = 192 bytes
bikeNomad 0:a8535703f23b 11 //
bikeNomad 0:a8535703f23b 12 // 40 LEDs max per string = 7680 bytes static
bikeNomad 0:a8535703f23b 13 //
bikeNomad 0:a8535703f23b 14 // 40 LEDs: 7680 + 40*3 = 7800 bytes
bikeNomad 0:a8535703f23b 15 // 80 LEDs: 7680 + 80*3 = 7920 bytes
bikeNomad 0:a8535703f23b 16
bikeNomad 0:a8535703f23b 17 #include "MKL25Z4.h"
bikeNomad 0:a8535703f23b 18 #include "LedStrip.h"
bikeNomad 0:a8535703f23b 19 #include "WS2811.h"
bikeNomad 0:a8535703f23b 20
bikeNomad 0:a8535703f23b 21 //
bikeNomad 0:a8535703f23b 22 // Configuration
bikeNomad 0:a8535703f23b 23 //
bikeNomad 0:a8535703f23b 24
bikeNomad 0:a8535703f23b 25 // Define MONITOR_TPM0_PWM as non-zero to monitor PWM timing on PTD0 and PTD1
bikeNomad 0:a8535703f23b 26 // PTD0 TPM0/CH0 PWM_1 J2/06
bikeNomad 0:a8535703f23b 27 // PTD1 TPM0/CH1 PWM_2 J2/12 (also LED_BLUE)
bikeNomad 0:a8535703f23b 28 #define MONITOR_TPM0_PWM 0
bikeNomad 0:a8535703f23b 29
bikeNomad 0:a8535703f23b 30 // define DEBUG_PIN to identify a pin in PORTD used for debug output
bikeNomad 0:a8535703f23b 31 // #define DEBUG_PIN 4 /* PTD4 debugOut */
bikeNomad 0:a8535703f23b 32
bikeNomad 0:a8535703f23b 33 #ifdef DEBUG_PIN
bikeNomad 0:a8535703f23b 34 #define DEBUG 1
bikeNomad 0:a8535703f23b 35 #endif
bikeNomad 0:a8535703f23b 36
bikeNomad 0:a8535703f23b 37 #if DEBUG
bikeNomad 0:a8535703f23b 38 #define DEBUG_MASK (1<<DEBUG_PIN)
bikeNomad 0:a8535703f23b 39 #define RESET_DEBUG (IO_GPIO->PDOR &= ~DEBUG_MASK)
bikeNomad 0:a8535703f23b 40 #define SET_DEBUG (IO_GPIO->PDOR |= DEBUG_MASK)
bikeNomad 0:a8535703f23b 41 #else
bikeNomad 0:a8535703f23b 42 #define DEBUG_MASK 0
bikeNomad 0:a8535703f23b 43 #define RESET_DEBUG (void)0
bikeNomad 0:a8535703f23b 44 #define SET_DEBUG (void)0
bikeNomad 0:a8535703f23b 45 #endif
bikeNomad 0:a8535703f23b 46
bikeNomad 0:a8535703f23b 47 static PORT_Type volatile * const IO_PORT = PORTD;
bikeNomad 0:a8535703f23b 48 static GPIO_Type volatile * const IO_GPIO = PTD;
bikeNomad 0:a8535703f23b 49
bikeNomad 0:a8535703f23b 50 // 48 MHz clock, no prescaling.
bikeNomad 0:a8535703f23b 51 #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000)
bikeNomad 0:a8535703f23b 52 #define USEC_TO_TICKS(usec) ((usec)*48)
bikeNomad 0:a8535703f23b 53 static const uint32_t CLK_NSEC = 1250;
bikeNomad 0:a8535703f23b 54 static const uint32_t tpm_period = NSEC_TO_TICKS(CLK_NSEC);
bikeNomad 0:a8535703f23b 55 static const uint32_t tpm_p0_period = NSEC_TO_TICKS(250);
bikeNomad 0:a8535703f23b 56 static const uint32_t tpm_p1_period = NSEC_TO_TICKS(650);
bikeNomad 0:a8535703f23b 57 static const uint32_t guardtime_period = USEC_TO_TICKS(55); // guardtime minimum 50 usec.
bikeNomad 0:a8535703f23b 58
bikeNomad 0:a8535703f23b 59 enum DMA_MUX_SRC {
bikeNomad 0:a8535703f23b 60 DMA_MUX_SRC_TPM0_CH_0 = 24,
bikeNomad 0:a8535703f23b 61 DMA_MUX_SRC_TPM0_CH_1,
bikeNomad 0:a8535703f23b 62 DMA_MUX_SRC_TPM0_Overflow = 54,
bikeNomad 0:a8535703f23b 63 };
bikeNomad 0:a8535703f23b 64
bikeNomad 0:a8535703f23b 65 enum DMA_CHAN {
bikeNomad 0:a8535703f23b 66 DMA_CHAN_START = 0,
bikeNomad 0:a8535703f23b 67 DMA_CHAN_0_LOW = 1,
bikeNomad 0:a8535703f23b 68 DMA_CHAN_1_LOW = 2,
bikeNomad 0:a8535703f23b 69 N_DMA_CHANNELS
bikeNomad 0:a8535703f23b 70 };
bikeNomad 0:a8535703f23b 71
bikeNomad 0:a8535703f23b 72 volatile bool WS2811::dma_done = true;
bikeNomad 0:a8535703f23b 73
bikeNomad 0:a8535703f23b 74 // class static
bikeNomad 0:a8535703f23b 75 bool WS2811::initialized = false;
bikeNomad 0:a8535703f23b 76
bikeNomad 0:a8535703f23b 77 // class static
bikeNomad 0:a8535703f23b 78 uint32_t WS2811::enabledPins = 0;
bikeNomad 0:a8535703f23b 79
bikeNomad 0:a8535703f23b 80 #define WORD_ALIGNED __attribute__ ((aligned(4)))
bikeNomad 0:a8535703f23b 81
bikeNomad 0:a8535703f23b 82 #define DMA_LEADING_ZEROS 2
bikeNomad 0:a8535703f23b 83 #define BITS_PER_RGB 24
bikeNomad 0:a8535703f23b 84 #define DMA_TRAILING_ZEROS 1
bikeNomad 0:a8535703f23b 85
bikeNomad 0:a8535703f23b 86 static struct {
bikeNomad 0:a8535703f23b 87 uint32_t start_t1_low[ DMA_LEADING_ZEROS ];
bikeNomad 0:a8535703f23b 88 uint32_t dmaWords[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
bikeNomad 0:a8535703f23b 89 uint32_t trailing_zeros_1[ DMA_TRAILING_ZEROS ];
bikeNomad 0:a8535703f23b 90
bikeNomad 0:a8535703f23b 91 uint32_t start_t0_high[ DMA_LEADING_ZEROS - 1 ];
bikeNomad 0:a8535703f23b 92 uint32_t allOnes[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
bikeNomad 0:a8535703f23b 93 uint32_t trailing_zeros_2[ DMA_TRAILING_ZEROS + 1 ];
bikeNomad 0:a8535703f23b 94 } dmaData WORD_ALIGNED;
bikeNomad 0:a8535703f23b 95
bikeNomad 0:a8535703f23b 96 // class static
bikeNomad 0:a8535703f23b 97 void WS2811::hw_init()
bikeNomad 0:a8535703f23b 98 {
bikeNomad 0:a8535703f23b 99 if (initialized) return;
bikeNomad 0:a8535703f23b 100
bikeNomad 0:a8535703f23b 101 dma_data_init();
bikeNomad 0:a8535703f23b 102 clock_init();
bikeNomad 0:a8535703f23b 103 dma_init();
bikeNomad 0:a8535703f23b 104 io_init();
bikeNomad 0:a8535703f23b 105 tpm_init();
bikeNomad 0:a8535703f23b 106
bikeNomad 0:a8535703f23b 107 initialized = true;
bikeNomad 0:a8535703f23b 108
bikeNomad 0:a8535703f23b 109 SET_DEBUG;
bikeNomad 0:a8535703f23b 110 RESET_DEBUG;
bikeNomad 0:a8535703f23b 111 }
bikeNomad 0:a8535703f23b 112
bikeNomad 0:a8535703f23b 113 // class static
bikeNomad 0:a8535703f23b 114 void WS2811::dma_data_init()
bikeNomad 0:a8535703f23b 115 {
bikeNomad 0:a8535703f23b 116 memset(dmaData.allOnes, 0xFF, sizeof(dmaData.allOnes));
bikeNomad 0:a8535703f23b 117
bikeNomad 0:a8535703f23b 118 #if DEBUG
bikeNomad 0:a8535703f23b 119 for (unsigned i = 0; i < BITS_PER_RGB * MAX_LEDS_PER_STRIP; i++)
bikeNomad 0:a8535703f23b 120 dmaData.dmaWords[i] = DEBUG_MASK;
bikeNomad 0:a8535703f23b 121 #endif
bikeNomad 0:a8535703f23b 122 }
bikeNomad 0:a8535703f23b 123
bikeNomad 0:a8535703f23b 124 // class static
bikeNomad 0:a8535703f23b 125
bikeNomad 0:a8535703f23b 126 /// Enable PORTD, DMA and TPM0 clocking
bikeNomad 0:a8535703f23b 127 void WS2811::clock_init()
bikeNomad 0:a8535703f23b 128 {
bikeNomad 0:a8535703f23b 129 SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK;
bikeNomad 0:a8535703f23b 130 SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK | SIM_SCGC6_TPM0_MASK; // Enable clock to DMA mux and TPM0
bikeNomad 0:a8535703f23b 131 SIM->SCGC7 |= SIM_SCGC7_DMA_MASK; // Enable clock to DMA
bikeNomad 0:a8535703f23b 132
bikeNomad 0:a8535703f23b 133 SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK
bikeNomad 0:a8535703f23b 134 }
bikeNomad 0:a8535703f23b 135
bikeNomad 0:a8535703f23b 136 // class static
bikeNomad 0:a8535703f23b 137
bikeNomad 0:a8535703f23b 138 /// Configure GPIO output pins
bikeNomad 0:a8535703f23b 139 void WS2811::io_init()
bikeNomad 0:a8535703f23b 140 {
bikeNomad 0:a8535703f23b 141 uint32_t m = 1;
bikeNomad 0:a8535703f23b 142 for (uint32_t i = 0; i < 32; i++) {
bikeNomad 0:a8535703f23b 143 // set up each pin
bikeNomad 0:a8535703f23b 144 if (m & enabledPins) {
bikeNomad 0:a8535703f23b 145 IO_PORT->PCR[i] = PORT_PCR_MUX(1) // GPIO
bikeNomad 0:a8535703f23b 146 | PORT_PCR_DSE_MASK; // high drive strength
bikeNomad 0:a8535703f23b 147 }
bikeNomad 0:a8535703f23b 148 m <<= 1;
bikeNomad 0:a8535703f23b 149 }
bikeNomad 0:a8535703f23b 150
bikeNomad 0:a8535703f23b 151 IO_GPIO->PDDR |= enabledPins; // set as outputs
bikeNomad 0:a8535703f23b 152
bikeNomad 0:a8535703f23b 153 #if MONITOR_TPM0_PWM
bikeNomad 0:a8535703f23b 154 // PTD0 CH0 monitor: TPM0, high drive strength
bikeNomad 0:a8535703f23b 155 IO_PORT->PCR[0] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
bikeNomad 0:a8535703f23b 156 // PTD1 CH1 monitor: TPM0, high drive strength
bikeNomad 0:a8535703f23b 157 IO_PORT->PCR[1] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
bikeNomad 0:a8535703f23b 158 IO_GPIO->PDDR |= 3; // set as outputs
bikeNomad 0:a8535703f23b 159 IO_GPIO->PDOR &= ~(enabledPins | 3); // initially low
bikeNomad 0:a8535703f23b 160 #else
bikeNomad 0:a8535703f23b 161 IO_GPIO->PDOR &= ~enabledPins; // initially low
bikeNomad 0:a8535703f23b 162 #endif
bikeNomad 0:a8535703f23b 163
bikeNomad 0:a8535703f23b 164 #if DEBUG
bikeNomad 0:a8535703f23b 165 IO_PORT->PCR[DEBUG_PIN] = PORT_PCR_MUX(1) | PORT_PCR_DSE_MASK;
bikeNomad 0:a8535703f23b 166 IO_GPIO->PDDR |= DEBUG_MASK;
bikeNomad 0:a8535703f23b 167 IO_GPIO->PDOR &= ~DEBUG_MASK;
bikeNomad 0:a8535703f23b 168 #endif
bikeNomad 0:a8535703f23b 169 }
bikeNomad 0:a8535703f23b 170
bikeNomad 0:a8535703f23b 171 // class static
bikeNomad 0:a8535703f23b 172
bikeNomad 0:a8535703f23b 173 /// Configure DMA and DMAMUX
bikeNomad 0:a8535703f23b 174 void WS2811::dma_init()
bikeNomad 0:a8535703f23b 175 {
bikeNomad 0:a8535703f23b 176 // reset DMAMUX
bikeNomad 0:a8535703f23b 177 DMAMUX0->CHCFG[DMA_CHAN_START] = 0;
bikeNomad 0:a8535703f23b 178 DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = 0;
bikeNomad 0:a8535703f23b 179 DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = 0;
bikeNomad 0:a8535703f23b 180
bikeNomad 0:a8535703f23b 181 // wire our DMA event sources into the first three DMA channels
bikeNomad 0:a8535703f23b 182 // t=0: all enabled outputs go high on TPM0 overflow
bikeNomad 0:a8535703f23b 183 DMAMUX0->CHCFG[DMA_CHAN_START] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_Overflow);
bikeNomad 0:a8535703f23b 184 // t=tpm_p0_period: all of the 0 bits go low.
bikeNomad 0:a8535703f23b 185 DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_0);
bikeNomad 0:a8535703f23b 186 // t=tpm_p1_period: all outputs go low.
bikeNomad 0:a8535703f23b 187 DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1);
bikeNomad 0:a8535703f23b 188
bikeNomad 0:a8535703f23b 189 NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler);
bikeNomad 0:a8535703f23b 190 NVIC_EnableIRQ(DMA0_IRQn);
bikeNomad 0:a8535703f23b 191 }
bikeNomad 0:a8535703f23b 192
bikeNomad 0:a8535703f23b 193 // class static
bikeNomad 0:a8535703f23b 194
bikeNomad 0:a8535703f23b 195 /// Configure TPM0 to do two different PWM periods at 800kHz rate
bikeNomad 0:a8535703f23b 196 void WS2811::tpm_init()
bikeNomad 0:a8535703f23b 197 {
bikeNomad 0:a8535703f23b 198 // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec)
bikeNomad 0:a8535703f23b 199 TPM_Type volatile *tpm = TPM0;
bikeNomad 0:a8535703f23b 200 tpm->SC = TPM_SC_DMA_MASK // enable DMA
bikeNomad 0:a8535703f23b 201 | TPM_SC_TOF_MASK // reset TOF flag if set
bikeNomad 0:a8535703f23b 202 | TPM_SC_CMOD(0) // disable clocks
bikeNomad 0:a8535703f23b 203 | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock
bikeNomad 0:a8535703f23b 204 tpm->MOD = tpm_period - 1; // 48MHz / 800kHz
bikeNomad 0:a8535703f23b 205
bikeNomad 0:a8535703f23b 206 // No Interrupts; High True pulses on Edge Aligned PWM
bikeNomad 0:a8535703f23b 207 tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
bikeNomad 0:a8535703f23b 208 tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
bikeNomad 0:a8535703f23b 209
bikeNomad 0:a8535703f23b 210 // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code)
bikeNomad 0:a8535703f23b 211 // 1.25 usec * 1/3 = 417 nsec
bikeNomad 0:a8535703f23b 212 tpm->CONTROLS[0].CnV = tpm_p0_period;
bikeNomad 0:a8535703f23b 213
bikeNomad 0:a8535703f23b 214 // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code)
bikeNomad 0:a8535703f23b 215 // 1.25 usec * 2/3 = 833 nsec
bikeNomad 0:a8535703f23b 216 tpm->CONTROLS[1].CnV = tpm_p1_period;
bikeNomad 0:a8535703f23b 217
bikeNomad 0:a8535703f23b 218 NVIC_SetVector(TPM0_IRQn, (uint32_t)&TPM0_IRQHandler);
bikeNomad 0:a8535703f23b 219 NVIC_EnableIRQ(TPM0_IRQn);
bikeNomad 0:a8535703f23b 220 }
bikeNomad 0:a8535703f23b 221
bikeNomad 0:a8535703f23b 222 WS2811::WS2811(unsigned n, unsigned pinNumber)
bikeNomad 0:a8535703f23b 223 : LedStrip(n)
bikeNomad 0:a8535703f23b 224 , pinMask(1U << pinNumber)
bikeNomad 0:a8535703f23b 225 {
bikeNomad 0:a8535703f23b 226 enabledPins |= pinMask;
bikeNomad 0:a8535703f23b 227 initialized = false;
bikeNomad 0:a8535703f23b 228 }
bikeNomad 0:a8535703f23b 229
bikeNomad 0:a8535703f23b 230 // class static
bikeNomad 0:a8535703f23b 231 void WS2811::startDMA()
bikeNomad 0:a8535703f23b 232 {
bikeNomad 0:a8535703f23b 233 hw_init();
bikeNomad 0:a8535703f23b 234
bikeNomad 0:a8535703f23b 235 wait_for_dma_done();
bikeNomad 0:a8535703f23b 236 dma_done = false;
bikeNomad 0:a8535703f23b 237
bikeNomad 0:a8535703f23b 238 DMA_Type volatile * dma = DMA0;
bikeNomad 0:a8535703f23b 239 TPM_Type volatile *tpm = TPM0;
bikeNomad 0:a8535703f23b 240 uint32_t nBytes = sizeof(dmaData.start_t1_low)
bikeNomad 0:a8535703f23b 241 + sizeof(dmaData.dmaWords)
bikeNomad 0:a8535703f23b 242 + sizeof(dmaData.trailing_zeros_1);
bikeNomad 0:a8535703f23b 243
bikeNomad 0:a8535703f23b 244 tpm->SC = TPM_SC_DMA_MASK // enable DMA
bikeNomad 0:a8535703f23b 245 | TPM_SC_TOF_MASK // reset TOF flag if set
bikeNomad 0:a8535703f23b 246 | TPM_SC_CMOD(0) // disable clocks
bikeNomad 0:a8535703f23b 247 | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock
bikeNomad 0:a8535703f23b 248 tpm->MOD = tpm_period - 1; // 48MHz / 800kHz
bikeNomad 0:a8535703f23b 249
bikeNomad 0:a8535703f23b 250 tpm->CNT = tpm_p0_period - 2 ;
bikeNomad 0:a8535703f23b 251 tpm->STATUS = 0xFFFFFFFF;
bikeNomad 0:a8535703f23b 252
bikeNomad 0:a8535703f23b 253 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 254 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 255 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 256
bikeNomad 0:a8535703f23b 257 // t=0: all outputs go high
bikeNomad 0:a8535703f23b 258 // triggered by TPM0_Overflow
bikeNomad 0:a8535703f23b 259 // source is one word of 0 then 24 x 0xffffffff, then another 0 word
bikeNomad 0:a8535703f23b 260 dma->DMA[DMA_CHAN_START].SAR = (uint32_t)(void*)dmaData.start_t0_high;
bikeNomad 0:a8535703f23b 261 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
bikeNomad 0:a8535703f23b 262
bikeNomad 0:a8535703f23b 263 // t=tpm_p0_period: some outputs (the 0 bits) go low.
bikeNomad 0:a8535703f23b 264 // Triggered by TPM0_CH0
bikeNomad 0:a8535703f23b 265 // Start 2 words before the actual data to avoid garbage pulses.
bikeNomad 0:a8535703f23b 266 dma->DMA[DMA_CHAN_0_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address
bikeNomad 0:a8535703f23b 267 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
bikeNomad 0:a8535703f23b 268
bikeNomad 0:a8535703f23b 269 // t=tpm_p1_period: all outputs go low.
bikeNomad 0:a8535703f23b 270 // Triggered by TPM0_CH1
bikeNomad 0:a8535703f23b 271 // source is constant 0x00000000 (first word of dmaWords)
bikeNomad 0:a8535703f23b 272 dma->DMA[DMA_CHAN_1_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address
bikeNomad 0:a8535703f23b 273 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
bikeNomad 0:a8535703f23b 274
bikeNomad 0:a8535703f23b 275 dma->DMA[DMA_CHAN_0_LOW].DAR
bikeNomad 0:a8535703f23b 276 = dma->DMA[DMA_CHAN_1_LOW].DAR
bikeNomad 0:a8535703f23b 277 = dma->DMA[DMA_CHAN_START].DAR
bikeNomad 0:a8535703f23b 278 = (uint32_t)(void*)&IO_GPIO->PDOR;
bikeNomad 0:a8535703f23b 279
bikeNomad 0:a8535703f23b 280 SET_DEBUG;
bikeNomad 0:a8535703f23b 281
bikeNomad 0:a8535703f23b 282 dma->DMA[DMA_CHAN_0_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
bikeNomad 0:a8535703f23b 283 | DMA_DCR_ERQ_MASK
bikeNomad 0:a8535703f23b 284 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
bikeNomad 0:a8535703f23b 285 | DMA_DCR_SINC_MASK // increment source each transfer
bikeNomad 0:a8535703f23b 286 | DMA_DCR_CS_MASK
bikeNomad 0:a8535703f23b 287 | DMA_DCR_SSIZE(0) // 32-bit source transfers
bikeNomad 0:a8535703f23b 288 | DMA_DCR_DSIZE(0); // 32-bit destination transfers
bikeNomad 0:a8535703f23b 289
bikeNomad 0:a8535703f23b 290 dma->DMA[DMA_CHAN_1_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
bikeNomad 0:a8535703f23b 291 | DMA_DCR_ERQ_MASK
bikeNomad 0:a8535703f23b 292 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
bikeNomad 0:a8535703f23b 293 | DMA_DCR_CS_MASK
bikeNomad 0:a8535703f23b 294 | DMA_DCR_SSIZE(0) // 32-bit source transfers
bikeNomad 0:a8535703f23b 295 | DMA_DCR_DSIZE(0); // 32-bit destination transfers
bikeNomad 0:a8535703f23b 296
bikeNomad 0:a8535703f23b 297 dma->DMA[DMA_CHAN_START].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
bikeNomad 0:a8535703f23b 298 | DMA_DCR_ERQ_MASK
bikeNomad 0:a8535703f23b 299 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
bikeNomad 0:a8535703f23b 300 | DMA_DCR_SINC_MASK // increment source each transfer
bikeNomad 0:a8535703f23b 301 | DMA_DCR_CS_MASK
bikeNomad 0:a8535703f23b 302 | DMA_DCR_SSIZE(0) // 32-bit source transfers
bikeNomad 0:a8535703f23b 303 | DMA_DCR_DSIZE(0);
bikeNomad 0:a8535703f23b 304
bikeNomad 0:a8535703f23b 305 tpm->SC |= TPM_SC_CMOD(1); // enable internal clocking
bikeNomad 0:a8535703f23b 306 }
bikeNomad 0:a8535703f23b 307
bikeNomad 0:a8535703f23b 308 void WS2811::writePixel(unsigned n, uint8_t *p)
bikeNomad 0:a8535703f23b 309 {
bikeNomad 0:a8535703f23b 310 uint32_t *dest = dmaData.dmaWords + n * BITS_PER_RGB;
bikeNomad 0:a8535703f23b 311 writeByte(*p++, pinMask, dest + 0); // G
bikeNomad 0:a8535703f23b 312 writeByte(*p++, pinMask, dest + 8); // R
bikeNomad 0:a8535703f23b 313 writeByte(*p, pinMask, dest + 16); // B
bikeNomad 0:a8535703f23b 314 }
bikeNomad 0:a8535703f23b 315
bikeNomad 0:a8535703f23b 316 // class static
bikeNomad 0:a8535703f23b 317 void WS2811::writeByte(uint8_t byte, uint32_t mask, uint32_t *dest)
bikeNomad 0:a8535703f23b 318 {
bikeNomad 0:a8535703f23b 319 for (uint8_t bm = 0x80; bm; bm >>= 1) {
bikeNomad 0:a8535703f23b 320 // MSBit first
bikeNomad 0:a8535703f23b 321 if (byte & bm)
bikeNomad 0:a8535703f23b 322 *dest |= mask;
bikeNomad 0:a8535703f23b 323 else
bikeNomad 0:a8535703f23b 324 *dest &= ~mask;
bikeNomad 0:a8535703f23b 325 dest++;
bikeNomad 0:a8535703f23b 326 }
bikeNomad 0:a8535703f23b 327 }
bikeNomad 0:a8535703f23b 328
bikeNomad 0:a8535703f23b 329 void WS2811::begin()
bikeNomad 0:a8535703f23b 330 {
bikeNomad 0:a8535703f23b 331 blank();
bikeNomad 0:a8535703f23b 332 show();
bikeNomad 0:a8535703f23b 333 }
bikeNomad 0:a8535703f23b 334
bikeNomad 0:a8535703f23b 335 void WS2811::blank()
bikeNomad 0:a8535703f23b 336 {
bikeNomad 0:a8535703f23b 337 memset(pixels, 0x00, numPixelBytes());
bikeNomad 0:a8535703f23b 338
bikeNomad 0:a8535703f23b 339 #if DEBUG
bikeNomad 0:a8535703f23b 340 for (unsigned i = DMA_LEADING_ZEROS; i < DMA_LEADING_ZEROS + BITS_PER_RGB; i++)
bikeNomad 0:a8535703f23b 341 dmaData.dmaWords[i] = DEBUG_MASK;
bikeNomad 0:a8535703f23b 342 #else
bikeNomad 0:a8535703f23b 343 memset(dmaData.dmaWords, 0x00, sizeof(dmaData.dmaWords));
bikeNomad 0:a8535703f23b 344 #endif
bikeNomad 0:a8535703f23b 345 }
bikeNomad 0:a8535703f23b 346
bikeNomad 0:a8535703f23b 347 void WS2811::show()
bikeNomad 0:a8535703f23b 348 {
bikeNomad 0:a8535703f23b 349
bikeNomad 0:a8535703f23b 350 uint16_t i, n = numPixels(); // 3 bytes per LED
bikeNomad 0:a8535703f23b 351 uint8_t *p = pixels;
bikeNomad 0:a8535703f23b 352
bikeNomad 0:a8535703f23b 353 for (i=0; i<n; i++ ) {
bikeNomad 0:a8535703f23b 354 writePixel(i, p);
bikeNomad 0:a8535703f23b 355 p += 3;
bikeNomad 0:a8535703f23b 356 }
bikeNomad 0:a8535703f23b 357 }
bikeNomad 0:a8535703f23b 358
bikeNomad 0:a8535703f23b 359 extern "C" void DMA0_IRQHandler()
bikeNomad 0:a8535703f23b 360 {
bikeNomad 0:a8535703f23b 361 DMA_Type volatile *dma = DMA0;
bikeNomad 0:a8535703f23b 362 TPM_Type volatile *tpm = TPM0;
bikeNomad 0:a8535703f23b 363
bikeNomad 0:a8535703f23b 364 uint32_t db;
bikeNomad 0:a8535703f23b 365
bikeNomad 0:a8535703f23b 366 db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR;
bikeNomad 0:a8535703f23b 367 if (db & DMA_DSR_BCR_DONE_MASK) {
bikeNomad 0:a8535703f23b 368 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 369 }
bikeNomad 0:a8535703f23b 370
bikeNomad 0:a8535703f23b 371 db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR;
bikeNomad 0:a8535703f23b 372 if (db & DMA_DSR_BCR_DONE_MASK) {
bikeNomad 0:a8535703f23b 373 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 374 }
bikeNomad 0:a8535703f23b 375
bikeNomad 0:a8535703f23b 376 db = dma->DMA[DMA_CHAN_START].DSR_BCR;
bikeNomad 0:a8535703f23b 377 if (db & DMA_DSR_BCR_DONE_MASK) {
bikeNomad 0:a8535703f23b 378 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
bikeNomad 0:a8535703f23b 379 }
bikeNomad 0:a8535703f23b 380
bikeNomad 0:a8535703f23b 381 tpm->SC = TPM_SC_TOF_MASK; // reset TOF flag; disable internal clocking
bikeNomad 0:a8535703f23b 382
bikeNomad 0:a8535703f23b 383 SET_DEBUG;
bikeNomad 0:a8535703f23b 384
bikeNomad 0:a8535703f23b 385 // set TPM0 to interrrupt after guardtime
bikeNomad 0:a8535703f23b 386 tpm->MOD = guardtime_period - 1; // 48MHz * 55 usec
bikeNomad 0:a8535703f23b 387 tpm->CNT = 0;
bikeNomad 0:a8535703f23b 388 tpm->SC = TPM_SC_PS(0) // 48MHz / 1 = 48MHz clock
bikeNomad 0:a8535703f23b 389 | TPM_SC_TOIE_MASK // enable interrupts
bikeNomad 0:a8535703f23b 390 | TPM_SC_CMOD(1); // and internal clocking
bikeNomad 0:a8535703f23b 391 }
bikeNomad 0:a8535703f23b 392
bikeNomad 0:a8535703f23b 393 extern "C" void TPM0_IRQHandler()
bikeNomad 0:a8535703f23b 394 {
bikeNomad 0:a8535703f23b 395 TPM0->SC = 0; // disable internal clocking
bikeNomad 0:a8535703f23b 396 TPM0->SC = TPM_SC_TOF_MASK;
bikeNomad 0:a8535703f23b 397 RESET_DEBUG;
bikeNomad 0:a8535703f23b 398 WS2811::dma_done = true;
bikeNomad 0:a8535703f23b 399 }