Lab_interaccio/2010/octint_v3/octint_v3 (Miguel Angel de Heras Gomez's conflicted copy 2010-09-01).pde
2025-02-25 21:29:42 +01:00

616 lines
18 KiB
Plaintext

/* Tinct firmware, version ??? (no version number scheme yet)
Written by Jonathan M. Guberman
jonathan@upwardnotnorthward.com
www.upwardnotnorthward.com
Released under a Creative Commons Attribution-Noncommercial-Sharealike license
Can be used and adapted for noncommercial purposes, as long as proper credit is
given to the original author, and any derivatives works are released under a
similar license.
*/
//Debugging definitions: uncomment the relevant line to turn it on
//#define REDALERT 100 //Draw colour is forced to red if the serial receive buffer has more than the specified number of characters in it
//#define REPORTBUFFER -1 //Sends out the current size of the Serial buffer using command ID 15 if the buffer size is greater than the defined value. Set to -1 to always report.
// Serial data transfer rate
#define BAUD 115200
/* Size of the serial buffer before the Tinct is forced to parse it continually.
The buffer size is 128 bytes, and if it gets there the Tinct can (and will) crash.
The largest command size is 9 bytes, so 119 is an absolute maximum value.
Set it lower than this to be safe.
If the Tinct hits this limit, it will start to flicker, and might miss commands,
but it won't crash. Probably.
*/
#define TOOFULL 100
//TLC5940NT pin definitions
#define VPRG 2
#define SIN 11 // MOSI - Hardware SPI, can't be changed
#define SCLK 13 // SCK - Hardware SPI, can't be changed
#define XLAT 4
#define BLANK 5
#define DCPRG 6
#define GSCLK 7
/* These pins are from the Hardware SPI, but aren't connected to the TLC5940
These definitions are only here for clarity, and aren't used. MISO can not
be used for anything else, while SS can be used for any OUTPUT. It can NOT
be used for an input. Currently, it is used for INCLOCKPIN, the 165's clock
pin setting.
*/
#define MISO 12 //MISO - Hardware SPI, can't be changed (not connected, can't be used for anything other than MISO)
#define SS 10 //SS - Hardware SPI, not used and therefore can be used for something else, but ONLY AS AN OUTPUT!!
// Default draw colour. Each channel can be between 0 and 4095.
int red = 0;
int green = 4095;
int blue = 0;
// Auxiliary analog output definitions
#define ANALOG0 3 //Output pin definitions
#define ANALOG1 4
boolean adc[2] = { //On or off state
0, 0};
byte analogval[2]; //The last reported value
byte tempADC; //Temporary storage for comparison purposes
/* The transistor bases are done via port manipulation:
RED goes to analog pin 5
GREEN goes to analog pin 0
BLUE goes to analog pin 1
*/
// Pin definitions for the 74HC164 SIPO shift register (drives button rows high)
#define DATAPIN 16 // aka analog pin 3 (what, you didn't know that analog pins 0-5 are also digital pins 14-19? Well, now you do!)
#define CLOCKPIN 3
// Pin definitions for the 74HC165 PISO shift register (reads button column state)
#define INDATAPIN 9
#define INCLOCKPIN 10
#define INLOADPIN 8 // toggling this tell the 165 to read the value into its memory for reading
// Holds the last state of the button press
boolean pressed[8][8] = {
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1}
};
/*This is the mapping of the physical position of the boards to the correct
output of the TLC5940s. The left halves are rotated to correspond to the
orientation of the boards*/
const byte remap[8][8] = {
{48,49,51,52, 12,13,14,15},
{50,53,54,55, 11,10, 9, 8},
{56,57,58,59, 7, 6, 5, 2},
{63,62,61,60, 4, 3, 1, 0},
{32,33,35,36, 28,29,30,31},
{34,37,38,39, 27,26,25,24},
{40,41,42,43, 23,22,21,18},
{47,46,45,44, 20,19,17,16},
};
// Holds the current colour level for each of the buttons
int levelR[64] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int levelG[64] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int levelB[64] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// Variables for interpreting the serial commands
byte tempR;
byte tempC;
byte lastread;
byte command = 0;
byte ready = true;
// For interrupt timing; needed only to do intermediate clock speeds
/* Divide interrupt frequency by a factor of FREQ. It is preferable to keep
FREQ as small as possible, and control the frequency of the interrupts
using the hardware clock. Setting it to 1 disables this entirely, which,
if it works, is ideal; this should be the same as commenting out the
"#define FREQ" statement entirely.
*/
#define FREQ 1 // How many interrupts occur before the serial commands are read
#if FREQ > 1
byte int_counter = 0;
#endif
// Transfer a character out over hardware SPI
char spi_transfer(volatile byte data)
{
SPDR = data; // Start the transmission
while (!(SPSR & (1<<SPIF))) // Wait the end of the transmission
{
};
return SPDR; // return the received byte
}
// Run this animation once at startup. Currently unfinished.
void startup(){
/*for(levelR[remap[3][3]] = 0; levelR[remap[3][3]] < 4000; levelR[remap[3][4]] = levelR[remap[3][3]] += 64){
makemagic();
}
for(levelB[remap[2][2]] = 0; levelB[remap[2][2]] < 4000; levelB[remap[2][2]] += 64){
levelR[remap[3][3]] -= 64;
makemagic();
}
for(levelG[remap[1][1]] = 0; levelG[remap[1][1]] < 4000; levelG[remap[1][1]] += 64){
levelB[remap[2][2]] -= 64;
makemagic();
}*/
for(byte x = 0; x < 8; ++x){
for(byte y = 0; y <8; ++y){
levelR[remap[x][y]] = 4095;
makemagic();
makemagic();
makemagic();
levelB[remap[x][y]] = 4095;
makemagic();
makemagic();
makemagic();
levelG[remap[x][y]] = 4095;
makemagic();
makemagic();
makemagic();
/*levelR[remap[x][y]] = 0;
makemagic();
makemagic();
makemagic();
levelB[remap[x][y]] = 0;
makemagic();
makemagic();
makemagic();
levelG[remap[x][y]] = 0;
makemagic();
makemagic();
makemagic();*/
}
}
/* levelR[remap[0][0]] = 4095;
levelG[remap[1][1]] = 4095;
levelB[remap[2][2]] = 4095;
levelR[remap[3][3]] = 4095;
levelG[remap[3][3]] = 4095;
levelB[remap[3][3]] = 4095;
levelR[remap[4][4]] = 4095;
levelG[remap[4][4]] = 4095;
levelB[remap[5][5]] = 4095;
levelR[remap[5][5]] = 4095;
levelG[remap[6][6]] = 4095;
levelB[remap[6][6]] = 4095;
levelR[remap[7][7]] = 4095;
levelG[remap[7][7]] = 4095;
levelB[remap[7][7]] = 4095;
*/
}
//The timer interrupt routine, which periodically interprets the serial commands
ISR(TIMER2_OVF_vect) {
sei(); //Reenable global interrupts, otherwise serial commands will get dropped
#if FREQ > 1
if(++int_counter == FREQ){ // Only do this once every FREQ-th interrupt
int_counter = 0;
#endif //FREQ
do{ // This do ensures that the data is always parsed at least once per cycle
if(Serial.available()){
#ifdef REDALERT // if REDALERT is defined, draw colour turns red when the buffer is getting dangerously full
if(Serial.available() > REDALERT){
blue = 0;
green = 0;
red = 4095;
}
#endif //REDALERT
#ifdef REPORTBUFFER // if REPORTBUFFER is defined, send out the size of the serial receive buffer using command ID 15
if(Serial.available() > REPORTBUFFER){
Serial.print(15 << 4, BYTE);
Serial.print(Serial.available(), BYTE);
}
#endif //REPORTBUFFER
if(ready){ // If the last command has finished executing, read in the next command and reset the command flag
command = Serial.read();
ready = false;
}
switch (command >> 4) { //Execute the appropriate command, but only if we have received enough bytes to complete it. We might one day add "partial completion" for long command strings.
case 1: // set colour
if( Serial.available() > 2 ) {
red = Serial.read() * 16;
green = Serial.read() * 16;
blue = Serial.read() * 16;
ready=true;
}
break;
case 2: // led_on
if( Serial.available() ) {
lastread = Serial.read();
tempR = lastread >> 4;
tempC = lastread & B1111;
levelR[remap[tempC][tempR]] = red;
levelG[remap[tempC][tempR]] = green;
levelB[remap[tempC][tempR]] = blue;
ready = true;
}
break;
case 3: // led_off
if( Serial.available() ) {
lastread = Serial.read();
tempR = lastread >> 4;
tempC = lastread & B1111;
levelR[remap[tempC][tempR]] = 0;
levelG[remap[tempC][tempR]] = 0;
levelB[remap[tempC][tempR]] = 0;
ready = true;
}
break;
case 4: // led_row1
if( Serial.available() ) {
tempR = command & B1111;
lastread = Serial.read();
for(tempC = 0; tempC < 8; ++tempC){
if(lastread & (1 << tempC) ){
levelR[remap[tempR][tempC]] = red;
levelG[remap[tempR][tempC]] = green;
levelB[remap[tempR][tempC]] = blue;
}
else {
levelR[remap[tempR][tempC]] = 0;
levelG[remap[tempR][tempC]] = 0;
levelB[remap[tempR][tempC]] = 0;
}
}
ready = true;
}
break;
case 5: // led_col1
if( Serial.available() ) {
tempC = command & B1111;
lastread = Serial.read();
for(tempR = 0; tempR < 8; ++tempR){
if(lastread & (1 << tempR) ){
levelR[remap[tempR][tempC]] = red;
levelG[remap[tempR][tempC]] = green;
levelB[remap[tempR][tempC]] = blue;
}
else {
levelR[remap[tempR][tempC]] = 0;
levelG[remap[tempR][tempC]] = 0;
levelB[remap[tempR][tempC]] = 0;
}
}
ready = true;
}
break;
case 8: //frame
if( Serial.available() > 7 ) {
for(tempR=0; tempR<8; ++tempR){
lastread = Serial.read();
for(tempC = 0; tempC < 8; ++tempC){
if(lastread & (1 << tempC) ){
levelR[remap[tempR][tempC]] = red;
levelG[remap[tempR][tempC]] = green;
levelB[remap[tempR][tempC]] = blue;
}
else {
levelR[remap[tempR][tempC]] = 0;
levelG[remap[tempR][tempC]] = 0;
levelB[remap[tempR][tempC]] = 0;
}
}
}
ready = true;
}
break;
case 9: //clear
if(command & 1){
for(int x = 0; x< 64;++x){
levelR[x] = red;
levelG[x] = green;
levelB[x] = blue;
}
}
else{
for(int x = 0; x<64;++x){
levelR[x] = 0;
levelG[x] = 0;
levelB[x] = 0;
}
}
ready = true;
break;
case 12:
switch(command & 15){
case 0:
adc[0] = true;
analogval[0] = (analogRead(ANALOG0) >> 2);
Serial.print(14 << 4, BYTE);
Serial.print(analogval[0], BYTE);
break;
case 1:
adc[1] = true;
analogval[1] = (analogRead(ANALOG1) >> 2);
Serial.print(14 << 4 | 1, BYTE);
Serial.print(analogval[1], BYTE);
break;
default:
break;
}
ready = true;
break;
case 13:
adc[command & 15] = false;
ready = true;
break;
default:
break;
}
}
}
// If the serial buffer is getting too close to full, keep executing the parsing until it falls below a given level
// This might cause flicker, or even dropped messages, but it should prevent a crash.
while (Serial.available() > TOOFULL);
#if FREQ > 1
}
#endif //FREQ
}
void setup() {
//Setup data directions, and set everything to the correct initial levels,
// For TLC5940
PORTC |= B00100011;
DDRC |= B00100011;
pinMode(VPRG, OUTPUT);
pinMode(SIN, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(XLAT, OUTPUT);
pinMode(BLANK, OUTPUT);
pinMode(DCPRG, OUTPUT);
pinMode(GSCLK, OUTPUT);
pinMode(MISO, INPUT);
pinMode(SS,OUTPUT);
digitalWrite(SS,HIGH); //disable device
digitalWrite(SIN, LOW);
digitalWrite(SCLK, LOW);
digitalWrite(XLAT, LOW);
digitalWrite(VPRG, LOW);
digitalWrite(BLANK, HIGH);
digitalWrite(GSCLK, HIGH);
digitalWrite(DCPRG, LOW); // USE EEPROM DC register if LOW
//Setup the Hardware SPI registers
// SPCR = 01010000
//interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
//sample on leading edge of clk,system clock/4 (fastest)
byte clr;
SPCR = (1<<SPE)|(1<<MSTR);
clr=SPSR;
clr=SPDR;
delay(10);
// 165 Setup
pinMode(INDATAPIN, INPUT);
pinMode(INCLOCKPIN, OUTPUT);
pinMode(INLOADPIN, OUTPUT);
// 164 Setup
pinMode(DATAPIN, OUTPUT);
pinMode(CLOCKPIN, OUTPUT);
// Start the serial port
Serial.begin(BAUD);
/* Setup the timer interrupt
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)
010 (2MHz) with FREQ set to 10 or less seem to work reasonably well
Trying to use 011 (500kHz), to eliminate the if statement needed for FREQ
Might even work as slow as 100 (250kHz).
*/
TCCR2A = 0;
TCCR2B = 0<<CS22 | 1<<CS21 | 1<<CS20;
//Timer2 Overflow Interrupt Enable
TIMSK2 = 1<<TOIE2;
delay(10);
// Run the startup animation
startup();
}
void loop () {
// Turn on the lights
makemagic();
// Check the button states
checkButtons();
// Check and report the ADC states, if necessary
//checkADC();
}
void makemagic(){
/* Clear the transistors, set the TLC5940 to the correct channel value with setGreys,
turn the transistor on to give voltage to the LEDs, and then pulse the TLC5940's clock.
*/
PORTC |= B00100011;
setGreysR();
PORTC &= B11011111;
feedPorts();
PORTC |= B00100011;
setGreysG();
PORTC &= B11111101;
feedPorts();
PORTC |= B00100011;
setGreysB();
PORTC &= B11111110;
feedPorts();
}
void setGreysR() {
digitalWrite(BLANK, HIGH);
digitalWrite(XLAT,LOW);
for(int i = 31; i>=0; i--){
spi_transfer( (levelR[2*i+1] & 0x0FF0) >> 4 );
spi_transfer( ((levelR[2*i+1] & 0xF) << 4) | ((levelR[2*i] & 0x0F00) >> 8) );
spi_transfer( levelR[2*i] & 0xFF);
}
digitalWrite(XLAT,HIGH);
digitalWrite(XLAT,LOW);
digitalWrite(BLANK, LOW);
}
void setGreysG() {
digitalWrite(BLANK, HIGH);
digitalWrite(XLAT,LOW);
for(int i = 31; i>=0; i--){
spi_transfer( (levelG[2*i+1] & 0x0FF0) >> 4 );
spi_transfer( ((levelG[2*i+1] & 0xF) << 4) | ((levelG[2*i] & 0x0F00) >> 8) );
spi_transfer( levelG[2*i] & 0xFF);
}
digitalWrite(XLAT,HIGH);
digitalWrite(XLAT,LOW);
digitalWrite(BLANK, LOW);
}
void setGreysB() {
digitalWrite(BLANK, HIGH);
digitalWrite(XLAT,LOW);
for(int i = 31; i>=0; i--){
spi_transfer( (levelB[2*i+1] & 0x0FF0) >> 4 );
spi_transfer( ((levelB[2*i+1] & 0xF) << 4) | ((levelB[2*i] & 0x0F00) >> 8) );
spi_transfer( levelB[2*i] & 0xFF);
}
digitalWrite(XLAT,HIGH);
digitalWrite(XLAT,LOW);
digitalWrite(BLANK, LOW);
}
void feedPorts() {
// Clock for TLC5940's PWM
digitalWrite(BLANK, HIGH);
digitalWrite(BLANK, LOW); //=all outputs ON, start PWM cycle
for (int i=0; i<4096; i++) {
pulseGSCLK();
}
}
void pulseGSCLK() {
//ultra fast pulse trick, using digitalWrite caused flickering
PORTD |= 0x80 ; // bring pin 7 high, but don't touch any of the other pins in PORTB
//16 nanosecs is the min pulse width for the 5940, but no pause seems needed here
PORTD &= 0x7F; // bring pin 7 low without touching the other pins in PORTB
}
void checkButtons(){
digitalWrite(CLOCKPIN,LOW);
digitalWrite(DATAPIN, HIGH);
for(byte c = 0; c < 8; c++){
digitalWrite(CLOCKPIN, HIGH);
digitalWrite(INLOADPIN, LOW); // read into register
digitalWrite(INLOADPIN, HIGH); // done reading into register, ready for us to read
for(int r=7; r >= 0; r--){ // read each of the 165's 8 inputs (or its snapshot of it rather)
// tell the 165 to send the first inputs pin state
digitalWrite(INCLOCKPIN, LOW);
// read the current output
//int tempvalue = digitalRead(INDATAPIN);
//Serial.print(tempvalue,DEC);
if(pressed[r][c] != digitalRead(INDATAPIN)){ // read the state
pressed[r][c] = digitalRead(INDATAPIN);
if(!pressed[r][c]){
on_release(r, c);
}
else {
on_press(r, c);
}
}
// tell the 165 we are done reading the state, the next inclockpin=0 will output the next input value
digitalWrite(INCLOCKPIN, 1);
}
//Serial.println();
digitalWrite(CLOCKPIN, LOW);
digitalWrite(DATAPIN, LOW);
}
//Serial.println("new row");
//delay(2000);
}
void on_press(byte r, byte c){
Serial.print( 1 << 4, BYTE);
//Serial.print( 1, BYTE);
Serial.print( (r << 4) | c, BYTE);
}
void on_release(byte r, byte c){
Serial.print( 0 << 4, BYTE);
//Serial.print( 0, BYTE);
Serial.print( (r << 4) | c, BYTE);
}
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);
}
}
}