375 lines
10 KiB
C++
375 lines
10 KiB
C++
#include <arduino.h>
|
|
/* 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;
|
|
|
|
// 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(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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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.write(tempvalue,DEC);
|
|
|
|
if(pressed[r][c] != digitalRead(INDATAPIN)){ // read the state
|
|
pressed[r][c] = digitalRead(INDATAPIN);
|
|
if(!pressed[r][c]){
|
|
on_release(r, c);
|
|
color_on(r,c);
|
|
}
|
|
else {
|
|
on_press(r, c);
|
|
color_off(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.writeln();
|
|
|
|
digitalWrite(CLOCKPIN, LOW);
|
|
digitalWrite(DATAPIN, LOW);
|
|
|
|
|
|
}
|
|
|
|
//Serial.writeln("new row");
|
|
//delay(2000);
|
|
|
|
}
|
|
|
|
void on_press(byte r, byte c){
|
|
Serial.write( (byte)1);
|
|
//Serial.write( 1, BYTE);
|
|
Serial.write( (r << 4) | c);
|
|
}
|
|
|
|
void on_release(byte r, byte c){
|
|
Serial.write( (byte)0 );
|
|
//Serial.write( 0, BYTE);
|
|
Serial.write( (r << 4) | c );
|
|
}
|
|
|
|
void color_on(byte x, byte y){
|
|
if ((x<4)&&(y==0))
|
|
{
|
|
for(int col=0; col <4; col++){
|
|
levelR[remap[col][y]] = 4095;
|
|
levelB[remap[col][y]] = 4095;
|
|
levelG[remap[col][y]] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void color_off(byte x, byte y){
|
|
if ((x<4)&&(y==0))
|
|
{
|
|
for(int col=0; col <4; col++){
|
|
levelR[remap[col][y]] = 0;
|
|
levelB[remap[col][y]] = 0;
|
|
levelG[remap[col][y]] = 0;
|
|
}
|
|
}
|
|
}
|