goodwatch/firmware/apps/clock.c

307 lines
7.5 KiB
C

/*! \file clock.c
\brief Main clock application, that runs by default.
This is the main clock application, which is seen by default
whenever the watch has nothing else to do. In addition to telling
the time, holding a button allows you to see additional
information, such as the date and git revision.
By default, the time is drawn. Hold 7 to run a self-test, 8 to
display the callsign, 9 for the day of the week, / for the date, 4
for the git tag, 5 for the date of flashing, 6 to toggle the CPU
load indicator, 0 for the name of the current working channel.
Hold 1 for the voltage, * for the CPU model number.
*/
#include <msp430.h>
#include <stdio.h>
#include "api.h"
#include "applist.h"
#define RTCAE (0x80) /* Real Time Clock Alarm enable */
//! If non-zero, we need to redraw the whole time.
static int redraw=0;
//! Last measured voltage.
static unsigned int vcc;
//! This hold the last character pressed.
static char lastchar=0;
//! Draws the time.
void draw_time(int always){
/* So at first glance, this might seem a bit complicated. Why draw
the seconds first, and why take so many opportunities to abort
and return without drawing anything?
The reason is that the vast majority of CPU time is spent drawing
the time to the LCD, and that anything we can do to shorten that
time in each quarter-second frame will increase the watch's
battery life. Therefore, we only draw the seconds if they have
changed, which lets us do nothing on three quarters of the
frames. Then we only draw the minutes and hours if the minutes
have changed, saving two thirds of the digit drawing on each
frame, and we only draw the tens digit if the ones digit is zero,
saving us half the LCD memory writes and trable lookups during
those frames.
This could be further improved by testing hours, but as that only
applies to one frame in 3600, it won't impact battery life very
much. When a button has been pressed or the mode changed, it is
set non-zero to force the whole frame to be drawn.
int2bcd() works by a ROM table lookup in bcd.o. The '6147 also
has a hardware BCD calculator, but as this isn't present in the
'6137 and only costs us 64 bytes of ROM, it isn't a very high
priority.
All the digits are drawn out of order for these fancy reasons.
Sorry about that.
*/
unsigned int hour=RTCHOUR;
static unsigned int min;
static unsigned int sec;
unsigned int lsec; //Lower digit of the seconds.
//Only draw once a second, unless a button is pressed.
if(sec==RTCSEC && !always)
return;
sec=RTCSEC;
lcd_digit(0,lsec=int2bcd(sec)&0xf); //Lower digit.
if(lsec && !always) //Only need to draw tens digit if ones is zero.
return;
lcd_digit(1,int2bcd(sec)>>4);
//If the minute hasn't changed, don't bother drawing it or the hour.
if(min==RTCMIN && !always)
return;
min=RTCMIN;
lcd_cleardigit(2); //Space between seconds and minutes.
lcd_digit(7,int2bcd(hour)>>4);
lcd_digit(6,int2bcd(hour)&0xf);
lcd_cleardigit(5); //Space between hours and minutes.
setcolon(1);
lcd_digit(4,int2bcd(min)>>4);
lcd_digit(3,int2bcd(min)&0xf);
setam(hour<12);
setpm(hour>=12);
// get alarm status
if (RTCAHOUR & RTCAE && RTCAMIN & RTCAE)
setplus(1);
else
setplus(0);
}
//! Draws the date as yyyy.mm.dd
void draw_date(){
unsigned int year=RTCYEAR;
unsigned int month=RTCMON;
unsigned int day=RTCDAY;
lcd_digit(7,(year/1000)%10);
lcd_digit(6,(year/100)%10);
lcd_digit(5,(year/10)%10);
lcd_digit(4,year%10);
setperiod(4,1);
setcolon(0);
lcd_digit(3,month/10);
lcd_digit(2,month%10);
setperiod(2,1);
lcd_digit(1,day/10);
lcd_digit(0,day%10);
setam(0);
setpm(0);
}
static const char *daysofweek[7]={
" sunday",
" monday",
" tuesday",
"wednesdy",
"thursday",
" friday",
"saturday"
};
//! Draws the day of the week.
static void draw_dow(){
lcd_string(daysofweek[RTCDOW]);
}
//! Draws the date as yyyy.mm.dd
static void draw_date_rom(){
unsigned int year=(romsavetime[4]+(romsavetime[5]<<8)) % 4096;;
unsigned int month=romsavetime[6];
unsigned int day=romsavetime[7];
lcd_digit(7,(year/1000)%10);
lcd_digit(6,(year/100)%10);
lcd_digit(5,(year/10)%10);
lcd_digit(4,year%10);
setperiod(4,1);
setcolon(0);
lcd_digit(3,month/10);
lcd_digit(2,month%10);
setperiod(2,1);
lcd_digit(1,day/10);
lcd_digit(0,day%10);
setam(0);
setpm(0);
}
//! Exit clock when the sidebutton is pressed.
int clock_exit(){
return 0;
}
//! Plays the time as audio.
void clock_playtime(int hold){
char buf[12];
sprintf(buf,"%02d %02d",
RTCHOUR, RTCMIN);
lcd_string(buf);
//Little delay, so we can quit early with dignity on accidental keypresses.
audio_morse(" ", 1);
audio_morse(buf, //Buffer to play.
hold //Play it only so long as the button is held down.
);
}
//! Draws the clock face in the main application.
void clock_draw(int forced){
//Use the SET button to reconfigure the time.
if(sidebutton_set()){
//Wait for the button to be released.
while(sidebutton_set());
//Switch to the setting applet.
app_set(&setting_applet);
}
if(!lastchar){
// Draw the time by default, but only if no buttons pushed.
draw_time(redraw);
if(redraw)
redraw--;
}
}
//! Entry to the clock app.
void clock_init(){
lastchar=0;
redraw=0;
lcd_zero();
draw_time(1);
}
//! A button has been pressed for the clock.
int clock_keypress(char ch){
lastchar=ch;
/* This function is called *once* per keypress event, while the
handlers in clock_draw() are called once per frame. It is very
important that anything that takes more than a frame be handled
here, rather than in clock_draw(), which is rerun for every frame.
*/
lcd_zero();
redraw++;
switch(ch){
case '7':
//Hold 7 to run the self-test after startup. Response codes try
//to roughly describe the fault.
post();
break;
case '8':
//8 shows the callsign.
lcd_string(CALLSIGN);
break;
case '9':
//Hold 9 to draw the day of the week.
draw_dow();
break;
case '/':
//Hold / to draw the date.
draw_date();
break;
case '4':
//4 shows the git revision.
lcd_hex(GITHASH);
lcd_cleardigit(7);
break;
case '5':
//5 shows the git date from Flash.
draw_date_rom();
break;
case '6':
//6 toggles the CPU load indicator.
flickermode=(flickermode?0:-1);
lcd_string(flickermode?"FLICK ON":"FLICKOFF");
break;
case '*':
//* shows the chip model number.
lcd_string(DEVICEID==DEVICEID6137?"430F6137":"430F6147");
break;
case '1':
/* 1 shows the the voltage. To save on power, the reference is
not active when idling. */
ref_on();
vcc=adc_getvcc();
ref_off();
lcd_number(vcc);
lcd_string("volt ");
setperiod(2,1);
break;
case '0':
//0 shows the name of the working channel.
if(has_radio)
lcd_string(codeplug_name());
else
lcd_string("NO RADIO");
break;
case '.':
//. shows the frequency of the working channel.
if(has_radio){
lcd_number(codeplug_getfreq()/10);
setperiod(5,1);
setperiod(2,1);
}else{
lcd_string("NO RADIO");
}
break;
case '+':
/* + beeps the time as morse code, which might be handy
for learning the code or when it's too dark to see the display.
*/
clock_playtime(1);
break;
}
return 1;
}