/* * 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.4t - for Stribe 0.4c & 0.4d Driver board Updated: Mar 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 int load = 2; int clock = 3; 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 int maxInUse = 16; // 16 x MAX7221 = 1024 LEDs = 16 columns x 64 rows 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); } // DB : variables for handling serial output - put these before setup so i can intialize them there int MILLISECONDS; int serialout[8]; int lastserialout[8]; 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) //DB init MILLISECONDS = 10; memset(serialout, 8, 0); memset(lastserialout, 8, 0); } //// Stribe code pretty much starts here // an array to save the cursor values (touchpoints) between iterations int cursor[8]; // 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? // DB : since there are no pointers, it should be 0. you can use memset regardless, in the Setup function // 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[] /// ** 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 int row; int subrow; int i; // iterator variable int pin; 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 int column; int matrix; int rowPatt; int hold = 10; // this is a vestige now replaced by trails int fred; // fred - never know when you'll need fred int test; int clearAll; char firstChar; char secondChar; int startup = 5; int trails = 10; // cycles before clear screen (when in default/preset cursor mode) - now controllable from Max -> ex: [w t 500] = (trails = 500) int preset = 0; // default cursor preset - now controllable from Max -> [w p 1] = on - default is currently OFF 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 int smoothed; int maxmspColumn; // 0-15 unsigned int maxmspRow; // 0-63 -- making it unsigned makes it a little faster in the / and % operations signed int maxmspVal; // 0/1 converted to -1/1 // It's automatically signed -- there's probably no need to be explicit 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 pins 0 to 7 and store them in the cursor[] array for (i = 0; i < 8; i++) { cursor[i] = analogRead(i); // DB : this is my code for writing out to the serialport without polling... if (millis() % MILLISECONDS == 0) { serialout[i] = cursor[i] / 4; if (serialout[i] > 0) { if (serialout[i] != lastserialout[i]) { Serial.print((byte)i); Serial.print((byte)serialout[i]); } } else if (lastserialout[i] > 0) { Serial.print((byte)i); Serial.print((byte)serialout[i]); } lastserialout[i] = serialout[i]; } // END DB serial write code } //// 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++) { maxLeft(e, rowPatt); } } else { for (e = 1; e <= 8; e++) { maxRight(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 = cursor mode ON -> read analog pins in a loop and display values as 2-col blobs aka "stupid but fast" mode clearAll=clearAll+1; if (clearAll>trails) { // clear all clearAll=0; 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; } // light up pattern maxOne(matrix, subrow, cursPatt[i]); // make this smart using the grid[] array and no more flickering cursors! /* 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