270 lines
11 KiB
Plaintext
270 lines
11 KiB
Plaintext
/************************************************************
|
|
* 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 <LiquidCrystal.h>
|
|
|
|
|
|
/******************************* Addressing variable declarations *****************************/
|
|
|
|
#include <EEPROM.h>
|
|
#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
|