/* "[bon]ome" - Arduino Based RGB Monome Clone FIRMWARE by Julien Bayle 08/30/2008 (named by Denis) Arduino Board is the "the brain" ( http://www.arduino...) Sparkfun Backpack is the "rgb 8x8 led matrix driver" (http://www.sparkfun.com/commerce/product_info.php?products_id=760) Complete schematics, parts list on my website It was a challenge with a real interesting purpose for my music live performance. I did it, with the help/advices of a lot of very cool folks quoted after. I did it with OpenSource mind and all releases/schematics/photos will be or are on my website. for new releases/modifications/questions/words/feelings : website : http://www.julienbayle.net/diy/LiveInterface/ email: julien.bayle@gmail.com -------------------------------------------------------------------------- This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. -------------------------------------------------------------------------- This code is quietly a direct translation of the series 64/128/256 protocol designed by Brian Crabtree & Joe Lake, and coded by Joe Lake. A big thanks to the following person & project: without them, I clearly couldn't succeed : Devon Jones and his code, his serial router in Python and ***HIS TIME*** for me : http://www.evilsoft.org/ Brad Hill aka unsped for his precious time too : http://www.flickr.com/photos/unsped/ The Arduinome Project folks : Ben Southall & Jordan Hochenbaum & Owen Vallis: http://sourceforge.net/projects/arduinome/?abmode=1 Brian Durocher for his genius idea with the backpack "hi-jacking" : http://dioioib.blogspot.com/ Sparkfun Electronics website/shop not for offering presents, just for selling parts :) http://www.sparkfun.com/ -------------------------------------------------------------------------- Additional technical specifications : For leds & buttons, I followed the OSC protocol specification excepted for several things... New commands or modifications of existing commands: * /rgbset made by Devon & Johnathan ; uses ID=10 normally attributed to /intensity ; I removed /intensity management. By sending "/rgbset c" ; c can equals from 0 or 7 (see RGB Backpack color coding system after) you set the currentColor variable used by /led, /ledrow & /ledcol * "/ledrgb x y c" is a new command (ID=15) made to control state of RGB leds x,y are coordinates of led c can equals from 0 or 7 (see RGB Backpack color coding system after) for further details about backpack: http://www.sparkfun.com/datasheets/Components/matrix_backpack.pdf * /clear command can be used as /clear = all off or as "/clear c" = all led with the color c passed as argument, /clear 0 = all off * /ledcol and /ledrow can be used without /rgbset before, in this case, they'd use red color by default TODO: /ledframe support -------------------------------------------------------------------------- // SPI byte for initialization SPCR |7 |6 | 5 | 4 | 3 | 2 | 1 | 0 | |SPIE | SPE | DORD | MSTR | CPOL | CPHA | SPR1 | SPR0 | SPIE - Enables the SPI interrupt when 1 SPE - Enables the SPI when 1 DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0 MSTR - Sets the Arduino in master mode when 1, slave mode when 0 CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0 CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0 SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz) // RGB Backpack color coding system Byte Color 0x00 Off 0x01 Red 0x02 Green 0x03 Blue 0x04 Red + Green 0x05 Green + Blue 0x06 Blue + Red 0x07 Red + Green + Blue // Timer interrupt initialization Settings for TCCR2B: (remember, interrupts occur every 255 cycles) 000 = Clock stopped 001 = No prescaling (16MHz) 010 = 1/8 (2MHz) 011 = 1/32 (500kHz) 100 = 1/64 (250kHz) 101 = 1/128 (125kHz) 110 = 1/256 (62.5khZ) 111 = 1/1024 (15.625kHz) Might even work as slow as 100 (250kHz). -------------------------------------------------------------------------- Please DO NOT email monome with technical questions and/or help regarding this code or clone. They are in NO WAY responsible or affiliated with this project other than they were our inspiration and we used many of their methods and pulled from their code. Additionally, while we are availble and willing to help as much as possible, we too CANNOT be held responsible for anything you do with this code. Please feel free to report any bugs, suggestions or improvements to us as they are all welcome. Again, we cannot be held responsible for any damages or harm caused by the use or misuse of this code or our instructions. Thank you for understanding. -------------------------------------------------------------------------- */ #define RATE 57600 // transmission rate in bauds // Table for matrix color storage int databuffer[8][8]; // Arduino pins for SPI link with backpack #define CHIPSELECT 10 //CS #define SPICLOCK 13 //SCK #define DATAOUT 11 //MOSI #define DATAIN 12 //MISO //164 (from serial to IC) // set which pins to use #define DATAPIN 3 // load data from computer to 164 #define CLOCKPIN 2 // pulse to load bits (one bit for each clock pulse) //165 (from IC to serial) #define INLOADPIN 8 // toggling this tells the 165 to read the byte into its memory for reading #define INDATAPIN 9 // pull the data out of the 165 bit by bit #define INCLOCKPIN 7 // pulse to pull bits (one bit for each clock pulse) byte byte0 = 0x00; // these are used to hold the serial data byte byte1 = 0x00; byte address = 0x00; // garbage byte to hold address of function byte state = 0x00; // garbage byte to hold color value byte x = 0x00; // garbage byte to hold x position byte y = 0x00; // garbage byte to hold y position byte rowBits = 0x00; // garbage byte to hold row data for led_row function byte colBits = 0x00; // garbage byte to hold column data for led_col function int b = 0; // these are used for buttons functions & debouncing int i = 0; int id = 0; int kButtonEventQueueSize = 16; // these are used for buttons debouncing int kButtonStateDown = 1; int kButtonStateUp = 0; int kButtonDownEvent = 1; int kButtonUpEvent = 0; int kButtonDownDefaultDebounceCount = 1; int kButtonUpDefaultDebounceCount = 12; int kButtonNewEvent = 1; int kButtonNoEvent = 0; byte t = 0; byte button_current[8]; byte button_last[8]; byte button_state[8]; byte button_debounce_count[8][8]; byte button_event[8]; // Auxiliary analog output definitions #define ANALOG0 1 //Output pin definitions #define ANALOG1 2 boolean adc[2] = { //On or off state 0, 0}; byte analogval[2]; //The last reported value byte tempADC; //Temporary storage for comparison purposes // pins, reminder // dataIn = 4 = PORTD, bit 4 // clock = 6 = PORTD, bit 6 byte DataMaskHigh = 1 << 4; byte DataMaskLow = ~DataMaskHigh; byte ClockMaskHigh = 1<<6; byte ClockMaskLow = ~ClockMaskHigh; byte LoadMaskHigh = 1 << 5; byte LoadMaskLow = ~LoadMaskHigh; int colorMapping[8]= { 0, 1, 2, 4, 3, 6, 5, 7 } ; // 0,1,2,3,4,5,6,7 int red,green,blue,currentcolor; // function to map color coding system to rgb system (only on/off for each component w/ this hardware) int findColor(int sum) { if(sum>2 && sum<7) { if (sum==3) return 4; else if (sum==4) return 4; else if (sum==6) return 5; else if (sum==5) return 6; } else return sum; } // SPI data load / transfer function char spi_transfer(volatile char data) { SPDR = data; // Start the transmission while (!(SPSR & (1< 0 && --button_debounce_count[row][index] == 0) { // if the the debounce counter has // been decremented to 0 (meaning the the button has been up for kButtonUpDefaultDebounceCount iterations button_event[row] = kButtonNewEvent << index; // queue up a button state change event if (button_current[row] & (1 << index)){ // and toggle the buttons debounce state. button_state[row] |= (1 << index); } else{ button_state[row] &= ~(1 << index); } } } } //164 (from serial to IC) // dataPin = 3 = PORTD, bit 3 // clockPin = 2 = PORTD, bit 2 byte dataPinMaskHigh = 1 << 3; byte dataPinMaskLow = ~dataPinMaskHigh; byte clockPinMaskHigh = 1 << 2; byte clockPinMaskLow = ~clockPinMaskHigh; //165 (from IC to serial) // inloadPin = 8 = PORTB, bit 0 // indataPin = 9 = PORTB, bit 1 // inclockPin = 7 = PORTD, bit 7 byte inloadPinMaskHigh = 1; byte inloadPinMaskLow = ~inloadPinMaskHigh; byte indataPinMask = 1 << 1; byte inclockPinMaskHigh = 1 << 7; byte inclockPinMaskLow = ~inclockPinMaskHigh; // this function uses clean data debounced by buttoncheck() to send safe pushes to the serial port void buttonpress () { PORTD &= dataPinMaskLow; // digitalWrite(dataPin, LOW); //164 for(i = 0; i < 8; i++){ PORTD |= clockPinMaskHigh; // digitalWrite(clockPin, HIGH); PORTD &= clockPinMaskLow; //digitalWrite(clockPin, LOW); PORTD |= dataPinMaskHigh; //digitalWrite(dataPin, HIGH); volatile int a = 0; while (a < 15) // Ben's delay tip because timer0 is disabled and can't do delays { a++; } button_last [i] = button_current [i]; //165 PORTB &= inloadPinMaskLow; // digitalWrite(inloadPin, LOW); PORTB |= inloadPinMaskHigh; // digitalWrite(inloadPin, HIGH); for(id = 0; id < 8; id++) { t = (PINB & indataPinMask) >> 1; //t = digitalRead(indataPin); t = (t == 0); if(t){ button_current [i] |= (1 << id); } else{ button_current [i] &= ~(1 << id); } buttonCheck(i, id); if (button_event[i] & (1 << id)) { button_event[i] &= ~(1 << id); if(button_state[i] & (1 << id)){ Serial.print((0 << 4) , BYTE); // message id 0 = key down Serial.print((id << 4) | i , BYTE); // (id,i) = (x,y) } else{ Serial.print((1 << 4) , BYTE); // message id 1 = key up Serial.print((id << 4) | i, BYTE); // (id,i) = (x,y) } } PORTD |= inclockPinMaskHigh; // digitalWrite(inclockPin, HIGH); PORTD &= inclockPinMaskLow; // digitalWrite(inclockPin, LOW); } } } byte WaitingForAddress = 1; // this routine is made to avoid data loss when arduino receive messages from serial port, while CPU process all his own tasks (led & button management, etc) // It is an Interrupt Sub Routine // actually, it is responsible of led management and contain all the decoding task sent by the OSC/Serial router ISR(TIMER2_OVF_vect) { // first up: enable interrupts to keep the serial // class responsive sei(); do { if (Serial.available()) { if (WaitingForAddress == 1) { byte0 = Serial.read(); address = byte0 >> 4; WaitingForAddress = 0; } // end if (WaitingForAddress == 1); if (Serial.available()) { WaitingForAddress = 1; byte1 = Serial.read(); switch(address) { case 10: // set colour - becareful, this ID is not series protocol compliant, it is used to intensity by those monome folks. if( Serial.available() > 2 ) { red = Serial.read() * 1; green = Serial.read() * 2; blue = Serial.read() * 4; currentcolor = findColor(red + green + blue); } break; case 15: // ledrgb state = byte0 & 15; x = byte1 >> 4; y = byte1 & B1111; databuffer[x][y] = state ; break; case 2: // led on if (currentcolor == NULL) currentcolor = 1; x = byte1 >> 4; y = byte1 & B1111; databuffer[x][y] = currentcolor ; break; case 3: // led off x = byte1 >> 4; y = byte1 & B1111; databuffer[x][y] = 0 ; break; case 4: // led_row1 y = byte0 & B1111; rowBits = byte1; for(int tempx = 0; tempx < 8; ++tempx) { if (rowBits & (1 << tempx)) { databuffer[tempx][y] = 1; } else { databuffer[tempx][y] = 0; } } break; case 5: // led_col1 x = byte0 & B1111; colBits = byte1; for(int tempy = 0; tempy < 8; ++tempy) { if (colBits & (1 << tempy)) { databuffer[x][tempy] = 1; } else { databuffer[x][tempy] = 0; } } break; case 9: // clear if ( (byte0 & 1) == 0 ) for (int y=0;y<8;y++) for (int x=0;x<8;x++) { databuffer[x][y] = 0; // all led off } else for (int y=0;y<8;y++) for (int x=0;x<8;x++) { databuffer[x][y] = byte0 & B0001; // all led on with a particular color = byte0 & 1 } break; } // end switch(address) } // end if (Serial.available() } // end if (Serial.available(); } // end do while (Serial.available() > 16); } void checkADC(){ // For all of the ADC's which are activated, check if the analog value has changed, // and send a message if it has. if(adc[0]){ tempADC = (analogRead(ANALOG0) >> 2); if(abs((int)analogval[0] - (int)tempADC) > 3 ){ analogval[0] = tempADC; Serial.print(14 << 4, BYTE); Serial.print(analogval[0], BYTE); } } if(adc[1]){ if(analogval[1] != (analogRead(ANALOG1) >> 2)){ analogval[1] = (analogRead(ANALOG1) >> 2); Serial.print(14 << 4 | 1, BYTE); Serial.print(analogval[1], BYTE); } } } // setup() is launched only at the Arduino reset/switch on and one time void setup () { // pins directions definitions pinMode(DATAPIN,OUTPUT); pinMode(CLOCKPIN,OUTPUT); pinMode(INDATAPIN,INPUT); pinMode(INCLOCKPIN,OUTPUT); pinMode(INLOADPIN,OUTPUT); byte clr; pinMode(DATAOUT,OUTPUT); pinMode(SPICLOCK,OUTPUT); pinMode(CHIPSELECT,OUTPUT); digitalWrite(CHIPSELECT,HIGH); //disable device // SPI initialization for communication between backpack and Arduino board /* see infos at the top */ SPCR = B01010001; //SPI Registers SPSR = SPSR & B11111110; //make sure the speed is 125KHz clr=SPSR; clr=SPDR; delay(10); DDRB |= (1 << PB2)|(1 << PB3)|(1 << PB5); // Serial communication with OSC/Serial router initialization (speed is 57600 bauds) Serial.begin(RATE); // Button management initialization buttonInit(); // Timer interrupt initialization /* see infos at the top */ TCCR2A = 0; // set counter to be clocked at 16Mhz/8 = 2Mhz TCCR2B = 1<