/************************************************************ * DMX-512 Read out on 2-line LCD Display * * Displays a selectable 8 values from the stream on a * * LCD display. Also displays the frames/second metric. * * Developed by Dan Julio (www.danjuliodesigns.com) * * Rev 2.0 Jan 1, 2010 * * Released under the WTFPL license with no warranties (e.g. * * the program could have bugs and I take no responsbility*) * * * * Based on DMX-512 Reception, Developed by Max Pierson * * Version Rev15 9 Oct 2010 * * Released under the WTFPL license, although I would * * appreciate Attribution and Share-Alike * * (blog.wingedvictorydesign.com) * * * * Modified to decouple processing of data with ISR handling * * of DMX frames. The ISRs now run by themselves to detect * * each frame. The ActionLoop routine displays whatever data * * is available when it runs. Done to allow code to measure * * frames per second. * * * * This version compiles under Arduino 0021. * * * * Arduino Port Connections: * * 0: RX - TTL RX input from MAX485 driver * * 1: TX - TTL TX output to MAX485 driver (unused) * * 2: nRE - Active low Receiver Enable to MAX485 * * 3: DE - Active high Driver Enabled to MAX485 * * 4: RS - LCD RS control signal * * 5: R/W - LCD R/W control signal * * 6: E - LCD enable control signal * * 7: D4 - LCD D[4] data signal (LCD in 4-bit mode) * * 8: D5 - LCD D[5] data signal * * 9: D6 - LCD D[6] data signal * * 10: D7 - LCD D[7] data signal * * 11: AddrUp - Active low AddrUp button input * * 12: AddrDown - Active low AddrDown button input * * 13: AddrFast - Active low AddrFast button input * * * * Note: Arduino configured to use internal pull-ups on the * * button inputs. * * * * * Program issues: * * - Program timing may fail when looking at DMX values at * * the end of the frame (e.g. DMX Addr 504 and above in * * a 512 entry frame) * *************************************************************/ #include /******************************* Addressing variable declarations *****************************/ #include #define NUMBER_OF_CHANNELS 8 //the number of channels we want to receive (8 by default). /* * Control Operation * Press Up Button - address increases by 1 * Press Down Button - starting address decreases by 1 * Press Up & Down simultaneously - starting address reset to 1 * Press Fast Button with either Up or Down - Starting address increments rapidly */ #define ADDR_UP_BUTTON 11 //the pin number of the address increment button #define ADDR_DOWN_BUTTON 12 //the pin number of the address decrement button #define ADDR_FAST_BUTTON 13 //the pin of the fast change modifier button unsigned int dmxaddress = 1; /* The starting dmx address we will be listening to. /******************************* MAX485 variable declarations *****************************/ #define RECEIVER_OUTPUT_ENABLE 2 /* receiver output enable (pin2) on the max485. * will be left low to set the max485 to receive data. */ #define DRIVER_OUTPUT_ENABLE 3 /* driver output enable (pin3) on the max485. * will left low to disable driver output. */ #define RX_PIN 0 // serial receive pin, which takes the incoming data from the MAX485. #define TX_PIN 1 // serial transmission pin /******************************* DMX variable declarations ********************************/ volatile byte i = 0; //dummy variable for dmxvalue[] volatile byte dmxreceived = 0; //the latest received value volatile unsigned int dmxcurrent = 0; //counter variable that is incremented every time we receive a value. volatile byte dmxvalue[NUMBER_OF_CHANNELS]; //stores the DMX values we're interested in using. volatile boolean dmxnewvalue = 0; //set to 1 when new dmx values are received. unsigned long curTime; unsigned long prevTime; unsigned long deltaT; unsigned int fpsArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // Array of frames/second for averaged display /******************************* Timer2 variable declarations *****************************/ volatile byte zerocounter = 0; /* a counter to hold the number of zeros received in sequence on the serial receive pin. * When we've received a minimum of 11 zeros in a row, we must be in a break. As written, * the timer2 ISR actually checks for 22 zeros in a row, for the full 88uS break. */ /******************************* Button variable declarations *****************************/ volatile byte debounceCount = 0; boolean prevAddrUp = 0; // Value from last sample boolean prevAddrDown = 0; boolean prevAddrFast = 0; boolean AddrUpPressed = 0; // Flag indicating button just pressed boolean AddrDownPressed = 0; boolean AddrUpDown = 0; // Flag indicating button held pressed boolean AddrDownDown = 0; boolean AddrFastDown = 0; /************************** LCD Display connection declarations ***************************/ LiquidCrystal lcd(4, 5, 6, 7, 8, 9, 10); unsigned int lcdCol[8] = {5, 8, 11, 14, 5, 8, 11, 14}; // Display starting column positions unsigned int lcdRow[8] = {0, 0, 0, 0, 1, 1, 1, 1}; // Display row positions void setup() { /******************************* Max485 configuration ***********************************/ pinMode(RECEIVER_OUTPUT_ENABLE, OUTPUT); pinMode(DRIVER_OUTPUT_ENABLE, OUTPUT); digitalWrite(RECEIVER_OUTPUT_ENABLE, LOW); digitalWrite(DRIVER_OUTPUT_ENABLE, LOW); //sets pins 3 and 4 to low to enable reciever mode on the MAX485. pinMode(RX_PIN, INPUT); //sets serial pin to receive data /******************************* Addressing subroutine *********************************/ pinMode(ADDR_UP_BUTTON, INPUT); digitalWrite(ADDR_UP_BUTTON, HIGH); //turns on the internal pull-up resistor pinMode(ADDR_DOWN_BUTTON, INPUT); digitalWrite(ADDR_DOWN_BUTTON, HIGH); pinMode(ADDR_FAST_BUTTON, INPUT); digitalWrite(ADDR_FAST_BUTTON, HIGH); dmxaddress = dmxaddress + 3; /* this will allow the USART receive interrupt to fire an additional 3 times for every dmx frame. * Here's why: * Once to account for the fact that DMX addresses run from 0-511, whereas channel numbers * start numbering at 1. * Once for the Mark After Break (MAB), which will be detected by the USART as a valid character * (a zero, eight more zeros, followed by a one) * Once for the START code that precedes the 512 DMX values (used for RDM). */ /******************************* LCD Initialization ************************************/ lcd.begin(16,2); delay(1); lcd.clear(); delay(1); lcd.setCursor(0,0); lcd.print(" DMX Readout"); delay(500); //let them see this for a split second! lcd.clear(); /******************************* USART configuration ************************************/ Serial.begin(250000); /* Each bit is 4uS long, hence 250Kbps baud rate */ cli(); //disable interrupts while we're setting bits in registers bitClear(UCSR0B, RXCIE0); //disable USART reception interrupt /******************************* Timer2 configuration ***********************************/ //NOTE: this will disable PWM on pins 3 and 11. bitClear(TCCR2A, COM2A1); bitClear(TCCR2A, COM2A0); //disable compare match output A mode bitClear(TCCR2A, COM2B1); bitClear(TCCR2A, COM2B0); //disable compare match output B mode bitSet(TCCR2A, WGM21); bitClear(TCCR2A, WGM20); //set mode 2, CTC. TOP will be set by OCRA. bitClear(TCCR2B, FOC2A); bitClear(TCCR2B, FOC2B); //disable Force Output Compare A and B. bitClear(TCCR2B, WGM22); //set mode 2, CTC. TOP will be set by OCRA. bitClear(TCCR2B, CS22); bitClear(TCCR2B, CS21); bitSet(TCCR2B, CS20); //no prescaler means the clock will increment every 62.5ns (assuming 16Mhz clock speed). OCR2A = 64; /* Set output compare register to 64, so that the Output Compare Interrupt will fire * every 4uS. */ bitClear(TIMSK2, OCIE2B); //Disable Timer/Counter2 Output Compare Match B Interrupt bitSet(TIMSK2, OCIE2A); //Enable Timer/Counter2 Output Compare Match A Interrupt bitClear(TIMSK2, TOIE2); //Disable Timer/Counter2 Overflow Interrupt Enable sei(); //reenable interrupts curTime = millis(); //get the first timestamp } //end setup() void loop() { // the processor gets parked here while the ISRs are doing their thing. delay(1); // Delay only a short time in each loop so we are responsive to new DMX data // Debounce and process the address buttons (sample every ~25 mSec) if (++debounceCount >= 25) { processAddressButtons(); debounceCount = 0; } if (dmxnewvalue == 1) { //when a new set of values are received, jump to action loop... action(); dmxnewvalue = 0; } } //end loop() //Timer2 compare match interrupt vector handler ISR(TIMER2_COMPA_vect) { if (bitRead(PIND, PIND0)) { // if a one is detected, we're not in a break, reset zerocounter. zerocounter = 0; } else { zerocounter++; // increment zerocounter if a zero is received. if (zerocounter == 20) // if 20 0's are received in a row (80uS break) { bitClear(TIMSK2, OCIE2A); //disable this interrupt and enable reception interrupt now that we're in a break. bitSet(UCSR0B, RXCIE0); } } } //end Timer2 ISR ISR(USART_RX_vect){ dmxreceived = UDR0; /* The receive buffer (UDR0) must be read during the reception ISR, or the ISR will just * execute again immediately upon exiting. */ // Note time of first character to measure frames/second if (dmxcurrent++ == 0) { prevTime = curTime; curTime = millis(); deltaT = curTime - prevTime; // in mSec, atomically computed } // Look for our data window if(dmxcurrent > dmxaddress) { //check if the current address is the one we want. dmxvalue[i++] = dmxreceived; if(i == NUMBER_OF_CHANNELS) { dmxnewvalue = 1; //set newvalue, so that the action loop can be executed. dmxcurrent = 0; //reset for the next frame zerocounter = 0; i = 0; bitClear(UCSR0B, RXCIE0); //Disable receiving serial data bitSet(TIMSK2, OCIE2A); //Enable Timer/Counter2 Output Compare Match A Interrupt for next break } } } // end ISR