/*
* Copyright (C) 2008, Josh Boughey, Vlad Spears, Jonathan and Matthew Edwards, except where otherwise noted.
* The Stribe Project: www.soundwidgets.com/smf/
This file is part of Stribe.
Stribe 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 3 of the License, or
(at your option) any later version.
Stribe 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.
You should have received a copy of the GNU General Public License
along with Stribe. If not, see .
*
*/
/*
code for the Stribe Prototype - very much "in progress"
Version: 0.4w - for Stribe 0.4c, 0.4d, and 0.4e Driver board
Updated: April 23, 2008
Code History:
--------------
* Being written by: Josh Boughey, Vlad Spears, J & M Edwards, your name here
* Nov 2007 - Jan 2008
Based on lots of example code kindly provided by the Arduino/Wiring communities: http://www.arduino.cc / http://www.wiring.org
Original MAX7219 code by:
Base: Nicholas Zambetti and Dave Mellis
Additions: Marcus Hannerstig, Tomek Ness
MAX7221-specific code:
[tbd]
SimpleMessageSystem library by:
Base: Thomas Ouellet Fredericks
Additions: Alexandre Quessy
NOTE: If you see your code here, NOT attributed, it is a mistake - so please let us know!
*/
#include
unsigned int load = 2;
unsigned int clock = 3;
unsigned int dataIn = 4; // assign analog pins (avoid pins 0 & 1 they are used for rx/tx)
// note: the documentation/comments for the original 7219 code assume you are using only 8 drivers cascaded
// since I am using 16 drivers I assumed I would need to scale everything up
// but so far the code seems to scale to 16 MAX7221's just fine
unsigned int maxInUse = 16; // 16 x MAX7221 = 1024 LEDs = 16 columns x 64 rows
unsigned int e = 0; // just a variable
// define max7219 registers (the stribe uses MAX7221s but this all still works the same)
byte max7219_reg_noop = 0x00;
byte max7219_reg_digit0 = 0x01;
byte max7219_reg_digit1 = 0x02;
byte max7219_reg_digit2 = 0x03;
byte max7219_reg_digit3 = 0x04;
byte max7219_reg_digit4 = 0x05;
byte max7219_reg_digit5 = 0x06;
byte max7219_reg_digit6 = 0x07;
byte max7219_reg_digit7 = 0x08;
byte max7219_reg_decodeMode = 0x09;
byte max7219_reg_intensity = 0x0a;
byte max7219_reg_scanLimit = 0x0b;
byte max7219_reg_shutdown = 0x0c;
byte max7219_reg_displayTest = 0x0f;
void putByte(byte data) {
byte i = 8;
byte mask;
while (i > 0) {
mask = 0x01 << (i - 1); // get bitmask
digitalWrite(clock, LOW); // tick
if (data & mask) { // choose bit
digitalWrite(dataIn, HIGH); // send 1
} else {
digitalWrite(dataIn, LOW); // send 0
}
digitalWrite(clock, HIGH); // tock
--i; // move to lesser bit
}
}
void maxSingle(byte reg, byte col) { // use this to address matrix 1 only
digitalWrite(load, LOW); // begin
putByte(reg); // specify register
putByte(col);//((data & 0x01) * 256) + data >> 1); // put data
digitalWrite(load, LOW); // load it
digitalWrite(load, HIGH);
}
void maxAll(byte reg, byte col) { // send the same pattern to all matrices
int c = 0;
digitalWrite(load, LOW); // begin
for (c = 1; c <= maxInUse; c++) {
putByte(reg); // specify register
putByte(col); //((data & 0x01) * 256) + data >> 1); // put data
}
digitalWrite(load, LOW);
digitalWrite(load, HIGH);
}
void maxRight(byte reg, byte col) { // send the same pattern to right 8 matrices
int c = 0;
digitalWrite(load, LOW); // begin
for (c = 1; c <= maxInUse; c++) {
putByte(reg); // specify register
if (c < 9) {
putByte(col); //((data & 0x01) * 256) + data >> 1); // put data
} else {
putByte(0);
}
}
digitalWrite(load, LOW);
digitalWrite(load, HIGH);
}
void maxLeft(byte reg, byte col) { // send the same pattern to left 8 matrices
int c = 0;
digitalWrite(load, LOW); // begin
for (c = 1; c <= maxInUse; c++) {
putByte(reg); // specify register
putByte(c > 8 ? col : 0); // a little more compact
/*if (c > 8) {
putByte(col); //((data & 0x01) * 256) + data >> 1); // put data
} else {
putByte(0);
}*/
}
digitalWrite(load, LOW);
digitalWrite(load, HIGH);
}
void maxOne(byte maxNr, byte reg, byte col) { // specify matrix/re (1-16) to update
int c = 0;
digitalWrite(load, LOW); // begin
for (c = maxInUse; c > maxNr; c--) {
putByte(0); // means no operation (does this clear buffer?)
putByte(0); // means no operation
}
putByte(reg); // specify register
putByte(col);//((data & 0x01) * 256) + data >> 1); // put data
for (c = maxNr - 1; c >= 1; c--) {
putByte(0); // means no operation
putByte(0); // means no operation
}
digitalWrite(load, LOW); // and load it
digitalWrite(load, HIGH);
}
void maxOnOff(byte a, byte e, byte x) {
maxOne(a, e, x);
delay(5);
maxOne(a, e, 0);
}
void setup() {
pinMode(dataIn, OUTPUT);
pinMode(clock, OUTPUT);
pinMode(load, OUTPUT);
Serial.begin(115200);
digitalWrite(13, HIGH); // why?
Serial.println("stribe: hello");
//////////////////////////////////////////////initialize max 7219 (7221)
maxAll(max7219_reg_scanLimit, 0x07);
maxAll(max7219_reg_decodeMode, 0x00); // using an led matrix (not digits)
maxAll(max7219_reg_shutdown, 0x01); // not in shutdown mode (need to check is this the same for 7221)
maxAll(max7219_reg_displayTest, 0x00); // no display test (ditto)
for (e = 1; e <= 8; e++) { // empty registers, turn all LEDs off (is there a simpler way to do this?)
maxAll(e, 0);
}
maxAll(max7219_reg_intensity, 0x0f); // the first 0x0f is the value you can set
// range: 0x00 to 0x0f (I haven't tried changing this from the default)
}
//// Stribe code pretty much starts here
// an array to save the cursor values (touchpoints) between iterations
int cursor[8];
int hold[8] = {0,0,0,0,0,0,0,0}; // a place to save values longer than 1 iteration
// an array of 128 pattern values (each value represents a horizontal row of 8)
// the first 64 (0-63) represent the left 1/2 of the display, the next 64 (64-127) represent the right half
// adding to or subtracting the value from the grid[] value will add or remove that led from the row pattern
// int grid[128] = {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,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 grid[128]; // <- does this initialize everything to 0, or null?
// you could do
// int grid[128];
// memset( grid, 0, sizeof(grid));
// and that would set them all to zero
int grid[128];
// jb: I tried that but memset() throws an error - maybe I need a library?
// here's a "monome" array - e.g. an 8x8 array of 8x2 blocks that makes a monome-like display on the stribe
// this will also come in handy for optimizing features such as meters
int monome[64];
// this row pattern array is to double the columns to get the doubled "cursor" effect
int cursPatt[8] = {3, 12, 48, 192, 3, 12, 48, 192}; // cursor mode
// here's the regular row pattern values
int pattern[8] = {1, 2, 4, 8, 16, 32, 64, 128}; // single column values [note you'll want to reverse these (so 128 is first) for version 0.1 and 0.2 of the driver board]
// colPatt for full columns
int colPatt[17] = {0, 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
// this is a kludge. It is used with maxOne() to flip a 1-8 array to an 8-1 array so I can use some old code (reverse cursor mode) with the new board wiring
int flip[9] = {0, 8, 7, 6, 5, 4, 3, 2, 1};
// flip a column's values for cursor mode - (probably a better way to do this)
int flipColumn[65] = {0, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
// curve
int curve[9] = {0, 8, 9, 11, 13, 15, 17, 20}; // use this to calibrate touch by adding this[] much to cursor[]
int touch[8]; // keep track of whether there was a change in state, e.g. 1=touch 0=lift
/// ** some notes ** ///
// maxOne draws one subrow of an 8x8 matrix and expects as its inputs: matrix (1-8) (top to bottom), subrow (1-8) (top to bottom), row pattern as a sum of columns (numbered 1, 2, 4, 8, 16, 32, 64, 128, respectively)
// we keep track of everything in the 128-element array called grid[] - it always holds the current row pattern value so just refer to it when you need to know and stuff it when you change it
// remember to add or subtract the new value to/from the existing value to keep things straight
/// ** ** ///
// some variables
unsigned int row;
unsigned int subrow;
unsigned int i; // iterator variable
unsigned int pin;
unsigned int a, b, c, d; // never know when you'll need some of these
// int columns = 128; // do I use this somewhere? // No, you don't
unsigned int column;
unsigned int matrix;
unsigned int rowPatt;
int fred; // fred - never know when you'll need fred
int test;
unsigned int clearAll;
char firstChar;
char secondChar;
unsigned int startup = 5;
unsigned int trails = 10; // cycles before clear screen (when in default/preset cursor mode) - now controllable from Max -> ex: [w t 500] = (trails = 500)
unsigned int preset = 1; // default cursor preset - now controllable from Max -> [w p 1] = on - default is currently ON
unsigned int on; // variable used when calc'ing grid[] patterns to determine whether to add or remove specified LED value from the pattern
// int smooth[10]; // tried smoothing inputs by averaging but the loop slowed things down too much - need a better method
unsigned int smoothed;
unsigned int faders = 1; // if this is 1, display last position
int maxmspColumn; // 0-15
unsigned int maxmspRow; // 0-63 -- making it unsigned makes it a little faster in the / and % operations
int maxmspVal; // 0/1 converted to -1/1
void loop() {
//// startup sequences and stored patterns
while (startup > 0) {
if (startup > 99) { // this means you want to run a stored pattern: 100, 101, etc
switch (startup) {
case 100: // all off / clear
for (e = 1; e <= 8; e++) {
maxAll(e, 0);
}
break;
case 101: // all on
for (e = 1; e <= 8; e++) {
maxAll(e, 255);
}
break;
// case 102: // curtain down
// case 103: // wipe right
case 200: // janijanietc
for (i = 0; i < 500; i++) { // popping lights startup sequence
maxOnOff(rand() % 16 + 1, rand() % 8 + 1, pow(2, rand() % 8));
delay(rand() % 99 + 1);
}
for (e = 1; e <= 8; e++) { // clear
maxAll(e, 0);
}
}
startup = 0;
break; // from the while to skip the default start seq
}
// default start seq
for (e = 8; e > 0; e--) {
maxAll(e, 255); // light em up
delay(50); // make the scrolling slow enough to see
}
--startup;
for (e = 1; e <= 8; e++) { // clear
maxAll(e, 0);
}
} // endwhile startup > 0
/* Controlling the Stribe i/o:
The Stribe consists of 16 8x8 LED matrices, numbered 1-16. 1-8 down the left side of the display, 9-16 down the right side of the display.
The subrows of each matrix are numbered 1-8 starting at the top.
Address each subrow with a bitmask 0-255 to set on/off the state of each subrow's 8 LEDs.
Note that the analog sensors go the opposite direction, e.g. 1 is the bottom value and 1024 is the top value, which is confusing, and accounts for most of the confusing code. :)
I did this to make the stribe more compatible with Max's matrixctrl object, and by extension, with the monome.
Communicate w/ Stribe via the following Max messages:
(The code nibbles a 'w' or an 'a' from the serial stream, then branches on what follows.)
From the Stribe:
a int int int int int int int int - 8 analog sensor values 0-1023
g whatever (char or int) - send a debug message to Max
s int int - [sensor ID] [value]
From Max:
w s int int int - accept 3 display values: [matrix 1-16] [subrow 1-8] [rowPattern 0-255]
w o - all LEDs on (soon deprecated - replaced by w x 101)
w c - all LEDs off (soon deprecated - replaced by w x 100)
w x int - show startup sequence [int] times (max 99)
w x 100 - all LEDs off
w x 101 - all LEDs on
w x 102 - 199 - reserved for "shortcut" sequences
w x 200 - play janijani startup sequence
w x 201 - 299 - reserved for startup sequences
w p int - toggle firmware cursor 1 or 0 (add more later)
w b int int - [subrow] [rowPatt] send same subrow and rowPatt to all 16 matrices at once
w e int int - [matrix] [rowPatt] send same rowPatt to all 8 rows of a specified matrix
w f int int int - same as 'w s' but lights the opposite row with the same pattern (so, 'w f 1 4 255' lights the whole of row 4 on the Stribe)
w m int int int - converts Max matrixctrl's [col] [row] [on/off] to [matrix] [subrow] [rowPattern]
a int - return the value of sensor 0-7 as [sensor ID] [value]
*/
//// read analog pins 0 to 7 and store the values in the cursor[] array (if different than hold[] value)) also update touch[] array
for (i = 0; i < 8; i++) {
// only update if it changed since last iteration
fred = analogRead(i);
if (preset) {
touch[i] = 1;
if (fred != hold[i]) {
cursor[i] = fred;
}
}
else {
cursor[i]=fred;
}
if (fred < 20) { // <-- change this value to set "bottom"
touch[i] = 0;
}
}
//// now read the serial stream and decide what to do
if (messageBuild() > 0) { // checks to see if serial message is complete
firstChar = messageGetChar(); // nibble the 1st word
// branch
switch (firstChar) {
case 'a': // this is a request for a sensor value - send back the sensor ID and the value as 's [sensor ID] [value]'
i = messageGetInt(); // nibble the sensor ID
fred = analogRead(i); // read the specified pin
messageSendChar('s'); // compile the return message
messageSendInt(i); // send back the same sensor ID
messageSendInt(fred); // send back the current value
messageEnd(); // Terminate the message
break; // Break from the switch
case 'r': // send all 8 sensor values as a serial msg
messageSendChar('a'); // put an 'a' onto front of message so Max knows these are analog sensor values
for (char i = 0; i < 8; i++) {
fred = cursor[i];
fred += (fred > 0 ? 1 : 0);
messageSendInt(fred);
}
messageEnd(); // Terminate the message
break; // Break from the switch
case 'w': // incoming stribe control message
secondChar = messageGetChar(); // nibble second character to see what to do
switch (secondChar) {
case 's': // here comes a maxOne()-style display message as 3 integers
matrix = messageGetInt(); // get 1st integer
subrow = messageGetInt(); // get 2nd integer
rowPatt = messageGetInt(); // get 3rd integer
maxOne(matrix, subrow, rowPatt); // light up the pattern
break; // Break from the switch
case 'b': // here comes a maxAll()-style display message as 2 integers
subrow = messageGetInt(); // get 1st integer
rowPatt = messageGetInt(); // get 2nd integer
maxAll(subrow, rowPatt); // light up the pattern
break; // Break from the switch
case 'e': // here comes a display message as 2 integers - 1st is matrix, then pattern - all rows of matrix get the pattern
matrix = messageGetInt(); // get 1st integer
rowPatt = messageGetInt(); // get 2nd integer
for (e = 1; e <= 8; e++) {
maxOne(matrix, e, rowPatt);
}
break; // Break from the switch
case 'f': // here comes a maxOne()-style display message as 3 integers - display it on both grids (full row)
matrix = messageGetInt(); // get 1st integer
subrow = messageGetInt(); // get 2nd integer
rowPatt = messageGetInt(); // get 3rd integer
maxOne(matrix, subrow, rowPatt); // light up the left side
maxOne(matrix + 8, subrow, rowPatt); // right side
break;
case 'h': // turn on/off a specified column 1-16 (l-r)
column = messageGetInt(); // get 1st integer 1-16
on = messageGetInt(); // get 2nd integer 1 or 0
rowPatt = on ? colPatt[column] : 0; // saves the if/else statement below
/* if (on) {
rowPatt = colPatt[column];
} else {
rowPatt = 0;
} */
if (column > 8) {
for (e = 1; e <= 8; e++) {
maxRight(e, rowPatt);
}
} else {
for (e = 1; e <= 8; e++) {
maxLeft(e, rowPatt);
}
}
break;
case 'm':
// [matrixctrl] message from Max/MSP:
// int maxmspColumn; // 0-15
// int maxmspRow; // 0-63
// int maxmspVal; // 0/1
// convert values from matrixctrl format to maxOne
// matrix = stacked big rows 1-16
// subrow = subrow number 1-8
// rowPatt = byte value in subrow
// we're addressing a 128 element array: grid[], which starts at grid[0]
// nibble three ints from Max/MSP
maxmspColumn = messageGetInt(); // [0-15]
maxmspRow = messageGetInt(); // [0-63]
maxmspVal = messageGetInt(); // [0-1] [0 means subtract it from pattern, 1 means add it]
if (maxmspVal == 0) { //change 0/1 to -1/1 to sign additions to subrow byte
maxmspVal = -1;
}
if (maxmspColumn <= 7) { //biggest pivot point is left and right column position
matrix = (maxmspRow / 8) + 1;
subrow = (maxmspRow % 8) + 1;
rowPatt = grid[maxmspRow] + ((1 << maxmspColumn) * maxmspVal);
grid[maxmspRow] = rowPatt;
}
else if (maxmspColumn >= 8) {
matrix = (maxmspRow / 8) + 9;
subrow = (maxmspRow % 8) + 1;
rowPatt = grid[maxmspRow + 64] + ((1 << (maxmspColumn - 8)) * maxmspVal);
grid[maxmspRow + 64] = rowPatt;
}
maxOne(matrix,subrow,rowPatt); // light up the pattern
break;
case 't': // trails
trails = messageGetInt();
trails |= 0x80000000; // Sets the sign bit (makes it non-negative)
++trails; // Adds one, just in case it was zero.
// Those two operations should result in less clock cycles than the if statement below
/* if (trails < 1) {
trails = 1; // trails must be non-zero
} */
break; // Break from the switch
case 'p': // toggle cursor mode
preset = messageGetInt(); // nibble incoming value, currently sets cursor to 1 or 0 / on/off
break; // Break from the switch
case 'y': // draw 8 cursors
preset = 0; // make sure default cursor is off
pin = 0;
row = 0;
for (i = 0; i < 8; i++) {
// keep in mind for counting, there are only 8 sensors, but 16 columns of leds, each sensor gets 2 columns in this mode (rowPatt=192), so it's sort of like there are 8 columns of leds, too
if (cursor[i] > 5) { // then light something up (low values sometimes trigger accidentally)
row = ((cursor[i] + curve[i]) / 16); // or something
row = (row < 1 ? 1 : row); // tidy up (is this causing the dropped leds in col 0?)
matrix = (cursor[i] / 128) + 1;
subrow = row - ((matrix - 1) << 3); // was "* 8" instead of "<< 3" but shifting in powers of 2 is much faster
subrow = flip[subrow];
matrix = flip[matrix];
if (i > 3) { // this means the sensor is in the righthand grid so add 8 to matrix
matrix += 8;
}
// light up pattern
maxOne(matrix, subrow, cursPatt[i]);
}
}
break; // Break from the switch
case 'v': // same as y but draw only specified cursor
i = messageGetChar(); // nibble second character to see what to do
preset = 0; // make sure default cursor is off
if (cursor[i] > 5) { // then light something up (low values sometimes trigger accidentally)
row = ((cursor[i] + curve[i]) / 16); // or something
row = (row < 1 ? 1 : row); // tidy up (is this causing the dropped leds in col 0?)
matrix = (cursor[i] / 128) + 1;
subrow = row - ((matrix - 1) << 3); // was "* 8" instead of "<< 3" but shifting in powers of 2 is much faster
subrow = flip[subrow];
matrix = flip[matrix];
if (i > 3) { // this means the sensor is in the righthand grid so add 8 to matrix
matrix += 8;
}
// light up pattern
maxOne(matrix, subrow, cursPatt[i]);
}
case 'z': // clear screen, then redraw 8 columns at updated locations - incoming string is "z int int int int int int int int"
preset = 0; // make sure default cursor is off
row = 0;
// read 8 new cursor values into cursor[] array
for (i = 0; i < 8; i++) {
cursor[i] = messageGetInt(); // nibble remaining 8 ints of string
}
// for (e = 1; e <= 8; e++) {
// maxAll(e, 0); // clear screen
// }
// now draw the 8 cursors
for (i = 0; i < 8; i++) {
// keep in mind for counting, there are only 8 sensors, but 16 columns of leds, each sensor gets 2 columns in this mode (rowPatt=192), so it's sort of like there are 8 columns of leds, too
if (cursor[i] > 5) { // then light something up (low values sometimes trigger accidentally)
row = ((cursor[i] + curve[i]) / 16); // or something
row = (row < 1 ? 1 : row); // tidy up (is this causing the dropped leds in col 0?)
matrix = (cursor[i] / 128) + 1;
subrow = row - ((matrix - 1) << 3); // was "* 8" instead of "<< 3" but shifting in powers of 2 is much faster
subrow = flip[subrow];
matrix = flip[matrix];
if (i > 3) { // this means the sensor is in the righthand grid so add 8 to matrix
matrix += 8;
}
// light up this pattern
maxOne(matrix, subrow, cursPatt[i]);
}
}
break; // Break from the switch
case 'x': // re-trigger the startup sequence
startup = messageGetInt(); // iterations
break; // Break from the switch
case 'c': // clear the display
/* for (i = 0; i < 128; i++) {
grid[i] = 0;
} */
// doing the same thing (faster) with memset
memset(grid, 0, sizeof(grid));
for (e = 1; e <= 8; e++) {
maxAll(e, 0);
}
break; // Break from the switch
case 'o': // light the whole display
for (e = 1; e <= 8; e++) {
maxAll(e, 255);
}
break; // Break from the switch
} // end secondChar switch
break;
} // end first Char switch
} // messagebuild
//// preset cursor mode (default to this until turned off from Max with [w p 0]
//
if (preset) { // preset=1 = firmware cursor ON = display values as 2-column blobs aka "stupid but fast" mode
clearAll=clearAll+1;
if (clearAll>trails) {
clearAll=0;
if (touch[0]+touch[1]+touch[2]+touch[3]+touch[4]+touch[5]+touch[6]+touch[7]>0) { // something changed so refresh
// clear all
for (e=1; e<=8; e++) { // turn all LEDs off (adjust the variable 'trails' to change delay)
maxAll(e,0);
}
}
}
pin = 0;
row = 0;
for (i = 0; i < 8; i++) {
/* debug
Serial.print(i);
Serial.print(":");
Serial.print(cursor[i]);
Serial.print("\t"); // print a tab character
*/
// keep in mind for counting, there are only 8 sensors, but 16 columns of leds, each sensor gets 2 columns in this mode (rowPatt=192), so it's sort of like there are 8 columns of leds, too
if (cursor[i] > 5) { // then light something up (low values sometimes trigger accidentally)
// Serial.print("sensor: "); Serial.print(i); Serial.print(" ");
// Serial.print("cursor: "); Serial.print(cursor[i]+1); Serial.print(" ");
row = ((cursor[i] + curve[i]) / 16); // or something
row = (row < 1 ? 1 : row); // tidy up (is this causing the dropped leds in col 0?)
/* if (row < 1) {
row = 1; // tidy up (is this causing the dropped leds in col 0?)
} */
// Serial.print("row: "); Serial.print(row); Serial.print(" ");
matrix = (cursor[i] / 128) + 1;
subrow = row - ((matrix - 1) << 3); // was "* 8" instead of "<< 3" but shifting in powers of 2 is much faster
subrow = flip[subrow];
matrix = flip[matrix];
// Serial.print("matrix: "); Serial.print(matrix); Serial.print(" ");
// Serial.print("subrow: "); Serial.print(" "); Serial.print(subrow);
// Serial.println(" ");
if (i > 3) { // this means the sensor is in the righthand grid so add 8 to matrix
matrix += 8;
}
//if (touch[0]+touch[1]+touch[2]+touch[3]+touch[4]+touch[5]+touch[6]+touch[7]>0) {
// light up pattern
maxOne(matrix, subrow, cursPatt[i]);
//}
/* debug
Serial.print("row:");
Serial.print(row);
Serial.print("\t");
Serial.print("matrix:");
Serial.print(matrix);
Serial.print("\t");
Serial.print("subrow:");
Serial.print(subrow);
Serial.print("\t"); // print a tab character
Serial.print("pattern:");
Serial.println(cursPatt[i]);
*/
}
}
} // endif
} // main loop