Created
December 27, 2014 13:20
-
-
Save goebish/6f414420c0cc39145021 to your computer and use it in GitHub Desktop.
TSA1605A Clock
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
// Arduino code for Alarm Clock with TSA1605A 8x14 segments micro leds display | |
// Goebish 2013 (twitter: @goebish) | |
// | |
// This code is distributed in the hope that it will be useful for everyone but it is distributed | |
// without any warranty. It is provided "as is" without warranty of any kind, either expressed or | |
// implied, including, but not limited to, the implied warranties of merchantability and fitness for a | |
// particular purpose. The entire risk as to the quality and performance of the code is with you. | |
// Should the code prove defective, you assume the cost of all necessary servicing, repair or | |
// correction. In no event will the author of the code be liable to you for damages, including | |
// any general, special, incidental or consequential damages arising out of the use of the code, | |
// including but not limited to loss of life, money, the loss of data or data being rendered | |
// inaccurate, or any other losses sustained by you or third parties or a failure of the code to | |
// operate with any other device(s), even if the author has been advised of the possibility of such | |
// possibilities. | |
// | |
// pictures: | |
// http://imgur.com/10iBfZd | |
// http://imgur.com/CL1NXCh | |
// | |
// parts: | |
// - atmega168 or atmega328 Arduino or compatible dev board (2009, UNO, nano, mini ...) this has not been tested with the Leonardo and shouldn't work on the MEGA | |
// - DS1307 RTC + 32.768kHz chrystal + 3V coin battery cell (CR2032) or DS1307 breakout module | |
// - TSA1605A 8x14 segments micro LEDs display | |
// - 14x 68 ohms resistors (one for each segments anode, they don't appear in my pictures, but you should really use them ;)) | |
// - 3x 74HC595 shift registers | |
// - 3x momentary pushbuttons (MODE/SET, PLUS, MINUS) + 3x 0.1µF capacitors | |
// - 10k ohms resistor (HC595s OE pullup) | |
// - piezzo buzzer + 100 ohms resistor | |
// - led + 8k ohms resistor (alarm enabled indicator) | |
// | |
// schematics: | |
// http://imgur.com/2fvOBml | |
// | |
// read through the code for usage description :) | |
#include <Wire.h> // I2C for DS1307 RTC | |
#include <EEPROM.h> // eeprom used to store alarm settings | |
#include <TimerOne.h> // get it from http://code.google.com/p/arduino-timerone/ | |
#define NO_PORTC_PINCHANGES | |
#define NO_PORTD_PINCHANGES | |
#include <PinChangeInt.h> // get it from http://code.google.com/p/arduino-pinchangeint/ | |
#if (ARDUINO >= 100) | |
#define WireWrite Wire.write | |
#define WireRead Wire.read() | |
#else | |
#define WireWrite Wire.send | |
#define WireRead Wire.receive() | |
#endif | |
// One side of the pushbuttons is connected to ground. The other sides connect to the Arduino pins. | |
// Put a 0.1µF cap between pushbuttons legs to provide hard debouncing | |
const int setButton = 9; | |
const int minusButton=10; | |
const int plusButton =11; | |
const int buzzer=2; // buzzer w/ 100ohms resistor | |
const int alarmLed=13; // led + 8k ohms resistor, use an high enough resistor value so the led isn't brighter than the display | |
// HC595s wiring: | |
// HC595-1, 2 & 3 pin 8 to GND | |
// HC595-1, 2 & 3 pin 10 to +5V | |
// HC595-1, 2 & 3 pin 16 to +5V | |
// HC595-1 QA to QH outputs --> TSA1605A cathodes 1 to 8 (pins 1, 22, 3, 19, 8, 15, 11, 12) | |
// HC595-2 QA to QH outputs --> TSA1605A G to N segments (pins 6, 13, 20, 18, 17, 2, 7, 10) | |
// HC595-3 QA to QF outputs --> TSA1605A A to F segments (pins 14, 16, 5, 4, 9, 21) | |
// place one 68 ohms resistor between each display segment anodes and it's HC959 output. | |
// do not modify the following pins assignments ! (direct port access in ISR, no digitalWrite) | |
const int dataPin1 = 5; // to HC595-1 (pin 14) cathodes driver | |
const int dataPin2 = 4; // to HC595-2 (pin 14) segments GHIJKLMN anodes driver | |
const int dataPin3 = 6; // to HC595-3 (pin 14) segments ABCDEF anodes driver | |
const int latchPin = 7; // to HC595-1, 2 & 3 (pin 12) | |
const int clockPin = 8; // to HC595-1, 2 & 3 (pin 11) | |
const int oePin = 3; // to HC595-1, 2 & 3 (pin 13) + 10k pull up resistor to +5V | |
#define DISPLAY_ISR_PERIOD 500 // 1/DISPLAY_ISR_PERIOD/1000 = display frequency, lower value produce brighter display. Don't go to low or the main loop won't have enough (if any) CPU cycles for itself. | |
//#define DISPLAY_ISR_PERIOD 2000 // for use without resistors on display anodes, USE AT YOUR OWN RISK ! | |
// DS1307 Real Time Clock | |
// SDA (pin 5) to Arduino A4 | |
// SCL (pin 6) to Arduino A5 | |
#define DS1307_ADDRESS 0x68 | |
typedef struct { | |
byte second; | |
byte minute; | |
byte hour; | |
byte weekDay; | |
byte monthDay; | |
byte month; | |
int year; | |
} | |
s_dateTime; | |
volatile s_dateTime curDateTime; | |
typedef struct { | |
byte enabled; | |
byte hour; | |
byte minute; | |
} s_alarmSettings; | |
volatile s_alarmSettings alarm = {false,0,0}; | |
// states | |
typedef enum e_states{ | |
CLOCK, | |
DATE, // return to CLOCK if SET button is not pushed for 5 seconds | |
SET_ALARM, // is SET button is pushed while "SET ALARM" is displayed, go directly to "SET_DATE", or wait 2s to enter SET_ALARM_ENABLED | |
SET_ALARM_ENABLED, // if OFF is choosen, go to WRITE_ALARM | |
SET_ALARM_HOUR, | |
SET_ALARM_MINUTE, // go to WRITE_ALARM when done | |
SET_DATE, // if SET button is pushed while "SET DATE" is displayed, go directly to "SET TIME", or wait 2s to enter SET_YEAR | |
SET_YEAR, | |
SET_MONTH, | |
SET_DAY, // go to WRITE_DATETIME after setting day | |
SET_TIME, // if SET button is pushed while "SET TIME" is displayed, return to CLOCK mode | |
SET_HOUR, | |
SET_MINUTE, | |
WRITE_DATETIME, // returns to CLOCK when done | |
WRITE_ALARM // returns to CLOCK when done | |
}; | |
volatile int currentState = CLOCK; | |
volatile unsigned long mode_start=0; | |
volatile unsigned long last_button_push=0; | |
const char* clock_format_no_tick = "%02d %02d %02d"; | |
const char* clock_format_tick = "%02d-%02d-%02d"; | |
const char* clock_format_date = "%02d %s"; | |
const char* set_hour_tick = " %02d "; | |
const char* set_hour_no_tick = "%02d %02d "; | |
const char* set_minute_tick = "%02d "; | |
const char* set_minute_no_tick = set_hour_no_tick; | |
const char* set_day_tick = " %s"; | |
const char* set_month_tick = set_minute_tick; | |
const char* set_year_tick = " %04d "; | |
const char* month[] = {"JANV ","FEV ","MARS ","AVRIL","MAI ","JUIN ","JUIL ","AOUT ","SEPT ","OCT ","NOV ","DEC "}; // french months, change them as you like but keep 5 characters per month | |
const int daysInMonth[12]={31,28,31,30,31,30,31,31,30,31,30,31}; | |
char displayBuffer[9]= "aaaaaaaa"; | |
// credits go to http://huinink.info/8-x-14-segment/ for most of the ascii table font, I only modified a few numbers to take advantage of the 14 segments | |
const byte ascii[] PROGMEM={ | |
0x00, 0x00, // space | |
0x01, 0x4A, // ! | |
0x22, 0x00, // " | |
0x0E, 0x55, // # | |
0x39, 0x55, // euro. for dollar use 0x2D, 0x55 | |
0x24, 0x88, // % | |
0x1D, 0x2B, // & | |
0x00, 0x08, // ' | |
0x39, 0x00, // { | |
0x0F, 0x00, // ) | |
0x00, 0xFF, // * | |
0x00, 0x55, // + | |
0x00, 0x80, // , | |
0x00, 0x11, // | |
0x08, 0x00, // dot? | |
0x00, 0x88, // / | |
0x3F, 0x00, // 0 | |
0x06, 0x08, // 1 | |
0x1B, 0x11, // 2 | |
0x0F, 0x10, // 3 | |
0x26, 0x11, // 4 | |
0x2D, 0x11, // 5 | |
0x3D, 0x11, // 6 | |
0x01, 0x88, // 7 | |
0x3F, 0x11, // 8 | |
0x2F, 0x11, // 9 | |
0x01, 0x11, // : | |
0x01, 0x80, // ; | |
0x00, 0x28, // < | |
0x00, 0x82, // > | |
0x08, 0x11, // = | |
0x03, 0x50, // ? | |
0x1F, 0x41, // @ | |
0x37, 0x11, // A | |
0x0F, 0x54, // B | |
0x39, 0x00, // C | |
0x0F, 0x44, // D | |
0x39, 0x01, // E | |
0x31, 0x01, // F | |
0x3D, 0x10, // G | |
0x36, 0x11, // H | |
0x09, 0x44, // I | |
0x1E, 0x00, // J | |
0x30, 0x29, // K | |
0x38, 0x00, // L | |
0x36, 0x0A, // M | |
0x36, 0x22, // N | |
0x3F, 0x00, // O | |
0x33, 0x11, // P | |
0x3F, 0x20, // Q | |
0x33, 0x31, // R | |
0x2D, 0x11, // S | |
0x01, 0x44, // T | |
0x3E, 0x00, // U | |
0x30, 0x88, // V | |
0x36, 0xA0, // W | |
0x00, 0xAA, // X | |
0x00, 0x4A, // Y | |
0x09, 0x88, // Z | |
0x39, 0x00, // [ | |
0x00, 0x88, // / | |
0x0F, 0x00, // ] | |
0x00, 0xA0, // ^ | |
0x08, 0x00, // _ | |
0x00, 0x02, // ` | |
0x3F, 0xFF // a --> test character | |
}; | |
void buttonsManager() | |
{ | |
if(millis()-last_button_push>150) { | |
switch(PCintPort::arduinoPin) { | |
case setButton: | |
if(currentState == SET_ALARM) | |
currentState = SET_DATE; | |
else if(currentState == SET_DATE) | |
currentState = SET_TIME; | |
else if(currentState==SET_TIME) | |
currentState = CLOCK; | |
else if(currentState==SET_MONTH) | |
{ | |
if( curDateTime.monthDay > daysInMonth[curDateTime.month-1]) | |
curDateTime.monthDay = daysInMonth[curDateTime.month-1]; | |
currentState++; | |
} | |
else if(currentState == SET_DAY) | |
currentState = WRITE_DATETIME; | |
else if(currentState == SET_MINUTE) | |
{ | |
curDateTime.second=0; | |
currentState = WRITE_DATETIME; | |
} | |
else if((currentState==SET_ALARM_ENABLED && alarm.enabled==false) || currentState==SET_ALARM_MINUTE) | |
currentState = WRITE_ALARM; | |
else | |
currentState++; | |
mode_start=millis(); | |
break; | |
case minusButton: | |
switch(currentState) { | |
case SET_HOUR: | |
if(curDateTime.hour<=0) | |
curDateTime.hour = 23; | |
else | |
curDateTime.hour--; | |
break; | |
case SET_MINUTE: | |
if(curDateTime.minute<=0) | |
curDateTime.minute = 59; | |
else | |
curDateTime.minute--; | |
break; | |
case SET_ALARM_ENABLED: | |
alarm.enabled = !alarm.enabled; | |
digitalWrite(alarmLed, alarm.enabled); | |
break; | |
case SET_ALARM_HOUR: | |
if(alarm.hour<=0) | |
alarm.hour = 23; | |
else | |
alarm.hour--; | |
break; | |
case SET_ALARM_MINUTE: | |
if(alarm.minute<=0) | |
alarm.minute = 59; | |
else | |
alarm.minute--; | |
break; | |
case SET_MONTH: | |
if(curDateTime.month<=1) | |
curDateTime.month = 12; | |
else | |
curDateTime.month--; | |
break; | |
case SET_DAY: | |
if(curDateTime.monthDay <= 1) | |
if(isLeapYear(curDateTime.year) && curDateTime.month==2) | |
curDateTime.monthDay = 29; | |
else | |
curDateTime.monthDay = daysInMonth[curDateTime.month-1]; | |
else | |
curDateTime.monthDay--; | |
break; | |
case SET_YEAR: | |
if(curDateTime.year<=2000) | |
curDateTime.year = 2099; | |
else | |
curDateTime.year--; | |
break; | |
} | |
break; | |
case plusButton: | |
switch(currentState) { | |
case SET_HOUR: | |
if(curDateTime.hour>=23) | |
curDateTime.hour = 0; | |
else | |
curDateTime.hour++; | |
break; | |
case SET_MINUTE: | |
if(curDateTime.minute>=59) | |
curDateTime.minute = 0; | |
else | |
curDateTime.minute++; | |
break; | |
case SET_ALARM_ENABLED: | |
alarm.enabled = !alarm.enabled; | |
digitalWrite(alarmLed, alarm.enabled); | |
break; | |
case SET_ALARM_HOUR: | |
if(alarm.hour>=59) | |
alarm.hour = 0; | |
else | |
alarm.hour++; | |
break; | |
case SET_ALARM_MINUTE: | |
if(alarm.minute>=59) | |
alarm.minute = 0; | |
else | |
alarm.minute++; | |
break; | |
case SET_MONTH: | |
if(curDateTime.month>=12) | |
curDateTime.month = 1; | |
else | |
curDateTime.month++; | |
break; | |
case SET_DAY: | |
if(isLeapYear(curDateTime.year) && curDateTime.month==2 && curDateTime.monthDay>=29) | |
curDateTime.monthDay = 1; | |
else if(curDateTime.monthDay >= daysInMonth[curDateTime.month-1]) | |
curDateTime.monthDay = 1; | |
else | |
curDateTime.monthDay++; | |
break; | |
case SET_YEAR: | |
if(curDateTime.year>=2099) | |
curDateTime.year = 2000; | |
else | |
curDateTime.year++; | |
break; | |
} | |
break; | |
} | |
last_button_push = millis(); | |
} | |
} | |
void setup() | |
{ | |
digitalWrite(oePin, HIGH); // disable output | |
pinMode(oePin, OUTPUT); | |
pinMode(dataPin1, OUTPUT); | |
pinMode(dataPin2, OUTPUT); | |
pinMode(dataPin3, OUTPUT); | |
pinMode(latchPin, OUTPUT); | |
pinMode(clockPin, OUTPUT); | |
pinMode(buzzer, OUTPUT); | |
digitalWrite(buzzer, LOW); | |
pinMode(alarmLed, OUTPUT); | |
// init buttons interrupt | |
pinMode(setButton, INPUT); | |
digitalWrite(setButton, HIGH); | |
PCintPort::attachInterrupt(setButton, &buttonsManager, FALLING); | |
pinMode(minusButton, INPUT); | |
digitalWrite(minusButton, HIGH); | |
PCintPort::attachInterrupt(minusButton, &buttonsManager, FALLING); | |
pinMode(plusButton, INPUT); | |
digitalWrite(plusButton, HIGH); | |
PCintPort::attachInterrupt(plusButton, &buttonsManager, FALLING); | |
// init I2C bus for DS1307 RTC | |
Wire.begin(); | |
// init display interrupt | |
Timer1.initialize(DISPLAY_ISR_PERIOD); | |
Timer1.attachInterrupt(displayISR); | |
getAlarmSettings(); | |
delay(1000); | |
} | |
bool isLeapYear(const int year) { | |
return year%4==0; // ok for 2000-2099 because 2000 is leap | |
} | |
void printDate() { | |
sprintf(displayBuffer,clock_format_date,curDateTime.monthDay, month[curDateTime.month-1]); | |
} | |
void printTime() { | |
sprintf(displayBuffer,curDateTime.second%2?clock_format_tick:clock_format_no_tick,curDateTime.hour,curDateTime.minute,curDateTime.second); | |
} | |
void buzz() { | |
unsigned long alarmStart=millis(); | |
while(digitalRead(setButton==HIGH) && millis()-alarmStart < 60000) { // one minute should be enough to wake up ;) | |
updateCurDateTime(); | |
printTime(); | |
for(int j=0; j<4; j++) { | |
digitalWrite(buzzer,HIGH); | |
delay(90); | |
digitalWrite(buzzer,LOW); | |
delay(45); | |
if(digitalRead(setButton)==LOW) { | |
currentState = CLOCK; | |
return; | |
} | |
} | |
sprintf(displayBuffer," ALARM "); | |
delay(400); | |
} | |
currentState = CLOCK; | |
} | |
void loop(){ | |
bool tick=millis()%600<300 && millis()-last_button_push > 400; | |
if(millis()-last_button_push>200 && (digitalRead(minusButton)==LOW || digitalRead(plusButton)==LOW)) { // buttons auto-repeat | |
buttonsManager(); | |
} | |
updateCurDateTime(); | |
switch(currentState) { | |
case CLOCK: | |
printTime(); | |
if(alarm.enabled && curDateTime.second==0 && curDateTime.hour==alarm.hour && curDateTime.minute==alarm.minute) | |
buzz(); | |
break; | |
case DATE: | |
printDate(); | |
if(millis()-mode_start>5000) // return to clock after 5s | |
currentState = CLOCK; | |
break; | |
case SET_ALARM: | |
if(millis()%1000<500) | |
sprintf(displayBuffer, "SET ALAR"); | |
else | |
sprintf(displayBuffer, "ET ALARM"); | |
if(millis()-mode_start>2000) // wait 2s to set alarm | |
currentState++; | |
break; | |
case SET_ALARM_ENABLED: | |
if(alarm.enabled) | |
sprintf(displayBuffer," ON "); | |
else | |
sprintf(displayBuffer," OFF "); | |
break; | |
case SET_ALARM_HOUR: | |
if(tick) | |
sprintf(displayBuffer, set_hour_tick, alarm.minute); | |
else | |
sprintf(displayBuffer, set_hour_no_tick, alarm.hour, alarm.minute); | |
break; | |
case SET_ALARM_MINUTE: | |
if(tick) | |
sprintf(displayBuffer, set_minute_tick, alarm.hour); | |
else | |
sprintf(displayBuffer, set_minute_no_tick, alarm.hour, alarm.minute); | |
break; | |
case SET_TIME: | |
sprintf(displayBuffer,"SET TIME"); | |
if(millis()-mode_start>2000) // wait 2s to set time | |
currentState++; | |
break; | |
case SET_HOUR: | |
if(tick) | |
sprintf(displayBuffer, set_hour_tick, curDateTime.minute); | |
else | |
sprintf(displayBuffer, set_hour_no_tick, curDateTime.hour, curDateTime.minute); | |
break; | |
case SET_MINUTE: | |
if(tick) | |
sprintf(displayBuffer, set_minute_tick, curDateTime.hour); | |
else | |
sprintf(displayBuffer, set_minute_no_tick, curDateTime.hour, curDateTime.minute); | |
break; | |
case SET_DATE: | |
sprintf(displayBuffer,"SET DATE"); | |
if(millis()-mode_start>2000) // wait 2s to set date | |
currentState++; | |
break; | |
case SET_MONTH: | |
if(tick) | |
sprintf(displayBuffer, set_month_tick, curDateTime.monthDay); | |
else | |
printDate(); | |
break; | |
case SET_DAY: | |
if(tick) | |
sprintf(displayBuffer, set_day_tick, month[curDateTime.month-1]); | |
else | |
printDate(); | |
break; | |
case SET_YEAR: | |
if(tick) | |
sprintf(displayBuffer, " "); | |
else | |
sprintf(displayBuffer, set_year_tick, curDateTime.year); | |
break; | |
case WRITE_DATETIME: | |
setDateTime(); | |
currentState=CLOCK; | |
break; | |
case WRITE_ALARM: | |
setAlarmSettings(); | |
currentState=CLOCK; | |
break; | |
} | |
} | |
void shiftBits (byte cathodes, byte segments1, byte segments2){ | |
PORTD |= B10000000; // HC595s latch high | |
for (int k=0; k < 8; k++){ | |
PORTB &= ~1; // HC595s clock low | |
if ( cathodes & (0b10000000 >> k) ) | |
PORTD |= 0b100000; // HC595-1 data high | |
else | |
PORTD &= ~0b100000; // HC595-1 data low | |
if ( segments2 & (0b10000000 >> k) ) | |
PORTD |= 0b10000; // HC595-2 data high | |
else | |
PORTD &= ~0b10000; // HC595-2 data low | |
if ( segments1 & (0b10000000 >> k) ) | |
PORTD |= 0b1000000; // HC595-3 data high | |
else | |
PORTD &= ~0b1000000; // HC595-3 data low | |
PORTB |= 1; // HC595s clock high | |
} | |
PORTD &= ~0b10000000; // HC595s latch low | |
} | |
void displayISR(){ | |
PORTD &= ~B1000; // HC595s output enabled | |
for (int cathode = 0; cathode <8; cathode++){ | |
shiftBits(0xFF-(1<<cathode),pgm_read_byte(&ascii[(2*(displayBuffer[cathode]-0x20))]), pgm_read_byte(&ascii[(1+2*(displayBuffer[cathode]-0x20))])); | |
} | |
PORTD |= B1000; // HC595s output disabled | |
} | |
byte bcdToDec(byte val) { | |
return ((val/16*10)+(val%16)); | |
} | |
byte decToBcd(byte val){ | |
return ((val/10*16)+(val%10)); | |
} | |
void updateCurDateTime() { | |
static unsigned long last_update = -200; | |
if((currentState<SET_HOUR || currentState>SET_MINUTE) && currentState != WRITE_DATETIME) { | |
if(millis()-last_update>100) { | |
Timer1.detachInterrupt(); // disable display ISR while using i2c bus | |
Wire.beginTransmission(DS1307_ADDRESS); | |
WireWrite(0); | |
Wire.endTransmission(); | |
Wire.requestFrom(DS1307_ADDRESS, 7); | |
curDateTime.second = bcdToDec(WireRead); | |
curDateTime.minute = bcdToDec(WireRead); | |
curDateTime.hour = bcdToDec(WireRead & 0b111111); | |
curDateTime.weekDay = bcdToDec(WireRead); | |
if(currentState>SET_DAY || currentState<SET_YEAR) { | |
curDateTime.monthDay = bcdToDec(WireRead); | |
curDateTime.month = bcdToDec(WireRead); | |
curDateTime.year = bcdToDec(WireRead)+2000; | |
} | |
if(curDateTime.second & 0b1000000) { | |
curDateTime.second = 0; // disable Clock Halt bit on fresh ds1307 chip | |
setDateTime(); | |
} | |
Timer1.attachInterrupt(displayISR); | |
} | |
} | |
} | |
void setDateTime() { | |
Timer1.detachInterrupt(); | |
Wire.beginTransmission(DS1307_ADDRESS); | |
WireWrite(0); | |
WireWrite(decToBcd(curDateTime.second)); | |
WireWrite(decToBcd(curDateTime.minute)); | |
WireWrite(decToBcd(curDateTime.hour)); | |
WireWrite(decToBcd(curDateTime.weekDay)); | |
WireWrite(decToBcd(curDateTime.monthDay)); | |
WireWrite(decToBcd(curDateTime.month)); | |
WireWrite(decToBcd(curDateTime.year-2000)); | |
WireWrite(0); | |
Wire.endTransmission(); | |
Timer1.attachInterrupt(displayISR); | |
} | |
void getAlarmSettings() | |
{ | |
if(EEPROM.read(0)==0x13 && EEPROM.read(1)==0x37) // check signature | |
{ | |
alarm.enabled = EEPROM.read(2); | |
alarm.hour = EEPROM.read(3); | |
alarm.minute = EEPROM.read(4); | |
} | |
digitalWrite(alarmLed, alarm.enabled); | |
} | |
void setAlarmSettings() | |
{ | |
EEPROM.write(0, 0x13); | |
EEPROM.write(1, 0x37); | |
EEPROM.write(2, alarm.enabled); | |
EEPROM.write(3, alarm.hour); | |
EEPROM.write(4, alarm.minute); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment