LEDstream.ino
1 // SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // Arduino "bridge" code between host computer and WS2801-based digital 6 // RGB LED pixels (e.g. Adafruit product ID #322). Intended for use 7 // with USB-native boards such as Teensy or Adafruit 32u4 Breakout; 8 // works on normal serial Arduinos, but throughput is severely limited. 9 // LED data is streamed, not buffered, making this suitable for larger 10 // installations (e.g. video wall, etc.) than could otherwise be held 11 // in the Arduino's limited RAM. 12 13 // Some effort is put into avoiding buffer underruns (where the output 14 // side becomes starved of data). The WS2801 latch protocol, being 15 // delay-based, could be inadvertently triggered if the USB bus or CPU 16 // is swamped with other tasks. This code buffers incoming serial data 17 // and introduces intentional pauses if there's a threat of the buffer 18 // draining prematurely. The cost of this complexity is somewhat 19 // reduced throughput, the gain is that most visual glitches are 20 // avoided (though ultimately a function of the load on the USB bus and 21 // host CPU, and out of our control). 22 23 // LED data and clock lines are connected to the Arduino's SPI output. 24 // On traditional Arduino boards, SPI data out is digital pin 11 and 25 // clock is digital pin 13. On both Teensy and the 32u4 Breakout, 26 // data out is pin B2, clock is B1. LEDs should be externally 27 // powered -- trying to run any more than just a few off the Arduino's 28 // 5V line is generally a Bad Idea. LED ground should also be 29 // connected to Arduino ground. 30 31 #include <SPI.h> 32 33 // LED pin for Adafruit 32u4 Breakout Board: 34 //#define LED_DDR DDRE 35 //#define LED_PORT PORTE 36 //#define LED_PIN _BV(PORTE6) 37 // LED pin for Teensy: 38 //#define LED_DDR DDRD 39 //#define LED_PORT PORTD 40 //#define LED_PIN _BV(PORTD6) 41 // LED pin for Arduino: 42 #define LED_DDR DDRB 43 #define LED_PORT PORTB 44 #define LED_PIN _BV(PORTB5) 45 46 // A 'magic word' (along with LED count & checksum) precedes each block 47 // of LED data; this assists the microcontroller in syncing up with the 48 // host-side software and properly issuing the latch (host I/O is 49 // likely buffered, making usleep() unreliable for latch). You may see 50 // an initial glitchy frame or two until the two come into alignment. 51 // The magic word can be whatever sequence you like, but each character 52 // should be unique, and frequent pixel values like 0 and 255 are 53 // avoided -- fewer false positives. The host software will need to 54 // generate a compatible header: immediately following the magic word 55 // are three bytes: a 16-bit count of the number of LEDs (high byte 56 // first) followed by a simple checksum value (high byte XOR low byte 57 // XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, 58 // where 0 = off and 255 = max brightness. 59 60 static const uint8_t magic[] = {'A','d','a'}; 61 #define MAGICSIZE sizeof(magic) 62 #define HEADERSIZE (MAGICSIZE + 3) 63 64 #define MODE_HEADER 0 65 #define MODE_HOLD 1 66 #define MODE_DATA 2 67 68 // If no serial data is received for a while, the LEDs are shut off 69 // automatically. This avoids the annoying "stuck pixel" look when 70 // quitting LED display programs on the host computer. 71 static const unsigned long serialTimeout = 15000; // 15 seconds 72 73 void setup() 74 { 75 // Dirty trick: the circular buffer for serial data is 256 bytes, 76 // and the "in" and "out" indices are unsigned 8-bit types -- this 77 // much simplifies the cases where in/out need to "wrap around" the 78 // beginning/end of the buffer. Otherwise there'd be a ton of bit- 79 // masking and/or conditional code every time one of these indices 80 // needs to change, slowing things down tremendously. 81 uint8_t 82 buffer[256], 83 indexIn = 0, 84 indexOut = 0, 85 mode = MODE_HEADER, 86 hi, lo, chk, i, spiFlag; 87 int16_t 88 bytesBuffered = 0, 89 hold = 0, 90 c; 91 int32_t 92 bytesRemaining; 93 unsigned long 94 startTime, 95 lastByteTime, 96 lastAckTime, 97 t; 98 99 LED_DDR |= LED_PIN; // Enable output for LED 100 LED_PORT &= ~LED_PIN; // LED off 101 102 Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! 103 104 SPI.begin(); 105 SPI.setBitOrder(MSBFIRST); 106 SPI.setDataMode(SPI_MODE0); 107 SPI.setClockDivider(SPI_CLOCK_DIV16); // 1 MHz max, else flicker 108 109 // Issue test pattern to LEDs on startup. This helps verify that 110 // wiring between the Arduino and LEDs is correct. Not knowing the 111 // actual number of LEDs connected, this sets all of them (well, up 112 // to the first 25,000, so as not to be TOO time consuming) to red, 113 // green, blue, then off. Once you're confident everything is working 114 // end-to-end, it's OK to comment this out and reprogram the Arduino. 115 uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; 116 for(char n=3; n>=0; n--) { 117 for(c=0; c<25000; c++) { 118 for(i=0; i<3; i++) { 119 for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); 120 } 121 } 122 delay(1); // One millisecond pause = latch 123 } 124 125 Serial.print("Ada\n"); // Send ACK string to host 126 127 startTime = micros(); 128 lastByteTime = lastAckTime = millis(); 129 130 // loop() is avoided as even that small bit of function overhead 131 // has a measurable impact on this code's overall throughput. 132 133 for(;;) { 134 135 // Implementation is a simple finite-state machine. 136 // Regardless of mode, check for serial input each time: 137 t = millis(); 138 if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { 139 buffer[indexIn++] = c; 140 bytesBuffered++; 141 lastByteTime = lastAckTime = t; // Reset timeout counters 142 } else { 143 // No data received. If this persists, send an ACK packet 144 // to host once every second to alert it to our presence. 145 if((t - lastAckTime) > 1000) { 146 Serial.print("Ada\n"); // Send ACK string to host 147 lastAckTime = t; // Reset counter 148 } 149 // If no data received for an extended time, turn off all LEDs. 150 if((t - lastByteTime) > serialTimeout) { 151 for(c=0; c<32767; c++) { 152 for(SPDR=0; !(SPSR & _BV(SPIF)); ); 153 } 154 delay(1); // One millisecond pause = latch 155 lastByteTime = t; // Reset counter 156 } 157 } 158 159 switch(mode) { 160 161 case MODE_HEADER: 162 163 // In header-seeking mode. Is there enough data to check? 164 if(bytesBuffered >= HEADERSIZE) { 165 // Indeed. Check for a 'magic word' match. 166 for(i=0; (i<MAGICSIZE) && (buffer[indexOut++] == magic[i++]);); 167 if(i == MAGICSIZE) { 168 // Magic word matches. Now how about the checksum? 169 hi = buffer[indexOut++]; 170 lo = buffer[indexOut++]; 171 chk = buffer[indexOut++]; 172 if(chk == (hi ^ lo ^ 0x55)) { 173 // Checksum looks valid. Get 16-bit LED count, add 1 174 // (# LEDs is always > 0) and multiply by 3 for R,G,B. 175 bytesRemaining = 3L * (256L * (long)hi + (long)lo + 1L); 176 bytesBuffered -= 3; 177 spiFlag = 0; // No data out yet 178 mode = MODE_HOLD; // Proceed to latch wait mode 179 } else { 180 // Checksum didn't match; search resumes after magic word. 181 indexOut -= 3; // Rewind 182 } 183 } // else no header match. Resume at first mismatched byte. 184 bytesBuffered -= i; 185 } 186 break; 187 188 case MODE_HOLD: 189 190 // Ostensibly "waiting for the latch from the prior frame 191 // to complete" mode, but may also revert to this mode when 192 // underrun prevention necessitates a delay. 193 194 if((micros() - startTime) < hold) break; // Still holding; keep buffering 195 196 // Latch/delay complete. Advance to data-issuing mode... 197 LED_PORT &= ~LED_PIN; // LED off 198 mode = MODE_DATA; // ...and fall through (no break): 199 200 case MODE_DATA: 201 202 while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte 203 if(bytesRemaining > 0) { 204 if(bytesBuffered > 0) { 205 SPDR = buffer[indexOut++]; // Issue next byte 206 bytesBuffered--; 207 bytesRemaining--; 208 spiFlag = 1; 209 } 210 // If serial buffer is threatening to underrun, start 211 // introducing progressively longer pauses to allow more 212 // data to arrive (up to a point). 213 if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { 214 startTime = micros(); 215 hold = 100 + (32 - bytesBuffered) * 10; 216 mode = MODE_HOLD; 217 } 218 } else { 219 // End of data -- issue latch: 220 startTime = micros(); 221 hold = 1000; // Latch duration = 1000 uS 222 LED_PORT |= LED_PIN; // LED on 223 mode = MODE_HEADER; // Begin next header search 224 } 225 } // end switch 226 } // end for(;;) 227 } 228 229 void loop() 230 { 231 // Not used. See note in setup() function. 232 }