VFD modular clock firmware
Dependencies: DipCortex-EEprom RTC flw mbed
Diff: main.cpp
- 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); + } +}