#include #include #include // I use the library that Matt Sparks created #include #include #include #include #include "settings.h" // uncomment the following to speed up the timer for testing //#define TESTMODE // Insert function prototypes -- Paul void ledsoff(void); void setup(void); void SWversion(void); /************************************************************************** * * * W O R D C L O C K - A clock that tells the time using words. * * * * Hardware: Arduino Dumelove with a set of individual LEDs under a word * * stencil. * * * * Copyright (C) 2009 Doug Jackson (doug@doughq.com) * * * *************************************************************************** * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * * MA 02111-1307 USA * * * *************************************************************************** * * Revision History * * Date By What * 20010205 DRJ Initial Creation of Arduino Version * - based on Wordclock.c - from PIC version * 20100428 DRJ Added support for DS1302 RTC Chip - Code verifies * device is present - and if so, uses it for timekeeping * 20100511 DRJ Added ability to detect old vs new Arduino boards * New board uses diferent time button sense. * 20100511 DRJ Minute LED support on Analog5-1 pins. * 20100927 DRJ Added brightness support by enabling interrupts and * using an ISR to control the display */ #define INIT_TIMER_COUNT 6 #define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT // there are only 20 brightness levels and 1024 lightsensor levels -> we need to // scale the brightness levels to something closer to avoid having to work with // floating point numbers int hour=12, minute=0, second=0; // incremented the second counter int count; int selftestmode; // 1 = in self test - flash display int DS1302Present=0; // flag to indicate that the 1302 is there.. 1 = present char Display1=0, Display2=0, Display3=0, Led1=0, Led2=0, Led3=0, Led4=0; int OldHardware = 0; // 1 = we are running on old hardware int BTNActive = 1; // the sense of the button inputs (Changes based on hardware type) int timercount=10; // used for interrupt counting to determine the brightnes of the display // hardware constants int LEDClockPin=5; // Arduino Pin#11 - 4094 Pin 3 clock int LEDDataPin=3; // Arduino Pin#5 - 4094 pin 2 Data int LEDStrobePin=4; // Arduino Pin#6 - 4094 pin 1 Strobe // buttons #define FWD_BUTTON_PIN 6 #define REV_BUTTON_PIN 7 #define BUTTON_PORT PIND #define FWD_BUTTON_MASK (1 << 6) #define REV_BUTTON_MASK (1 << 7) // delay in ms before repeating key #define BUTTON_DELAY_LONG 1000 // delay in ms before repeating key a second time #define BUTTON_DELAY_SHORT 200 // length of buffer where we receive data over uart #define INPUTBUFFER_LENGTH 16 // 1302 RTC Constants int DS1302IOPin=10; int DS1302CEPin=8; int DS1302CLKPin=9; // Minute LED Pins int LED1PIN=19; // Arduino analog 5 int LED2PIN=18; // Arduino analog 4 int LED3PIN=17; // Arduino analog 3 int LED4PIN=16; // Arduino analog 2 int current_brightness=0; char ambient_light_to_brightness[1024]; String inputBuffer = ""; // input buffer for serial port volatile boolean inputBufferComplete = false; // define the language to be used for this project: #include LANGUAGE // The language pack /* Create buffers */ char buf[50]; // time output string for debugging // create an object that talks to the RTC DS1302 rtc(DS1302CEPin, DS1302IOPin, DS1302CLKPin); // ProtoThread structures (one for each thread) static struct pt blink_thread_pt, lightsensor_thread_pt, buttons_thread_pt, uart_thread_pt, wordclock_thread_pt; void print_DS1302time() { #if (PRINT_DEBUG_LEVEL <= 0) /* Get the current time and date from the chip */ Time t = rtc.time(); /* Format the time and date and insert into the temporary buffer */ snprintf(buf, sizeof(buf), "D: DS1302 time: %02d:%02d:%02d", t.hr, t.min, t.sec); /* Print the formatted string to serial so we can see the time */ Serial.println(buf); #endif } void setup() { int n; // a, b, c and e are needed to compute lookup table for ambient light sensor float a, b, c, e; // initialise the hardware // initialize the appropriate pins as outputs: pinMode(LEDClockPin, OUTPUT); pinMode(LEDDataPin, OUTPUT); pinMode(LEDStrobePin, OUTPUT); pinMode(FWD_BUTTON_PIN, INPUT); pinMode(REV_BUTTON_PIN, INPUT); // setup 1302 pinMode(DS1302IOPin, OUTPUT); pinMode(DS1302CEPin, OUTPUT); pinMode(DS1302CLKPin, OUTPUT); // Minute LEDS pinMode(LED1PIN, OUTPUT); pinMode(LED2PIN, OUTPUT); pinMode(LED3PIN, OUTPUT); pinMode(LED4PIN, OUTPUT); //analogReference(DEFAULT); analogReference(INTERNAL); //analogReference(EXTERNAL); // known 'bug' in AVR ADC: after switching analog reference, first sample // could be corrupt and should be ignored --> discard this sample now analogRead(LIGHTSENSOR_INPUTPIN); current_brightness=MAXBRIGHTNESS; //Serial.begin(9600); // setup the serial port to 9600 baud Serial.begin(115200); // setup the serial port to 115200 baud inputBuffer.reserve(INPUTBUFFER_LENGTH); SWversion(); // Display the version number of the software // test whether the DS1302 is there #if (PRINT_DEBUG_LEVEL <= 0) Serial.print("D: Verifying DS1302 "); #endif // start by verifying that the chip has a valid signature if (rtc.read_register(0x20) == 0xa5) { // Signature is there - set the present flag and mmove on DS1302Present=1; #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("present - Valid Signature"); #endif rtc.write_protect(false); rtc.halt(false); } else { // Signature isnt there - may be a new chip - // do a write to see if it will hold the signature rtc.write_register(0x20,0xa5); if (rtc.read_register(0x20) == 0xa5) { // We can store data - assume that it is a new chip that needs initialisation // Start by turning off write protection and clearing the clock halt flag. rtc.write_protect(false); rtc.halt(false); // Make a new time object to set the date and time Time t(2010, 04, 28, 10, 19, 00, 01); // Set the time and date on the chip rtc.time(t); // set the DS1302 present flag DS1302Present=1; #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("present - new chip initialised."); #endif } else { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("absent"); #endif } } // compute ambient light level to brightness level mapping a = LIGHTSENSOR_ALPHA1 * LIGHTSENSOR_TOP / log(LIGHTSENSOR_ALPHA2 / LIGHTSENSOR_ALPHA1); b = LIGHTSENSOR_ALPHA1 / a; c = MINBRIGHTNESS - a; e = 2.718281828459045297; for (n = 0; n < sizeof(ambient_light_to_brightness); n++) { if (n <= LIGHTSENSOR_BOTTOM) ambient_light_to_brightness[n] = MINBRIGHTNESS; else if (n >= LIGHTSENSOR_TOP) ambient_light_to_brightness[n] = MAXBRIGHTNESS; else { ambient_light_to_brightness[n] = round(((float) a) * pow(e, ((float) b) * ((float) n)) + (float) c); if (ambient_light_to_brightness[n] < MINBRIGHTNESS) ambient_light_to_brightness[n] = MINBRIGHTNESS; if (ambient_light_to_brightness[n] > MAXBRIGHTNESS) ambient_light_to_brightness[n] = MAXBRIGHTNESS; } } // determine whether we are running on old or new hardware // old hardware tied the push buttons to ground using 4k7 resistors // and relied on the buttons to pull them high // new hardware uses internal pullups, and uses the buttons // to pull the inputs down. digitalWrite(FWD_BUTTON_PIN,HIGH); // Turn on weak pullups digitalWrite(REV_BUTTON_PIN,HIGH); // Turn on weak pullups OldHardware=0; if ( digitalRead(FWD_BUTTON_PIN)==0 && digitalRead(REV_BUTTON_PIN)==0) { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: Detected Old Hardware"); #endif OldHardware=1; // we have old hardware BTNActive = 1; // True = active for old hardware digitalWrite(FWD_BUTTON_PIN,LOW); // Turn off weak pullups digitalWrite(REV_BUTTON_PIN,LOW); // Turn off weak pullups } else { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: Detected New Hardware"); #endif OldHardware=0; // we have new hardware BTNActive = 0; // True = active for old hardware } // Initialise Timer2: Timer Prescaler /64, TCCR2A = 0; TCCR2B |= (1<= INPUTBUFFER_LENGTH) { Serial.print("E: input buffer overflow; resetting buffer"); inputBuffer = ""; } } } } // Interrupt handler - Arduino runs at 16 Mhz, so we have 1000 Overflows per second... // 1/ ((16000000 / 64) / 256) = 1 / 1000 ISR(TIMER2_OVF_vect) { RESET_TIMER2; if (timercount-- <= current_brightness) { // Now we write the actual values to the hardware shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display3); shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display2); shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, Display1); digitalWrite(LEDStrobePin,HIGH); //delay(2); digitalWrite(LEDStrobePin,LOW); digitalWrite(LED1PIN,Led1); digitalWrite(LED2PIN,Led2); digitalWrite(LED3PIN,Led3); digitalWrite(LED4PIN,Led4); } else { shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, 0); shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, 0); shiftOut(LEDDataPin, LEDClockPin, MSBFIRST, 0); digitalWrite(LEDStrobePin,HIGH); //delay(2); digitalWrite(LEDStrobePin,LOW); digitalWrite(LED1PIN,0); digitalWrite(LED2PIN,0); digitalWrite(LED3PIN,0); digitalWrite(LED4PIN,0); } if (timercount == 0) timercount = N_PWM_STEPS - 1; } void ledsoff(void) { Display1=0; Display2=0; Display3=0; Led1=0; Led2=0; Led3=0; Led4=0; } void incrementtime(void) { // increment the time counters keeping care to rollover as required second=0; if (++minute >= 60) { minute=0; if (++hour == 25) { hour=1; } } /* // debug outputs Serial.println(); if (DS1302Present==1) print_DS1302time(); else Serial.print("Arduino Time: " ); Serial.print(hour); Serial.print(":"); Serial.print(minute); Serial.print(":"); Serial.print(second); Serial.print(" ");*/ } void SWversion(void) { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D:"); Serial.println("D: Wordclock -Arduino v3.0a - reduced brightness version"); Serial.print("D: "); Serial.print(LANGUAGE); Serial.println(" header file used"); Serial.println("D: (c)2009, 2010, 2011 Doug Jackson"); #endif } void process_buttons(unsigned char button_status) { char buttonPressed = 0, fwdButtonPressed = 0, revButtonPressed = 0, buttonStatus = 0; static unsigned long buttonPressedMillis = 0; static unsigned long button_delay = BUTTON_DELAY_LONG; unsigned long millisNow = 0, new_button_delay; if (BTNActive) { if ((button_status & FWD_BUTTON_MASK) > 0) fwdButtonPressed = 1; if ((button_status & REV_BUTTON_MASK) > 0) revButtonPressed = 1; } else { if ((button_status & FWD_BUTTON_MASK) == 0) fwdButtonPressed = 1; if ((button_status & REV_BUTTON_MASK) == 0) revButtonPressed = 1; } if (fwdButtonPressed && revButtonPressed) { selftestmode != selftestmode; } // test to see if both buttons are being held down // if so - start a self test till both buttons are held // down again. //if ( digitalRead(FWD_BUTTON_PIN)==BTNActive && digitalRead(REV_BUTTON_PIN)==BTNActive) if (selftestmode) { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: Selftest Mode TRUE"); #endif for(int i=0; i<100; i++) { Display1=255; Display2=255; Display3=255; delay(101-i); ledsoff();delay(101-i); if (digitalRead(FWD_BUTTON_PIN)==1) selftestmode=!selftestmode; } displaytime(); } if (fwdButtonPressed) { // the forward button is down // and it has been more than one second since we // last looked #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: Forward Button Down"); #endif incrementtime(); second++; // Increment the second counter to ensure that the name // flash doesnt happen when setting time if (DS1302Present==1) { // Make a new time object to set the date and time Time t(2010, 04, 28, hour, minute, second, 01); // Set the time and date on the chip rtc.time(t); } displaytime(); } if (revButtonPressed) { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: Backwards Button Down"); #endif minute--; minute--; second=0; // decrement the minute counter if (minute<0) { minute=58; if (--hour <0) hour=23; } incrementtime(); second++; // Increment the second counter to ensure that the name // flash doesnt happen when setting time if (DS1302Present==1) { // Make a new time object to set the date and time Time t(2010, 04, 28, hour, minute, second, 01); // Set the time and date on the chip rtc.time(t); } displaytime(); } // key repeat delay code: millisNow = millis(); if (millisNow - buttonPressedMillis < BUTTON_DELAY_LONG) new_button_delay = BUTTON_DELAY_SHORT; else new_button_delay = BUTTON_DELAY_LONG; delay(button_delay); button_delay = new_button_delay; buttonPressedMillis = millis(); } static int uart_thread(struct pt *pt) { char cmd, arg1, arg2, arg3; int length; PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, inputBufferComplete == true); length = inputBuffer.length(); if (length == 0) { Serial.println("E: received 0 characters"); } else { cmd = inputBuffer.charAt(0); switch(cmd) { case 'H': // process hello request Serial.println("ELLO"); break; case 'S': // process set request if (length < 4) Serial.println("E: too few arguments"); else { #if (PRINT_DEBUG_LEVEL <= 0) Serial.println("D: set request"); #endif arg1 = inputBuffer.charAt(1); arg2 = inputBuffer.charAt(2); arg3 = inputBuffer.charAt(3); switch(arg1) { case 'Y': // set year break; case 'M': // set month break; case 'D': // set day of month break; case 'd': // set day of week break; case 'h': // set hour (24 hour format) hour = 10 * (arg2 - 0x30) + (arg3 - 0x30); break; case 'm': // set minutes minute = 10 * (arg2 - 0x30) + (arg3 - 0x30); break; case 's': // set seconds second = 10 * (arg2 - 0x30) + (arg3 - 0x30); break; default: break; } // Set the time and date on the chip Time t(2010, 4, 28, hour, minute, second, 1); rtc.time(t); displaytime(); } break; case 'G': // process get request if (length < 2) Serial.println("E: too few arguments"); else { arg1 = inputBuffer.charAt(1); switch(arg1) { case 'Y': // get year break; case 'M': // get month break; case 'D': // get day of month break; case 'd': // get day of week break; case 'h': // get hour (24 hour format) Serial.print("g: h: "); if (hour < 10) Serial.print(0, DEC); Serial.println(hour); break; case 'm': // get minutes Serial.print("g: m: "); if (minute < 10) Serial.print(0, DEC); Serial.println(minute); break; case 's': // get seconds Serial.print("g: s: "); if (second < 10) Serial.print(0, DEC); Serial.println(second); break; default: break; } } break; default: Serial.print("E: unknown command: 0x"); Serial.println(cmd, HEX); break; } } inputBuffer = ""; inputBufferComplete = false; } PT_END(pt); } static int buttons_thread(struct pt *pt) { PT_BEGIN(pt); while(1) { if (BTNActive) { PT_WAIT_UNTIL(pt, ((BUTTON_PORT & FWD_BUTTON_MASK) > 0) || ((BUTTON_PORT & REV_BUTTON_MASK) > 0)); process_buttons(BUTTON_PORT); } else { PT_WAIT_UNTIL(pt, ((BUTTON_PORT & FWD_BUTTON_MASK) == 0) || ((BUTTON_PORT & REV_BUTTON_MASK) == 0)); process_buttons(BUTTON_PORT); } } PT_END(pt); } static int lightsensor_thread(struct pt *pt) { static unsigned long msTick =0; // the number of Millisecond Ticks since we last static unsigned long int lightlevel_avg; unsigned long int lightlevel_sample; static char millisWillOverflow = 0; static char n_lightlevel_samples = 0; PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, millisWillOverflow ? (millis() - msTick > 99) : ((millis() < 4294967295 - 98) && (millis() - msTick > 99))); msTick=millis(); lightlevel_sample = analogRead(LIGHTSENSOR_INPUTPIN); // update average if (n_lightlevel_samples < (1 << LIGHTSENSOR_WEIGHT)) { // add (1 << (LIGHTSENSOR_WEIGHT - 1)) to average before division so that // the average is round()-ed instead of floor()-ed lightlevel_avg = (n_lightlevel_samples * lightlevel_avg + lightlevel_sample + (1 << (LIGHTSENSOR_WEIGHT - 1))) / (n_lightlevel_samples + 1); n_lightlevel_samples++; } else { // do not update n_lightlevel_samples to prevent overflow // add (1 << (LIGHTSENSOR_WEIGHT - 1)) to average before division so that // the average is round()-ed instead of floor()-ed lightlevel_avg = ( ((1 << LIGHTSENSOR_WEIGHT) - 1) * lightlevel_avg + lightlevel_sample + (1 << (LIGHTSENSOR_WEIGHT - 1))) >> LIGHTSENSOR_WEIGHT; } // compute new brightness level /* // linear method if (lightlevel_avg <= LIGHTSENSOR_BOTTOM) current_brightness = MINBRIGHTNESS; else if (lightlevel_avg >= LIGHTSENSOR_TOP) current_brightness = MAXBRIGHTNESS; else { current_brightness = (brightness_per_unit_light * (lightlevel_avg - LIGHTSENSOR_BOTTOM)) / (1 << LIGHTSENSOR_SCALE) + MINBRIGHTNESS; } */ current_brightness = ambient_light_to_brightness[lightlevel_avg]; /*Serial.print("D: lightsensor: "); Serial.print(lightlevel_sample); Serial.print(", avg: "); Serial.print(lightlevel_avg); Serial.print(", brightness: "); Serial.println(current_brightness);*/ } PT_END(pt); } static int blink_thread(struct pt *pt) { static unsigned long msTick =0; // the number of Millisecond Ticks since we last static char millisWillOverflow = 0; PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, millisWillOverflow ? (millis() - msTick > 999) : ((millis() < 4294967295 - 998) && (millis() - msTick > 999))); msTick=millis(); #if (PRINT_DEBUG_LEVEL <= 1) Serial.println("D: blink thread is alive"); #endif // Flash the onboard Pin13 Led so we know something is hapening! digitalWrite(13,HIGH); delay(50); digitalWrite(13,LOW); delay(50); digitalWrite(13,HIGH); delay(50); digitalWrite(13,LOW); } PT_END(pt); } static int wordclock_thread(struct pt *pt) { static unsigned int counter = 0; static unsigned long msTick =0; // the number of Millisecond Ticks since we last static char millisWillOverflow = 0; #ifdef TESTMODE second=59; #endif //Serial.println("Loop Started"); PT_BEGIN(pt); msTick=millis(); // Initialise the msTick counter // heart of the timer - keep looking at the millisecond timer on the Arduino // and increment the seconds counter every 1000 ms while(1) { PT_WAIT_UNTIL(pt, millisWillOverflow ? (millis() - msTick > 999) : ((millis() < 4294967295 - 998) && (millis() - msTick > 999))); msTick=millis(); if (msTick > 4294967295 - 998) { // millis() will overflow --> adjust msTick msTick = 999 - (4294967295 - msTick); millisWillOverflow = 1; } else millisWillOverflow = 0; second++; //test to see if we need to increment the time counters if (second==60) { incrementtime(); displaytime(); #if (PRINT_DEBUG_LEVEL <= 1) Serial.print("D: time: "); if (hour < 10) Serial.print(0, DEC); Serial.print(hour, DEC); Serial.print(":"); if (minute < 10) Serial.print(0, DEC); Serial.print(minute, DEC); Serial.print(":"); if (second < 10) Serial.print(0, DEC); Serial.println(second, DEC); #endif } if (DS1302Present==1) { // Get the current time and date from the chip Time t = rtc.time(); second=t.sec; minute=t.min; hour=t.hr; } #if (USELIGHTSENSOR == 0) // set the brightnes level based on the current hour - night=7pm - 6.59am if ((hour < DAYLIGHTHOUR) | (hour >= NIGHTLIGHTHOUR)) current_brightness=MINBRIGHTNESS; else current_brightness=MAXBRIGHTNESS; #endif PT_END(pt); } } void loop() { // call each thread continuously wordclock_thread(&wordclock_thread_pt); buttons_thread(&buttons_thread_pt); #if (USELIGHTSENSOR == 1) lightsensor_thread(&lightsensor_thread_pt); #endif blink_thread(&blink_thread_pt); uart_thread(&uart_thread_pt); }