Skip to content

Instantly share code, notes, and snippets.

@goebish
Created December 27, 2014 13:20
Show Gist options
  • Save goebish/6f414420c0cc39145021 to your computer and use it in GitHub Desktop.
Save goebish/6f414420c0cc39145021 to your computer and use it in GitHub Desktop.
TSA1605A Clock
// 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