├── .gitignore ├── LICENSE ├── OctoWS2811Ext.cpp ├── OctoWS2811Ext.h ├── README.md └── TeensyStripController.ino /.gitignore: -------------------------------------------------------------------------------- 1 | *.elf 2 | *.hex 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Swisslizard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /OctoWS2811Ext.cpp: -------------------------------------------------------------------------------- 1 | /* OctoWS2811 - High Performance WS2811 LED Display Library 2 | http://www.pjrc.com/teensy/td_libs_OctoWS2811.html 3 | Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | /* Remark: 25 | This is a slightly extended version of the original OctoWS2811 lib. It contains 26 | a extra method to set the number of leds per strip dynamically. 27 | 28 | Thanks to the author of the lib for his excellent work. 29 | */ 30 | 31 | 32 | 33 | #include 34 | #include "OctoWS2811Ext.h" 35 | 36 | 37 | uint16_t OctoWS2811Ext::stripLen; 38 | void * OctoWS2811Ext::frameBuffer; 39 | void * OctoWS2811Ext::drawBuffer; 40 | uint8_t OctoWS2811Ext::params; 41 | DMAChannel OctoWS2811Ext::dma1; 42 | DMAChannel OctoWS2811Ext::dma2; 43 | DMAChannel OctoWS2811Ext::dma3; 44 | 45 | static uint8_t ones = 0xFF; 46 | static volatile uint8_t update_in_progress = 0; 47 | static uint32_t update_completed_at = 0; 48 | 49 | 50 | OctoWS2811Ext::OctoWS2811Ext(uint32_t numPerStrip, void *frameBuf, void *drawBuf, uint8_t config) 51 | { 52 | stripLen = numPerStrip; 53 | frameBuffer = frameBuf; 54 | drawBuffer = drawBuf; 55 | params = config; 56 | } 57 | 58 | // Waveform timing: these set the high time for a 0 and 1 bit, as a fraction of 59 | // the total 800 kHz or 400 kHz clock cycle. The scale is 0 to 255. The Worldsemi 60 | // datasheet seems T1H should be 600 ns of a 1250 ns cycle, or 48%. That may 61 | // erroneous information? Other sources reason the chip actually samples the 62 | // line close to the center of each bit time, so T1H should be 80% if TOH is 20%. 63 | // The chips appear to work based on a simple one-shot delay triggered by the 64 | // rising edge. At least 1 chip tested retransmits 0 as a 330 ns pulse (26%) and 65 | // a 1 as a 660 ns pulse (53%). Perhaps it's actually sampling near 500 ns? 66 | // There doesn't seem to be any advantage to making T1H less, as long as there 67 | // is sufficient low time before the end of the cycle, so the next rising edge 68 | // can be detected. T0H has been lengthened slightly, because the pulse can 69 | // narrow if the DMA controller has extra latency during bus arbitration. If you 70 | // have an insight about tuning these parameters AND you have actually tested on 71 | // real LED strips, please contact paul@pjrc.com. Please do not email based only 72 | // on reading the datasheets and purely theoretical analysis. 73 | #define WS2811_TIMING_T0H 60 74 | #define WS2811_TIMING_T1H 176 75 | 76 | // Discussion about timing and flicker & color shift issues: 77 | // http://forum.pjrc.com/threads/23877-WS2812B-compatible-with-OctoWS2811Ext-library?p=38190&viewfull=1#post38190 78 | 79 | 80 | void OctoWS2811Ext::begin(void) 81 | { 82 | uint32_t bufsize, frequency; 83 | bufsize = stripLen*24; 84 | 85 | // set up the buffers 86 | memset(frameBuffer, 0, bufsize); 87 | if (drawBuffer) { 88 | memset(drawBuffer, 0, bufsize); 89 | } else { 90 | drawBuffer = frameBuffer; 91 | } 92 | 93 | // configure the 8 output pins 94 | GPIOD_PCOR = 0xFF; 95 | pinMode(2, OUTPUT); // strip #1 96 | pinMode(14, OUTPUT); // strip #2 97 | pinMode(7, OUTPUT); // strip #3 98 | pinMode(8, OUTPUT); // strip #4 99 | pinMode(6, OUTPUT); // strip #5 100 | pinMode(20, OUTPUT); // strip #6 101 | pinMode(21, OUTPUT); // strip #7 102 | pinMode(5, OUTPUT); // strip #8 103 | 104 | // create the two waveforms for WS2811 low and high bits 105 | switch (params & 0xF0) { 106 | case WS2811_400kHz: 107 | frequency = 400000; 108 | break; 109 | case WS2811_800kHz: 110 | frequency = 800000; 111 | break; 112 | case WS2813_800kHz: 113 | frequency = 800000; 114 | break; 115 | default: 116 | frequency = 800000; 117 | } 118 | 119 | FTM2_SC = 0; 120 | FTM2_CNT = 0; 121 | uint32_t mod = (F_BUS + frequency / 2) / frequency; 122 | FTM2_MOD = mod - 1; 123 | FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); 124 | FTM2_C0SC = 0x69; 125 | FTM2_C1SC = 0x69; 126 | FTM2_C0V = (mod * WS2811_TIMING_T0H) >> 8; 127 | FTM2_C1V = (mod * WS2811_TIMING_T1H) >> 8; 128 | // pin 32 is FTM2_CH0, PTB18, triggers DMA(port B) on rising edge 129 | // pin 25 is FTM2_CH1, PTB19 130 | CORE_PIN32_CONFIG = PORT_PCR_IRQC(1)|PORT_PCR_MUX(3); 131 | //CORE_PIN25_CONFIG = PORT_PCR_MUX(3); // testing only 132 | 133 | // DMA channel #1 sets WS2811 high at the beginning of each cycle 134 | dma1.source(ones); 135 | dma1.destination(GPIOD_PSOR); 136 | dma1.transferSize(1); 137 | dma1.transferCount(bufsize); 138 | dma1.disableOnCompletion(); 139 | 140 | // DMA channel #2 writes the pixel data at 23% of the cycle 141 | dma2.sourceBuffer((uint8_t *)frameBuffer, bufsize); 142 | dma2.destination(GPIOD_PDOR); 143 | dma2.transferSize(1); 144 | dma2.transferCount(bufsize); 145 | dma2.disableOnCompletion(); 146 | 147 | // DMA channel #3 clear all the pins low at 69% of the cycle 148 | dma3.source(ones); 149 | dma3.destination(GPIOD_PCOR); 150 | dma3.transferSize(1); 151 | dma3.transferCount(bufsize); 152 | dma3.disableOnCompletion(); 153 | dma3.interruptAtCompletion(); 154 | 155 | // route the edge detect interrupts to trigger the 3 channels 156 | dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_PORTB); 157 | dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH0); 158 | dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH1); 159 | DMAPriorityOrder(dma3, dma2, dma1); 160 | 161 | // enable a done interrupts when channel #3 completes 162 | dma3.attachInterrupt(isr); 163 | //pinMode(9, OUTPUT); // testing: oscilloscope trigger 164 | } 165 | 166 | void OctoWS2811Ext::isr(void) 167 | { 168 | //digitalWriteFast(9, HIGH); 169 | //Serial1.print("."); 170 | //Serial1.println(dma3.CFG->DCR, HEX); 171 | //Serial1.print(dma3.CFG->DSR_BCR > 24, HEX); 172 | dma3.clearInterrupt(); 173 | 174 | //Serial1.print("*"); 175 | update_completed_at = micros(); 176 | update_in_progress = 0; 177 | //digitalWriteFast(9, LOW); 178 | } 179 | 180 | int OctoWS2811Ext::busy(void) 181 | { 182 | if (update_in_progress) return 1; 183 | // busy for 50 us after the done interrupt, for WS2811 reset 184 | if (micros() - update_completed_at < 50) return 1; 185 | return 0; 186 | } 187 | 188 | void OctoWS2811Ext::show(void) 189 | { 190 | // wait for any prior DMA operation 191 | //Serial1.print("1"); 192 | while (update_in_progress) ; 193 | //Serial1.print("2"); 194 | // it's ok to copy the drawing buffer to the frame buffer 195 | // during the 50us WS2811 reset time 196 | if (drawBuffer != frameBuffer) { 197 | // TODO: this could be faster with DMA, especially if the 198 | // buffers are 32 bit aligned... but does it matter? 199 | memcpy(frameBuffer, drawBuffer, stripLen * 24); 200 | } 201 | // wait for WS2811 reset 202 | while (micros() - update_completed_at < 50) ; 203 | // ok to start, but we must be very careful to begin 204 | // without any prior 3 x 800kHz DMA requests pending 205 | 206 | FTM2_C0SC = 0x28; 207 | FTM2_C1SC = 0x28; 208 | uint32_t cv = FTM2_C0V; 209 | noInterrupts(); 210 | // CAUTION: this code is timing critical. 211 | while (FTM2_CNT <= cv) ; 212 | while (FTM2_CNT > cv) ; // wait for beginning of an 800 kHz cycle 213 | while (FTM2_CNT < cv) ; 214 | FTM2_SC = 0; // stop FTM2 timer (hopefully before it rolls over) 215 | FTM2_CNT = 0; 216 | update_in_progress = 1; 217 | //digitalWriteFast(9, HIGH); // oscilloscope trigger 218 | PORTB_ISFR = (1<<18); // clear any prior rising edge 219 | uint32_t tmp __attribute__((unused)); 220 | FTM2_C0SC = 0x28; 221 | tmp = FTM2_C0SC; // clear any prior timer DMA triggers 222 | FTM2_C0SC = 0x69; 223 | FTM2_C1SC = 0x28; 224 | tmp = FTM2_C1SC; 225 | FTM2_C1SC = 0x69; 226 | dma1.enable(); 227 | dma2.enable(); // enable all 3 DMA channels 228 | dma3.enable(); 229 | FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart FTM2 timer 230 | //digitalWriteFast(9, LOW); 231 | 232 | //Serial1.print("3"); 233 | interrupts(); 234 | //Serial1.print("4"); 235 | } 236 | 237 | void OctoWS2811Ext::setStripLength(uint16_t length) 238 | { 239 | stripLen=length; 240 | } 241 | 242 | 243 | void OctoWS2811Ext::setPixel(uint32_t num, int color) 244 | { 245 | uint32_t strip, offset, mask32, *p; 246 | 247 | switch (params & 7) { 248 | case WS2811_RBG: 249 | color = (color&0xFF0000) | ((color<<8)&0x00FF00) | ((color>>8)&0x0000FF); 250 | break; 251 | case WS2811_GRB: 252 | color = ((color<<8)&0xFF0000) | ((color>>8)&0x00FF00) | (color&0x0000FF); 253 | break; 254 | case WS2811_GBR: 255 | color = ((color<<16)&0xFF0000) | ((color>>8)&0x00FFFF); 256 | break; 257 | case WS2811_BRG: 258 | color = ((color<<8)&0xFFFF00) | ((color>>16)&0x0000FF); 259 | break; 260 | case WS2811_BGR: 261 | color = ((color<<16)&0xFF0000) | (color&0x00FF00) | ((color>>16)&0x0000FF); 262 | break; 263 | default: 264 | break; 265 | } 266 | 267 | strip = num / stripLen; // Cortex-M4 has 2 cycle unsigned divide :-) 268 | offset = num % stripLen; 269 | 270 | p = ((uint32_t *) drawBuffer) + offset * 6; 271 | 272 | mask32 = (0x01010101) << strip; 273 | 274 | // Set bytes 0-3 275 | *p &= ~mask32; 276 | *p |= (((color & 0x800000) >> 23) | ((color & 0x400000) >> 14) | ((color & 0x200000) >> 5) | ((color & 0x100000) << 4)) << strip; 277 | 278 | // Set bytes 4-7 279 | *++p &= ~mask32; 280 | *p |= (((color & 0x80000) >> 19) | ((color & 0x40000) >> 10) | ((color & 0x20000) >> 1) | ((color & 0x10000) << 8)) << strip; 281 | 282 | // Set bytes 8-11 283 | *++p &= ~mask32; 284 | *p |= (((color & 0x8000) >> 15) | ((color & 0x4000) >> 6) | ((color & 0x2000) << 3) | ((color & 0x1000) << 12)) << strip; 285 | 286 | // Set bytes 12-15 287 | *++p &= ~mask32; 288 | *p |= (((color & 0x800) >> 11) | ((color & 0x400) >> 2) | ((color & 0x200) << 7) | ((color & 0x100) << 16)) << strip; 289 | 290 | // Set bytes 16-19 291 | *++p &= ~mask32; 292 | *p |= (((color & 0x80) >> 7) | ((color & 0x40) << 2) | ((color & 0x20) << 11) | ((color & 0x10) << 20)) << strip; 293 | 294 | // Set bytes 20-23 295 | *++p &= ~mask32; 296 | *p |= (((color & 0x8) >> 3) | ((color & 0x4) << 6) | ((color & 0x2) << 15) | ((color & 0x1) << 24)) << strip; 297 | } 298 | 299 | int OctoWS2811Ext::getPixel(uint32_t num) 300 | { 301 | uint32_t strip, offset, mask; 302 | uint8_t bit, *p; 303 | int color=0; 304 | 305 | strip = num / stripLen; 306 | offset = num % stripLen; 307 | bit = (1<>= 1) { 310 | if (*p++ & bit) color |= mask; 311 | } 312 | switch (params & 7) { 313 | case WS2811_RBG: 314 | color = (color&0xFF0000) | ((color<<8)&0x00FF00) | ((color>>8)&0x0000FF); 315 | break; 316 | case WS2811_GRB: 317 | color = ((color<<8)&0xFF0000) | ((color>>8)&0x00FF00) | (color&0x0000FF); 318 | break; 319 | case WS2811_GBR: 320 | color = ((color<<8)&0xFFFF00) | ((color>>16)&0x0000FF); 321 | break; 322 | case WS2811_BRG: 323 | color = ((color<<16)&0xFF0000) | ((color>>8)&0x00FFFF); 324 | break; 325 | case WS2811_BGR: 326 | color = ((color<<16)&0xFF0000) | (color&0x00FF00) | ((color>>16)&0x0000FF); 327 | break; 328 | default: 329 | break; 330 | } 331 | return color; 332 | } 333 | -------------------------------------------------------------------------------- /OctoWS2811Ext.h: -------------------------------------------------------------------------------- 1 | /* OctoWS2811 - High Performance WS2811 LED Display Library 2 | http://www.pjrc.com/teensy/td_libs_OctoWS2811.html 3 | Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | 25 | /* Remark: 26 | This is a slightly extended version of the original OctoWS2811 lib. It contains 27 | a extra method to set the number of leds per strip dynamically. 28 | 29 | Thanks to the author of the lib for his excellent work. 30 | */ 31 | 32 | 33 | #ifndef OctoWS2811Ext_h 34 | #define OctoWS2811Ext_h 35 | 36 | #include 37 | #include "DMAChannel.h" 38 | 39 | #if TEENSYDUINO < 121 40 | #error "Teensyduino version 1.21 or later is required to compile this library." 41 | #endif 42 | #ifdef __AVR__ 43 | #error "OctoWS2811Ext does not work with Teensy 2.0 or Teensy++ 2.0." 44 | #endif 45 | 46 | #define WS2811_RGB 0 // The WS2811 datasheet documents this way 47 | #define WS2811_RBG 1 48 | #define WS2811_GRB 2 // Most LED strips are wired this way 49 | #define WS2811_GBR 3 50 | #define WS2811_BRG 4 51 | #define WS2811_BGR 5 52 | 53 | #define WS2811_800kHz 0x00 // Nearly all WS2811 are 800 kHz 54 | #define WS2811_400kHz 0x10 // Adafruit's Flora Pixels 55 | #define WS2813_800kHz 0x20 // WS2813 are close to 800 kHz but has 300 us frame set delay 56 | 57 | class OctoWS2811Ext { 58 | public: 59 | OctoWS2811Ext(uint32_t numPerStrip, void *frameBuf, void *drawBuf, uint8_t config = WS2811_GRB); 60 | void begin(void); 61 | 62 | void setStripLength(uint16_t length); 63 | void setPixel(uint32_t num, int color); 64 | void setPixel(uint32_t num, uint8_t red, uint8_t green, uint8_t blue) { 65 | setPixel(num, color(red, green, blue)); 66 | } 67 | int getPixel(uint32_t num); 68 | 69 | void show(void); 70 | int busy(void); 71 | 72 | int numPixels(void) { 73 | return stripLen * 8; 74 | } 75 | int color(uint8_t red, uint8_t green, uint8_t blue) { 76 | return (red << 16) | (green << 8) | blue; 77 | } 78 | 79 | 80 | private: 81 | static uint16_t stripLen; 82 | static void *frameBuffer; 83 | static void *drawBuffer; 84 | static uint8_t params; 85 | static DMAChannel dma1, dma2, dma3; 86 | static void isr(void); 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TeensyStripController 1.03 2 | ========================== 3 | 4 | Firmware for a Teensy 3.1 or Teensy 3.2 to control WS2811/WS2812 based ledstrips. Fully compatible with the DirectOutput Framework. 5 | 6 | ![Teensy 3.1 with OctoWS2811 adaptor](http://www.pjrc.com/store/octo28_adaptor_6.jpg) 7 | 8 | 9 | Hardware 10 | -------- 11 | This code has been designed for Teensy 3.1 or Teensy 3.2. For easy installation of the ledstrips is is highly recommended to get a OctoWS2811 adaptor board as well. 12 | Both boards are available at http://pjrc.com/store/ 13 | For the Teensy be sure to get a Teensy 3.2, preferably the version which has the pins already soldered in. 14 | 15 | Ledstrips using the WS2812 are widely available on Ebay, AliExpress, Adafruit (Neopixels) and many other sources. Be careful when ordering ledstrips. After all these are low cost china products with all the pros and cons these products have. 16 | 17 | Make sure your power supply is strong enough. Every led needs up to 60ma and having strips with a lot of leds can easily drive you into high power requirements (e.g. 1m of ledstrip with 60 leds needs a PSU delivering at least 3.6A). Use thick wires for the power connections to the ledstrips and be sure to inject power after every 100-200 leds at least. Be careful to do the wiring correctly, otherwise the ledstrip, the wiring or even the PSU can get pretty hot which can be a fire hazard and which is bad for the lifetime of the whole setup. If using a lot of leds, be even more careful! A lot of leds means a lot of amperes and a lot of amperes with low voltage is also used for welding! 18 | 19 | Software 20 | -------- 21 | The code of this project can be compiled and installed on the Teensy with the Arduino IDE with installed Teensyduino extensions. 22 | 23 | Currently the firmware supports up to 1100 leds on each of the output 8 channels. Keep in mind that every led on a strip takes a little bit of time to update. To get some etimate on the max framerate you can achieve take the number of leds on your longest connected strip and multiply by 30microseconds. This will give you the approx time technically required for a single update of the strip. If you divide 1000000 by the result of the first calculation, you get the max update frequency (real update frequency will be a bit lower). Example: 500 (leds) * 30 microsconds= 15000 microseconds. 1000000 / 15000 = 66 Hz max update frequency (try to stay well above 30hz with your setup). 24 | 25 | To drive the controller at least DirectOutput Framework R3 is required. Check out the DOF docu page on _BuiltIn Output controllers_ for details on the configuration of DOF for the TeensyStrip controller. 26 | 27 | Integrated Product 28 | ------------------ 29 | 30 | The Oak Micros Pinball Addressable LEDs (PAL) board is a pre-built integrated product that can be used to control up to 8 addressable LEDs strips. It uses the Teensy 3.2 and the latest software from this site. It includes a pushbutton which can be used to initiate a LED test when powered up and has a pluggable screw connector for power and 6 LED connections. The remaining two other LED connections are available on an optional 0.1" header. 31 | 32 | ![Oak Micros PAL board](http://vpforums.org/imghost/24/pal_board.jpg) 33 | 34 | For further information read the [user guide](https://drive.google.com/open?id=1Zk_5RxsWX4VIPhT4XtGlj1rNlBTug39a) and see [this thread](https://www.vpforums.org/index.php?showtopic=43482) on VPForums.org. 35 | 36 | Documentation 37 | ------------- 38 | More information can be found in the wiki for this project: https://github.com/DirectOutput/TeensyStripController/wiki 39 | 40 | 41 | Firmware Downloads 42 | ------------------ 43 | Compiled firmware files can be downloaded from: https://github.com/DirectOutput/TeensyStripController/releases 44 | 45 | License 46 | ------- 47 | See License file in the repo. 48 | -------------------------------------------------------------------------------- /TeensyStripController.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************** 2 | ** TeensyStrip Controller 3 | ** ---------------------- 4 | ** 5 | ** This Sketch turns a Teensy 3.1, 3.2 or later into a controller for WS2811/WS2812 based led strips. 6 | ** This strip controller was originally designed for use with the Direct Output Framework, but since 7 | ** the communication protocol is simple and communication uses the virtual com port of the Teensy 8 | ** it should be easy to controll the strips from other applications as well. 9 | ** 10 | ** The most important part of the code is a slightly hacked version (a extra method to set the length of the 11 | ** strips dynamiccaly has been added) of Paul Stoffregens excellent OctoWS2811 LED Library. 12 | ** For more information on the lib check out: 13 | ** https://github.com/PaulStoffregen/OctoWS2811 14 | ** http://www.pjrc.com/teensy/td_libs_OctoWS2811.html 15 | ** 16 | *********************************************************************************************************/ 17 | /* 18 | License: 19 | -------- 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | 36 | Required Connections 37 | -------------------- 38 | pin 2: LED Strip #1 OctoWS2811 drives 8 LED Strips. 39 | pin 14: LED strip #2 All 8 are the same length. 40 | pin 7: LED strip #3 41 | pin 8: LED strip #4 A 100 ohm resistor should used 42 | pin 6: LED strip #5 between each Teensy pin and the 43 | pin 20: LED strip #6 wire to the LED strip, to minimize 44 | pin 21: LED strip #7 high frequency ringining & noise. 45 | pin 5: LED strip #8 46 | pin 15 & 16 - Connect together, but do not use 47 | pin 4 - Do not use 48 | pin 3 - Do not use as PWM. Normal use is ok. 49 | 50 | */ 51 | 52 | #include "OctoWS2811Ext.h" //A slightly hacked version of the OctoWS2811 lib which allows for dynamic setting of the number of leds is used. 53 | 54 | //Definiton of Major and Minor part of the firmware version. This value can be received using the V command. 55 | //If something is changed in the code the number should be increased. 56 | #define FirmwareVersionMajor 1 57 | #define FirmwareVersionMinor 3 58 | 59 | //Defines the max number of leds which is allowed per ledstrip. 60 | //This number is fine for Teensy 3.2, 3.1. For newer Teensy versions (they dont exists yet) it might be possible to increase this number. 61 | #define MaxLedsPerStrip 1100 62 | 63 | //Defines the Pinnumber to which the built in led of the Teensy is connected. 64 | //For Teensy 3.2, 3.1 this is pin 13, if you use a newer Teensy version (not available at the time of writing) you might need to change this number. 65 | #define LedPin 13 66 | 67 | // Defines the Pinnumber for the test button which is low when pressed 68 | #define TestPin 17 69 | 70 | //Memory buffers for the OctoWS2811 lib 71 | DMAMEM int displayMemory[MaxLedsPerStrip*6]; 72 | int drawingMemory[MaxLedsPerStrip*6]; 73 | 74 | //Variable used to control the blinking and flickering of the led of the Teensy 75 | elapsedMillis BlinkTimer; 76 | int BlinkMode; 77 | elapsedMillis BlinkModeTimeoutTimer; 78 | 79 | //Config definition for the OctoWS2811 lib 80 | const int config = WS2811_RGB | WS2811_800kHz; //Dont change the color order (even if your strip are GRB). DOF takes care of this issue (see config of ledstrip toy) 81 | 82 | OctoWS2811Ext leds(MaxLedsPerStrip, displayMemory, drawingMemory, config); 83 | 84 | word configuredStripLength=144; 85 | 86 | //Setup of the system. Is called once on startup. 87 | void setup() { 88 | Serial.begin(9600); // This has no effect. USB bitrate (12MB) will be used anyway. 89 | 90 | //Initialize the lib for the leds 91 | leds.setStripLength(configuredStripLength); 92 | leds.begin(); 93 | leds.show(); 94 | 95 | //Initialize the led pin 96 | pinMode(LedPin,OUTPUT); 97 | 98 | //Initialize and find value of the test pin 99 | pinMode(TestPin,INPUT_PULLUP); 100 | if (! digitalRead(TestPin)) { 101 | // run test if button is grounded 102 | Test(); 103 | } 104 | 105 | SetBlinkMode(0); 106 | } 107 | 108 | //Main loop of the programm gets called again and again. 109 | void loop() { 110 | // put your main code here, to run repeatedly: 111 | 112 | //Check if data is available 113 | if (Serial.available()) { 114 | 115 | byte receivedByte = Serial.read(); 116 | switch (receivedByte) { 117 | case 'L': 118 | //Set length of strips 119 | SetLedStripLength(); 120 | break; 121 | case 'F': 122 | //Fill strip area with color 123 | Fill(); 124 | break; 125 | case 'R': 126 | //receive data for strips 127 | ReceiveData(); 128 | break; 129 | case 'O': 130 | //output data on strip 131 | OutputData(); 132 | break; 133 | case 'C': 134 | //Clears all previously received led data 135 | ClearAllLedData(); 136 | break; 137 | case 'V': 138 | //Send the firmware version 139 | SendVersion(); 140 | break; 141 | case 'M': 142 | //Get max number of leds per strip 143 | SendMaxNumberOfLeds(); 144 | break; 145 | default: 146 | // no unknown commands allowed. Send NACK (N) 147 | Nack(); 148 | break; 149 | } 150 | 151 | 152 | SetBlinkMode(1); 153 | 154 | 155 | } 156 | Blink(); 157 | } 158 | 159 | 160 | //Sets the mode for the blinking of the led 161 | void SetBlinkMode(int Mode) { 162 | BlinkMode=Mode; 163 | BlinkModeTimeoutTimer=0; 164 | } 165 | 166 | //Controls the blinking of the led 167 | void Blink() { 168 | switch(BlinkMode) { 169 | case 0: 170 | //Blinkmode 0 is only active after the start of the Teensy until the first command is received. 171 | if(BlinkTimer<1500) { 172 | digitalWrite(LedPin,0); 173 | } else if(BlinkTimer<1600) { 174 | digitalWrite(LedPin,1); 175 | } else { 176 | BlinkTimer=0; 177 | digitalWrite(LedPin,0); 178 | } 179 | break; 180 | case 1: 181 | //Blinkmode 1 is activated when the Teensy receives a command 182 | //Mode expires 500ms after the last command has been received resp. mode has been set 183 | if(BlinkTimer>30) { 184 | BlinkTimer=0; 185 | digitalWrite(LedPin,!digitalRead(LedPin)); 186 | } 187 | if(BlinkModeTimeoutTimer>500) { 188 | SetBlinkMode(2); 189 | } 190 | break; 191 | case 2: 192 | //Blinkmode 2 is active while the Teensy is waiting for more commands 193 | if(BlinkTimer<1500) { 194 | digitalWrite(LedPin,0); 195 | } else if(BlinkTimer<1600) { 196 | digitalWrite(LedPin,1); 197 | } else if(BlinkTimer<1700) { 198 | digitalWrite(LedPin,0); 199 | } else if(BlinkTimer<1800) { 200 | digitalWrite(LedPin,1); 201 | }else { 202 | BlinkTimer=0; 203 | digitalWrite(LedPin,0); 204 | } 205 | default: 206 | //This should never be active 207 | //The code is only here to make it easier to determine if a wrong Blinkcode has been set 208 | if(BlinkTimer>2000) { 209 | BlinkTimer=0; 210 | digitalWrite(LedPin,!digitalRead(LedPin)); 211 | } 212 | break; 213 | } 214 | 215 | } 216 | 217 | 218 | //Outputs the data in the ram to the ledstrips 219 | void OutputData() { 220 | leds.show(); 221 | Ack(); 222 | } 223 | 224 | 225 | //Fills the given area of a ledstrip with a color 226 | void Fill() { 227 | word firstLed=ReceiveWord(); 228 | 229 | word numberOfLeds=ReceiveWord(); 230 | 231 | int ColorData=ReceiveColorData(); 232 | 233 | if( firstLed<=configuredStripLength*8 && numberOfLeds>0 && firstLed+numberOfLeds-1<=configuredStripLength*8 ) { 234 | word endLedNr=firstLed+numberOfLeds; 235 | for(word ledNr=firstLed; ledNr0 && firstLed+numberOfLeds-1<=configuredStripLength*8 ) { 255 | //FirstLedNr and numberOfLeds are valid. 256 | //Receive and set color data 257 | 258 | word endLedNr=firstLed+numberOfLeds; 259 | for(word ledNr=firstLed; ledNrMaxLedsPerStrip) { 275 | //stripLength is either to small or above the max number of leds allowed 276 | Nack(); 277 | } else { 278 | //stripLength is in the valid range 279 | configuredStripLength=stripLength; 280 | leds.setStripLength(stripLength); 281 | leds.begin(); //Reinitialize the OctoWS2811 lib (not sure if this is needed) 282 | 283 | Ack(); 284 | } 285 | } 286 | 287 | //Clears the data for all configured leds 288 | void ClearAllLedData() { 289 | for(word ledNr=0;ledNr>8; 306 | Serial.write(B); 307 | B=MaxLedsPerStrip&255; 308 | Serial.write(B); 309 | Ack(); 310 | } 311 | 312 | 313 | //Sends a ack (A) 314 | void Ack() { 315 | Serial.write('A'); 316 | } 317 | 318 | //Sends a NACK (N) 319 | void Nack() { 320 | Serial.write('N'); 321 | } 322 | 323 | //Receives 3 bytes of color data. 324 | int ReceiveColorData() { 325 | while(!Serial.available()) {}; 326 | int colorValue=Serial.read(); 327 | while(!Serial.available()) {}; 328 | colorValue=(colorValue<<8)|Serial.read(); 329 | while(!Serial.available()) {}; 330 | colorValue=(colorValue<<8)|Serial.read(); 331 | 332 | return colorValue; 333 | 334 | 335 | } 336 | 337 | //Receives a word value. High byte first, low byte second 338 | word ReceiveWord() { 339 | while(!Serial.available()) {}; 340 | word wordValue=Serial.read()<<8; 341 | while(!Serial.available()) {}; 342 | wordValue=wordValue|Serial.read(); 343 | 344 | return wordValue; 345 | } 346 | 347 | // Colors for testing - assumes WS2812 color order of G, R, B 348 | /* 349 | #define RED 0x00FF00 350 | #define GREEN 0xFF0000 351 | #define BLUE 0x0000FF 352 | #define YELLOW 0xFFFF00 353 | #define PINK 0x10FF88 354 | #define ORANGE 0x45FF00 355 | #define WHITE 0xFFFFFF 356 | #define BLACK 0x000000 357 | */ 358 | 359 | // Less intense colors for testing - assumes WS2812 color order of G, R, B 360 | #define RED 0x001600 361 | #define GREEN 0x160000 362 | #define BLUE 0x000016 363 | #define YELLOW 0x141000 364 | #define PINK 0x001209 365 | #define ORANGE 0x041000 366 | #define WHITE 0x101010 367 | #define BLACK 0x000000 368 | 369 | 370 | void Test() { 371 | int microsec = 3000000; // change them all in 3 seconds 372 | 373 | ColorWipe(RED, microsec); 374 | ColorWipe(GREEN, microsec); 375 | ColorWipe(BLUE, microsec); 376 | ColorWipe(YELLOW, microsec); 377 | ColorWipe(PINK, microsec); 378 | ColorWipe(ORANGE, microsec); 379 | ColorWipe(WHITE, microsec); 380 | ColorWipe(BLACK, microsec); 381 | } 382 | 383 | void ColorWipe(int color, int wait) 384 | { 385 | for (int i=0; i < leds.numPixels(); i++) { 386 | leds.setPixel(i, color); 387 | } 388 | 389 | // wait before turning on LEDs and also turn on indicator LED 390 | delayMicroseconds(100000); 391 | digitalWrite(LedPin,1); 392 | leds.show(); 393 | 394 | // wait for desginated timeout and then turn off indicator LED 395 | delayMicroseconds(wait); 396 | digitalWrite(LedPin,0); 397 | } 398 | --------------------------------------------------------------------------------