#include #include #include // I use the library that Matt Sparks created #include #include #include #include // 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); // specify the language - defines the header to be included // available choices - English, Danish, French, German //#define LANGUAGE "English.h" #define LANGUAGE "Dutch.h" /************************************************************************** * * * 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 */ // set SKIPSELFTEST to 1 to skip selftest #define SKIPSELFTEST 1 // set USELIGHTSENSOR to 1 to use ambient light sensor connected to ADC0 (pin // 23) #define USELIGHTSENSOR 1 // analog input put to which ambiend light sensor is connected #define LIGHTSENSOR_INPUTPIN 0 // bottom of light sensor (ambient light values at or lower than this level will // be mapped to MINBRIGHTNESS) #define LIGHTSENSOR_BOTTOM 0 // top of light sensor (ambient light values at or higher than this level will // be mapped to MAXBRIGHTNESS) #define LIGHTSENSOR_TOP 900 // weight for exponential decaying averaging (actual weigth is 2 ^ LIGHTSENSOR_WEIGHT) #define LIGHTSENSOR_WEIGHT 4 // base of exponential mapping (must be > 1) #define LIGHTSENSOR_BASE 1.40 #define N_PWM_STEPS 11 // DAY Brightness setting 0 = off, N_PWM_STEPS - 1 = full #define MAXBRIGHTNESS 10 // start MAXBRIGHTNESS at DAYLIGHTHOUR (7 am) #define DAYLIGHTHOUR 7 // NIGHT Brightness setting 0 = off, N_PWM_STEPS - 1 = full #define MINBRIGHTNESS 2 // start MINBRIGHTNESS at NIGHTLIGHTHOUR (7 pm) #define NIGHTLIGHTHOUR 19 #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 #define LIGHTSENSOR_SCALE 10 unsigned int brightness_per_unit_light = ((1 << LIGHTSENSOR_SCALE) * (MAXBRIGHTNESS - MINBRIGHTNESS)) / (LIGHTSENSOR_TOP - LIGHTSENSOR_BOTTOM); int hour=12, minute=0, second=0; static unsigned long msTick =0; // the number of Millisecond Ticks since we last // 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 // 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_brightnes=0; char ambient_light_to_brightness[1024]; // 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 buttons_thread_pt, wordclock_pt; void print_DS1302time() { /* 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), "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); } void setup() { int n; // 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_brightnes=MAXBRIGHTNESS; //Serial.begin(9600); // setup the serial port to 9600 baud Serial.begin(115200); // setup the serial port to 115200 baud SWversion(); // Display the version number of the software // test whether the DS1302 is there Serial.print("Verifying DS1302 "); // 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; Serial.println("present - Valid Signature"); 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; Serial.println("present - new chip initialised."); } else Serial.println("absent"); } // compute ambient light level to brightness level mapping 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( pow((double) LIGHTSENSOR_BASE, ( (double) brightness_per_unit_light * ((double) n - (double) LIGHTSENSOR_BOTTOM) / (double) (1 << LIGHTSENSOR_SCALE))) / pow((double) LIGHTSENSOR_BASE, (double) (MAXBRIGHTNESS - (double) MINBRIGHTNESS)) * ((double) MAXBRIGHTNESS - (double) MINBRIGHTNESS) + (double) MINBRIGHTNESS ); /* Serial.print(n); Serial.print(": "); Serial.println(ambient_light_to_brightness[n], DEC); */ } } // 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) { Serial.println("Detected Old Hardware"); 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 { Serial.println("Detected New Hardware"); OldHardware=0; // we have new hardware BTNActive = 0; // True = active for old hardware } // Initialise Timer2: Timer Prescaler /64, TCCR2A = 0; TCCR2B |= (1<= 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) { Serial.println(); Serial.println("Wordclock -Arduino v3.0a - reduced brightness version"); Serial.print(LANGUAGE); Serial.println(" header file used"); Serial.println("(c)2009, 2010, 2011 Doug Jackson"); } 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; // 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 (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; } if (selftestmode) { Serial.println("Selftest Mode TRUE"); 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 Serial.println("Forward Button Down"); 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 (digitalRead(REV_BUTTON_PIN)==BTNActive ) if (revButtonPressed) { Serial.println("Backwards Button Down"); 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 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 wordclock(struct pt *pt) { static unsigned long int lightlevel_avg; unsigned long int lightlevel_sample; static char n_lightlevel_samples = 0; static unsigned int counter = 0; static char millisWillOverflow = 0; #ifdef TESTMODE second=59; #endif //Serial.println("Loop Started"); PT_BEGIN(pt); // 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++; // 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); //test to see if we need to increment the time counters if (second==60) { incrementtime(); displaytime(); } 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; } // set the brightnes level based on the current hour - night=7pm - 6.59am // #if (USELIGHTSENSOR == 1) 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_brightnes = MINBRIGHTNESS; else if (lightlevel_avg >= LIGHTSENSOR_TOP) current_brightnes = MAXBRIGHTNESS; else { current_brightnes = (brightness_per_unit_light * (lightlevel_avg - LIGHTSENSOR_BOTTOM)) / (1 << LIGHTSENSOR_SCALE) + MINBRIGHTNESS; } */ current_brightnes = ambient_light_to_brightness[lightlevel_avg]; Serial.print("lightsensor: "); Serial.print(lightlevel_sample); Serial.print(" / "); Serial.print(lightlevel_avg); Serial.print(", brightness: "); Serial.println(current_brightnes); #else if ((hour < DAYLIGHTHOUR) | (hour >= NIGHTLIGHTHOUR)) current_brightnes=MINBRIGHTNESS; else current_brightnes=MAXBRIGHTNESS; #endif PT_END(pt); } } void loop() { // call each function continuously wordclock(&wordclock_pt); buttons_thread(&buttons_thread_pt); }