Skip to content

Instantly share code, notes, and snippets.

@runion
Created December 13, 2012 21:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runion/4280328 to your computer and use it in GitHub Desktop.
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…
#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