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.
WS2811.cpp
00001 // 800 KHz WS2811 driver driving potentially many LED strings. 00002 // Uses 3-phase DMA 00003 // 16K SRAM less stack, etc. 00004 // 00005 // Per LED: 3 bytes (malloc'd) for RGB data 00006 // 00007 // Per LED strip / per LED 00008 // 96 bytes (static) for bit data 00009 // + 96 bytes (static) for ones data 00010 // = 192 bytes 00011 // 00012 // 40 LEDs max per string = 7680 bytes static 00013 // 00014 // 40 LEDs: 7680 + 40*3 = 7800 bytes 00015 // 80 LEDs: 7680 + 80*3 = 7920 bytes 00016 #include <mbed.h> 00017 #include "MKL25Z4.h" 00018 00019 #ifndef MBED_WS2811_H 00020 #include "WS2811.h " 00021 #endif 00022 00023 #if defined(WS2811_DEBUG_PIN) 00024 #define DEBUG_MASK (1<<WS2811_DEBUG_PIN) 00025 #define RESET_DEBUG (WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK) 00026 #define SET_DEBUG (WS2811_IO_GPIO->PDOR |= DEBUG_MASK) 00027 #else 00028 #define DEBUG_MASK 0 00029 #define RESET_DEBUG (void)0 00030 #define SET_DEBUG (void)0 00031 #endif 00032 00033 static volatile unsigned dma_done = 3; 00034 00035 // 48 MHz clock, no prescaling. 00036 #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000) 00037 #define USEC_TO_TICKS(usec) ((usec)*48) 00038 #define CLK_NSEC 1250 00039 #define tpm_period NSEC_TO_TICKS(CLK_NSEC) 00040 #define tpm_p0_period NSEC_TO_TICKS(250) 00041 #define tpm_p1_period NSEC_TO_TICKS(650) 00042 #define guardtime_us 55 00043 #define guardtime_period USEC_TO_TICKS(guardtime_us) /* guardtime minimum 50 usec. */ 00044 00045 enum DMA_MUX_SRC { 00046 DMA_MUX_SRC_TPM0_CH_0 = 24, 00047 DMA_MUX_SRC_TPM0_CH_1, 00048 DMA_MUX_SRC_TPM0_Overflow = 54, 00049 }; 00050 00051 enum DMA_CHAN { 00052 DMA_CHAN_START = 0, 00053 DMA_CHAN_0_LOW = 1, 00054 DMA_CHAN_1_LOW = 2, 00055 N_DMA_CHANNELS 00056 }; 00057 00058 // class static 00059 template <unsigned MAX_LEDS_PER_STRIP> 00060 void WS2811<MAX_LEDS_PER_STRIP>::wait_for_dma_done() 00061 { 00062 if ((DMA0->DMA[DMA_CHAN_1_LOW].DSR_BCR & DMA_DSR_BCR_BSY_MASK) == 0) 00063 return; 00064 while ((DMA0->DMA[DMA_CHAN_1_LOW].DSR_BCR & DMA_DSR_BCR_BSY_MASK)) 00065 __WFI(); 00066 wait_us(guardtime_us); 00067 } 00068 00069 // class static 00070 template <unsigned MAX_LEDS_PER_STRIP> 00071 bool WS2811<MAX_LEDS_PER_STRIP>::initialized = false; 00072 00073 // class static 00074 template <unsigned MAX_LEDS_PER_STRIP> 00075 uint32_t WS2811<MAX_LEDS_PER_STRIP>::enabledPins = 0; 00076 00077 #define WORD_ALIGNED __attribute__ ((aligned(4))) 00078 00079 // class static 00080 template <unsigned MAX_LEDS_PER_STRIP> 00081 struct WS2811<MAX_LEDS_PER_STRIP>::DMALayout WS2811<MAX_LEDS_PER_STRIP>::dmaData WORD_ALIGNED; 00082 00083 // class static 00084 template <unsigned MAX_LEDS_PER_STRIP> 00085 void WS2811<MAX_LEDS_PER_STRIP>::hw_init() 00086 { 00087 if (initialized) return; 00088 00089 dma_data_init(); 00090 clock_init(); 00091 dma_init(); 00092 io_init(); 00093 tpm_init(); 00094 00095 initialized = true; 00096 00097 SET_DEBUG; 00098 RESET_DEBUG; 00099 } 00100 00101 // class static 00102 template <unsigned MAX_LEDS_PER_STRIP> 00103 void WS2811<MAX_LEDS_PER_STRIP>::dma_data_init() 00104 { 00105 memset(dmaData.allOnes, 0xFF, sizeof(dmaData.allOnes)); 00106 } 00107 00108 // class static 00109 00110 /// Enable PORTD, DMA and TPM0 clocking 00111 template <unsigned MAX_LEDS_PER_STRIP> 00112 void WS2811<MAX_LEDS_PER_STRIP>::clock_init() 00113 { 00114 SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK; 00115 SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK | SIM_SCGC6_TPM0_MASK; // Enable clock to DMA mux and TPM0 00116 SIM->SCGC7 |= SIM_SCGC7_DMA_MASK; // Enable clock to DMA 00117 00118 SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK 00119 } 00120 00121 // class static 00122 00123 /// Configure GPIO output pins 00124 template <unsigned MAX_LEDS_PER_STRIP> 00125 void WS2811<MAX_LEDS_PER_STRIP>::io_init() 00126 { 00127 uint32_t m = 1; 00128 for (uint32_t i = 0; i < 32; i++) { 00129 // set up each pin 00130 if (m & enabledPins) { 00131 WS2811_IO_PORT->PCR[i] = PORT_PCR_MUX(1) // GPIO 00132 | PORT_PCR_DSE_MASK; // high drive strength 00133 } 00134 m <<= 1; 00135 } 00136 00137 WS2811_IO_GPIO->PDDR |= enabledPins; // set as outputs 00138 00139 #if WS2811_MONITOR_TPM0_PWM 00140 // PTD0 CH0 monitor: TPM0, high drive strength 00141 WS2811_IO_PORT->PCR[0] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK; 00142 // PTD1 CH1 monitor: TPM0, high drive strength 00143 WS2811_IO_PORT->PCR[1] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK; 00144 WS2811_IO_GPIO->PDDR |= 3; // set as outputs 00145 WS2811_IO_GPIO->PDOR &= ~(enabledPins | 3); // initially low 00146 #else 00147 WS2811_IO_GPIO->PDOR &= ~enabledPins; // initially low 00148 #endif 00149 00150 #ifdef WS2811_DEBUG_PIN 00151 WS2811_IO_PORT->PCR[WS2811_DEBUG_PIN] = PORT_PCR_MUX(1) | PORT_PCR_DSE_MASK; 00152 WS2811_IO_GPIO->PDDR |= DEBUG_MASK; 00153 WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK; 00154 #endif 00155 } 00156 00157 // class static 00158 00159 /// Configure DMA and DMAMUX 00160 template <unsigned MAX_LEDS_PER_STRIP> 00161 void WS2811<MAX_LEDS_PER_STRIP>::dma_init() 00162 { 00163 // reset DMAMUX 00164 DMAMUX0->CHCFG[DMA_CHAN_START] = 0; 00165 DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = 0; 00166 DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = 0; 00167 00168 // wire our DMA event sources into the first three DMA channels 00169 // t=0: all enabled outputs go high on TPM0 overflow 00170 DMAMUX0->CHCFG[DMA_CHAN_START] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_Overflow); 00171 // t=tpm_p0_period: all of the 0 bits go low. 00172 DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_0); 00173 // t=tpm_p1_period: all outputs go low. 00174 DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1); 00175 00176 NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler); 00177 NVIC_EnableIRQ(DMA0_IRQn); 00178 } 00179 00180 // class static 00181 00182 /// Configure TPM0 to do two different PWM periods at 800kHz rate 00183 template <unsigned MAX_LEDS_PER_STRIP> 00184 void WS2811<MAX_LEDS_PER_STRIP>::tpm_init() 00185 { 00186 // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec) 00187 TPM_Type volatile *tpm = TPM0; 00188 tpm->SC = TPM_SC_DMA_MASK // enable DMA 00189 | TPM_SC_TOF_MASK // reset TOF flag if set 00190 | TPM_SC_CMOD(0) // disable clocks 00191 | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock 00192 tpm->MOD = tpm_period - 1; // 48MHz / 800kHz 00193 00194 // No Interrupts; High True pulses on Edge Aligned PWM 00195 tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK; 00196 tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK; 00197 00198 // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code) 00199 // 1.25 usec * 1/3 = 417 nsec 00200 tpm->CONTROLS[0].CnV = tpm_p0_period; 00201 00202 // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code) 00203 // 1.25 usec * 2/3 = 833 nsec 00204 tpm->CONTROLS[1].CnV = tpm_p1_period; 00205 } 00206 00207 // class static 00208 template <unsigned MAX_LEDS_PER_STRIP> 00209 void WS2811<MAX_LEDS_PER_STRIP>::startDMA() 00210 { 00211 if (!initialized) hw_init(); 00212 00213 wait_for_dma_done(); 00214 00215 DMA_Type volatile * dma = DMA0; 00216 TPM_Type volatile *tpm = TPM0; 00217 uint32_t nBytes = sizeof(dmaData.start_t1_low) 00218 + sizeof(dmaData.dmaWords) 00219 + sizeof(dmaData.trailing_zeros_1); 00220 00221 tpm->SC = TPM_SC_DMA_MASK // enable DMA 00222 | TPM_SC_TOF_MASK // reset TOF flag if set 00223 | TPM_SC_CMOD(0) // disable clocks 00224 | TPM_SC_PS(0); // 48MHz / 1 = 48MHz clock 00225 tpm->MOD = tpm_period - 1; // 48MHz / 800kHz 00226 00227 tpm->CNT = tpm_p0_period - 2 ; 00228 tpm->STATUS = 0xFFFFFFFF; 00229 00230 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00231 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00232 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00233 00234 // t=0: all outputs go high 00235 // triggered by TPM0_Overflow 00236 // source is one word of 0 then 24 x 0xffffffff, then another 0 word 00237 dma->DMA[DMA_CHAN_START].SAR = (uint32_t)(void*)dmaData.start_t0_high; 00238 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes 00239 00240 // t=tpm_p0_period: some outputs (the 0 bits) go low. 00241 // Triggered by TPM0_CH0 00242 // Start 2 words before the actual data to avoid garbage pulses. 00243 dma->DMA[DMA_CHAN_0_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address 00244 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes 00245 00246 // t=tpm_p1_period: all outputs go low. 00247 // Triggered by TPM0_CH1 00248 // source is constant 0x00000000 (first word of dmaWords) 00249 dma->DMA[DMA_CHAN_1_LOW].SAR = (uint32_t)(void*)dmaData.start_t1_low; // set source address 00250 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes 00251 00252 dma->DMA[DMA_CHAN_0_LOW].DAR 00253 = dma->DMA[DMA_CHAN_1_LOW].DAR 00254 = dma->DMA[DMA_CHAN_START].DAR 00255 = (uint32_t)(void*)&WS2811_IO_GPIO->PDOR; 00256 00257 SET_DEBUG; 00258 00259 dma->DMA[DMA_CHAN_0_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer 00260 | DMA_DCR_ERQ_MASK 00261 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer 00262 | DMA_DCR_SINC_MASK // increment source each transfer 00263 | DMA_DCR_CS_MASK 00264 | DMA_DCR_SSIZE(0) // 32-bit source transfers 00265 | DMA_DCR_DSIZE(0); // 32-bit destination transfers 00266 00267 dma->DMA[DMA_CHAN_1_LOW].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer 00268 | DMA_DCR_ERQ_MASK 00269 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer 00270 | DMA_DCR_CS_MASK 00271 | DMA_DCR_SSIZE(0) // 32-bit source transfers 00272 | DMA_DCR_DSIZE(0); // 32-bit destination transfers 00273 00274 dma->DMA[DMA_CHAN_START].DCR = DMA_DCR_EINT_MASK // enable interrupt on end of transfer 00275 | DMA_DCR_ERQ_MASK 00276 | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer 00277 | DMA_DCR_SINC_MASK // increment source each transfer 00278 | DMA_DCR_CS_MASK 00279 | DMA_DCR_SSIZE(0) // 32-bit source transfers 00280 | DMA_DCR_DSIZE(0); 00281 00282 tpm->SC |= TPM_SC_CMOD(1); // enable internal clocking 00283 } 00284 00285 #if !INSTANTIATE_TEMPLATES 00286 00287 extern "C" void DMA0_IRQHandler() 00288 { 00289 DMA_Type volatile *dma = DMA0; 00290 TPM_Type volatile *tpm = TPM0; 00291 00292 uint32_t db; 00293 00294 db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR; 00295 if (db & DMA_DSR_BCR_DONE_MASK) { 00296 dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00297 } 00298 00299 db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR; 00300 if (db & DMA_DSR_BCR_DONE_MASK) { 00301 dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00302 } 00303 00304 db = dma->DMA[DMA_CHAN_START].DSR_BCR; 00305 if (db & DMA_DSR_BCR_DONE_MASK) { 00306 dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status 00307 } 00308 00309 tpm->SC = TPM_SC_TOF_MASK; // reset TOF flag; disable internal clocking 00310 00311 SET_DEBUG; 00312 } 00313 00314 #endif
Generated on Wed Jul 13 2022 13:18:58 by 1.7.2