VFD modular clock firmware

Dependencies:   DipCortex-EEprom RTC flw mbed

main.cpp

Committer:
Backstrom
Date:
2015-02-24
Revision:
11:34b344fdec98
Parent:
5:5c073029c416

File content as of revision 11:34b344fdec98:

/*
 * 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;
volatile bool g_updateFLW = false;

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)
        g_updateFLW = true;
    
    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'); 
        }
        
        if (g_updateFLW)
        {
            g_updateFLW = false;
            
            strncpy(flwWord, flw.getWord(), 4);
            flwOffset += flwOffsetDirection;
    
            if (flwOffset <= 0) {
                flwOffset = 0;
                flwOffsetDirection = 1;    
            }
            else if (flwOffset > display.digits() -4) {
                flwOffset = 3;    
                flwOffsetDirection = -1;
            }
        }

        wait(0.1);
    }
}