Created
December 13, 2012 21:55
-
-
Save runion/4280328 to your computer and use it in GitHub Desktop.
This is the Arduino code for my solar display controller. It communicates with a computer using Serial Over USB and receives text in response, and displays that text on two serial LCDs attached to the Arduino. The text is generated in a python program running on a webserver, which periodically updates it to reflect current solar conditions. Ther…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "Tlc5940.h" // external library for TLC lighting controller | |
// http://code.google.com/p/tlc5940arduino/ | |
#include <SoftwareSerial.h> // standard/built-in external library for creating virtual serial ports. Used to output to the LCD displays | |
#define txPin1 2 // name pin 2 on the Arduino "txPin1" for later use | |
#define txPin2 4 // pin 4 connects to the second LCD display | |
#define photocellPin 5 // et cetera | |
#define buttonOne 6 // et cetera | |
#define buttonTwo 8 | |
#define buttonThree 5 | |
#define buttonFour 12 | |
SoftwareSerial LCD1 = SoftwareSerial(0, txPin1); // initiate pin 2 aka txPin1 for serial communications, and call it LCD1 | |
SoftwareSerial LCD2 = SoftwareSerial(0, txPin2); | |
int photocellVal = 0; // this variable holds the light value | |
int secondsToWait = 10; // seconds before timeout if no serial response | |
// LED pin #s on the TLC | |
int led1_blue = 1; | |
int led1_green = 2; | |
int led1_red = 3; | |
int led2_blue = 4; | |
int led2_green = 5; | |
int led2_red = 6; | |
int led3_blue = 11; | |
int led3_green = 12; | |
int led3_red = 13; | |
int led4_blue = 8; | |
int led4_green = 9; | |
int led4_red = 10; | |
// this is just how the chip was wired | |
// the variables above aren't used in the program but they are a useful reference to how the TLC controller is wired | |
int lastButtonOneVal = LOW; // programming buttons is no picnic. | |
int buttonOneState = LOW; // Is the button down? Was it down before or has that changed? | |
int switchingOne = 0; // And that's not the half of it. | |
/* Buttons can rapidly switch ("bounce") when they're being pressed before they settle down, so we require the button state to be the same for at least 50ms before we trigger anything */ | |
int lastButtonTwoVal = LOW; | |
int buttonTwoState = LOW; | |
int switchingTwo = 0; | |
int lastButtonThreeVal = LOW; | |
int buttonThreeState = LOW; | |
int switchingThree = 0; | |
int lastButtonFourVal = LOW; | |
int buttonFourState = LOW; | |
int switchingFour = 0; | |
int curLedPin = 3; // 3 corresponds to the red color for the first LED; see above | |
int ledBrightness = 100; // when we set LEDs on or off, we also set the brightness | |
const int LCDdelay=10; // conservative, 2 actually works | |
String timeoutError = "Response Timeout"; | |
// like I said, programming buttons is no picnic. We "debounce" buttons by requiring them to be in the same state for 50ms | |
long debounceDelay = 50; | |
long lastDebounceTimeBOne = 0; | |
long lastDebounceTimeBTwo = 0; | |
long lastDebounceTimeBThree = 0; | |
long lastDebounceTimeBFour = 0; | |
void setup() | |
{ | |
/* Call Tlc.init() to setup the tlc. | |
You can optionally pass an initial PWM value (0 - 4095) for all channels.*/ | |
Tlc.init(); | |
LCD1.begin(9600); // 9600 baud is the data rate between the Arduino and the serial LCD | |
LCD2.begin(9600); | |
clearLCD(1); // this function is defined below | |
clearLCD(2); | |
// disableSplashScreen(1); | |
// disableSplashScreen(2); | |
pinMode(photocellPin, INPUT); | |
pinMode(buttonOne, INPUT); | |
pinMode(buttonTwo, INPUT); | |
pinMode(buttonThree, INPUT); | |
pinMode(buttonFour, INPUT); | |
clearLCD(1); | |
lcdPosition(0,0,1); // position the cursor at the first character in the first row for LCD 1. This function is defined below | |
// this initiates the serial connection between the Arduino and the PC it's connected to. | |
// the PC needs to have a program running that communicates over serial as well, | |
// although for testing purposes the Arduino IDE has this by default | |
Serial.begin(9600); | |
LCD1.print("Select Program: Press a button"); | |
} | |
// this function runs forever. When the Arduino gets to the end it just starts up at the beginning | |
void loop() | |
{ | |
if(Serial.available() > 0) { // If there is data sent over the serial connection between the Arduino and the PC | |
clearLCD(2); // clear LCD 2 | |
lcdPosition(0,0,2); // position the cursor at the first character of the first row of LCD 2 | |
while(Serial.available()) { | |
// read each character over serial and write it out to the LCD, one character at a time | |
LCD2.write(Serial.read()); | |
} | |
} | |
int buttonOneVal = digitalRead(buttonOne); // read buttonOne's state. Its digital so it'll be either HIGH (pressed) or LOW (not pressed) | |
int buttonTwoVal = digitalRead(buttonTwo); | |
int buttonThreeVal = digitalRead(buttonThree); | |
int buttonFourVal = digitalRead(buttonFour); | |
photocellVal = analogRead(photocellPin); // nominal values between 0-255 | |
// LED brightness is set between 1 and 1024. I chose photocellVal * 2 after some experimentation, we won't need full brightness. | |
ledBrightness = 2 * photocellVal; | |
/* | |
* BUTTON PRESSING CODE! Beware, dragons lie ahead! | |
* if we didn't have to debounce (make sure the value stays constant for 50ms) we could just immediately trigger our buttonPress(1) function | |
*/ | |
if (buttonOneVal != lastButtonOneVal) { | |
// Say you press button one. buttonOneVal is HIGH, lastButtonOneVal is LOW. != literally means "not equal" | |
// the purpose of the code below is to get the lastDebounceTimeBOne to set to the time we first detected the button state changed | |
if (switchingOne == 0) { // two equals signs "==" compares two values, this returns true or false | |
lastDebounceTimeBOne = millis(); | |
// millis is a built-in function that gives a number of milliseconds since the Arduino started | |
// we use it like a stopwatch | |
switchingOne = 1; // one equals sign sets a value | |
} | |
if (switchingOne == 1 && (millis() - lastDebounceTimeBOne) > debounceDelay) { | |
// the if statement here and 3 variable assignments below ensure that | |
// the buttonPress() function is only run once per button press | |
// we don't want to keep running that function every 50ms | |
buttonOneState = buttonOneVal; | |
lastButtonOneVal = buttonOneVal; | |
switchingOne = 0; | |
// and finally, do the thing: | |
buttonPress(1, buttonOneState, ledBrightness); | |
} | |
} | |
// see, what did I tell you? DRAGONS! | |
// same as above but for button 2 | |
if (buttonTwoVal != lastButtonTwoVal) { | |
if (switchingTwo == 0) { | |
lastDebounceTimeBTwo = millis(); | |
switchingTwo = 1; | |
} | |
if (switchingTwo == 1 && (millis() - lastDebounceTimeBTwo) > debounceDelay) { | |
buttonTwoState = buttonTwoVal; | |
lastButtonTwoVal = buttonTwoVal; | |
switchingTwo = 0; | |
buttonPress(2, buttonTwoState, ledBrightness); | |
} | |
} | |
// same as above but for button 3 | |
if (buttonThreeVal != lastButtonThreeVal) { | |
if (switchingThree == 0) { | |
lastDebounceTimeBThree = millis(); | |
switchingThree = 1; | |
} | |
if (switchingThree == 1 && (millis() - lastDebounceTimeBThree) > debounceDelay) { | |
buttonThreeState = buttonThreeVal; | |
lastButtonThreeVal = buttonThreeVal; | |
switchingThree = 0; | |
buttonPress(3, buttonThreeState, ledBrightness); | |
} | |
} | |
// same as above but for button 4 | |
if (buttonFourVal != lastButtonFourVal) { | |
if (switchingFour == 0) { | |
lastDebounceTimeBFour = millis(); | |
switchingFour = 1; | |
} | |
if (switchingFour == 1 && (millis() - lastDebounceTimeBFour) > debounceDelay) { | |
buttonFourState = buttonFourVal; | |
lastButtonFourVal = buttonFourVal; | |
switchingFour = 0; | |
buttonPress(4, buttonFourState, ledBrightness); | |
} | |
} | |
} // end of the main loop(). Everything below this line is a function that gets called either in setup() or loop() | |
// wbp: goto with row & column | |
void lcdPosition(int row, int col, int which) { | |
if(which == 1) { | |
LCD1.write(0xFE); //command flag | |
LCD1.write((col + row*64 + 128)); //position | |
} else { | |
LCD2.write(0xFE); //command flag | |
LCD2.write((col + row*64 + 128)); //position | |
} | |
delay(LCDdelay); // sit and do nothing for LCDdelay ms, to make sure we don't send any commands until the display is ready | |
} | |
void clearLCD(int which){ | |
if(which == 1) { | |
LCD1.write(0xFE); //command flag | |
LCD1.write(0x01); //clear command. | |
} else { | |
LCD2.write(0xFE); //command flag | |
LCD2.write(0x01); //clear command. | |
} | |
delay(LCDdelay); | |
} | |
void disableSplashScreen(int which){ | |
if(which == 1) { | |
LCD1.write(0xFE); //command flag | |
LCD1.write(0x7C); //clear command. | |
} else { | |
LCD2.write(0xFE); //command flag | |
LCD2.write(0x7C); //clear command. | |
} | |
delay(LCDdelay); | |
} | |
void backlightOn() { //turns on the backlight | |
LCD1.write(0x7C); //command flag for backlight stuff | |
LCD1.write(157); //light level. | |
LCD2.write(0x7C); //command flag for backlight stuff | |
LCD2.write(157); //light level. | |
delay(LCDdelay); | |
} | |
void backlightOff(){ //turns off the backlight | |
LCD1.write(0x7C); //command flag for backlight stuff | |
LCD1.write(128); //light level for off. | |
LCD2.write(0x7C); //command flag for backlight stuff | |
LCD2.write(128); //light level for off. | |
delay(LCDdelay); | |
} | |
void serCommand(int which){ //a general function to call the command flag for issuing all other commands | |
if(which == 1) { | |
LCD1.write(0xFE); | |
} else { | |
LCD2.write(0xFE); | |
} | |
} | |
// this function isn't used in the current program. | |
// it's defined as "char" instead of "void" like other functions, because | |
// instead of returning nothing (void) it returns a character | |
char getColor(int current) { | |
int redPins[] = {2, 5, 12, 9}; // these are the pins on the TLC chip that are connected to the red leg on the RGB LEDs | |
int greenPins[] = {1, 4, 11, 8}; | |
int bluePins[] = {0, 3, 10, 7}; | |
for(int x = 0; x < sizeof(redPins); x++) { // loop through all (4) red pins and see if the current variable is one of them | |
if (current == redPins[x]) { | |
return 'r'; | |
} | |
} | |
for(int x = 0; x < sizeof(greenPins); x++) { | |
if (current == greenPins[x]) { | |
return 'g'; | |
} | |
} | |
for(int x = 0; x < sizeof(bluePins); x++) { | |
if (current == bluePins[x]) { | |
return 'b'; | |
} | |
} | |
return 'X'; | |
} | |
// This is where the magic happens | |
void buttonPress(int button, int buttonState, int ledBrightness) { | |
if(button == 1) { | |
// "Current" | |
if (buttonState == HIGH) { | |
Tlc.clear(); | |
Tlc.set(2, ledBrightness); // LED pin 2 is the red color on the first RGB LED. Light it up while we wait for the response. | |
Tlc.update(); | |
clearLCD(1); | |
lcdPosition(0,0,1); | |
LCD1.print("Updating..."); | |
Serial.println("1"); // send the PC attached to the arduino the single character 1 | |
// The PC is programmed to reply with some text that is generated in another program. | |
// The text file is available here: http://www.skyfactory.com/about/solar/current.txt | |
waitForResponse(0); // when we get the response, we light up LED 0 (blue) | |
} | |
} | |
if(button == 2) { | |
// "Daily" | |
// The text file is available here: http://www.skyfactory.com/about/solar/daily.txt | |
if (buttonState == HIGH) { | |
Tlc.clear(); | |
Tlc.set(5, ledBrightness); | |
Tlc.update(); | |
clearLCD(1); | |
lcdPosition(0,0,1); | |
LCD1.print("Updating..."); | |
Serial.println("2"); | |
waitForResponse(3); | |
} | |
} | |
if(button == 3) { | |
// "Weekly/Monthly" | |
// The text file is available here: http://www.skyfactory.com/about/solar/wk_mo.txt | |
if (buttonState == HIGH) { | |
Tlc.clear(); | |
Tlc.set(12, ledBrightness); | |
Tlc.update(); | |
clearLCD(2); | |
lcdPosition(0,0,2); | |
LCD2.print("Updating..."); | |
Serial.println("3"); | |
waitForResponse(10); | |
} | |
} | |
if(button == 4) { | |
// "Lifetime" | |
// The text file is available here: http://www.skyfactory.com/about/solar/lifetime.txt | |
if (buttonState == HIGH) { | |
Tlc.clear(); | |
Tlc.set(9, ledBrightness); | |
Tlc.update(); | |
clearLCD(2); | |
lcdPosition(0,0,2); | |
LCD2.print("Updating..."); | |
Serial.println("4"); | |
waitForResponse(7); | |
} | |
} | |
} | |
void waitForResponse(int ledToLightUp) { | |
int done = 0; | |
int waited = 0; | |
while (waited < 10 * secondsToWait && done == 0) { | |
// this while loop runs until secondsToWait seconds has passed or a response is recieved over the serial connection | |
if(Serial.available() > 0) { // if we've got a response ready to be read | |
clearLCD(1); // clear both LCDs | |
clearLCD(2); | |
lcdPosition(0,0,1); // position the cursor at row 0 column 0 of LCD 1 | |
int numChars = 0; // counter for how many characters we've read. | |
while(Serial.available()) { // read all the characters (the text files are exactly 64 characters, we pad them with spaces if need be) | |
if(numChars == 32) { // After 32 characters we switch to outputting to LCD2 | |
lcdPosition(0,0,2); | |
} | |
if(numChars < 32) { | |
LCD1.print((char) Serial.read()); | |
} else { | |
LCD2.print((char) Serial.read()); | |
} | |
numChars++; | |
} | |
done = 1; // since we got our response, we can break out of the while loop | |
photocellVal = analogRead(photocellPin); // again, nominal values are 0-255 | |
ledBrightness = 2 * photocellVal; // again, ledBrightness can be 0-1023 but practically we don't need to make them fully bright | |
Tlc.clear(); // turn off all LEDs | |
Tlc.set(ledToLightUp, ledBrightness); | |
Tlc.update(); // you call Tlc.set() once for each LED you want to change the values of, and when you're all done up call update() | |
} else { | |
waited++; | |
delay(100); // sit here for 100ms then run the while loop again | |
} | |
} | |
if(done == 0) { // fail | |
clearLCD(1); | |
clearLCD(2); | |
lcdPosition(0,0,1); | |
LCD1.print(timeoutError); | |
lcdPosition(0,0,2); | |
LCD2.print(timeoutError); | |
} | |
} | |
// this function is not used in this program | |
// it's defined as int instead of void because it returns an integer (aka a whole number) instead of nothing | |
int nextChannel() { | |
int returnVal = 0; | |
int ledPins[] = {0, 1, 2, 3, 4, 5, 10, 11, 12, 7, 8, 9}; | |
int numPins = 12; | |
for(int thisPin = 0; thisPin < numPins; thisPin++) { | |
if(curLedPin == ledPins[thisPin]) { | |
if(thisPin == 11) { | |
returnVal = ledPins[0]; | |
} else { | |
returnVal = ledPins[thisPin + 1]; | |
} | |
} | |
} | |
return returnVal; | |
} | |
// this function is not used in this program | |
int prevChannel() { | |
int returnVal = 0; | |
int ledPins[] = {10, 9, 8, 13, 12, 11, 6, 5, 4, 3, 2, 1}; | |
int numPins = 12; | |
for(int thisPin = 0; thisPin < numPins; thisPin++) { | |
if(curLedPin == ledPins[thisPin]) { | |
if(thisPin == 11) { | |
returnVal = ledPins[0]; | |
} else { | |
returnVal = ledPins[thisPin + 1]; | |
} | |
} | |
} | |
return returnVal; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment