/*TI 5940 16-port LED driver = overlapped fade across 16 LEDs at a low background level * Peter Mackey June 2007 Pratt Digital Arts pmackey@pratt.edu * direct adressing to PORTB, smooth flickerless fading (thanks to Amp on the Arduino forum) * additional logic from David Cuartielles's & Marcus Hannerstig's LEDdriver demo = see the TLC5940 data sheet for the logic behind these pulse sequences */ // TEST NG //using the pin codes on the TLC5940NT to name the Arduino ports #define VPRG 2 //"chip pin 27 to Arduino pin 2" #define SIN 3 #define SCLK 7 #define XLAT 4 #define BLANK 5 #define DCPRG 6 #define GSCLK 8 //note: but using PORTB method #define MSINTRVL 10 //could be used to delay updating of incrementFades() #define FADEMIN 0 //lowest fade level LEDs will reach (min would be 0, max 4065) #define FADEINCR 1 //determines how many steps it takes to run the desired range (lower=longer) #define SPKROUT 10 // speaker output #define MUTE 12 // mute button int fadeLevel[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //stores a level for each of 16 ports int faderNdx = 00; //counter used in this fading sequence int fadeDelay[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // stores a delay value for each port int scanDirection = 0; // direction of scan, 0 = left to right, 1 = right to left, int fadeState[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //stores the direction of fading for each port 1,0,-1 //start with first port int previousMillis = 0; int counter = 0; // used to count knigt rider iterations for tone freq. int next; //used for limit checking in fading function float prevMillis; //used for a timing delay int word[] = {0,0,0,0,0,0,0,0,0,0,0,0}; //temp storage for reversing bits in a word (for greyscale setting) long time = 0; // the last time the output pin was toggled long debounce = 200; // the debounce time, increase if the output flickers int reading; // the current reading from the input pin int previous = LOW; // the previous reading from the input pin int mute = 1; void setup() { pinMode(VPRG, OUTPUT); pinMode(SIN, OUTPUT); pinMode(SCLK, OUTPUT); pinMode(XLAT, OUTPUT); pinMode(BLANK, OUTPUT); pinMode(DCPRG, OUTPUT); pinMode(GSCLK, OUTPUT); //could also set DDRB directly pinMode(SPKROUT, OUTPUT); pinMode(MUTE, INPUT); beginSerial(9600); //in case of debugging Serial.println("Ready..."); preset(); //input Dot Correction data } void loop () { setGreys(); feedPorts(); readSwitch(); knightRider(300,0,4096); } void readSwitch(){ reading = digitalRead(MUTE); // if we just pressed the button (i.e. the input went from LOW to HIGH), // and we've waited long enough since the last press to ignore any noise... if (reading == HIGH && previous == LOW && millis() - time > debounce) { // ... invert the output if (mute == 1){ mute = 0; }else{ mute = 1; } // ... and remember when the last button press was time = millis(); } previous = reading; } void incrementFades() { //a very particular sequence: fade up from LED 0 to 15 then fade down in same direction //overlaps incoming&outgoing adjacent ports' fade level for (faderNdx=0; faderNdx<=8; faderNdx++) { if (fadeState[faderNdx]!=0) { //if the state for this LED is not 0... if (fadeState[faderNdx]==1) { //then fade up... next = fadeLevel[faderNdx]+FADEINCR; if (next<4095) { fadeLevel[faderNdx] = next; //...by incrementing the value in the fadeLevel array fadeLevel[16-faderNdx] = next; //...by incrementing the value in the fadeLevel array } else { //set the state for this LED to fade down fadeLevel[faderNdx] = 4095; //be sure is at "max level" fadeLevel[16-faderNdx] = 4095; //be sure is at "max level" fadeState[faderNdx] = -1; //flip my sign //AND...make next neighbor begin a fade up if (faderNdx<8) { fadeState[faderNdx+1] = 1; }else { fadeState[0] = 1; } }//------------------------------------------------ } //end fading up else { //fade down instead........................... next = fadeLevel[faderNdx]-FADEINCR; if (next>=FADEMIN) { //----------------- fadeLevel[faderNdx] = next; fadeLevel[16-faderNdx] = next; } else { //set me to fade down fadeLevel[faderNdx] = FADEMIN; //be sure at minimum level fadeLevel[16-faderNdx] = FADEMIN; //be sure at minimum level fadeState[faderNdx] = 0; //stop fading me until neighbor sets me to fade up }//------------------------------------------------ }//end fading down }//end check for state not 0 }// end of cycle thru each port } void fadeIn(int ledId, int fadeIncr, int fadeTo) { next = fadeLevel[ledId]+fadeIncr; if (nextfadeTo && fadeState[ledId] == 1) { fadeState[ledId] = 1; fadeLevel[ledId] = next; //...by incrementing the value in the fadeLevel array }else if (fadeRepeat == 1 && fadeState[ledId] == 1){ fadeState[ledId] = 0; }else if (fadeRepeat == 0 && fadeState[ledId] == 1){ fadeLevel[ledId] = 0; fadeState[ledId] = -1; } } void fadeInOut(int ledId, int fadeIncr, int fadeFrom, int fadeTo, int fadeRepeat = 1) { fadeIn(ledId,fadeIncr,fadeTo); fadeOut(ledId,fadeIncr,fadeFrom, fadeRepeat); } void knightRider(int fadeIncr, int fadeFrom, int fadeTo){ if (mute == 0){ if (millis() - previousMillis > (counter / 20)) { previousMillis = millis(); // remember the last time we blinked the LED digitalWrite(SPKROUT,HIGH); } } if (scanDirection == 0){ counter++; for (int ledId=0; ledId<=15; ledId++) { fadeDelay[ledId]++; if (fadeDelay[ledId] >= MSINTRVL*ledId){ fadeInOut(ledId,fadeIncr,fadeFrom,fadeTo,0); } } if (fadeState[14] == -1){ scanDirection = 1; for (int ledId=0; ledId<=15; ledId++) { fadeState[ledId] = 0; } } }else{ counter --; for (int ledId=15; ledId>=0; ledId--) { fadeDelay[ledId]--; if (fadeDelay[ledId] <= MSINTRVL*ledId){ fadeInOut(ledId,fadeIncr,fadeFrom,fadeTo,0); } } if (fadeState[2] == -1 && fadeState[15] == -1){ scanDirection = 0; for (int ledId=0; ledId<=15; ledId++) { fadeState[ledId] = 0; } } } } //=======5940 control====================================== void setGreys() { //data for each port (12 bit word * 16 ports =192 bits in this loop)... //read the fadeLevel array for (int i=15; i>=0; i--) { // ports, count DOWN int datb = fadeLevel[i]; //load fade level bits into the temp array BACKWARDS for (int j=11; j>=0; j--) { word[j]=(datb & 1); //& bitwise AND datb >>= 1; //shift right and assign // (maybe there's a slicker way to do this!? but this works...) } //send the data to the 5940 for (int j=0; j<12; j++) { digitalWrite(SIN,word[j]); pulseSCLK(); } } digitalWrite(XLAT, HIGH); digitalWrite(XLAT, LOW); } void feedPorts() { //The actual sequencing of the PWM data into the LEDs, must do constantly... digitalWrite(BLANK, HIGH); digitalWrite(BLANK, LOW); //=all outputs ON, start PWM cycle for (int i=0; i<4096; i++) { pulseGSCLK(); } } //DOT CORRECTION...do once void preset() { //Input nDotCorrexi Data //16 outputs, 64 posssible levels of adjustment, 6 bits/chan = 96 bits total //[use if any LEDs in array are physically too bright] digitalWrite(DCPRG, HIGH); //leaving it H is my arbitrary choice (="write to register not EEPROM") digitalWrite(VPRG, HIGH); //=inputting data into dot correx register digitalWrite(BLANK, HIGH); //=all outputs off, when this goes high it resets the greyscale counter digitalWrite(SIN, LOW); //to start dot correction digitalWrite(XLAT, LOW); //begin loading in the dot correx data, most significant bit first... //but here we are not correcting anything, so LSB is going first! for (int i=0; i<16; i++) { //16 ports for (int j=0; j<6; j++) { //6 bits of data for each port digitalWrite(SIN, HIGH); //for now, 111111 for everybody pulseSCLK(); digitalWrite(SIN, LOW); } } //----doing the FIRST GREYSCALE SETTING here because of the unique 193rd clock pulse digitalWrite(XLAT, HIGH); //latch the dot data into the dot correx register digitalWrite(XLAT, LOW); digitalWrite(VPRG, LOW); //entering greyscale mode for (int i=0; i<16; i++) { //16 ports int datb = 4095; //using same fade level for all ports this first time for (int j=0; j<12; j++) { //data for each port, all the same value to start digitalWrite(SIN, datb & 01); pulseSCLK(); datb>>=1; } } digitalWrite(XLAT, HIGH); //latch the greyscale data digitalWrite(XLAT, LOW); pulseSCLK(); //193rd clock pulse only need to do the FIRST time after dot correx digitalWrite(BLANK, LOW); //=all outputs ON, start PWM cycle... moved here } //SCLK used in dot correx and greyscale setting void pulseSCLK() { digitalWrite(SCLK, HIGH); digitalWrite(SCLK, LOW); } void pulseGSCLK() { //ultra fast pulse trick, using digitalWrite caused flickering PORTB=0x01; //bring PORTB0 high (pin 8), other ports go low [0x01 does only pin 8, 0x21 also lifts pin 13] //16nanosecs is the min pulse width for the 5940, but no pause seems needed here PORTB=0x20; //keep pin13 high [0x00 would be all low] }