From 57c5c1ecf50b7347b9fdfe48e034d33e5dd6baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Thu, 26 Nov 2015 18:04:43 +0100 Subject: [PATCH 1/7] added the LightberryHDUSBAPA102.1.ino as a source file Former-commit-id: 4923f654cefc5a08df5424e6a2553111e6914b10 --- .../LightberryHDUSBAPA1021.1.ino | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino new file mode 100644 index 00000000..e4eca0a4 --- /dev/null +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -0,0 +1,271 @@ +// Arduino "bridge" code between host computer and WS2801-based digital +// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use +// with USB-native boards such as Teensy or Adafruit 32u4 Breakout; +// works on normal serial Arduinos, but throughput is severely limited. +// LED data is streamed, not buffered, making this suitable for larger +// installations (e.g. video wall, etc.) than could otherwise be held +// in the Arduino's limited RAM. + +// Some effort is put into avoiding buffer underruns (where the output +// side becomes starved of data). The WS2801 latch protocol, being +// delay-based, could be inadvertently triggered if the USB bus or CPU +// is swamped with other tasks. This code buffers incoming serial data +// and introduces intentional pauses if there's a threat of the buffer +// draining prematurely. The cost of this complexity is somewhat +// reduced throughput, the gain is that most visual glitches are +// avoided (though ultimately a function of the load on the USB bus and +// host CPU, and out of our control). + +// LED data and clock lines are connected to the Arduino's SPI output. +// On traditional Arduino boards, SPI data out is digital pin 11 and +// clock is digital pin 13. On both Teensy and the 32u4 Breakout, +// data out is pin B2, clock is B1. LEDs should be externally +// powered -- trying to run any more than just a few off the Arduino's +// 5V line is generally a Bad Idea. LED ground should also be +// connected to Arduino ground. + +// -------------------------------------------------------------------- +// This file is part of Adalight. + +// Adalight is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// Adalight is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with Adalight. If not, see +// . +// -------------------------------------------------------------------- + +#include + +// LED pin for Adafruit 32u4 Breakout Board: +//#define LED_DDR DDRE +//#define LED_PORT PORTE +//#define LED_PIN _BV(PORTE6) +// LED pin for Teensy: +//#define LED_DDR DDRD +//#define LED_PORT PORTD +//#define LED_PIN _BV(PORTD6) +// LED pin for Arduino: +#define LED_DDR DDRB +#define LED_PORT PORTB +#define LED_PIN _BV(PORTB5) + +// A 'magic word' (along with LED count & checksum) precedes each block +// of LED data; this assists the microcontroller in syncing up with the +// host-side software and properly issuing the latch (host I/O is +// likely buffered, making usleep() unreliable for latch). You may see +// an initial glitchy frame or two until the two come into alignment. +// The magic word can be whatever sequence you like, but each character +// should be unique, and frequent pixel values like 0 and 255 are +// avoided -- fewer false positives. The host software will need to +// generate a compatible header: immediately following the magic word +// are three bytes: a 16-bit count of the number of LEDs (high byte +// first) followed by a simple checksum value (high byte XOR low byte +// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, +// where 0 = off and 255 = max brightness. + +static const uint8_t magic[] = {'A','d','a'}; +#define MAGICSIZE sizeof(magic) +#define HEADERSIZE (MAGICSIZE + 3) + +#define MODE_HEADER 0 +#define MODE_HOLD 1 +#define MODE_DATA 2 + +#define DATA_LED A5 +#define SPI_LED A3 + +// If no serial data is received for a while, the LEDs are shut off +// automatically. This avoids the annoying "stuck pixel" look when +// quitting LED display programs on the host computer. +static const unsigned long serialTimeout = 15000; // 15 seconds + +void setup() +{ + // Dirty trick: the circular buffer for serial data is 256 bytes, + // and the "in" and "out" indices are unsigned 8-bit types -- this + // much simplifies the cases where in/out need to "wrap around" the + // beginning/end of the buffer. Otherwise there'd be a ton of bit- + // masking and/or conditional code every time one of these indices + // needs to change, slowing things down tremendously. + uint8_t + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag; + int16_t + bytesBuffered = 0, + hold = 0, + c; + int32_t + bytesRemaining; + unsigned long + startTime, + lastByteTime, + lastAckTime, + t; + bool + data_in_led = false, + spi_out_led = false; + + LED_DDR |= LED_PIN; // Enable output for LED + LED_PORT &= ~LED_PIN; // LED off + pinMode(DATA_LED, OUTPUT); //data in led + pinMode(SPI_LED, OUTPUT); //data out led + + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV16); // 1 MHz max, else flicker + + // Issue test pattern to LEDs on startup. This helps verify that + // wiring between the Arduino and LEDs is correct. Not knowing the + // actual number of LEDs connected, this sets all of them (well, up + // to the first 25,000, so as not to be TOO time consuming) to red, + // green, blue, then off. Once you're confident everything is working + // end-to-end, it's OK to comment this out and reprogram the Arduino. + uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; + for(int i=0; i<5; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(char n=3; n>=0; n--) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + } + for(int i=0; i<16; i++){ + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + delay(1); // One millisecond pause = latch + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + } + + Serial.print("Ada\n"); // Send ACK string to host + + startTime = micros(); + lastByteTime = lastAckTime = millis(); + + // loop() is avoided as even that small bit of function overhead + // has a measurable impact on this code's overall throughput. + + for(;;) { + digitalWrite(DATA_LED, LOW); + digitalWrite(SPI_LED, LOW); + // Implementation is a simple finite-state machine. + // Regardless of mode, check for serial input each time: + t = millis(); + if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { + buffer[indexIn++] = c; + bytesBuffered++; + lastByteTime = lastAckTime = t; // Reset timeout counters + } else { + // No data received. If this persists, send an ACK packet + // to host once every second to alert it to our presence. + if((t - lastAckTime) > 1000) { + Serial.print("Ada\n"); // Send ACK string to host + lastAckTime = t; // Reset counter + } + // If no data received for an extended time, turn off all LEDs. + if((t - lastByteTime) > serialTimeout) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(i=0; i<1; i++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } + } + delay(1); // One millisecond pause = latch + lastByteTime = t; // Reset counter + } + } + + switch(mode) { + + case MODE_HEADER: + + // In header-seeking mode. Is there enough data to check? + if(bytesBuffered >= HEADERSIZE) { + // Indeed. Check for a 'magic word' match. + for(i=0; (i 0) and multiply by 3 for R,G,B. + bytesRemaining = 4L * (256L * (long)hi + (long)lo) +4L + (256L *(long)hi + (long)lo +15)/16; + bytesBuffered -= 3; + spiFlag = 0; // No data out yet + mode = MODE_HOLD; // Proceed to latch wait mode + digitalWrite(DATA_LED, data_in_led = !data_in_led); + } else { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind + } + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte + if(bytesRemaining > 0) { + if(bytesBuffered > 0) { + SPDR = buffer[indexOut++]; // Issue next byte + bytesBuffered--; + bytesRemaining--; + spiFlag = 1; + } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + // if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + // startTime = micros(); + // hold = 100 + (32 - bytesBuffered) * 10; + // mode = MODE_HOLD; +//} + } else { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search + } + } // end switch + } // end for(;;) +} + +void loop() +{ + // Not used. See note in setup() function. +} From cf359e51821ecba664b7cf43e66b2b10288c9955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Thu, 26 Nov 2015 18:21:20 +0100 Subject: [PATCH 2/7] corrected the source to exactly match the protocol of the APA 102 LED's Former-commit-id: 10d097ff4bbcfa3c717c72138ea0cb1d9e629565 --- dependencies/LightberryHDUSBAPA1021.1.zip | Bin 4555 -> 4567 bytes .../LightberryHDUSBAPA1021.1.ino | 30 ++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1.zip b/dependencies/LightberryHDUSBAPA1021.1.zip index 45d92c04034381b43ff720b959060fccd2bb4217..7a9e6cb1202ba4dc6c24628541a91c3284ab6cb6 100644 GIT binary patch delta 4368 zcmY+I^-~m%_J-*$>0EM2rIBS>Qc{pcmu`d=0ci=B?(Xi8PAO?nx>uHPX;yjx5kbQH zxp(G%=_(iUpDcEz$90+;#TQLlhGh#h^*H9bo|vydU_S7_JvT3D`}v=L68z#)wija zTgsw!dGA|*8B5c~N|v%=RtNcgN@aq2*PS%!cxO&0vm*M<)fqwE0MY-b!hR zh2Ah@moP%mQG**mg0W7R*mA^^8(197A%@_&OVHC$d1vp-bY}Vf4t~@x@+}aXX`J*O z8UEgDOENJ_4=J{(XV`U-!~)y4;_f_ewn)Ymr)@<;%CJ)+l(%o}Ll@y^wL{V);N*-? zc*0d46X|6A?*0wRUx?FKQ%#DVGKzdN*yp2p>45|CkTKpit6B*SheH#9g}Qo7`JAz z^b8D+0`5GT3hs>%d7t1Y5~}LDrWG>hS8ATnHiXIM&N4%nOy^NWe5!JH1i8*0?+s+U zV%0Ue&8fMWk)~=6M@L3phnMe{h*iQ*Du=zi1cq{3^oYc^?3<4H6Ov(BkKDGUtTpjB-6xT);!hO0wJ!ue)9|qK{?Y zaUJFF8^}gR1riP(?wP6zIc+~C@qQa82)X+raQ6L#7^FE`%=SR35T7RK;zII-!ceCL zZ^SHnTvElM?LBX%w`p3(PLoP#{^G>T^y<{bbw7lxAA?>9H=>0?cyihg$PZ2E;W-Um z{PCgSi`zPC@e}+CfLT}Zowtw@)K)|CJ{yn{U>BqBIj&+ZF&ke(y3BCohVl8NG>L+c zL>a*ekirAn3A|_R!0!YS^C2XGWa>VHvPaZ!&enZ?M%^{Zx%Tm%R$J^tH+X}mV{x{2 zE2SG^)$oErK^rW6y}j%Px2S+WeJD)F2Kj~+(apQnQn;q7C~m0Oz}K#P!C{!)!?f|= ziziP%$@U(tpUD+%db?ep!bFRa9YL@~i_Z$j7VB0Ww_d&*{h!&)IX4JQbAuJ{I{GVm zHM@U+`35w5Sn3K|U~)=#`D$$cp!$}Q?1B#J{@5@0ryF|0v(9xAF0MLz+eo*^uemQq@s%Y6))2~<9iLrxqvl}knOG}~P zHJX8*mbb~7IwxV^raLk6g4XWN<`=h~QesR=uJ9XqHWxdQRS6^4;#r~s(Yza&_Tl z{+HcTk`e__VeXb|dihs64obYmZ(oO7hRKP`4{)roZ|~|R)qHGYUR*LBajv0Pam&`*+tDoGP<-mlC`14L6MRmFL~7Tezjm z^WT;CmA*3j_kOCNb+^?-N_(v!?z5{JRPSNj$l%LsPjEf5oudO)8^y1oF7nc?@w1B8 zUErrRKzp-)OT=ad;g$ZaODlmqJW;DknRkK9rxDBku988sS-@|S*3{80re-&^XoB%^ zfsygx%!)1~zv8pn6iNeF^SE9+I6&TL<+0-fAQbkRH6~T;gQ^Hoatx=`hY;CEhGTfY zjuy=y?)*`KkMbAFqP;1WrwbgN(99P2Az*n~Fu1$;CF#Z)wV8)iZF7ttoP-Wkc@>aw z9fa)U_+9VfBm7)qr=>z{cQ<&gnt?xY0LxAQr0VG@h1sbXn=}A7BZnbB& z?`JZp>nnUoACqXmz+G7k4w5({I5x{hM&z0!7{yuppF3)l%T~i*z1^y6AM@Q>^vG;4 zDm-?@9Ceu|G2OjCj3pCa!=G7?*5_vjFJA>#6im01hJ>gs(eosw=2BJ$@-M3#uxKXh z{t6yh2@307ZfDbdxXxYLi9$>nkJn3E7*t@LwX3a-@3l4_G@{6_g?AMpq^JQ5;Tu61 z74OyOTbqJC+k1FAo}rJ*pdNc>_ybQY99Lkla`*S(`T5HDe2PZpRs+xz7(H?jbEof8 zUX$NRY3crD{ll#$Ifq^b)gi;dBd9Rqel+K>M-n*-kwcFEcV@XW5>-xjjgi58{yqiv}MJM>^k|*wL3-j)Mfva?uqj48ip@HCirzdcxn9HRoNEt{= zSKfz}y4Gnu4^BDJGyeFuV5J}q<06f1s+6}o>jWt$UQZ?N5vezeb-@S4O|FIgU(qReK+j(FG5 zlsr+cB#G-T&kmrLr5(?z(fqPns-0XGC}J~4H03=mY2@TC5Gcdk{vu?L%u)mvnqosT zB^g8`B14r7to}j#uo;Y8VijcDXpH8I(l6dkd*VFBPPO(@p1%%pt3adH#J+&%B{I- z;e!QApC!kpSDS}rtCLH1t)yoz(PNRt-gmt#K|fVD@%Gk`Xa7cO>?qv8> zi#G)f=~<6;y{y#4x8sJR)3B%R8m!Ybs09?+Jd2)Foop zZS~lwJuaGU1RjTyD|%$_WCAc`9RiaDbV3WXsd$1!{vggy&p?HB-mPwYMle+I z=#r6OHad=&$^w$buYdrfX`#spuV&dyltJo{VJJ5~_e*xp*2%bt+5@tjLWxm$WFl}8 z6j%C`ZP`$qaWagMTD58)x46+uPp*7x&~mb7(WTpruDrhsF8Vt~UE-W)puD)%o`e0= zBZj4Q7{0dIs=SeTG~M@ee2f08H*c3};eG*Px_NYC_aUpsT$Mz-DZB`7vc*9Ec$QG9 zlU`?9lOxO|r*igCtO2?da>Y+8QFFT0= z;!eCPGj8f`B|EN=J5_K7p1JAv17*WtRFw2k^AE2-d!b8WWI%8lY9!_H(tWy4($a$pZL6rBi#EH z-Z;ebmHJ%p=l(|Ht;3;|q--)fr>EhwcZZh_xyY+zc84bp2Z)v5p&j!6L8}&@hZ#S- zbU~7#h!<2$ZGjGvymaw1!E_-UN54M;edpfdqk|7MMs%6B5?`5(#DN-mL1nj;IS#HL2xU)%iB7`#^6fBdkhWPS0D*;cD=y+48iIe zCKR!o0c&4U7u%6hwqE2xVOTXo{M#JH$p&7Wbzyz6D$ettGr|{22HR%zD%T}7tX-Z7 z@$QDgj_qg7)aPgj;y z#iJK23b$_HeD6!mGD@ZU9n&Q7 zxGHRgB{Mk_SeWM2)#TffZMV;;H2OiBLE&>!&i8=N3x{Loqwh>TURRAcM74K~CUp3DsB2q44rAMKG@Hj;2QEwfXlI+q`92P>g|Ao*&qGN`F@AEJkRJ5ZUus zpKBZQTpOe!I(=PFWPE9KVTJlAsXXN89^`vQsHcUEqk#24B0~K?M&dsw4Mim*&I7Uc zcjBMN{%_*pRa^hN|1U;_<@^84MViJVW8reun{wCxgZ&rR)50hC_j8@6aWAK004KPc99J=4WV{NTP6JLIS>El<-939G!Ly{^gX|nV zJ$?N8*c=|Jeir2>N!8vYGto@%sW6SSnrOGuI#Kh~I*crqxzm{nl1P0yK7RkG*O>%X zM=CNigM`NEq~3=I<~5*yR)w9N5M49jZ@1kjq=c`;gx#p~Ec>)`0kVRJ6Je;e*s(Y|x*9z-P!Y9Z1yK0c>;4#hJ zInzk9N2}6AC26(@Vr6w^@W=)?y)TxfJHWO%OrMP`ZPpU1nyG5Qv*8u|Qe}i|m7a|zt0{dv05zLdV`iof{0ItD;*Eq8bI%KsZ3V*ghP0PRnX#-1^0SwOy z3t$Fv4e$xNcDOS>6~y^gSzr>7$Bp$Oa5))_2n1|c*&sYEma+cXxHaDZf?Ul5mTjhq zgQ#YFZ9ZcL!dQ#XICnv`o{Y`G-Yhl{u?ANexrgWy1e$J61XLb716IHY_@3rTNY^Sj zkJA8}OX|WUQGb9Ado@Y9O#`HS?7^Q5ypx>bR<}|20A3ZwY4`}UON1R-n&{BcbxrFB zU}%Fx{Se+gIBr*D2R)F&g`u%|VX^vWfF4+m*R*|^u7B8GI~`nS4MU)d!;x^}8d}S= zIPs6Ub)*fwf>5WMk|8puU$7AA%$ZJfM&z-60A&p$9S9d{mZfmif|nq+kQbT2hS&?j zezhpA^FRC$9q(aN6Em;NAR{Xw;El~}4$bnS@>$W^OluhY)U9A3XKJ11Dhv`bdt>46 zz+I4Xq<^8qG=s<@GhH(kj0!HXxFGa}Ev)$C*Pp8!Z7t?L7ICS&d=i^b-Ix#x*N6*3 zV@bnyuA#a$Z+%7VjD;OMk%$5}aO^A2bh$51B)y=fk|nSTIBr7wr~pWJ?aulldQR$ul%p)mL}@ z(f#o9^PB!i-F-f~yB%M{-o~1P0F(F!@-zt=;R9cPGWX4x7MN2uulmc)Y2=CebVD8-V?C@~4T6Jeh-c7UFVJsx<;fLM- z%^@0ZA-cplqRj`utM2^60w5Vlrhl4|r~#qe85X}4=_&l4_B>o&jg-Q#>#gy-+tIzE zUpo`S*GfGMzG(8~d#$a*J@)OnmSYC2f-Qk;5blop1o=6r~a{VKSi)WhIpN;;f%?orIc@jydNPS!5f|gDeOg zXFyeC#JL80IDG&^yx&RYubzT9*MFAjs3Tr*e0sW_z;rqkAaV8-p-m&ST0{B1m3)d7 zP|9B5GSM9JK*Wbl2c>YX1xzO~&UJtQat3f*Al^t)G=-+qRiUa%1k4HtYzh&RwbS+t z)OzoACdP@q6>yd0i;2#>X;~;5NW?pc#?)liwfr#>cmib5l>e31Dgvo(RDWLqim|?+ z!p}!PpPoYhrTu!ENRLj0KLyjH**4iNu7IaeTYcPC1CAe(${(oAIiwKOSC=hu>T)T<;g1 zPMf@?RWA7Cx_@;&;uXzm@qd9jX*DV$w^!E>yb$-@8oj-_0-EG)m?>S9mZiHvOpCt z-eIN&lgvwiKrPk_YJZ2IT6ntJTV=r#q$zLPqs2mHqGu+dI_wbLgUu?9ih-hd_5~fG z71eZ77J;#j=OwTN$UYY(i5K;W)RY8_5E{e{RcU_=sQMc-5$4o_HlbuPgF)F6ZSWg; zV7j8xM=!u>np>C>_IaPfC*|4x7SjH{O#?(;|2#~T{9LG`+JEqE?zV;(#A*3+QFky| zFgI;jPaFI_0yn_l(!7d;**PY`MejXfLo_|`v8p0JPXYGDPcdk$(7C4LBk+Nz+*IBb(-fNgiTik>iG6NVj@`;nZhnFaDM3 z$!`~`NI%$Tu748>y}n2#2cTJOC^MJ0FVye#4fxF9pePsm403N4P}Ek)*zZ2SZ_zsO zpG1L`+EPKN-Ia8{Uy6TYcEK*&ceB?!Jw7_(l|H#Uq`Z=VB-1l)icNw%QWOWz$zyLn zeX{`~ILcFKo%=_?!N9m%3K`AM#)DVG(fGbTc?C{NHh;;4j_R|Qlw4gI>R4sG;_~M9 z^3#W_;TOI4guR~n?2}(8(4elOU_On3G)Nf4M~29isH6j+AyQL44TllbO%pMTeCZ*U zx?M|4l@$s@u9k$x%49ICq9<7|smXtZ7&l%r;5?NHuhFD?Ov=bFRFKA?r?M5$t9rWa zyLcMuB7b1tyZi!OGEHr|e92UNvqK_3V6qJqhF^gZOO*+x32^RjZy7rn$#d#SwP=Pi zL7_6saFI4L*IdF-LdHe#<}+~34UAp~UUxU_>maB#Ozc7T&8 zq)_(9S0XDTIpKI8e(DmeK@w{ToxC)YQ~(Yy|9>c#kdNvara}JF#1>^Pj$nHqoZ{EP zL5sqT%#H7^MuZnXQ-pdGkK>Vgo3ef~pjR!J4O`f=Cq+4?N-j>$Rq`QGOgf#Gs;GrD z7JhIn0WZW!JXuV8CQk6Hn%d%|x>$mo#C-o3T=CEIF8$sH{hgb>diBp}((fGs_TH;W z@PA(l(UX*(YqN)8?zmC86URmp18H-D7?&Beyy&8RnHg8%(-xAxS$p&ft6*xG;R4M) zRK4FNdvfZS0&@TIQ|gvWdyMlNY^va&BZ-t^mLO^~6ky870eO;3@Ip3s%CM+eY4| z-d@koVFtTWtOCN#?U#JDT`_OY3qVq}K+ej&DO{~8A&}l;Ux%_ns$N1MfbJIiMjQrW zWc9c#rvh!MIqLbGNtV8nV>~*O$XXYyxjGkjn!2m6OopOp$ zruy71B9(IK#BBf=IGJdWu$4mVkJ^<4yt$Ggghl@6-G>WmP486*;Hgng9SG10SsjH0 zo$A!D0V$~-Fsl;Ne{mka@lWMb%70}Gx{6Au5=vG7Bc7CC{8R$?2MGQjk-Ru(`}2Q*;kqOIL)AVv_>WytJE%#1 zk1NR-RZ|;f_F1E54T*1&bkJMk+K2X%msqKlkV_EKiNUy3z~rOS5Vio~!+(of&F_U; z9TjPIz{?Vkg3T7+f+Wyw>}aWJJ}A2ff<7KviVBFa<-#{C44zu&te28YL(@W|Vyt5A zvyutnfm=?m(Tthne=te;)o|6kDXuTY6osx`e(Gisr+>c&#n|7j!9{r41@(>84@CC| z=@FGTs_t~@ndSb2rGAqk;eUUuo$qK>T~u`JqDb|&DKy_IljtB`1#1fzKWcH(xS)58 z%Xm$_trJ$|sQqz!Q0Z>B0>tg!y;1L|CPE1Rr7Ez6dmQ+WaoSP`SaMJ-d0~Y*=$*C> zd+$|J*{xJN7wTkVz9?{OHS0P4?8FFC^IBKnS}Uf@tyN=+9r6ynV}EH(_?GV~Fpf8s zRQ^08*eYJ%tUZ?2TSc`$*D%OO$-WcQJqHR0892!_rl<=urT0>mn^ij6=pnG!Jjkpt z`XY`!V_uj^QjZ(nX%)87c`9(e3R)N|z7(;3%42u6WwsOS3B4vcuc22Z^*VOlT7)ZW zCNRT2k~@ryO|fdgPk*yi${K`LOmLID9@_m|d8hYCXVT%OM!URP%_!VcZ0CG|S8yM% zq)>iq0`w`}(nJM?;@#P_bKk^=X*rO1?n?UBFi) zgDkDKE%w(aH-DGkhrvQ=N|Pl&(K{2uUl5aT#&GIOFCzGjZ|(FlOTa5=`l41dU-A=P z<$yH4KjPQDTw9zRb8z3)%Em&9brqdj5m~Pw%LSVq%L_`qDV&@ftIif#^$sS>jiu4= z;y&8=&yAOnS(=FDH>0T0O>XDmNUVO7=N+aihAWPXaA;MzyNdFRD?`6PN;apPIuKu{u5y@23u9E>TF zuNqLBjQZ%sYl^Z??>3&_X#4?CO928u02BZK0001Yp>~tr5JWPZ6U3ldCD8$#6U3ld zCD8#JeIoXa9MJ(#O928u02BZK00;ngp>{`GCH(9;5C8y?D3eqXRW5v=0; n--) { for(c=0; c<25000; c++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte for(i=0; i<3; i++) { - for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); - } - for(i=0; i<1; i++) { - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); //BGR } } - for(int i=0; i<16; i++){ - for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + for(int i=0; i<4; i++){ //Stop Frame + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); } delay(1); // One millisecond pause = latch digitalWrite(SPI_LED, spi_out_led = !spi_out_led); @@ -181,14 +179,18 @@ void setup() } // If no data received for an extended time, turn off all LEDs. if((t - lastByteTime) > serialTimeout) { - for(c=0; c<25000; c++) { - for(i=0; i<3; i++) { - for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); - } - for(i=0; i<1; i++) { - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); - } + for(i=0;i<4;i++) { //Start Frame + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } + for(c=0; c<25000; c++) { + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness Byte + for(i=0; i<3; i++) { + for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); //BGR } + } + for(i=0;i<4;i++) { //Stop Frame + for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + } delay(1); // One millisecond pause = latch lastByteTime = t; // Reset counter } From f910c717bbf022458049df92ec10da09429dc2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Sun, 29 Nov 2015 19:24:01 +0100 Subject: [PATCH 3/7] reformated code (Arduino IDE auto-reformat) Former-commit-id: 8f94b2bb865337ba7b0538617857432ea0376aea --- .../LightberryHDUSBAPA1021.1.ino | 208 +++++++++--------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino index 5d67cedf..7df0e0d9 100644 --- a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -71,7 +71,7 @@ // XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, // where 0 = off and 255 = max brightness. -static const uint8_t magic[] = {'A','d','a'}; +static const uint8_t magic[] = {'A', 'd', 'a'}; #define MAGICSIZE sizeof(magic) #define HEADERSIZE (MAGICSIZE + 3) @@ -96,31 +96,31 @@ void setup() // masking and/or conditional code every time one of these indices // needs to change, slowing things down tremendously. uint8_t - buffer[256], - indexIn = 0, - indexOut = 0, - mode = MODE_HEADER, - hi, lo, chk, i, spiFlag; + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag; int16_t - bytesBuffered = 0, - hold = 0, - c; + bytesBuffered = 0, + hold = 0, + c; int32_t - bytesRemaining; + bytesRemaining; unsigned long - startTime, - lastByteTime, - lastAckTime, - t; - bool - data_in_led = false, - spi_out_led = false; - + startTime, + lastByteTime, + lastAckTime, + t; + bool + data_in_led = false, + spi_out_led = false; + LED_DDR |= LED_PIN; // Enable output for LED LED_PORT &= ~LED_PIN; // LED off pinMode(DATA_LED, OUTPUT); //data in led pinMode(SPI_LED, OUTPUT); //data out led - + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! SPI.begin(); @@ -135,18 +135,18 @@ void setup() // green, blue, then off. Once you're confident everything is working // end-to-end, it's OK to comment this out and reprogram the Arduino. uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; - for(int i=0; i<4; i++){ //Start Frame - for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + for (int i = 0; i < 4; i++) { //Start Frame + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); } - for(char n=3; n>=0; n--) { - for(c=0; c<25000; c++) { - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte - for(i=0; i<3; i++) { - for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); //BGR + for (char n = 3; n >= 0; n--) { + for (c = 0; c < 25000; c++) { + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte + for (i = 0; i < 3; i++) { + for (SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); //BGR } } - for(int i=0; i<4; i++){ //Stop Frame - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + for (int i = 0; i < 4; i++) { //Stop Frame + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); } delay(1); // One millisecond pause = latch digitalWrite(SPI_LED, spi_out_led = !spi_out_led); @@ -160,109 +160,109 @@ void setup() // loop() is avoided as even that small bit of function overhead // has a measurable impact on this code's overall throughput. - for(;;) { + for (;;) { digitalWrite(DATA_LED, LOW); digitalWrite(SPI_LED, LOW); // Implementation is a simple finite-state machine. // Regardless of mode, check for serial input each time: t = millis(); - if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { + if ((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) { buffer[indexIn++] = c; bytesBuffered++; lastByteTime = lastAckTime = t; // Reset timeout counters } else { // No data received. If this persists, send an ACK packet // to host once every second to alert it to our presence. - if((t - lastAckTime) > 1000) { + if ((t - lastAckTime) > 1000) { Serial.print("Ada\n"); // Send ACK string to host lastAckTime = t; // Reset counter } // If no data received for an extended time, turn off all LEDs. - if((t - lastByteTime) > serialTimeout) { - for(i=0;i<4;i++) { //Start Frame - for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + if ((t - lastByteTime) > serialTimeout) { + for (i = 0; i < 4; i++) { //Start Frame + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); } - for(c=0; c<25000; c++) { - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness Byte - for(i=0; i<3; i++) { - for(SPDR = 0x00; !(SPSR & _BV(SPIF)); ); //BGR + for (c = 0; c < 25000; c++) { + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness Byte + for (i = 0; i < 3; i++) { + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); //BGR } } - for(i=0;i<4;i++) { //Stop Frame - for(SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); + for (i = 0; i < 4; i++) { //Stop Frame + for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); } delay(1); // One millisecond pause = latch lastByteTime = t; // Reset counter } } - switch(mode) { + switch (mode) { - case MODE_HEADER: + case MODE_HEADER: - // In header-seeking mode. Is there enough data to check? - if(bytesBuffered >= HEADERSIZE) { - // Indeed. Check for a 'magic word' match. - for(i=0; (i 0) and multiply by 3 for R,G,B. - bytesRemaining = 4L * (256L * (long)hi + (long)lo) +4L + (256L *(long)hi + (long)lo +15)/16; - bytesBuffered -= 3; - spiFlag = 0; // No data out yet - mode = MODE_HOLD; // Proceed to latch wait mode - digitalWrite(DATA_LED, data_in_led = !data_in_led); - } else { - // Checksum didn't match; search resumes after magic word. - indexOut -= 3; // Rewind - } - } // else no header match. Resume at first mismatched byte. - bytesBuffered -= i; - } - break; - - case MODE_HOLD: - - // Ostensibly "waiting for the latch from the prior frame - // to complete" mode, but may also revert to this mode when - // underrun prevention necessitates a delay. - - if((micros() - startTime) < hold) break; // Still holding; keep buffering - - // Latch/delay complete. Advance to data-issuing mode... - LED_PORT &= ~LED_PIN; // LED off - mode = MODE_DATA; // ...and fall through (no break): - - case MODE_DATA: - digitalWrite(SPI_LED, spi_out_led = !spi_out_led); - while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte - if(bytesRemaining > 0) { - if(bytesBuffered > 0) { - SPDR = buffer[indexOut++]; // Issue next byte - bytesBuffered--; - bytesRemaining--; - spiFlag = 1; + // In header-seeking mode. Is there enough data to check? + if (bytesBuffered >= HEADERSIZE) { + // Indeed. Check for a 'magic word' match. + for (i = 0; (i < MAGICSIZE) && (buffer[indexOut++] == magic[i++]);); + if (i == MAGICSIZE) { + // Magic word matches. Now how about the checksum? + hi = buffer[indexOut++]; + lo = buffer[indexOut++]; + chk = buffer[indexOut++]; + if (chk == (hi ^ lo ^ 0x55)) { + // Checksum looks valid. Get 16-bit LED count, add 1 + // (# LEDs is always > 0) and multiply by 3 for R,G,B. + bytesRemaining = 4L * (256L * (long)hi + (long)lo) + 4L + (256L * (long)hi + (long)lo + 15) / 16; + bytesBuffered -= 3; + spiFlag = 0; // No data out yet + mode = MODE_HOLD; // Proceed to latch wait mode + digitalWrite(DATA_LED, data_in_led = !data_in_led); + } else { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind + } + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if ((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); + while (spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte + if (bytesRemaining > 0) { + if (bytesBuffered > 0) { + SPDR = buffer[indexOut++]; // Issue next byte + bytesBuffered--; + bytesRemaining--; + spiFlag = 1; + } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + // if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + // startTime = micros(); + // hold = 100 + (32 - bytesBuffered) * 10; + // mode = MODE_HOLD; + //} + } else { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search } - // If serial buffer is threatening to underrun, start - // introducing progressively longer pauses to allow more - // data to arrive (up to a point). - // if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { - // startTime = micros(); - // hold = 100 + (32 - bytesBuffered) * 10; - // mode = MODE_HOLD; -//} - } else { - // End of data -- issue latch: - startTime = micros(); - hold = 1000; // Latch duration = 1000 uS - LED_PORT |= LED_PIN; // LED on - mode = MODE_HEADER; // Begin next header search - } } // end switch } // end for(;;) } From 4cdba8cdb689cf158074b179136c3f5a75d98f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Sun, 29 Nov 2015 19:31:51 +0100 Subject: [PATCH 4/7] fixed boot animation Former-commit-id: 418ed83a4bd4ddaae79bf41de7fd9fb0794f8239 --- .../LightberryHDUSBAPA1021.1.ino | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino index 7df0e0d9..e10c8c3b 100644 --- a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -135,10 +135,10 @@ void setup() // green, blue, then off. Once you're confident everything is working // end-to-end, it's OK to comment this out and reprogram the Arduino. uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 }; - for (int i = 0; i < 4; i++) { //Start Frame - for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); - } for (char n = 3; n >= 0; n--) { + for (int i = 0; i < 4; i++) { //Start Frame + for (SPDR = 0x00; !(SPSR & _BV(SPIF)); ); + } for (c = 0; c < 25000; c++) { for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); //Brightness byte for (i = 0; i < 3; i++) { @@ -148,9 +148,10 @@ void setup() for (int i = 0; i < 4; i++) { //Stop Frame for (SPDR = 0xFF; !(SPSR & _BV(SPIF)); ); } + delay(1); // One millisecond pause = latch - digitalWrite(SPI_LED, spi_out_led = !spi_out_led); } + digitalWrite(SPI_LED, spi_out_led = !spi_out_led); Serial.print("Ada\n"); // Send ACK string to host From f950ec7df6dbd5627bd6870616736f725a2dbb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Sun, 29 Nov 2015 19:35:26 +0100 Subject: [PATCH 5/7] changed the SPI frequency to 2 Mhz. APA102 can handle MUCH higher frequencies than WS2801, so 2Mhz is no big deal for them Former-commit-id: 814faa648e4f39d7105710ff872bde00aaeaada9 --- .../LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino index e10c8c3b..4a02d71a 100644 --- a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -126,7 +126,7 @@ void setup() SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - SPI.setClockDivider(SPI_CLOCK_DIV16); // 1 MHz max, else flicker + SPI.setClockDivider(SPI_CLOCK_DIV8); // 2Mhz // Issue test pattern to LEDs on startup. This helps verify that // wiring between the Arduino and LEDs is correct. Not knowing the From 8d52466acdaa180651db17eb818c77606075e3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Sun, 29 Nov 2015 19:38:40 +0100 Subject: [PATCH 6/7] re-added buffer underrun handling should not be necessary because the number of LED's in a usual Lightberry setup is to low, but it's nice to have implemented. Former-commit-id: def3c46687670439be572e6b9c2dd1de5be2f1b5 --- .../LightberryHDUSBAPA1021.1.ino | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino index 4a02d71a..603599c9 100644 --- a/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino +++ b/dependencies/LightberryHDUSBAPA1021.1/LightberryHDUSBAPA1021.1.ino @@ -252,11 +252,11 @@ void setup() // If serial buffer is threatening to underrun, start // introducing progressively longer pauses to allow more // data to arrive (up to a point). - // if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { - // startTime = micros(); - // hold = 100 + (32 - bytesBuffered) * 10; - // mode = MODE_HOLD; - //} + if ((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) { + startTime = micros(); + hold = 100 + (32 - bytesBuffered) * 10; + mode = MODE_HOLD; + } } else { // End of data -- issue latch: startTime = micros(); From b898f25f980f804df4fff6aa555f3c386b456d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6ck=2C=20Florian?= Date: Sun, 29 Nov 2015 19:56:33 +0100 Subject: [PATCH 7/7] updated zip to match newest source Former-commit-id: 2e54f40d48a993a66f07f5b655965847a9bf7d14 --- dependencies/LightberryHDUSBAPA1021.1.zip | Bin 4567 -> 4548 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dependencies/LightberryHDUSBAPA1021.1.zip b/dependencies/LightberryHDUSBAPA1021.1.zip index 7a9e6cb1202ba4dc6c24628541a91c3284ab6cb6..af1527aecae1ce355ffcfb87ef4cbd813a79f5fa 100644 GIT binary patch delta 4318 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 M0002I5C8xG06yM4nE(I) delta 4367 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 E3nIf=oB#j-