wordclock/wordclock/wordclock.ino

655 lines
19 KiB
C++

#include <stdio.h>
#include <string.h>
#include <DS1302.h> // I use the library that Matt Sparks created
#include <avr/interrupt.h>
#include <avr/io.h>
#include <math.h>
#include <pt.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);
// 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<<CS22);
TCCR2B &= ~((1<<CS21) | (1<<CS20));
// Use normal mode
TCCR2B &= ~((1<<WGM21) | (1<<WGM20));
// Use internal clock - external clock not used in Arduino
ASSR |= (0<<AS2);
//Timer2 Overflow Interrupt Enable
TIMSK2 |= 1<<TOIE2;
RESET_TIMER2;
sei();
Serial.println("Finished setting up Timer2 Interrupt");
msTick=millis(); // Initialise the msTick counter
#if (SKIPSELFTEST != 1)
selftest(); // validate the hardware for the user
#endif
selftestmode=0;
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;
}
displaytime(); // display the current time
}
// 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_brightnes){
// 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) {
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);
}