#define BUBBLETIME 45 // MS to turn the motor on for Don't lower this number! Instead use the motorArray with negative numbers // if for some reason you need smaller bubbles...(otherwise the register clock fucks up!) #define DELAYTIME 650 // MS to delay between bubbles. // Tubes / numbering are assumed to be sequential. #define FIRSTTUBE 0 #define LASTTUBE 59 /*************************************** Shifter library drives the array of 74HC595 shift registers that control the bubbles! ****************************************/ #include #define SER_Pin 4 // SER_IN #define RCLK_Pin 5 // L_CLOCK #define SRCLK_Pin 6 // CLOCK #define NUM_REGISTERS 8 // How many 74HC595 registers we have. // Pin on the arduino tied to the register enable pin // (must be driven to ground to enable!) #define ENABLE_PIN 7 //initalize shifter library: Shifter shifter(SER_Pin, RCLK_Pin, SRCLK_Pin, NUM_REGISTERS); /***************************************************************************** WS2801 Library controls the LED strings *****************************************************************************/ #include #include "SPI.h" #include "WS2801.h" // Choose which 2 pins you will use for output. // Can be any valid output pins, or can use on-board SPI? int dataPin = 2; int clockPin = 3; // Don't forget to connect the ground wire to Arduino ground, // and the +5V wire to a +5V supply // Set the first variable to the NUMBER of pixels. 20 pixels in a string WS2801 strip = WS2801(60, dataPin, clockPin); // Optional: leave off pin numbers to use hardware SPI // (pinout is then specific to each board and can't be changed) //WS2801 strip = WS2801(25); /********************************* Code used to manage "fudge factors" for each motor *********************************/ // Constant fudge factors for each motor. Negative numbers mean less bubbles, positive numbers mean more bubles. const signed char motorArray[60] = { 5, // Motor 0 5, // Motor 1 -12, // Motor 2 0, // Motor 3 -17, // Motor 4 -8, // Motor 5 -10, // Motor 6 -10, // Motor 7 0, // Motor 8 -12, // Motor 9 -10, // Motor 10 5, // Motor 11 -8, // Motor 12 -3, // Motor 13 -5, // Motor 14 0, // Motor 15 0, // Motor 16 15, // Motor 17 30, // Motor 18 5, // Motor 19 3, // Motor 20 12, // Motor 21 3, // Motor 22 0, // Motor 23 40, // Motor 24 7, // Motor 25 5, // Motor 26 25, // Motor 27 15, // Motor 28 10, // Motor 29 12, // Motor 30 20, // Motor 31 8, // Motor 32 8, // Motor 33 -5, // Motor 34 -5, // Motor 35 10, // Motor 36 5, // Motor 37 13, // Motor 38 -5, // Motor 39 18, // Motor 40 25, // Motor 41 15, // Motor 42 15, // Motor 43 3, // Motor 44 -3, // Motor 45 15, // Motor 46 25, // Motor 47 15, // Motor 48 0, // Motor 49 3, // Motor 50 5, // Motor 51 10, // Motor 52 5, // Motor 53 5, // Motor 54 8, // Motor 55 10, // Motor 56 50, // Motor 57 3, // Motor 58 8 // Motor 59 } ; // Temp variable used for between function call state keeping (which light to color next, etc...) int c = 0; void setup() { // Note: These next 8 lines are very critical. We MUST be sure // that the outputs of the shift registers are turned off before we // enable the motors (output enable). shifter.clear(); shifter.write(); delay(250); //VERY IMPORTANT! // Once we have "cleared" the shifter // It is safe to enable the output...but.... pinMode(ENABLE_PIN, OUTPUT); digitalWrite(ENABLE_PIN, LOW); // THESE TWO LINES are also needed! (I don't know why!) shifter.clear(); shifter.write(); strip.begin(); // Update LED contents, to start they are all 'off' strip.show(); // Inital "bootup animation //sCurve(); } // This method runs continiously.... void loop() { //sCurve(); //sCurve(); delay(1000); /* keepDry(); delay(250); keepDry(); delay(250); keepDry(); delay(250); */ keepDry(); delay(500); //chessboard(); keepDry(); delay(4000); //chessboard(); gvu20(); delay(2000); keepDry(); delay(500); keepDry(); delay(4000); gvuLogo(); sCurve(); //pwmTest(); //flash0(); //oneMotor(); //letterTest(); } unsigned char bubblesAskedFor[80]; void setBubble(int pos, unsigned char bubble) { // A 0 in the bubble argument means no bubble, a 1 means a bubble. bubblesAskedFor[pos] = bubble; } void clearLine() { for (int i = 0; i < 60; i++) { bubblesAskedFor[i] = 0; } } // You call this function after loading 0's or 1's for bubbles in each position using the // setBubble function. It does the real work of adding in (or subtracting) the fudge factor miliseconds.... void doLine() { // Find the maximum fudge factor: int myMax = motorArray[0]; for(int i = 0; i < 60; i++) { if (motorArray[i] > myMax) { myMax = motorArray[i]; } } int StartedAt = myMax; int currentTime = myMax; // Do this while we still have some time left to bubble! while( BUBBLETIME + currentTime > 0) { // Set any bubbles that are requested, and motorArray entry equals the // current start time to be on: int changed = 0; for (int i = 0; i < 60; i++) { if ( bubblesAskedFor[i] == 1 && motorArray[i] == currentTime ) { shifter.setPin(i, 1); changed = 1; } } // end for each tube. if (changed == 1) { shifter.write(); // Assume this takes around 1ms....(close enough...) } else { delay(1); } currentTime = currentTime -1; } // End while still time to go! //Oh by the way....try and keep a minimum 20 ms delay between the first ontime and the last offtime...why...who the fuck // knows, but it keeps water from flowing the fuck everywhere! Seriously.....some funky timing issue on the register clock buss // as far as I can tell....aparently the serial shift registers can't shift faster than that? shifter.clear(); shifter.write(); } void gvu20() { // For testing int lineDelay = DELAYTIME; char ourBytes1[8] = { 0x30, 0x3, 0x0, 0x3, 0x30, 0x21, 0x1, 0xc0 }; oneLine( ourBytes1 ); delay(lineDelay); char ourBytes2[8] = { 0x48, 0x4, 0x80, 0x1, 0x20, 0x12, 0x0, 0x20 }; oneLine( ourBytes2 ); delay(lineDelay); char ourBytes3[8] = { 0x0, 0x4, 0x0, 0x1, 0x20, 0x12, 0x0, 0x10}; oneLine( ourBytes3 ); delay(lineDelay); char ourBytes4[8] = { 0x48, 0x2, 0x0, 0x1, 0x20, 0x12, 0x3, 0x90 }; oneLine( ourBytes4 ); delay(lineDelay); char ourBytes5[8] = { 0x48, 0x1, 0x0, 0x1, 0x20, 0xc, 0x1, 0x20 }; oneLine( ourBytes5 ); delay(lineDelay); char ourBytes6[8] = { 0x30, 0x7, 0x80, 0x1, 0xe0, 0xc, 0x1, 0xe0 }; oneLine( ourBytes6 ); delay(lineDelay); } // end gvu20 void eyes() { // For testing int lineDelay = DELAYTIME; char ourBytes1[8] = { 0x0, 0x0, 0x6, 0x0, 0x3, 0xff, 0x0, 0x0 }; oneLine( ourBytes1 ); delay(lineDelay); char ourBytes2[8] = { 0x0, 0x0, 0x1f, 0x80, 0x1, 0xff, 0x80, 0x0 }; oneLine( ourBytes2 ); delay(lineDelay); char ourBytes3[8] = {0x0, 0x3f, 0xff, 0x80, 0x1, 0xff, 0xc0, 0x0 }; oneLine( ourBytes3 ); delay(lineDelay); char ourBytes4[8] = { 0x0, 0x7f, 0xff, 0x80, 0x10, 0xd, 0xc0, 0x0 }; oneLine( ourBytes4 ); delay(lineDelay); char ourBytes5[8] = { 0x0, 0xff, 0xe8, 0x0, 0x18, 0x0, 0xe0, 0x0 }; oneLine( ourBytes5 ); delay(lineDelay); char ourBytes6[8] = { 0x1, 0xc0, 0x0, 0x0, 0x38, 0x51, 0xe0, 0x0 }; oneLine( ourBytes6 ); delay(lineDelay); char ourBytes7[8] = { 0x1, 0x80, 0x0, 0x0, 0x3a, 0x5b, 0xf0, 0x0 }; oneLine( ourBytes7 ); delay(lineDelay); char ourBytes8[8] = { 0x0, 0x0, 0x0, 0x0, 0x7e, 0xb, 0xf8, 0x0 }; oneLine( ourBytes8 ); delay(lineDelay); char ourBytes9[8] = { 0x0, 0x0, 0x0, 0x0, 0x75, 0xe7, 0xf8, 0x0 }; oneLine( ourBytes9 ); delay(lineDelay); char ourBytes10[8] = { 0x0, 0x1c, 0x7c, 0x0, 0x77, 0xff, 0xfc, 0x0 }; oneLine( ourBytes10 ); delay(lineDelay); char ourBytes11[8] = { 0x0, 0x1d, 0xff, 0x80, 0x7f, 0xff, 0xfc, 0x0 }; oneLine( ourBytes11 ); delay(lineDelay); char ourBytes12[8] = { 0x0, 0x1f, 0xff, 0x80, 0x7c, 0xff, 0xfc, 0x0 }; oneLine( ourBytes12 ); delay(lineDelay); char ourBytes13[8] = { 0x0, 0x3f, 0xfc, 0x80, 0x0, 0xff, 0xde, 0x0 }; oneLine( ourBytes13 ); delay(lineDelay); char ourBytes14[8] = { 0x0, 0x3f, 0xbc, 0x0, 0x60, 0xff, 0x9e, 0x0 }; oneLine( ourBytes14 ); delay(lineDelay); char ourBytes15[8] = { 0x0, 0xf, 0xf0, 0x0, 0x40, 0x1c, 0x1e, 0x0 }; oneLine( ourBytes15 ); delay(lineDelay); char ourBytes16[8] = { 0x0, 0x4, 0x30, 0x0, 0x0, 0x0, 0x1c, 0x0 }; oneLine( ourBytes16 ); delay(lineDelay); char ourBytes17[8] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0 }; oneLine( ourBytes17 ); delay(lineDelay); char ourBytes18[8] = { 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x3c, 0x0 }; oneLine( ourBytes18 ); delay(lineDelay); char ourBytes19[8] = { 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x3c, 0x0 }; oneLine( ourBytes19 ); delay(lineDelay); char ourBytes20[8] = { 0x0, 0x0, 0x0, 0x0, 0x30, 0x0, 0x3c, 0x0 }; oneLine( ourBytes20 ); delay(lineDelay); char ourBytes21[8] = { 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x3c, 0x0 }; oneLine( ourBytes21 ); delay(lineDelay); } void gvuLogo() { // For testing int lineDelay = DELAYTIME; char ourBytes1[8] = { 0x0, 0xc0, 0x39, 0xc0, 0x0, 0x3f, 0xe0, 0x0 }; oneLine( ourBytes1 ); delay(lineDelay); char ourBytes2[8] = { 0x1, 0xc0, 0x38, 0xe0, 0x0, 0x7f, 0xfc, 0x0 }; oneLine( ourBytes2 ); delay(lineDelay); char ourBytes3[8] = {0x1, 0xc0, 0x38, 0x60, 0x0, 0x0, 0x1e, 0x0 }; oneLine( ourBytes3 ); delay(lineDelay); char ourBytes4[8] = { 0x1, 0xc0, 0x38, 0x60, 0x30, 0x0, 0x7, 0x0 }; oneLine( ourBytes4 ); delay(lineDelay); char ourBytes5[8] = { 0x1, 0xc0, 0x38, 0x38, 0x70, 0x0, 0x7, 0x80 }; oneLine( ourBytes5 ); delay(lineDelay); char ourBytes6[8] = { 0x1, 0xc0, 0x38, 0x38, 0x70, 0x2, 0x7, 0x80 }; oneLine( ourBytes6 ); delay(lineDelay); char ourBytes7[8] = { 0x1, 0xc0, 0x38, 0x1c, 0xe0, 0xe0, 0x7, 0x80 }; oneLine( ourBytes7 ); delay(lineDelay); char ourBytes8[8] = { 0x1, 0xc0, 0x38, 0xf, 0xe0, 0xe0, 0x7, 0x80 }; oneLine( ourBytes8 ); delay(lineDelay); char ourBytes9[8] = { 0x1, 0xc0, 0x38, 0xf, 0xc0, 0xe0, 0xf, 0x0 }; oneLine( ourBytes9 ); delay(lineDelay); char ourBytes10[8] = { 0x1, 0xf0, 0xf0, 0x7, 0x80, 0xf8, 0x7e, 0x0 }; oneLine( ourBytes10 ); delay(lineDelay); char ourBytes11[8] = { 0x0, 0x7f, 0xe0, 0x7, 0x0, 0x7f, 0xf0, 0x0 }; oneLine( ourBytes11 ); delay(lineDelay); char ourBytes12[8] = { 0x0, 0xe, 0x0, 0x2, 0x0, 0x7, 0x80, 0x0 }; oneLine( ourBytes12 ); delay(lineDelay); } // end gvuLogo // Draws one line of a bitmap. we expect theBits to be an 8 byte array! void oneLine( char theBits[]) { for( int i = 0; i < 60; i++ ) { //Get the right byte! int index = i / 8; char ourByte = theBits[index]; // Get the right bit by making a mask int bitNum = i % 8; ourByte = ourByte & (1 << (7-bitNum) ); // Grab that bit if (ourByte != 0) { setBubble(i, 1); } } // end for each tube. doLine(); clearLine(); } // end oneLine // Default pulse train with R/G/B scrolling LED's. Basic demo and it keeps the motors pumping air to keep // water out of the lines (and works to exercise all the parts for a burn in test). void keepDry() { delay(DELAYTIME); for( int i = FIRSTTUBE; i <= LASTTUBE; i++) { setBubble(i,1); if ( (i + c) % 3 == 0) { strip.setPixelColor(i, 10, 254,10 ); } else if ((i+c) % 3 == 1) { strip.setPixelColor(i, 254,10,10 ); } else { strip.setPixelColor(i, 10,10,254 ); } } // end for all items. c = c+1; // don't wrap to negative... if (c > 32000) { c = 0; } strip.show(); doLine(); clearLine(); } // end keepDry void sCurve() { for( int i = FIRSTTUBE; i <= LASTTUBE; i++) { setBubble(i,1); //shifter.setPin(i, HIGH); if ( (i + c) % 3 == 0) { strip.setPixelColor(i, 10, 254,10 ); } else if ((i+c) % 3 == 1) { strip.setPixelColor(i, 254,10,10 ); } else { strip.setPixelColor(i, 10,10,254 ); } strip.show(); doLine(); clearLine(); c = c+1; } // end for all items forwards. for( int i = LASTTUBE-1; i > FIRSTTUBE; i--) { setBubble(i,1); //shifter.setPin(i, HIGH); if ( (i + c) % 3 == 0) { strip.setPixelColor(i, 10, 254,10 ); } else if ((i+c) % 3 == 1) { strip.setPixelColor(i, 254,10,10 ); } else { strip.setPixelColor(i, 10,10,254 ); } strip.show(); doLine(); clearLine(); c = c+1; } // end for all items backwards. // don't wrap to negative... if (c > 32000) { c = 0; } } // end S-curve // Every other tube spits out a bubble every other time. // void chessboard() { delay(DELAYTIME/2); for( int i = FIRSTTUBE; i <= LASTTUBE; i++) { if ( (i+c) % 2 == 0) { setBubble(i,HIGH); strip.setPixelColor(i,255,255,255); } else { shifter.setPin(i, LOW); strip.setPixelColor(i,0,0,0); } } // end for all items. c = c+1; // don't wrap to negative... if (c > 32000) { c = 0; } strip.show(); doLine(); clearLine(); delay(BUBBLETIME); } // end chessboard /************************** older code! ***********/ // For testing, with single motor: void oneMotor() { #define THEMOTOR 15 delay(DELAYTIME); strip.setPixelColor(THEMOTOR, 10, 254,10 ); strip.show(); shifter.setPin(THEMOTOR,HIGH); shifter.write(); delay(BUBBLETIME); shifter.clear(); shifter.write(); } // end oneMotor // Testing running the motors at a 50% duty cycle. //They were not noticibly quieter, although the pulses were "softer" // but had to run twice as long to get the same size bubble. May be useful for // dithering without having one bubble end before another? void pwmTest() { delay(DELAYTIME); // Shift the RGB lights... for( int i = FIRSTTUBE; i <= LASTTUBE; i++) { if ( (i + c) % 3 == 0) { strip.setPixelColor(i, 10, 254,10 ); } else if ((i+c) % 3 == 1) { strip.setPixelColor(i, 254,10,10 ); } else { strip.setPixelColor(i, 10,10,254 ); } } // end for all items. strip.setPixelColor(0,25,25,25); c = c+1; // don't wrap c to negative... if (c > 32000) { c = 0; } strip.show(); for(int i = 0; i < BUBBLETIME*2; i += 2) { for( int j = FIRSTTUBE; j <= LASTTUBE; j++) { shifter.setPin(j,HIGH); } // End for each pin. shifter.write(); delay(1); shifter.clear(); shifter.write(); delay(1); } // end for BUBBLETIME } // end PWMtest // A basic example showing how to flash one LED and turn on one pump (number 0) void flash0() { // Turned off for one second: strip.setPixelColor(0, Color(0,0,0) ); strip.show(); shifter.clear(); shifter.write(); delay(1000); // All on for 1/10th of a second, rotating color if (c == 0) { strip.setPixelColor(0, Color(25,255,25) ); } else if (c == 1) { strip.setPixelColor(0,Color(255,0,0) ); } else { strip.setPixelColor(0, Color(25,25,255) ); c = -1; } c = c + 1; if (c > 32000) { // Don't wrap our temp variable! c = 0; } strip.show(); shifter.setPin(0,HIGH); shifter.write(); delay(1000); //colorWipe(Color(0, 0, 255), 50); //rainbow(20); //rainbowCycle(20); } // end flash15 ///// Some leftover functions from the LED strip library demo code: void rainbow(uint8_t wait) { int i, j; for (j=0; j < 256; j++) { // 3 cycles of all 256 colors in the wheel for (i=0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel( (i + j) % 255)); } strip.show(); // write all the pixels out delay(wait); } } // Slightly different, this one makes the rainbow wheel equally distributed // along the chain void rainbowCycle(uint8_t wait) { int i, j; for (j=0; j < 256 * 5; j++) { // 5 cycles of all 25 colors in the wheel for (i=0; i < strip.numPixels(); i++) { // tricky math! we use each pixel as a fraction of the full 96-color wheel // (thats the i / strip.numPixels() part) // Then add in j which makes the colors go around per pixel // the % 96 is to make the wheel cycle around strip.setPixelColor(i, Wheel( ((i * 256 / strip.numPixels()) + j) % 256) ); } strip.show(); // write all the pixels out delay(wait); } } // fill the dots one after the other with said color // good for testing purposes void colorWipe(uint32_t c, uint8_t wait) { int i; for (i=0; i < strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } /* Helper functions */ // Create a 24 bit color value from R,G,B uint32_t Color(byte r, byte g, byte b) { uint32_t c; c = r; c <<= 8; c |= g; c <<= 8; c |= b; return c; } //Input a value 0 to 255 to get a color value. //The colours are a transition r - g -b - back to r uint32_t Wheel(byte WheelPos) { if (WheelPos < 85) { return Color(WheelPos * 3, 255 - WheelPos * 3, 0); } else if (WheelPos < 170) { WheelPos -= 85; return Color(255 - WheelPos * 3, 0, WheelPos * 3); } else { WheelPos -= 170; return Color(0, WheelPos * 3, 255 - WheelPos * 3); } }