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.
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:
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 pin | Arduino name | Timer | Channel |
---|---|---|---|
PTA3 | TPM0 | CH0 | |
PTC1 | A5 | TPM0 | CH0 |
PTD0 | D10 | TPM0 | CH0 |
PTE24 | TPM0 | CH0 | |
PTA4 | D4 | TPM0 | CH1 |
PTC2 | A4 | TPM0 | CH1 |
PTD1 | D13/LED_BLUE | TPM0 | CH1 |
PTE25 | TPM0 | CH1 | |
PTA5 | D5 | TPM0 | CH2 |
PTC3 | TPM0 | CH2 | |
PTD2 | D11 | TPM0 | CH2 |
PTE29 | TPM0 | CH2 | |
PTC4 | TPM0 | CH3 | |
PTD3 | D12 | TPM0 | CH3 |
PTE30 | TPM0 | CH3 | |
PTC8 | D6 | TPM0 | CH4 |
PTD4 | D2 | TPM0 | CH4 |
PTE31 | TPM0 | CH4 | |
PTA0 | TPM0 | CH5 | |
PTC9 | D7 | TPM0 | CH5 |
PTD5 | D9 | TPM0 | CH5 |
PTE26 | TPM0 | CH5 | |
PTA12 | D3 | TPM1 | CH0 |
PTB0 | A0 | TPM1 | CH0 |
PTE20 | TPM1 | CH0 | |
PTA13 | D8 | TPM1 | CH1 |
PTB1 | A1 | TPM1 | CH1 |
PTE21 | TPM1 | CH1 | |
PTA1 | D0/USBRX | TPM2 | CH0 |
PTB18 | LED_RED | TPM2 | CH0 |
PTB2 | A2 | TPM2 | CH0 |
PTE22 | TPM2 | CH0 | |
PTA2 | D1/USBTX | TPM2 | CH1 |
PTB19 | LED_GREEN | TPM2 | CH1 |
PTB3 | A3 | TPM2 | CH1 |
PTE23 | TPM2 | CH1 |
WS2811.cpp@3:df4319053bfa, 2015-06-12 (annotated)
- Committer:
- Ned Konz
- Date:
- Fri Jun 12 18:23:03 2015 -0700
- Revision:
- 3:df4319053bfa
- Parent:
- 2:9447404f2d16
- Child:
- 4:990838718b51
Trying to get dma_done to work
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Ned Konz |
3:df4319053bfa | 1 | // 800 KHz WS2811 driver driving potentially many LED strings. |
Ned Konz |
3:df4319053bfa | 2 | // Uses 3-phase DMA |
Ned Konz |
3:df4319053bfa | 3 | // 16K SRAM less stack, etc. |
Ned Konz |
3:df4319053bfa | 4 | // |
Ned Konz |
3:df4319053bfa | 5 | // Per LED: 3 bytes (malloc'd) for RGB data |
Ned Konz |
3:df4319053bfa | 6 | // |
Ned Konz |
3:df4319053bfa | 7 | // Per LED strip / per LED |
Ned Konz |
3:df4319053bfa | 8 | // 96 bytes (static) for bit data |
Ned Konz |
3:df4319053bfa | 9 | // + 96 bytes (static) for ones data |
Ned Konz |
3:df4319053bfa | 10 | // = 192 bytes |
Ned Konz |
3:df4319053bfa | 11 | // |
Ned Konz |
3:df4319053bfa | 12 | // 40 LEDs max per string = 7680 bytes static |
Ned Konz |
3:df4319053bfa | 13 | // |
Ned Konz |
3:df4319053bfa | 14 | // 40 LEDs: 7680 + 40*3 = 7800 bytes |
Ned Konz |
3:df4319053bfa | 15 | // 80 LEDs: 7680 + 80*3 = 7920 bytes |
Ned Konz |
3:df4319053bfa | 16 | #include <mbed.h> |
Ned Konz |
3:df4319053bfa | 17 | #include "MKL25Z4.h" |
Ned Konz |
3:df4319053bfa | 18 | |
Ned Konz |
3:df4319053bfa | 19 | #ifndef MBED_WS2811_H |
Ned Konz |
3:df4319053bfa | 20 | #include "WS2811.h" |
Ned Konz |
3:df4319053bfa | 21 | #endif |
Ned Konz |
3:df4319053bfa | 22 | |
Ned Konz |
3:df4319053bfa | 23 | #if defined(WS2811_DEBUG_PIN) |
Ned Konz |
3:df4319053bfa | 24 | #define DEBUG 1 |
Ned Konz |
3:df4319053bfa | 25 | #define DEBUG_MASK (1<<WS2811_DEBUG_PIN) |
Ned Konz |
3:df4319053bfa | 26 | #define RESET_DEBUG (WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK) |
Ned Konz |
3:df4319053bfa | 27 | #define SET_DEBUG (WS2811_IO_GPIO->PDOR |= DEBUG_MASK) |
Ned Konz |
3:df4319053bfa | 28 | #else |
Ned Konz |
3:df4319053bfa | 29 | #define DEBUG_MASK 0 |
Ned Konz |
3:df4319053bfa | 30 | #define RESET_DEBUG (void)0 |
Ned Konz |
3:df4319053bfa | 31 | #define SET_DEBUG (void)0 |
Ned Konz |
3:df4319053bfa | 32 | #endif |
Ned Konz |
3:df4319053bfa | 33 | |
Ned Konz |
3:df4319053bfa | 34 | static volatile unsigned dma_done = 0; |
Ned Konz |
3:df4319053bfa | 35 | |
Ned Konz |
3:df4319053bfa | 36 | // 48 MHz clock, no prescaling. |
Ned Konz |
3:df4319053bfa | 37 | #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000) |
Ned Konz |
3:df4319053bfa | 38 | #define USEC_TO_TICKS(usec) ((usec)*48) |
Ned Konz |
3:df4319053bfa | 39 | #define CLK_NSEC 1250 |
Ned Konz |
3:df4319053bfa | 40 | #define tpm_period NSEC_TO_TICKS(CLK_NSEC) |
Ned Konz |
3:df4319053bfa | 41 | #define tpm_p0_period NSEC_TO_TICKS(250) |
Ned Konz |
3:df4319053bfa | 42 | #define tpm_p1_period NSEC_TO_TICKS(650) |
Ned Konz |
3:df4319053bfa | 43 | #define guardtime_period USEC_TO_TICKS(55) /* guardtime minimum 50 usec. */ |
Ned Konz |
3:df4319053bfa | 44 | |
Ned Konz |
3:df4319053bfa | 45 | enum DMA_MUX_SRC { |
Ned Konz |
3:df4319053bfa | 46 | DMA_MUX_SRC_TPM0_CH_0 = 24, |
Ned Konz |
3:df4319053bfa | 47 | DMA_MUX_SRC_TPM0_CH_1, |
Ned Konz |
3:df4319053bfa | 48 | DMA_MUX_SRC_TPM0_Overflow = 54, |
Ned Konz |
3:df4319053bfa | 49 | }; |
Ned Konz |
3:df4319053bfa | 50 | |
Ned Konz |
3:df4319053bfa | 51 | enum DMA_CHAN { |
Ned Konz |
3:df4319053bfa | 52 | DMA_CHAN_START = 0, |
Ned Konz |
3:df4319053bfa | 53 | DMA_CHAN_0_LOW = 1, |
Ned Konz |
3:df4319053bfa | 54 | DMA_CHAN_1_LOW = 2, |
Ned Konz |
3:df4319053bfa | 55 | N_DMA_CHANNELS |
Ned Konz |
3:df4319053bfa | 56 | }; |
Ned Konz |
3:df4319053bfa | 57 | |
Ned Konz |
3:df4319053bfa | 58 | // class static |
Ned Konz |
3:df4319053bfa | 59 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 60 | void WS2811<MAX_LEDS_PER_STRIP>::wait_for_dma_done() { while (dma_done < 1) __WFI(); } |
Ned Konz |
3:df4319053bfa | 61 | |
Ned Konz |
3:df4319053bfa | 62 | // class static |
Ned Konz |
3:df4319053bfa | 63 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 64 | bool WS2811<MAX_LEDS_PER_STRIP>::initialized = false; |
Ned Konz |
3:df4319053bfa | 65 | |
Ned Konz |
3:df4319053bfa | 66 | // class static |
Ned Konz |
3:df4319053bfa | 67 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 68 | uint32_t WS2811<MAX_LEDS_PER_STRIP>::enabledPins = 0; |
Ned Konz |
3:df4319053bfa | 69 | |
Ned Konz |
3:df4319053bfa | 70 | #define WORD_ALIGNED __attribute__ ((aligned(4))) |
Ned Konz |
3:df4319053bfa | 71 | |
Ned Konz |
3:df4319053bfa | 72 | // class static |
Ned Konz |
3:df4319053bfa | 73 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 74 | struct WS2811<MAX_LEDS_PER_STRIP>::DMALayout WS2811<MAX_LEDS_PER_STRIP>::dmaData WORD_ALIGNED; |
Ned Konz |
3:df4319053bfa | 75 | |
Ned Konz |
3:df4319053bfa | 76 | // class static |
Ned Konz |
3:df4319053bfa | 77 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 78 | void WS2811<MAX_LEDS_PER_STRIP>::hw_init() |
Ned Konz |
3:df4319053bfa | 79 | { |
Ned Konz |
3:df4319053bfa | 80 | if (initialized) return; |
Ned Konz |
3:df4319053bfa | 81 | |
Ned Konz |
3:df4319053bfa | 82 | dma_data_init(); |
Ned Konz |
3:df4319053bfa | 83 | clock_init(); |
Ned Konz |
3:df4319053bfa | 84 | dma_init(); |
Ned Konz |
3:df4319053bfa | 85 | io_init(); |
Ned Konz |
3:df4319053bfa | 86 | tpm_init(); |
Ned Konz |
3:df4319053bfa | 87 | |
Ned Konz |
3:df4319053bfa | 88 | initialized = true; |
Ned Konz |
3:df4319053bfa | 89 | |
Ned Konz |
3:df4319053bfa | 90 | SET_DEBUG; |
Ned Konz |
3:df4319053bfa | 91 | RESET_DEBUG; |
Ned Konz |
3:df4319053bfa | 92 | } |
Ned Konz |
3:df4319053bfa | 93 | |
Ned Konz |
3:df4319053bfa | 94 | // class static |
Ned Konz |
3:df4319053bfa | 95 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 96 | void WS2811<MAX_LEDS_PER_STRIP>::dma_data_init() |
Ned Konz |
3:df4319053bfa | 97 | { |
Ned Konz |
3:df4319053bfa | 98 | memset(dmaData.allOnes, 0xFF, sizeof(dmaData.allOnes)); |
Ned Konz |
3:df4319053bfa | 99 | |
Ned Konz |
3:df4319053bfa | 100 | #if DEBUG |
Ned Konz |
3:df4319053bfa | 101 | for (unsigned i = 0; i < BITS_PER_RGB * MAX_LEDS_PER_STRIP; i++) |
Ned Konz |
3:df4319053bfa | 102 | dmaData.dmaWords[i] = DEBUG_MASK; |
Ned Konz |
3:df4319053bfa | 103 | #endif |
Ned Konz |
3:df4319053bfa | 104 | } |
Ned Konz |
3:df4319053bfa | 105 | |
Ned Konz |
3:df4319053bfa | 106 | // class static |
Ned Konz |
3:df4319053bfa | 107 | |
Ned Konz |
3:df4319053bfa | 108 | /// Enable PORTD, DMA and TPM0 clocking |
Ned Konz |
3:df4319053bfa | 109 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 110 | void WS2811<MAX_LEDS_PER_STRIP>::clock_init() |
Ned Konz |
3:df4319053bfa | 111 | { |
Ned Konz |
3:df4319053bfa | 112 | SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK; |
Ned Konz |
3:df4319053bfa | 113 | SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK | SIM_SCGC6_TPM0_MASK; // Enable clock to DMA mux and TPM0 |
Ned Konz |
3:df4319053bfa | 114 | SIM->SCGC7 |= SIM_SCGC7_DMA_MASK; // Enable clock to DMA |
Ned Konz |
3:df4319053bfa | 115 | |
Ned Konz |
3:df4319053bfa | 116 | SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK |
Ned Konz |
3:df4319053bfa | 117 | } |
Ned Konz |
3:df4319053bfa | 118 | |
Ned Konz |
3:df4319053bfa | 119 | // class static |
Ned Konz |
3:df4319053bfa | 120 | |
Ned Konz |
3:df4319053bfa | 121 | /// Configure GPIO output pins |
Ned Konz |
3:df4319053bfa | 122 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 123 | void WS2811<MAX_LEDS_PER_STRIP>::io_init() |
Ned Konz |
3:df4319053bfa | 124 | { |
Ned Konz |
3:df4319053bfa | 125 | uint32_t m = 1; |
Ned Konz |
3:df4319053bfa | 126 | for (uint32_t i = 0; i < 32; i++) { |
Ned Konz |
3:df4319053bfa | 127 | // set up each pin |
Ned Konz |
3:df4319053bfa | 128 | if (m & enabledPins) { |
Ned Konz |
3:df4319053bfa | 129 | WS2811_IO_PORT->PCR[i] = PORT_PCR_MUX(1) // GPIO |
Ned Konz |
3:df4319053bfa | 130 | | PORT_PCR_DSE_MASK; // high drive strength |
Ned Konz |
3:df4319053bfa | 131 | } |
Ned Konz |
3:df4319053bfa | 132 | m <<= 1; |
Ned Konz |
3:df4319053bfa | 133 | } |
Ned Konz |
3:df4319053bfa | 134 | |
Ned Konz |
3:df4319053bfa | 135 | WS2811_IO_GPIO->PDDR |= enabledPins; // set as outputs |
Ned Konz |
3:df4319053bfa | 136 | |
Ned Konz |
3:df4319053bfa | 137 | #if WS2811_MONITOR_TPM0_PWM |
Ned Konz |
3:df4319053bfa | 138 | // PTD0 CH0 monitor: TPM0, high drive strength |
Ned Konz |
3:df4319053bfa | 139 | WS2811_IO_PORT->PCR[0] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK; |
Ned Konz |
3:df4319053bfa | 140 | // PTD1 CH1 monitor: TPM0, high drive strength |
Ned Konz |
3:df4319053bfa | 141 | WS2811_IO_PORT->PCR[1] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK; |
Ned Konz |
3:df4319053bfa | 142 | WS2811_IO_GPIO->PDDR |= 3; // set as outputs |
Ned Konz |
3:df4319053bfa | 143 | WS2811_IO_GPIO->PDOR &= ~(enabledPins | 3); // initially low |
Ned Konz |
3:df4319053bfa | 144 | #else |
Ned Konz |
3:df4319053bfa | 145 | WS2811_IO_GPIO->PDOR &= ~enabledPins; // initially low |
Ned Konz |
3:df4319053bfa | 146 | #endif |
Ned Konz |
3:df4319053bfa | 147 | |
Ned Konz |
3:df4319053bfa | 148 | #if DEBUG |
Ned Konz |
3:df4319053bfa | 149 | WS2811_IO_PORT->PCR[WS2811_DEBUG_PIN] = PORT_PCR_MUX(1) | PORT_PCR_DSE_MASK; |
Ned Konz |
3:df4319053bfa | 150 | WS2811_IO_GPIO->PDDR |= DEBUG_MASK; |
Ned Konz |
3:df4319053bfa | 151 | WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK; |
Ned Konz |
3:df4319053bfa | 152 | #endif |
Ned Konz |
3:df4319053bfa | 153 | } |
Ned Konz |
3:df4319053bfa | 154 | |
Ned Konz |
3:df4319053bfa | 155 | // class static |
Ned Konz |
3:df4319053bfa | 156 | |
Ned Konz |
3:df4319053bfa | 157 | /// Configure DMA and DMAMUX |
Ned Konz |
3:df4319053bfa | 158 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 159 | void WS2811<MAX_LEDS_PER_STRIP>::dma_init() |
Ned Konz |
3:df4319053bfa | 160 | { |
Ned Konz |
3:df4319053bfa | 161 | // reset DMAMUX |
Ned Konz |
3:df4319053bfa | 162 | DMAMUX0->CHCFG[DMA_CHAN_START] = 0; |
Ned Konz |
3:df4319053bfa | 163 | DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = 0; |
Ned Konz |
3:df4319053bfa | 164 | DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = 0; |
Ned Konz |
3:df4319053bfa | 165 | |
Ned Konz |
3:df4319053bfa | 166 | // wire our DMA event sources into the first three DMA channels |
Ned Konz |
3:df4319053bfa | 167 | // t=0: all enabled outputs go high on TPM0 overflow |
Ned Konz |
3:df4319053bfa | 168 | DMAMUX0->CHCFG[DMA_CHAN_START] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_Overflow); |
Ned Konz |
3:df4319053bfa | 169 | // t=tpm_p0_period: all of the 0 bits go low. |
Ned Konz |
3:df4319053bfa | 170 | DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_0); |
Ned Konz |
3:df4319053bfa | 171 | // t=tpm_p1_period: all outputs go low. |
Ned Konz |
3:df4319053bfa | 172 | DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1); |
Ned Konz |
3:df4319053bfa | 173 | |
Ned Konz |
3:df4319053bfa | 174 | NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler); |
Ned Konz |
3:df4319053bfa | 175 | NVIC_EnableIRQ(DMA0_IRQn); |
Ned Konz |
3:df4319053bfa | 176 | } |
Ned Konz |
3:df4319053bfa | 177 | |
Ned Konz |
3:df4319053bfa | 178 | // class static |
Ned Konz |
3:df4319053bfa | 179 | |
Ned Konz |
3:df4319053bfa | 180 | /// Configure TPM0 to do two different PWM periods at 800kHz rate |
Ned Konz |
3:df4319053bfa | 181 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 182 | void WS2811<MAX_LEDS_PER_STRIP>::tpm_init() |
Ned Konz |
3:df4319053bfa | 183 | { |
Ned Konz |
3:df4319053bfa | 184 | // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec) |
Ned Konz |
3:df4319053bfa | 185 | TPM_Type volatile *tpm = TPM0; |
Ned Konz |
3:df4319053bfa | 186 | tpm->SC = TPM_SC_DMA_MASK // enable DMA |
Ned Konz |
3:df4319053bfa | 187 | | TPM_SC_TOF_MASK // reset TOF flag if set |
Ned Konz |
3:df4319053bfa | 188 | | TPM_SC_CMOD(0) // disable clocks |
Ned Konz |
3:df4319053bfa | 189 | | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock |
Ned Konz |
3:df4319053bfa | 190 | tpm->MOD = tpm_period - 1; // 48MHz / 800kHz |
Ned Konz |
3:df4319053bfa | 191 | |
Ned Konz |
3:df4319053bfa | 192 | // No Interrupts; High True pulses on Edge Aligned PWM |
Ned Konz |
3:df4319053bfa | 193 | tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK; |
Ned Konz |
3:df4319053bfa | 194 | tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK; |
Ned Konz |
3:df4319053bfa | 195 | |
Ned Konz |
3:df4319053bfa | 196 | // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code) |
Ned Konz |
3:df4319053bfa | 197 | // 1.25 usec * 1/3 = 417 nsec |
Ned Konz |
3:df4319053bfa | 198 | tpm->CONTROLS[0].CnV = tpm_p0_period; |
Ned Konz |
3:df4319053bfa | 199 | |
Ned Konz |
3:df4319053bfa | 200 | // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code) |
Ned Konz |
3:df4319053bfa | 201 | // 1.25 usec * 2/3 = 833 nsec |
Ned Konz |
3:df4319053bfa | 202 | tpm->CONTROLS[1].CnV = tpm_p1_period; |
Ned Konz |
3:df4319053bfa | 203 | |
Ned Konz |
3:df4319053bfa | 204 | NVIC_SetVector(TPM0_IRQn, (uint32_t)&TPM0_IRQHandler); |
Ned Konz |
3:df4319053bfa | 205 | NVIC_EnableIRQ(TPM0_IRQn); |
Ned Konz |
3:df4319053bfa | 206 | } |
Ned Konz |
3:df4319053bfa | 207 | |
Ned Konz |
3:df4319053bfa | 208 | // class static |
Ned Konz |
3:df4319053bfa | 209 | template <unsigned MAX_LEDS_PER_STRIP> |
Ned Konz |
3:df4319053bfa | 210 | void WS2811<MAX_LEDS_PER_STRIP>::startDMA() |
Ned Konz |
3:df4319053bfa | 211 | { |
Ned Konz |
3:df4319053bfa | 212 | if (!initialized) hw_init(); |
Ned Konz |
3:df4319053bfa | 213 | |
Ned Konz |
3:df4319053bfa | 214 | wait_for_dma_done(); |
Ned Konz |
3:df4319053bfa | 215 | dma_done = 0; |
Ned Konz |
3:df4319053bfa | 216 | |
Ned Konz |
3:df4319053bfa | 217 | DMA_Type volatile * dma = DMA0; |
Ned Konz |
3:df4319053bfa | 218 | TPM_Type volatile *tpm = TPM0; |
Ned Konz |
3:df4319053bfa | 219 | uint32_t nBytes = sizeof(dmaData.start_t1_low) |
Ned Konz |
3:df4319053bfa | 220 | + sizeof(dmaData.dmaWords) |
Ned Konz |
3:df4319053bfa | 221 | + sizeof(dmaData.trailing_zeros_1); |
Ned Konz |
3:df4319053bfa | 222 | |
Ned Konz |
3:df4319053bfa | 223 | tpm->SC = TPM_SC_DMA_MASK // enable DMA |
Ned Konz |
3:df4319053bfa | 224 | | TPM_SC_TOF_MASK // reset TOF flag if set |
Ned Konz |
3:df4319053bfa | 225 | | TPM_SC_CMOD(0) // disable clocks |
Ned Konz |
3:df4319053bfa | 226 | | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock |
Ned Konz |
3:df4319053bfa | 227 | tpm->MOD = tpm_period - 1; // 48MHz / 800kHz |
Ned Konz |
3:df4319053bfa | 228 | |
Ned Konz |
3:df4319053bfa | 229 | tpm->CNT = tpm_p0_period - 2 ; |
Ned Konz |
3:df4319053bfa | 230 | tpm->STATUS = 0xFFFFFFFF; |
Ned Konz |
3:df4319053bfa | 231 | |
Ned Konz |
3:df4319053bfa | 232 | dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 233 | dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 234 | dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 235 | |
Ned Konz |
3:df4319053bfa | 236 | // t=0: all outputs go high |
Ned Konz |
3:df4319053bfa | 237 | // triggered by TPM0_Overflow |
Ned Konz |
3:df4319053bfa | 238 | // source is one word of 0 then 24 x 0xffffffff, then another 0 word |
Ned Konz |
3:df4319053bfa | 239 | dma->DMA[DMA_CHAN_START].SAR = (uint32_t)(void*)dmaData.start_t0_high; |
Ned Konz |
3:df4319053bfa | 240 | dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes |
Ned Konz |
3:df4319053bfa | 241 | |
Ned Konz |
3:df4319053bfa | 242 | // t=tpm_p0_period: some outputs (the 0 bits) go low. |
Ned Konz |
3:df4319053bfa | 243 | // Triggered by TPM0_CH0 |
Ned Konz |
3:df4319053bfa | 244 | // Start 2 words before the actual data to avoid garbage pulses. |
Ned Konz |
3:df4319053bfa | 245 | dma->DMA[DMA_CHAN_0_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address |
Ned Konz |
3:df4319053bfa | 246 | dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes |
Ned Konz |
3:df4319053bfa | 247 | |
Ned Konz |
3:df4319053bfa | 248 | // t=tpm_p1_period: all outputs go low. |
Ned Konz |
3:df4319053bfa | 249 | // Triggered by TPM0_CH1 |
Ned Konz |
3:df4319053bfa | 250 | // source is constant 0x00000000 (first word of dmaWords) |
Ned Konz |
3:df4319053bfa | 251 | dma->DMA[DMA_CHAN_1_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address |
Ned Konz |
3:df4319053bfa | 252 | dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes |
Ned Konz |
3:df4319053bfa | 253 | |
Ned Konz |
3:df4319053bfa | 254 | dma->DMA[DMA_CHAN_0_LOW].DAR |
Ned Konz |
3:df4319053bfa | 255 | = dma->DMA[DMA_CHAN_1_LOW].DAR |
Ned Konz |
3:df4319053bfa | 256 | = dma->DMA[DMA_CHAN_START].DAR |
Ned Konz |
3:df4319053bfa | 257 | = (uint32_t)(void*)&WS2811_IO_GPIO->PDOR; |
Ned Konz |
3:df4319053bfa | 258 | |
Ned Konz |
3:df4319053bfa | 259 | SET_DEBUG; |
Ned Konz |
3:df4319053bfa | 260 | |
Ned Konz |
3:df4319053bfa | 261 | dma->DMA[DMA_CHAN_0_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer |
Ned Konz |
3:df4319053bfa | 262 | | DMA_DCR_ERQ_MASK |
Ned Konz |
3:df4319053bfa | 263 | | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer |
Ned Konz |
3:df4319053bfa | 264 | | DMA_DCR_SINC_MASK // increment source each transfer |
Ned Konz |
3:df4319053bfa | 265 | | DMA_DCR_CS_MASK |
Ned Konz |
3:df4319053bfa | 266 | | DMA_DCR_SSIZE(0) // 32-bit source transfers |
Ned Konz |
3:df4319053bfa | 267 | | DMA_DCR_DSIZE(0); // 32-bit destination transfers |
Ned Konz |
3:df4319053bfa | 268 | |
Ned Konz |
3:df4319053bfa | 269 | dma->DMA[DMA_CHAN_1_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer |
Ned Konz |
3:df4319053bfa | 270 | | DMA_DCR_ERQ_MASK |
Ned Konz |
3:df4319053bfa | 271 | | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer |
Ned Konz |
3:df4319053bfa | 272 | | DMA_DCR_CS_MASK |
Ned Konz |
3:df4319053bfa | 273 | | DMA_DCR_SSIZE(0) // 32-bit source transfers |
Ned Konz |
3:df4319053bfa | 274 | | DMA_DCR_DSIZE(0); // 32-bit destination transfers |
Ned Konz |
3:df4319053bfa | 275 | |
Ned Konz |
3:df4319053bfa | 276 | dma->DMA[DMA_CHAN_START].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer |
Ned Konz |
3:df4319053bfa | 277 | | DMA_DCR_ERQ_MASK |
Ned Konz |
3:df4319053bfa | 278 | | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer |
Ned Konz |
3:df4319053bfa | 279 | | DMA_DCR_SINC_MASK // increment source each transfer |
Ned Konz |
3:df4319053bfa | 280 | | DMA_DCR_CS_MASK |
Ned Konz |
3:df4319053bfa | 281 | | DMA_DCR_SSIZE(0) // 32-bit source transfers |
Ned Konz |
3:df4319053bfa | 282 | | DMA_DCR_DSIZE(0); |
Ned Konz |
3:df4319053bfa | 283 | |
Ned Konz |
3:df4319053bfa | 284 | tpm->SC |= TPM_SC_CMOD(1); // enable internal clocking |
Ned Konz |
3:df4319053bfa | 285 | } |
Ned Konz |
3:df4319053bfa | 286 | |
Ned Konz |
3:df4319053bfa | 287 | #if !INSTANTIATE_TEMPLATES |
Ned Konz |
3:df4319053bfa | 288 | |
Ned Konz |
3:df4319053bfa | 289 | extern "C" void DMA0_IRQHandler() |
Ned Konz |
3:df4319053bfa | 290 | { |
Ned Konz |
3:df4319053bfa | 291 | DMA_Type volatile *dma = DMA0; |
Ned Konz |
3:df4319053bfa | 292 | TPM_Type volatile *tpm = TPM0; |
Ned Konz |
3:df4319053bfa | 293 | |
Ned Konz |
3:df4319053bfa | 294 | uint32_t db; |
Ned Konz |
3:df4319053bfa | 295 | |
Ned Konz |
3:df4319053bfa | 296 | db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR; |
Ned Konz |
3:df4319053bfa | 297 | if (db & DMA_DSR_BCR_DONE_MASK) { |
Ned Konz |
3:df4319053bfa | 298 | dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 299 | } |
Ned Konz |
3:df4319053bfa | 300 | |
Ned Konz |
3:df4319053bfa | 301 | db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR; |
Ned Konz |
3:df4319053bfa | 302 | if (db & DMA_DSR_BCR_DONE_MASK) { |
Ned Konz |
3:df4319053bfa | 303 | dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 304 | } |
Ned Konz |
3:df4319053bfa | 305 | |
Ned Konz |
3:df4319053bfa | 306 | db = dma->DMA[DMA_CHAN_START].DSR_BCR; |
Ned Konz |
3:df4319053bfa | 307 | if (db & DMA_DSR_BCR_DONE_MASK) { |
Ned Konz |
3:df4319053bfa | 308 | dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status |
Ned Konz |
3:df4319053bfa | 309 | } |
Ned Konz |
3:df4319053bfa | 310 | |
Ned Konz |
3:df4319053bfa | 311 | tpm->SC = TPM_SC_TOF_MASK; // reset TOF flag; disable internal clocking |
Ned Konz |
3:df4319053bfa | 312 | |
Ned Konz |
3:df4319053bfa | 313 | SET_DEBUG; |
Ned Konz |
3:df4319053bfa | 314 | |
Ned Konz |
3:df4319053bfa | 315 | #if 0 |
Ned Konz |
3:df4319053bfa | 316 | // set TPM0 to interrrupt after guardtime |
Ned Konz |
3:df4319053bfa | 317 | tpm->MOD = guardtime_period - 1; // 48MHz * 55 usec |
Ned Konz |
3:df4319053bfa | 318 | tpm->CNT = 0; |
Ned Konz |
3:df4319053bfa | 319 | tpm->SC = TPM_SC_PS(0) // 48MHz / 1 = 48MHz clock |
Ned Konz |
3:df4319053bfa | 320 | | TPM_SC_TOIE_MASK // enable interrupts |
Ned Konz |
3:df4319053bfa | 321 | | TPM_SC_CMOD(1); // and internal clocking |
Ned Konz |
3:df4319053bfa | 322 | #endif |
Ned Konz |
3:df4319053bfa | 323 | |
Ned Konz |
3:df4319053bfa | 324 | dma_done++; |
Ned Konz |
3:df4319053bfa | 325 | } |
Ned Konz |
3:df4319053bfa | 326 | |
Ned Konz |
3:df4319053bfa | 327 | extern "C" void TPM0_IRQHandler() |
Ned Konz |
3:df4319053bfa | 328 | { |
Ned Konz |
3:df4319053bfa | 329 | TPM0->SC = 0; // disable internal clocking |
Ned Konz |
3:df4319053bfa | 330 | TPM0->SC = TPM_SC_TOF_MASK; |
Ned Konz |
3:df4319053bfa | 331 | RESET_DEBUG; |
Ned Konz |
3:df4319053bfa | 332 | dma_done = 3; |
Ned Konz |
3:df4319053bfa | 333 | } |
Ned Konz |
3:df4319053bfa | 334 | |
Ned Konz |
3:df4319053bfa | 335 | #endif |