VFD modular clock firmware

Dependencies:   DipCortex-EEprom RTC flw mbed

Revision:
0:f6e68b4ce169
Child:
5:5c073029c416
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Feb 09 13:40:46 2015 +0000
@@ -0,0 +1,650 @@
+/*
+ * VFD Modular Clock - mbed
+ * (C) 2011-14 Akafugu Corporation
+ *
+ * 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.
+ *
+ */
+
+#include "global.h"
+#include "mbed.h"
+
+#include "VFDDisplay.h"
+#include "IV18Display.h"
+
+#include "prefs.h"
+#include "gps.h"
+#include "menu.h"
+#include "button.h"
+#include "rtc.h"
+#include "ds3231m.h"
+#include "beep.h"
+#include "flw.h"
+
+IV18Display display(PinMap::data, PinMap::clock, PinMap::latch, PinMap::blank);
+
+Menu menu;
+
+I2C i2c(PinMap::sda, PinMap::scl);
+DS3231M rtc(i2c);
+//RTC rtc;
+
+DigitalOut led(PinMap::led);
+Beep piezo(PinMap::piezo);
+
+FourLetterWord flw(&i2c);
+char flwWord[5];
+int flwOffset;
+int flwOffsetDirection = 1;
+volatile bool haveEEPROM;
+
+Ticker blanker;
+Ticker multiplexer;
+Ticker button_ticker;
+Ticker rtc_ticker;
+Ticker tenth_ticker;
+
+volatile state_t g_clock_state = STATE_CLOCK;
+uint32_t gps_last_update = 0xffff;
+
+uint8_t calculate_segments_7(uint8_t character);
+
+// Alarm
+volatile bool g_alarm_on;
+volatile bool g_alarming;
+
+void write_vfd_8bit(uint8_t data);
+void write_vfd_iv18(uint8_t digit, uint8_t segments);
+void write_vfd(uint8_t digit, uint8_t segments);
+void demo_cycle(char* buf);
+
+void blank_tick()
+{
+    static uint32_t cnt = 0;
+    
+    if ((cnt%10) > (10-display.getBrightness())) {
+        display.blank(false);
+    }
+    else {
+       display.blank(true);
+    }
+    
+    cnt++;
+}
+
+void multiplex_tick()
+{
+    display.multiplexTick();
+}
+
+void rtc_tick()
+{
+    if (haveEEPROM)
+        strncpy(flwWord, flw.getWord(), 4);
+    
+    flwOffset += flwOffsetDirection;
+    
+    if (flwOffset <= 0) {
+        flwOffset = 0;
+        flwOffsetDirection = 1;    
+    }
+    else if (flwOffset > display.digits() -4) {
+        flwOffset = 3;    
+        flwOffsetDirection = -1;
+    }
+    
+    rtc.tick();
+}
+
+void tenth_tick()
+{
+    rtc.tenth_tick(); 
+}
+
+void counterTest()
+{
+    display.cls();
+    for (uint16_t i = 0; i < 200; i++) {
+        display.printf("cnt %3d", i);
+        wait(0.01);
+    }
+}
+
+void timeAndDateTest()
+{
+    uint8_t hour = 15;
+    uint8_t min = 59;
+    uint8_t sec = 55;
+    
+    display.cls();
+    for (uint8_t i = 0; i < 7; i++) {
+        display.printf("%02d-%02d-%02d", hour, min, sec);
+        sec++;
+        if (sec == 60) { sec = 0; min++;  }
+        if (min == 60) { min = 0; hour++; }
+        wait(1.0);    
+    }
+    
+    display.printf("%02d-%02d-%02d  Monday 2014-05-12", hour, min, sec);
+
+    while (!display.scrollFinished()) {                 
+        display.scroll();    
+        wait(0.25);
+    }
+    
+    display.resetScroll();
+
+    for (uint8_t i = 0; i < 5; i++) {
+        display.printf("%2d-%02d-%02d", hour, min, sec);
+        sec++;
+        if (sec == 60) { sec = 0; min++;  }
+        if (min == 60) { min = 0; hour++; }
+        wait(1.0);    
+    }
+    
+}
+
+int dayOfWeek(int y, int m, int d)
+{
+    static const int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+    y -= m < 3;
+    return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
+}
+
+void welcomeMessage()
+{
+    const char* buf = " Akafugu VFD Modular Clock";    
+    
+    display.cls();
+    display.printf(buf);
+        
+    // scroll forward
+    while (!display.scrollFinished()) {                 
+        display.scroll(); 
+        led = !led;   
+        wait(0.20);
+    }
+
+    display.cls();
+}
+
+// helper function for handling time/alarm setting
+uint8_t handleTimeSetting(uint8_t setting, uint8_t min, uint8_t max, 
+                          bool backButton, bool nextButton)
+{
+    if (backButton) {
+        if (setting == 0) {
+            setting = max;
+        }
+        else {
+            setting--;
+        }
+    }
+    else if (nextButton) {
+        if (setting == max) {
+            setting = 0;
+        }
+        else {
+            setting++;
+        }
+    }
+    
+    return setting;
+}
+
+const uint32_t MENU_TIMEOUT = 3 * 10; // each cycle 0.1s, 3 secs total
+struct tm* tm_;
+
+int main()
+{
+    bool suppress_button_sound = false;
+    uint8_t button3_holdcounter = 0;
+    uint32_t menu_countdown = MENU_TIMEOUT;
+
+    // Enable extra memory banks
+    LPC_SYSCON->SYSAHBCLKCTRL |= 0x1 << 26; // RAM1
+    LPC_SYSCON->SYSAHBCLKCTRL |= 0x1 << 27; // USBSRAM
+
+    // PWM
+    // See: http://developer.mbed.org/forum/mbed/topic/3276/
+    /*
+    LPC_CT16B0->TCR = 2;
+    LPC_CT16B0->PR = 19;
+    LPC_CT16B0->MR0 = 48000; // 1ms
+    LPC_CT16B0->TCR = 1;
+    */
+    
+    BUTTON_STATE buttons;
+        
+    rtc.begin();
+    init_prefs();
+    
+    // FLW initialization
+    tm_ = rtc.getTime();
+    
+    flw.begin(tm_->tm_min * 60 + tm_->tm_sec);
+    flw.setCensored(true);
+
+    memset(flwWord, 0, 5);
+    haveEEPROM = flw.hasEeprom();
+    if (haveEEPROM)
+        strncpy(flwWord, flw.getWord(), 4);
+    else
+        memset(flwWord, 0, 4);    
+
+    led = 0;
+    
+    initialize_buttons();
+    menu.setDigits(display.digits());
+    
+    blanker.attach_us(&blank_tick, 100);
+    multiplexer.attach_us(&multiplex_tick, 2000);
+    button_ticker.attach_us(&button_tick, 5000);
+    rtc_ticker.attach(&rtc_tick, 1.0);
+    tenth_ticker.attach(&tenth_tick, 0.1);
+
+    PrefsData* prefs = get_prefs();
+    display.setBrightness(prefs->prefs.brightness);
+
+    wait(1.0);
+    //welcomeMessage();
+    led = 0;
+    
+#ifdef HAVE_GPS
+    gps_init();
+#endif // HAVE_GPS
+
+    /*
+    // button test
+    while (1) {
+        get_button_state(&buttons);
+        
+        display.printf("%d-%d-%d %d", buttons.b1_keydown, buttons.b2_keydown, buttons.b3_keydown, get_keystatus());
+        wait(0.1);
+    }
+    */
+    
+    while (1) {
+        get_button_state(&buttons);
+        prefs = get_prefs();
+        
+        led = 0;
+        
+#ifdef HAVE_GPS
+        // Read GPS and update RTC if needed
+        if (gpsDataReady()) {
+            char* nmea = gpsNMEA();
+            bool error = false;
+            bool fix = false;
+
+            if ( prefs->prefs.gps && strncmp( nmea, "$GPRMC,", 7 ) == 0 ) {
+                time_t t = parseGPSdata(nmea, error, fix, prefs->prefs.gps_tzh, prefs->prefs.gps_tzm);
+                time_t delta = t - gps_last_update;
+
+                if (fix && !error && delta > 60) {
+                    led = 1;
+                    struct tm *tmp = localtime(&t);
+                    rtc.setTime(tmp);
+                    gps_last_update = t;
+                }
+            }
+        }
+#endif // HAVE_GPS
+        
+        if (buttons.b1_keyup || buttons.b2_keyup || buttons.b3_keyup) {            
+            if (suppress_button_sound || g_alarming) {
+                suppress_button_sound = false;
+                buttons.b1_keyup = buttons.b2_keyup = buttons.b3_keyup = 0;
+            }
+            else {
+                //piezo.play('g');
+            }
+            
+            button3_holdcounter = 0;
+            g_alarming = false;
+        }
+        
+        switch (g_clock_state) {            
+            case STATE_CLOCK:
+            {
+                tm_ = rtc.getTime();
+
+                // button 1 triggers menu
+                if (buttons.b1_keyup) {
+                    //display.incBrightness();
+                    display.printf("%s                ", menu.reset());
+                    display.setAlarmIndicator(false);
+                    display.setGPSIndicator(false);
+                    set_extra_prefs(tm_->tm_year+1900, tm_->tm_mon+1, tm_->tm_mday);
+                    g_clock_state = STATE_MENU;
+                    menu_countdown = MENU_TIMEOUT;
+                    buttons.b1_keyup = 0;
+                    break;
+                }
+                
+                // Button 2 cycles through display mode
+                if (buttons.b2_keyup) {
+                    display.toggleTimeMode();
+                    buttons.b2_keyup = 0;
+                }
+                
+                // Button 3 toggles FLW mode
+                if (buttons.b3_keyup && haveEEPROM) {
+                    buttons.b3_keyup = 0;
+                    button3_holdcounter = 0;
+                    g_clock_state = STATE_FLW;
+                }
+                
+                // Hold button 3 to set alarm
+                if (buttons.b3_repeat) {
+                    button3_holdcounter++;
+                    buttons.b3_repeat = 0;
+                    suppress_button_sound = true;
+                    
+                    if (button3_holdcounter == 6) {
+                        g_alarm_on = !g_alarm_on;
+                        piezo.play('a');                        
+                    }
+                }
+                                
+                // Enter auto date
+                if (prefs->prefs.auto_date && tm_->tm_sec == 50) {
+                    display.cls();
+                    display.printTimeLong(tm_, rtc.getTenths());
+                    g_clock_state = STATE_AUTO_DATE;
+                }
+                else if (haveEEPROM && prefs->prefs.flw > 0 && tm_->tm_sec == 30) {
+                    g_clock_state = STATE_AUTO_FLW;
+                }
+                else {
+                    display.printTime(tm_, rtc.getTenths());
+                }
+            }
+            break;
+            case STATE_MENU:
+            {
+                // button 1 goes to next menu item
+                if (buttons.b1_keyup) {
+                    display.printf("%s                ", menu.next()); 
+                    menu_countdown = MENU_TIMEOUT;
+                    buttons.b1_keyup = 0;
+                }
+                                
+                // Button 2 selects menu item
+                if (buttons.b2_keyup || buttons.b2_repeat) {
+                    bool setTime, setAlarm;
+                    
+                    display.printf("%8s                ", menu.select(setTime, setAlarm));
+                    
+                    if (setTime) {
+                        menu.leave();
+                        display.setBlinkMode(VFDDisplay::Hours);
+                        display.blink(true);
+                        tm_ = rtc.getTime(); // get time from RTC to use as basis for settings
+                        tm_->tm_sec = 0;                        
+                        g_clock_state = STATE_SET_CLOCK_HH;   
+                    }
+                    else if (setAlarm) {
+                        menu.leave();
+                        display.setBlinkMode(VFDDisplay::Minutes);
+                        display.blink(true);
+                        tm_ = rtc.getAlarm();
+                        g_clock_state = STATE_SET_ALARM_HH;
+                    }
+                    
+                    menu_countdown = MENU_TIMEOUT;
+                    buttons.b2_keyup = 0;
+                }
+                // Button 3 leaves menu
+                else if (buttons.b3_keyup) {
+                    buttons.b3_keyup = 0;
+                    menu.leave();
+                    g_clock_state = STATE_CLOCK;
+                }
+                
+                menu_countdown--;
+                
+                // exit menu if unused for preset time
+                if (menu_countdown == 0) {
+                    menu.leave();
+                    g_clock_state = STATE_CLOCK;
+                }
+                
+                // If g_clock_state has changed, we are leaving the menu, update parameters
+                if (g_clock_state == STATE_CLOCK) {
+                    display.setGPSIndicator(true);
+                    
+                    tm_ = rtc.getTime();
+                    int year, month, day;
+                    
+                    get_extra_prefs(year, month, day);
+                    tm_->tm_year = year - 1900;
+                    tm_->tm_mon  = month-1;
+                    tm_->tm_mday = day;
+                    
+                    tm_->tm_wday = dayOfWeek(year, month, day);
+                    
+                    rtc.setTime(tm_);
+
+                    wait(0.05);
+                    display.setGPSIndicator(false);
+                }
+            }
+            break;
+            case STATE_AUTO_DATE:
+            {
+                // exit scroll if any button is pressed
+                if (buttons.b1_keyup || buttons.b2_keyup || buttons.b3_keyup) {
+                    display.cls();
+                    g_clock_state = STATE_CLOCK;  
+                    
+                    buttons.b1_keyup = 0;
+                    buttons.b2_keyup = 0;
+                    buttons.b3_keyup = 0;
+                }
+                
+                static uint8_t scroll_cnt = 0;
+                
+                if (display.scrollFinished()) {
+                    display.cls();
+                    g_clock_state = STATE_CLOCK;  
+                }
+                else {
+                    if (++scroll_cnt == 2) {
+                        display.scroll();
+                        scroll_cnt = 0;
+                    }
+                }
+            }
+            break;
+            case STATE_SET_CLOCK_HH:
+            {
+                display.printTimeSet(tm_);
+
+                if (buttons.b1_repeat || buttons.b2_repeat) {
+                    display.blink(false);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, buttons.b1_repeat, buttons.b2_repeat);
+                    buttons.b1_repeat = 0;
+                    buttons.b2_repeat = 0;
+                }
+                if (buttons.b1_keyup) {
+                    display.blink(true);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, true, false);
+                    buttons.b1_keyup = 0;
+                }
+                else if (buttons.b2_keyup) { // Button 2 
+                    display.blink(true);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, false, true);
+                    buttons.b2_keyup = 0;
+                }
+                else if (buttons.b3_keyup) { // Button 3 moves to minute setting
+                    display.setBlinkMode(VFDDisplay::Minutes);
+                    g_clock_state = STATE_SET_CLOCK_MM;
+                    buttons.b3_keyup = 0;
+                }
+            }
+            break;
+            case STATE_SET_CLOCK_MM:
+            {
+                display.printTimeSet(tm_);
+
+                if (buttons.b1_repeat || buttons.b2_repeat) {
+                    display.blink(false);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, buttons.b1_repeat, buttons.b2_repeat);
+                    buttons.b1_repeat = 0;
+                    buttons.b2_repeat = 0;
+                }
+                if (buttons.b1_keyup) {
+                    display.blink(true);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, true, false);
+                    buttons.b1_keyup = 0;
+                }
+                else if (buttons.b2_keyup) { // Button 2 
+                    display.blink(true);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, false, true);
+                    buttons.b2_keyup = 0;
+                }
+                else if (buttons.b3_keyup) { // Button 3 moves to second setting
+                    display.setBlinkMode(VFDDisplay::Seconds);
+                    g_clock_state = STATE_SET_CLOCK_SS;
+                    buttons.b3_keyup = 0;
+                }
+            }
+            break;
+            case STATE_SET_CLOCK_SS:
+            {
+                display.printTimeSet(tm_);
+
+                if (buttons.b1_repeat || buttons.b2_repeat) {
+                    display.blink(false);
+                    tm_->tm_sec = handleTimeSetting(tm_->tm_sec, 0, 59, buttons.b1_repeat, buttons.b2_repeat);
+                    buttons.b1_repeat = 0;
+                    buttons.b2_repeat = 0;
+                }
+                if (buttons.b1_keyup) {
+                    display.blink(true);
+                    tm_->tm_sec = handleTimeSetting(tm_->tm_sec, 0, 59, true, false);
+                    buttons.b1_keyup = 0;
+                }
+                else if (buttons.b2_keyup) { // Button 2 
+                    display.blink(true);
+                    tm_->tm_sec = handleTimeSetting(tm_->tm_sec, 0, 59, false, true);
+                    buttons.b2_keyup = 0;
+                }
+                else if (buttons.b3_keyup) { // Button 3 moves to minute setting
+                    display.setBlinkMode(VFDDisplay::Full);
+                    display.blink(false);
+                    rtc.setTime(tm_);
+                    g_clock_state = STATE_CLOCK;
+                    buttons.b3_keyup = 0;
+                }
+            }
+            break;
+            case STATE_SET_ALARM_HH:
+            {
+                display.printTimeSet(tm_, false);
+
+                if (buttons.b1_repeat || buttons.b2_repeat) {
+                    display.blink(false);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, buttons.b1_repeat, buttons.b2_repeat);
+                    buttons.b1_repeat = 0;
+                    buttons.b2_repeat = 0;
+                }
+                if (buttons.b1_keyup) {
+                    display.blink(true);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, true, false);
+                    buttons.b1_keyup = 0;
+                }
+                else if (buttons.b2_keyup) { // Button 2 
+                    display.blink(true);
+                    tm_->tm_hour = handleTimeSetting(tm_->tm_hour, 0, 23, false, true);
+                    buttons.b2_keyup = 0;
+                }
+                else if (buttons.b3_keyup) { // Button 3 moves to minute setting
+                    display.setBlinkMode(VFDDisplay::Seconds);
+                    g_clock_state = STATE_SET_ALARM_MM;
+                    buttons.b3_keyup = 0;
+                }
+            }
+            break;
+            case STATE_SET_ALARM_MM:
+            {
+                display.printTimeSet(tm_, false);
+
+                if (buttons.b1_repeat || buttons.b2_repeat) {
+                    display.blink(false);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, buttons.b1_repeat, buttons.b2_repeat);
+                    buttons.b1_repeat = 0;
+                    buttons.b2_repeat = 0;
+                }
+                if (buttons.b1_keyup) {
+                    display.blink(true);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, true, false);
+                    buttons.b1_keyup = 0;
+                }
+                else if (buttons.b2_keyup) { // Button 2 
+                    display.blink(true);
+                    tm_->tm_min = handleTimeSetting(tm_->tm_min, 0, 59, false, true);
+                    buttons.b2_keyup = 0;
+                }
+                else if (buttons.b3_keyup) { // Button 3 moves to minute setting
+                    display.setBlinkMode(VFDDisplay::Full);
+                    display.blink(false);
+                    rtc.setAlarm(tm_->tm_hour, tm_->tm_min, 0);
+                    g_clock_state = STATE_CLOCK;
+                    buttons.b3_keyup = 0;
+                }
+            }
+            break;
+            case STATE_AUTO_FLW:
+            {
+                tm_ = rtc.getTime();
+                
+                if (tm_->tm_sec >= 37) {
+                    g_clock_state = STATE_CLOCK;
+                }
+            }
+            // fall-through
+            case STATE_FLW:
+            {
+                // exit if any button is pressed
+                if (buttons.b1_keyup || buttons.b2_keyup || buttons.b3_keyup) {
+                    display.cls();
+                    g_clock_state = STATE_CLOCK;  
+                    
+                    buttons.b1_keyup = 0;
+                    buttons.b2_keyup = 0;
+                    buttons.b3_keyup = 0;
+                }
+                
+                if (flwOffset == 0)
+                    display.printf("%s        ", flwWord);
+                else 
+                    display.printf("%*s" "%s        ", flwOffset, " ", flwWord);
+            }
+            break;
+            default:
+                break;  
+        }
+        
+        if (g_alarm_on && rtc.checkAlarm()) {
+            g_alarming = true;
+        }
+        
+        display.setAlarmIndicator(g_alarm_on);
+        display.setGPSIndicator(g_alarming);
+        
+        if (g_alarming) {
+            piezo.play('g'); 
+        }
+
+        wait(0.1);
+    }
+}