From 125ddfb687febbaea6ee74fd56e26e1dc776110d Mon Sep 17 00:00:00 2001 From: Max Regan Date: Thu, 6 Jun 2019 22:30:27 -0700 Subject: [PATCH] Enable buttons and the display. Kind-of-sort-of usable-ish. --- ButtonManager.cpp | 15 +- ButtonManager.h | 3 +- DisplayDriver.cpp | 61 ++++-- DisplayDriver.h | 21 +- DisplayTimeTask.cpp => DisplayTimeScreen.cpp | 112 +++++----- DisplayTimeTask.h => DisplayTimeScreen.h | 29 ++- LowPowerTaskScheduler.h | 23 +- Makefile | 11 +- RtcDriver.cpp | 89 +++++--- RtcDriver.h | 16 +- DisplayManager.h => Screen.h | 19 +- ScreenManager.cpp | 129 +++++++++++ ScreenManager.h | 68 ++++++ SetTimeScreen.cpp | 219 +++++++++++++++++++ SetTimeScreen.h | 69 ++++++ Task.h | 38 +++- TaskScheduler.h | 2 +- Time.h | 141 ++++++++++++ main.cpp | 68 +++--- 19 files changed, 937 insertions(+), 196 deletions(-) rename DisplayTimeTask.cpp => DisplayTimeScreen.cpp (52%) rename DisplayTimeTask.h => DisplayTimeScreen.h (66%) rename DisplayManager.h => Screen.h (80%) create mode 100644 ScreenManager.cpp create mode 100644 ScreenManager.h create mode 100644 SetTimeScreen.cpp create mode 100644 SetTimeScreen.h diff --git a/ButtonManager.cpp b/ButtonManager.cpp index 78edf38..8e8dab2 100644 --- a/ButtonManager.cpp +++ b/ButtonManager.cpp @@ -49,6 +49,10 @@ ReturnCode ButtonManager::init() SET(EXTI->FTSR, 1u << btn.m_gpio_idx); } + CLR(SYSCFG->EXTICR[0], + SYSCFG_EXTICR1_EXTI3 | SYSCFG_EXTICR1_EXTI2 | + SYSCFG_EXTICR1_EXTI1 | SYSCFG_EXTICR1_EXTI0); + NVIC_EnableIRQ(EXTI0_1_IRQn); NVIC_EnableIRQ(EXTI2_3_IRQn); NVIC_EnableIRQ(EXTI4_15_IRQn); @@ -82,7 +86,7 @@ NextTime ButtonManager::execute() } // TODO: Call less frequently, and let the buttonmanager re-add itself to the task list on interrupts - return NextTime::in(Common::Time::millis(1000)); + return NextTime::asap(); } void ButtonManager::set_callback(Button btn, ChangeCallback callback) @@ -119,7 +123,7 @@ void ButtonManager::irq() bool is_pressed = !(idr & (1 << btn.m_gpio_idx)); // Check if the button state has changed, and timestamp it - if (is_pressed && (btn.m_state != ButtonState::PRESSED)) { + if (is_pressed && (btn.m_state == ButtonState::NOT_PRESSED)) { btn.m_state = ButtonState::PRESSED; btn.m_state_change_ts = systime; } else if (!is_pressed && (btn.m_state == ButtonState::PRESSED)) { @@ -132,16 +136,23 @@ void ButtonManager::irq() } } + extern "C" void EXTI_1_0_IRQHandler() { + static uint32_t irq_count = 0; ButtonManager::irq(); + irq_count++; } extern "C" void EXTI_3_2_IRQHandler() { + static uint32_t irq_count = 0; ButtonManager::irq(); + irq_count++; } extern "C" void EXTI_15_4_IRQHandler() { + static uint32_t irq_count = 0; ButtonManager::irq(); + irq_count++; } } diff --git a/ButtonManager.h b/ButtonManager.h index d017f65..a59dd74 100644 --- a/ButtonManager.h +++ b/ButtonManager.h @@ -21,6 +21,7 @@ #pragma once +#include #include "Task.h" #include "ReturnCode.h" @@ -59,7 +60,7 @@ public: NOT_PRESSED }; - typedef void (*ChangeCallback)(ButtonState); + using ChangeCallback = std::function; Common::Schedule::NextTime execute() override; diff --git a/DisplayDriver.cpp b/DisplayDriver.cpp index fe83575..4d652f1 100644 --- a/DisplayDriver.cpp +++ b/DisplayDriver.cpp @@ -76,7 +76,7 @@ void DisplayDriver::set_dirty(unsigned int y) } } -void DisplayDriver::set_bit(unsigned int x, unsigned int y, uint8_t val) +void DisplayDriver::set_bit(uint32_t x, uint32_t y, uint8_t val) { if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) { return; @@ -93,7 +93,7 @@ void DisplayDriver::set_bit(unsigned int x, unsigned int y, uint8_t val) set_dirty(y); } -void DisplayDriver::set_byte(unsigned int x, unsigned int y, uint8_t val) +void DisplayDriver::set_byte(uint32_t x, uint32_t y, uint8_t val) { // if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) { // return; @@ -129,9 +129,9 @@ unsigned char ReverseBitsLookupTable(unsigned char v) * (obviously) requires that everything is aligned correctly. */ -void DisplayDriver::clear_glyph_aligned(int x_off, int y_off, const struct font *, const struct glyph *g) +void DisplayDriver::clear_glyph_aligned(uint32_t x_off, uint32_t y_off, const struct font *, const struct glyph *g) { - for (int16_t y = y_off; y < y_off + g->rows && y < DISPLAY_HEIGHT; y++) { + for (uint32_t y = y_off; y < y_off + g->rows && y < DISPLAY_HEIGHT; y++) { uint8_t *start = (uint8_t *) &m_buffer.lines[y].data[(x_off) >> 3]; uint8_t *end = (uint8_t *) &m_buffer.lines[y].data[(x_off + g->advance) >> 3]; memset(start, 0xFF, end - start); @@ -176,13 +176,13 @@ void DisplayDriver::clear_glyph_aligned(int x_off, int y_off, const struct font // } -void DisplayDriver::clear_glyph_unaligned(int x_off, int y_off, const struct font *font, const struct glyph *g) +void DisplayDriver::clear_glyph_unaligned(uint32_t x_off, uint32_t y_off, const struct font *font, const struct glyph *g) { int16_t x = 0; if (x & 7) { while ((x & 7) && x < g->advance) { // TODO: use a switch on (x & 7) instead? - for (int16_t y = 0; y < g->rows && y < DISPLAY_HEIGHT; y++) { + for (int16_t y = 0; y < g->rows && y < (int16_t) DISPLAY_HEIGHT; y++) { set_bit(x_off + x, y_off + y + font->size - g->top, 0); } x++; @@ -190,7 +190,7 @@ void DisplayDriver::clear_glyph_unaligned(int x_off, int y_off, const struct fon } while (g->advance - x > 0) { - for (int16_t y = 0; y < g->rows && y < DISPLAY_HEIGHT; y++) { + for (int16_t y = 0; y < g->rows && y < (int16_t) DISPLAY_HEIGHT; y++) { set_bit(x_off + x, y_off + y + font->size - g->top, 0); } x++; @@ -199,7 +199,7 @@ void DisplayDriver::clear_glyph_unaligned(int x_off, int y_off, const struct fon } -void DisplayDriver::write_glyph_unaligned(int x_off, int y_off, const struct font *font, const struct glyph *g) +void DisplayDriver::write_glyph_unaligned(uint32_t x_off, uint32_t y_off, const struct font *font, const struct glyph *g) { int byte_cols = g->cols / 8; if (g->cols & 7) { @@ -241,7 +241,7 @@ void DisplayDriver::write_glyph_unaligned(int x_off, int y_off, const struct fon * This variant is ~4x faster than the unaligned version, but * requires that everything is aligned correctly. */ -void DisplayDriver::write_glyph_aligned(int x_off, int y_off, +void DisplayDriver::write_glyph_aligned(uint32_t x_off, uint32_t y_off, const struct font *font, const struct glyph *g) { int byte_cols = g->cols / 8; @@ -261,20 +261,25 @@ void DisplayDriver::write_glyph_aligned(int x_off, int y_off, } } -void DisplayDriver::char_at(int *x_off, int y_off, char c, const struct font *font) +void DisplayDriver::char_at(uint32_t *x_off, uint32_t y_off, char c, const struct font *font) { const struct glyph *g = glyph_for_char(font, c); if (g == NULL) { return; } + if (*x_off + g->left + g->cols > DISPLAY_WIDTH) { + return; + } + if (!(*x_off & 7) && !((*x_off + g->advance) & 7)) { clear_glyph_aligned(*x_off, y_off, font, g); } else { clear_glyph_unaligned(*x_off, y_off, font, g); } - // TODO: Check the right glyph boundary (g->left + g->cols) + // FIXME: REALLY DO THIS! + // Check the right glyph boundary (g->left + g->cols) if (!((*x_off + g->left) & 7)) { write_glyph_aligned(*x_off, y_off, font, g); } else { @@ -287,15 +292,24 @@ void DisplayDriver::char_at(int *x_off, int y_off, char c, const struct font *fo *x_off += g->advance; } -void DisplayDriver::string_at(int x_off, int y_off, const char *string, const struct font *font) +void DisplayDriver::string_at(uint32_t *x_off, uint32_t y_off, const char *string, const struct font *font) { int i = 0; while (string[i]) { - char_at(&x_off, y_off, string[i], font); + char_at(x_off, y_off, string[i], font); i++; } } +// TODO: Implement this +// void DisplayDriver::rect_at(int x_off, int y_off, +// int width, int height, +// bool is_black, +// int line_width) +// { + +// } + void DisplayDriver::refresh() { if (!m_is_dirty) { @@ -325,20 +339,25 @@ void DisplayDriver::clear() //TODO: put me somewhere fonty const struct glyph *DisplayDriver::glyph_for_char(const struct font *font, char c) { - // TODO: This is almost the least efficient way imaginable to implement this - for (int i = 0; i < font->max; i++) { - const struct glyph *g = font->glyphs[i]; - if (g == NULL) { - continue; - } + std::size_t low = 0; + std::size_t high = font->count - 1; + while (low <= high) { + std::size_t mid = (high - low) / 2; + mid += low; + + const struct glyph *g = font->glyphs[mid]; if (g->glyph == c) { return g; } - } + if (g->glyph > c) { + high = mid - 1; + } else { + low = mid + 1; + } + } return NULL; } - } diff --git a/DisplayDriver.h b/DisplayDriver.h index 4ad3d62..209d5d5 100644 --- a/DisplayDriver.h +++ b/DisplayDriver.h @@ -42,10 +42,11 @@ public: /** * DisplayDriver */ - void set_bit(unsigned int x, unsigned int y, uint8_t val); - void set_byte(unsigned int x, unsigned int y, uint8_t val); - void char_at(int *x_off, int y_off, char c, const struct font *font); - void string_at(int x_off, int y_off, const char *string, const struct font *font); + void set_bit(uint32_t x, uint32_t y, uint8_t val); + void set_byte(uint32_t x, uint32_t y, uint8_t val); + void char_at(uint32_t *x_off, uint32_t y_off, char c, const struct font *font); + void string_at(uint32_t *x_off, uint32_t y_off, + const char *string, const struct font *font); void refresh(); void clear(); @@ -61,10 +62,14 @@ private: void buffer_init(); void set_dirty(unsigned int y); const struct glyph *glyph_for_char(const struct font *font, char c); - void clear_glyph_aligned(int x_off, int y_off, const struct font * font, const struct glyph *g); - void clear_glyph_unaligned(int x_off, int y_off, const struct font * font, const struct glyph *g); - void write_glyph_aligned(int x_off, int y_off, const struct font * font, const struct glyph *g); - void write_glyph_unaligned(int x_off, int y_off, const struct font * font, const struct glyph *g); + void clear_glyph_aligned(uint32_t x_off, uint32_t y_off, + const struct font * font, const struct glyph *g); + void clear_glyph_unaligned(uint32_t x_off, uint32_t y_off, + const struct font * font, const struct glyph *g); + void write_glyph_aligned(uint32_t x_off, uint32_t y_off, + const struct font * font, const struct glyph *g); + void write_glyph_unaligned(uint32_t x_off, uint32_t y_off, + const struct font * font, const struct glyph *g); static constexpr uint32_t DISPLAY_WIDTH = 144; static constexpr uint32_t DISPLAY_HEIGHT = 168; diff --git a/DisplayTimeTask.cpp b/DisplayTimeScreen.cpp similarity index 52% rename from DisplayTimeTask.cpp rename to DisplayTimeScreen.cpp index f796d56..fea2404 100644 --- a/DisplayTimeTask.cpp +++ b/DisplayTimeScreen.cpp @@ -19,7 +19,8 @@ * THE SOFTWARE. */ -#include "DisplayTimeTask.h" +#include "DisplayTimeScreen.h" +#include "RtcDriver.h" #include "SystemTime.h" #include "font-notomono-29.h" @@ -29,14 +30,16 @@ using Common::ReturnCode; using Common::Time; using Common::Schedule::NextTime; -static const font &font_large = font_notomono_68; -static const font &font_default = font_notomono_29; +static const struct font &font = font_notomono_68; -DisplayTimeTask::DisplayTimeTask(BSP::DisplayDriver &driver) +DisplayTimeScreen::DisplayTimeScreen(BSP::DisplayDriver &driver, + ScreenManager &manager, + Screen &menu_screen) : m_driver(driver) - , m_has_cleared(false) , m_last_time() - , m_display_seconds(true) + , m_manager(manager) + , m_menu_screen(menu_screen) + , m_display_seconds(false) {} static char get_char_for_digit(uint8_t bcd_digit) @@ -47,10 +50,8 @@ static char get_char_for_digit(uint8_t bcd_digit) return bcd_digit + '0'; } - -ReturnCode DisplayTimeTask::init() +ReturnCode DisplayTimeScreen::init() { - SET_TO(GPIOA->MODER, GPIO_MODER_MODE0, 1u << GPIO_MODER_MODE1_Pos); GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; @@ -60,7 +61,7 @@ ReturnCode DisplayTimeTask::init() return ReturnCode::OK; } -void DisplayTimeTask::display_number(uint32_t x, uint32_t y, uint32_t tens, uint32_t ones, const font &f) +void DisplayTimeScreen::display_number(uint32_t *x, uint32_t y, uint32_t tens, uint32_t ones, const struct font &f) { char time_str[3] = { 0 }; @@ -71,68 +72,69 @@ void DisplayTimeTask::display_number(uint32_t x, uint32_t y, uint32_t tens, uint m_driver.string_at(x, y, time_str, &f); } -void DisplayTimeTask::display_time() +void DisplayTimeScreen::display_time() { - BSP::time_bcd time; + Common::WallClockTime time; BSP::RtcDriver::get_time(time); - if (!m_has_cleared) { - m_driver.clear(); + // FIXME: Don't clear every redraw. Something is broken on screen + // switching. enable/disable not called? + m_driver.clear(); + + uint32_t x = 0; + const uint32_t y_space = (m_driver.get_height() - (2 * font.size)) / 3; + const uint32_t x_space = (m_driver.get_width() - (2 * 54)) / 2; + + if (m_last_time.get_hours_24() != time.get_hours_24()) { + x = x_space; + display_number(&x, y_space, + time.get_hours_12_tens(), time.get_hours_12_ones(), font); } - int i = 0; - - SET(GPIOA->ODR, GPIO_ODR_OD1); - CLR(GPIOA->ODR, GPIO_ODR_OD1); - - if (m_last_time.hour_tens != time.hour_tens || - m_last_time.hour_ones != time.hour_ones || - !m_has_cleared) { - display_number(8, (i * font_default.size) + (i + 1) * 4, time.hour_tens, time.hour_ones, font_default); + if (m_last_time.get_minutes() != time.get_minutes()) { + x = x_space; + display_number(&x, y_space * 2 + font.size, + time.get_minutes_tens(), time.get_minutes_ones(), font); } - SET(GPIOA->ODR, GPIO_ODR_OD1); - CLR(GPIOA->ODR, GPIO_ODR_OD1); - - i++; - if (m_last_time.minute_tens != time.minute_tens || - m_last_time.minute_ones != time.minute_ones || - !m_has_cleared) { - display_number(8, (i * font_default.size) + (i + 1) * 4, time.minute_tens, time.minute_ones, font_default); - } - - SET(GPIOA->ODR, GPIO_ODR_OD1); - CLR(GPIOA->ODR, GPIO_ODR_OD1); - - i++; if (m_display_seconds) { - if (m_last_time.second_tens != time.second_tens || - m_last_time.second_ones != time.second_ones || - !m_has_cleared) { - display_number(8, (i * font_default.size) + (i + 1) * 4, time.second_tens, time.second_ones, font_default); - } + // display_number(&x, y, + // time.get_seconds_tens(), time.get_seconds_ones(), font_default); } - // m_has_cleared = true; m_last_time = time; - - SET(GPIOA->ODR, GPIO_ODR_OD1); - CLR(GPIOA->ODR, GPIO_ODR_OD1); - m_driver.refresh(); - - SET(GPIOA->ODR, GPIO_ODR_OD1); - CLR(GPIOA->ODR, GPIO_ODR_OD1); } -NextTime DisplayTimeTask::execute() +NextTime DisplayTimeScreen::execute() { + display_time(); Common::time_t now; BSP::SystemTimer::get_time(now); - uint32_t next_secs = Time::to_seconds(now) + 1; - - display_time(); - + uint32_t next_secs = Time::to_seconds(now) + (m_display_seconds ? 1 : 60); return NextTime::at(Time::seconds(next_secs)); } + + +void DisplayTimeScreen::enable() { + m_driver.clear(); + m_last_time = {}; +} + +void DisplayTimeScreen::disable() { + m_driver.clear(); +} + +void DisplayTimeScreen::notify_up_button() { + /* TODO: This should open a menu first */ + m_manager.push_screen(m_menu_screen); +} + +void DisplayTimeScreen::notify_middle_button() { + +} + +void DisplayTimeScreen::notify_down_button() { + +} diff --git a/DisplayTimeTask.h b/DisplayTimeScreen.h similarity index 66% rename from DisplayTimeTask.h rename to DisplayTimeScreen.h index c3406bd..f78aa0e 100644 --- a/DisplayTimeTask.h +++ b/DisplayTimeScreen.h @@ -23,27 +23,36 @@ #include "macros.h" -#include "DisplayDriver.h" -#include "ReturnCode.h" -#include "Task.h" -#include "RtcDriver.h" +#include "ScreenManager.h" +#include "Screen.h" -class DisplayTimeTask : public Common::Schedule::Task { +class DisplayTimeScreen : public Screen { public: - DisplayTimeTask(BSP::DisplayDriver &display); + DisplayTimeScreen(BSP::DisplayDriver &display, + ScreenManager &m_manager, + Screen &m_menu_screen); Common::ReturnCode init(); - Common::Schedule::NextTime execute(); + Common::Schedule::NextTime execute() override; + + void enable() override; + void disable() override; + + void notify_up_button() override; + void notify_middle_button() override; + void notify_down_button() override; private: void display_time(); - void display_number(uint32_t x, uint32_t y, uint32_t tens, uint32_t ones, const font &f); + void display_number(uint32_t *x, uint32_t y, uint32_t tens, uint32_t ones, const font &f); BSP::DisplayDriver &m_driver; - bool m_has_cleared; - BSP::time_bcd m_last_time; + Common::WallClockTime m_last_time; + + ScreenManager &m_manager; + Screen &m_menu_screen; const bool m_display_seconds; }; diff --git a/LowPowerTaskScheduler.h b/LowPowerTaskScheduler.h index 28bdb62..6c3fdee 100644 --- a/LowPowerTaskScheduler.h +++ b/LowPowerTaskScheduler.h @@ -48,12 +48,25 @@ public: } } - void add_task(Task &task, NextTime &time) override + void add_task(Task &task, const NextTime &time) override { if (m_task_count == MAX_TASKS || time.get_type() == ScheduleType::NEVER) { return; } + // If the task is already in the task list, don't add, but update + for (size_t i = 0; i < m_task_count; i++) { + TaskEvent &event = m_tasks[i]; + if (event.m_task == &task) { + // Task is already in the list + if (time < event.m_time) { + // Provided time is sooner than the existing time. Update. + event.m_time = time; + } + return; + } + } + m_tasks[m_task_count++] = TaskEvent(task, time); } @@ -116,10 +129,10 @@ private: } if (!execed && (next_time - time > Time::millis(2))) { - Common::ReturnCode rc = BSP::RtcDriver::set_wakeup_in(next_time - time); - if (rc == Common::ReturnCode::OK) { - BSP::LowPower::stop(); - } + // Common::ReturnCode rc = BSP::RtcDriver::set_wakeup_in(next_time - time); + // if (rc == Common::ReturnCode::OK) { + // BSP::LowPower::stop(); + // } } m_cycle_count++; diff --git a/Makefile b/Makefile index ffed375..95eac4a 100644 --- a/Makefile +++ b/Makefile @@ -73,10 +73,11 @@ OUTPUT_ELF ?= $(OUTPUT_NAME).elf DEVICE_DEFINE = $(subst XX,xx,$(shell echo $(DEVICE_FAMILY) | tr '[:lower:]' '[:upper:]')) -CPU_FLAGS = -mthumb -mcpu=cortex-m0 -mfloat-abi=soft +CPU_FLAGS = -mthumb -mcpu=cortex-m0plus -mfloat-abi=soft # C pedantism -CFLAGS = -Wall -Wextra -Wpedantic +CFLAGS = -Wall -Wextra -Wpedantic -Werror +CXX_FLAGS = -Wsuggest-override -Wsuggest-final-methods -Wsuggest-final-types # Debug/optimization CFLAGS += -Os -ggdb -g3 CFLAGS += -fdata-sections -ffunction-sections @@ -90,7 +91,7 @@ CFLAGS += -I./lib/stm32/$(DEVICE_LINE)/Include CFLAGS += -I./lib/CMSIS/Core/Include CFLAGS += -I./lib/fonts/ -CXX_FLAGS = -std=c++14 -fno-exceptions -fno-rtti +CXX_FLAGS += -std=c++14 -fno-exceptions -fno-rtti # Startup Definitions ASFLAGS += $(CPU_FLAGS) @@ -130,7 +131,7 @@ $(OUTPUT_BIN): $(OUTPUT_ELF) $(OUTPUT_ELF): $(LINKER_SCRIPT) $(OBJS) @echo "LD $@" - $(LD) -T $(LINKER_SCRIPT) $(LDFLAGS) -o $(OUTPUT_ELF) $(OBJS) + @$(LD) -T $(LINKER_SCRIPT) $(LDFLAGS) -o $(OUTPUT_ELF) $(OBJS) # # Utilities @@ -141,7 +142,7 @@ STM32FLASH_DEVICE = /dev/ttyUSB0 .PHONY: flash flash: $(OUTPUT_BIN) @echo "FLASH $(OUTPUT_BIN)" - $(STM32_PROG) -vb 3 --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go + $(STM32_PROG) --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go .PHONY: clean clean: diff --git a/RtcDriver.cpp b/RtcDriver.cpp index 3b89402..317730f 100644 --- a/RtcDriver.cpp +++ b/RtcDriver.cpp @@ -21,6 +21,7 @@ #include "RtcDriver.h" #include "macros.h" +#include namespace BSP { @@ -29,6 +30,13 @@ using Common::time_t; RtcDriver::RtcSystemTimer RtcDriver::m_sys_timer; +ReturnCode RtcDriver::init() +{ + init_hw(); + + return ReturnCode::OK; +} + void RtcDriver::enable_rtc_write() { /*ALRMAR, RTC_ALRMAR_MSK4 | RTC_ALRMAR_MSK3 | RTC_ALRMAR_MSK2 | RTC_ALRMAR_MSK1); // Only calculate alarms when second rolls over - SET_TO(RTC->ALRMASSR, RTC_ALRMASSR_MASKSS, 0); + SET_TO(RTC->ALRMASSR, RTC_ALRMASSR_MASKSS, RTC_ALRMASSR_MASKSS); SET(RTC->CR, RTC_CR_ALRAE | RTC_CR_ALRAIE); SET(EXTI->IMR, EXTI_IMR_IM17); @@ -84,7 +92,8 @@ ReturnCode RtcDriver::init_hw() /*PRER, RTC_PRER_PREDIV_A, 0); + // XXX reset to 0, this is to enable easier debugging + SET_TO(RTC->PRER, RTC_PRER_PREDIV_A, 0); /*PRER, RTC_PRER_PREDIV_S, (LSE_CLOCK_FREQ - 1)); @@ -121,32 +130,60 @@ ReturnCode RtcDriver::init_hw() return ReturnCode::OK; } -ReturnCode RtcDriver::get_time(time_bcd &tm_bcd) +ReturnCode RtcDriver::get_time(Common::WallClockTime &wall_time) { /*DR; + // TODO: reread TR + PM for consistency uint32_t time = RTC->TR; + unsigned int hours = 0, minutes = 0, seconds = 0; - tm_bcd.hour_tens = STM32_GET_FIELD(time, RTC_TR_HT); - tm_bcd.hour_ones = STM32_GET_FIELD(time, RTC_TR_HU); + hours += 10 * STM32_GET_FIELD(time, RTC_TR_HT); + hours += STM32_GET_FIELD(time, RTC_TR_HU); - tm_bcd.minute_tens = STM32_GET_FIELD(time, RTC_TR_MNT); - tm_bcd.minute_ones = STM32_GET_FIELD(time, RTC_TR_MNU); + minutes += 10 * STM32_GET_FIELD(time, RTC_TR_MNT); + minutes += STM32_GET_FIELD(time, RTC_TR_MNU); - tm_bcd.second_tens = STM32_GET_FIELD(time, RTC_TR_ST); - tm_bcd.second_ones = STM32_GET_FIELD(time, RTC_TR_SU); + seconds += 10 * STM32_GET_FIELD(time, RTC_TR_ST); + seconds += STM32_GET_FIELD(time, RTC_TR_SU); - tm_bcd.pm = STM32_GET_FIELD(time, RTC_TR_PM); + if (STM32_GET_FIELD(time, RTC_TR_PM)) { + hours += 12; + } + + new (&wall_time) Common::WallClockTime(hours, minutes, seconds); return ReturnCode::OK; } -ReturnCode RtcDriver::init() +ReturnCode RtcDriver::set_time(const Common::WallClockTime &wall_time) { - init_hw(); + enable_rtc_write(); + + RTC->ISR = RTC_ISR_INIT; + while (!(RTC->ISR & RTC_ISR_INITF)) {} + + /*CR, RTC_CR_FMT); + + uint32_t time = 0; + SET_TO(time, RTC_TR_PM, wall_time.get_is_pm() << RTC_TR_PM_Pos); + SET_TO(time, RTC_TR_HT, wall_time.get_hours_12_tens() << RTC_TR_HT_Pos); + SET_TO(time, RTC_TR_HU, wall_time.get_hours_12_ones() << RTC_TR_HU_Pos); + SET_TO(time, RTC_TR_MNT, wall_time.get_minutes_tens() << RTC_TR_MNT_Pos); + SET_TO(time, RTC_TR_MNU, wall_time.get_minutes_ones() << RTC_TR_MNU_Pos); + SET_TO(time, RTC_TR_ST, wall_time.get_seconds_tens() << RTC_TR_ST_Pos); + SET_TO(time, RTC_TR_SU, wall_time.get_seconds_tens() << RTC_TR_SU_Pos); + RTC->TR = time; + + CLR(RTC->ISR, RTC_ISR_INIT); + + disable_rtc_write(); return ReturnCode::OK; } @@ -156,7 +193,8 @@ ReturnCode RtcDriver::set_wakeup_in(Common::time_t wakeup_delay) /* 17.85 years, so this is fine. */ - uint64_t delay_cycles = Common::Time::to_micros(wakeup_delay) * LSE_CLOCK_FREQ / Common::Time::MICROS_PER_SEC; + uint64_t delay_cycles = Common::Time::to_micros(wakeup_delay) * LSE_CLOCK_FREQ / + Common::Time::MICROS_PER_SEC; enable_rtc_write(); @@ -203,23 +241,24 @@ ReturnCode RtcDriver::set_wakeup_in(Common::time_t wakeup_delay) return ReturnCode::OK; } -Common::time_t RtcDriver::RtcSystemTimer::get_time() -{ - uint32_t new_secs, old_secs, ssr, subsecond; - do { - old_secs = m_seconds; - ssr = RTC->SSR & 0xFFFF; - new_secs = m_seconds; - } while (new_secs != old_secs); + Common::time_t RtcDriver::RtcSystemTimer::get_time() + { + uint32_t new_secs, old_secs, ssr, subsecond; + do { + old_secs = m_seconds; + ssr = RTC->SSR & 0xFFFF; + new_secs = m_seconds; + } while (new_secs != old_secs); - new_secs = new_secs * LSE_CLOCK_FREQ; - /** SSR is a countdown register */ - subsecond = (new_secs + LSE_CLOCK_FREQ - 1 - ssr) * Common::Time::MILLIS_PER_SEC / LSE_CLOCK_FREQ; - return Common::Time::millis(subsecond); + new_secs = new_secs * LSE_CLOCK_FREQ; + /** SSR is a countdown register */ + subsecond = (new_secs + LSE_CLOCK_FREQ - 1 - ssr) * Common::Time::MILLIS_PER_SEC / LSE_CLOCK_FREQ; + return Common::Time::millis(subsecond); } void RtcDriver::RtcSystemTimer::increment_seconds() { + /** TODO: Atomic increment */ m_seconds++; } diff --git a/RtcDriver.h b/RtcDriver.h index 0a8b742..73dd7fe 100644 --- a/RtcDriver.h +++ b/RtcDriver.h @@ -31,16 +31,6 @@ namespace BSP { -struct time_bcd { - uint8_t hour_tens; - uint8_t hour_ones; - uint8_t minute_tens; - uint8_t minute_ones; - uint8_t second_tens; - uint8_t second_ones; - bool pm; -}; - class RtcDriver { public: @@ -52,7 +42,8 @@ public: }; static Common::ReturnCode init(); static void increment_seconds(); - static Common::ReturnCode get_time(time_bcd &tm_bcd); + static Common::ReturnCode get_time(Common::WallClockTime &tm_bcd); + static Common::ReturnCode set_time(const Common::WallClockTime &tm_bcd); static Common::ReturnCode set_wakeup_in(Common::time_t wakeup_delay); private: @@ -80,8 +71,7 @@ private: void increment_seconds(); private: - /** I'll be dead before this rolls over */ - /** FIXME FIXME FIXME: XXX This should be an atomic */ + /** ~136 years. Good enough for me. */ uint32_t m_seconds; static constexpr uint32_t LSE_CLOCK_FREQ = 32768; diff --git a/DisplayManager.h b/Screen.h similarity index 80% rename from DisplayManager.h rename to Screen.h index 3d86ebb..982d880 100644 --- a/DisplayManager.h +++ b/Screen.h @@ -21,23 +21,16 @@ #pragma once -#include "macros.h" - -#include "DisplayDriver.h" #include "ReturnCode.h" #include "Task.h" -class ScreenManager : public Common::Schedule::Task { +class Screen : public Common::Schedule::Task { public: - ScreenManager(BSP::DisplayDriver &display); + virtual void enable() = 0; + virtual void disable() = 0; - Common::Schedule::NextTime execute(); - -private: - - void display_menu(); - - BSP::DisplayDriver &m_driver; - bool m_displayed; + virtual void notify_up_button() = 0; + virtual void notify_middle_button() = 0; + virtual void notify_down_button() = 0; }; diff --git a/ScreenManager.cpp b/ScreenManager.cpp new file mode 100644 index 0000000..f8a4584 --- /dev/null +++ b/ScreenManager.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 Max Regan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ScreenManager.h" +#include "ButtonManager.h" + +// TODO: Basically all of the error checking + +using Common::Schedule::Task; +using Common::Schedule::NextTime; +using Common::ReturnCode; + +ScreenManager::ScreenManager(Common::Schedule::TaskScheduler &scheduler, + BSP::DisplayDriver &display, + BSP::ButtonManager &buttons) + : m_scheduler(scheduler) + , m_screen_stack{nullptr} + , m_screen_stack_depth(0) + , m_display(display) + , m_buttons(buttons) +{ +} + +ReturnCode ScreenManager::init() +{ + m_buttons.set_callback( + BSP::ButtonManager::Button::UP, + [this](BSP::ButtonManager::ButtonState state) { + if (state == BSP::ButtonManager::ButtonState::PRESSED) { + current_screen()->notify_up_button(); + }}); + + m_buttons.set_callback( + BSP::ButtonManager::Button::MID, + [this](BSP::ButtonManager::ButtonState state) { + if (state == BSP::ButtonManager::ButtonState::PRESSED) { + return current_screen()->notify_middle_button(); + }}); + + m_buttons.set_callback( + BSP::ButtonManager::Button::DOWN, + [this](BSP::ButtonManager::ButtonState state) { + if (state == BSP::ButtonManager::ButtonState::PRESSED) { + current_screen()->notify_down_button(); + }}); + + m_display.clear(); + + return ReturnCode::OK; +} + +NextTime ScreenManager::execute() +{ + // TODO: Handle updating execute times when the screen is swapped out. + return current_screen()->execute(); +} + +ReturnCode ScreenManager::set_root_screen(Screen &screen) +{ + m_screen_stack[m_screen_stack_depth] = &screen; + m_screen_stack_depth = 1; + current_screen()->enable(); + m_scheduler.add_task(*this, NextTime::asap()); + + return ReturnCode::OK; +} + +ReturnCode ScreenManager::push_screen(Screen &screen) +{ + if (m_screen_stack_depth == MAX_SCREEN_STACK) { + return ReturnCode::FAIL; + } + + // TODO: "Lock" changes during this operation. Don't allow + // disable/enable to push/pop. + + current_screen()->disable(); + m_screen_stack[m_screen_stack_depth] = &screen; + m_screen_stack_depth++; + current_screen()->enable(); + m_scheduler.add_task(*this, NextTime::asap()); + + return ReturnCode::OK; +} + +ReturnCode ScreenManager::pop_screen() { + if (m_screen_stack_depth == 1) { + return ReturnCode::FAIL; + } + + current_screen()->disable(); + m_screen_stack_depth--; + m_screen_stack[m_screen_stack_depth] = nullptr; + current_screen()->enable(); + m_scheduler.add_task(*this, NextTime::asap()); + + return ReturnCode::OK; +} + +ReturnCode ScreenManager::set_screen(Screen &screen) { + if (m_screen_stack_depth == 1) { + return ReturnCode::FAIL; + } + + current_screen()->disable(); + m_screen_stack[m_screen_stack_depth - 1] = &screen; + current_screen()->enable(); + m_scheduler.add_task(*this, NextTime::asap()); + + return ReturnCode::OK; +} diff --git a/ScreenManager.h b/ScreenManager.h new file mode 100644 index 0000000..8c2c368 --- /dev/null +++ b/ScreenManager.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 Max Regan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include + +#include "macros.h" + +#include "DisplayDriver.h" +#include "ButtonManager.h" +#include "ReturnCode.h" +#include "Screen.h" +#include "Task.h" + +class ScreenManager : public Common::Schedule::Task { +public: + + ScreenManager(Common::Schedule::TaskScheduler &scheduler, + BSP::DisplayDriver &display, + BSP::ButtonManager &buttons); + + Common::ReturnCode init(); + Common::ReturnCode set_root_screen(Screen &screen); + + Common::Schedule::NextTime execute() override; + + Common::ReturnCode pop_screen(); + Common::ReturnCode push_screen(Screen &screen); + Common::ReturnCode set_screen(Screen &screen); + +private: + + inline Screen *current_screen() { + return m_screen_stack[m_screen_stack_depth - 1]; + } + void notify_up_screen(); + void notify_middle_screen(); + void notify_down_screen(); + + static constexpr std::size_t MAX_SCREEN_STACK = 5; + + Common::Schedule::TaskScheduler &m_scheduler; + + std::array m_screen_stack; + std::size_t m_screen_stack_depth; + + BSP::DisplayDriver &m_display; + BSP::ButtonManager &m_buttons; +}; diff --git a/SetTimeScreen.cpp b/SetTimeScreen.cpp new file mode 100644 index 0000000..6dd4101 --- /dev/null +++ b/SetTimeScreen.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 Max Regan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "SetTimeScreen.h" +#include "SystemTime.h" +#include "RtcDriver.h" + +#include "font-notomono-29.h" + +using Common::ReturnCode; +using Common::Time; +using Common::Schedule::NextTime; + +static const font &font = font_notomono_29; + +SetTimeScreen::SetTimeScreen(BSP::DisplayDriver &display, + ScreenManager &manager) + : m_display(display) + , m_manager(manager) + , m_state(SetState::HOURS) + , m_is_acked(false) + , m_time() +{} + +ReturnCode SetTimeScreen::init() +{ + return ReturnCode::OK; +} + +NextTime SetTimeScreen::execute() +{ + //TODO: Fix this so it doesn't constantly refresh + refresh(); + + return NextTime::never(); +} + +static char get_char_for_digit(uint8_t bcd_digit) +{ + if (bcd_digit > 9) { + return '0'; + } + return bcd_digit + '0'; +} + +void SetTimeScreen::display_number(uint32_t *x, uint32_t y, + uint32_t tens, uint32_t ones, const struct font &f) +{ + char time_str[3] = { 0 }; + time_str[0] = get_char_for_digit(tens); + time_str[1] = get_char_for_digit(ones); + time_str[2] = '\0'; + + m_display.string_at(x, y, time_str, &f); +} + +void SetTimeScreen::render_time() +{ + uint32_t x = 0; + uint32_t y = 32; + + display_number(&x, y, + m_time.get_hours_12_tens(), m_time.get_hours_12_ones(), + font); + + display_number(&x, y, + m_time.get_minutes_tens(), m_time.get_minutes_ones(), + font); + + display_number(&x, y, + m_time.get_seconds_tens(), m_time.get_seconds_ones(), + font); + + m_display.refresh(); +} + +void SetTimeScreen::draw_line(uint32_t x, uint32_t y, uint32_t width) +{ + for (uint32_t i = 0; i < width; i += 8) { + m_display.set_byte(x + i, y, 0); + m_display.set_byte(x + i, y + 1, 0); + } +} + +void SetTimeScreen::render_selection() +{ + switch (m_state) { + case SetState::HOURS: + draw_line(0, 64, 24 * 2); + break; + case SetState::MINUTES: + draw_line(48, 64, 24 * 2); + break; + case SetState::SECONDS: + draw_line(96, 64, 24 * 2); + break; + case SetState::AM_PM: + break; + case SetState::ACK_NACK: + break; + } +} + +void SetTimeScreen::refresh() +{ + m_display.clear(); + + render_time(); + render_selection(); + + m_display.refresh(); +} + +void SetTimeScreen::enable() +{ + + BSP::RtcDriver::get_time(m_time); + m_state = SetState::HOURS; + m_is_acked = true; + + refresh(); +} + +void SetTimeScreen::disable() +{ + m_display.clear(); +} + +void SetTimeScreen::notify_up_button() +{ + + switch (m_state) { + case SetState::HOURS: + m_time.increment_hours(); + break; + case SetState::MINUTES: + m_time.increment_minutes(); + break; + case SetState::SECONDS: + m_time.increment_seconds(); + break; + case SetState::AM_PM: + m_time.toggle_am_pm(); + break; + case SetState::ACK_NACK: + m_is_acked = !m_is_acked; + break; + } + + refresh(); +} + +void SetTimeScreen::notify_middle_button() +{ + switch (m_state) { + case SetState::HOURS: + m_state = SetState::MINUTES; + break; + case SetState::MINUTES: + m_state = SetState::SECONDS; + break; + case SetState::SECONDS: + m_state = SetState::AM_PM; + break; + case SetState::AM_PM: + m_state = SetState::ACK_NACK; + break; + case SetState::ACK_NACK: + if (m_is_acked) { + BSP::RtcDriver::set_time(m_time); + } + m_manager.pop_screen(); + break; + } + + refresh(); +} + +void SetTimeScreen::notify_down_button() +{ + switch(m_state) { + case SetState::HOURS: + m_time.decrement_hours(); + break; + case SetState::MINUTES: + m_time.decrement_minutes(); + break; + case SetState::SECONDS: + m_time.decrement_seconds(); + break; + case SetState::AM_PM: + m_time.toggle_am_pm(); + break; + case SetState::ACK_NACK: + m_is_acked = !m_is_acked; + break; + } + + refresh(); +} diff --git a/SetTimeScreen.h b/SetTimeScreen.h new file mode 100644 index 0000000..d907d9e --- /dev/null +++ b/SetTimeScreen.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 Max Regan + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "macros.h" + +#include "ScreenManager.h" +#include "DisplayDriver.h" +#include "ReturnCode.h" +#include "Task.h" +#include "Time.h" +#include "fontem.h" + +class SetTimeScreen : public Screen { +public: + + SetTimeScreen(BSP::DisplayDriver &display, + ScreenManager &m_manager); + + Common::ReturnCode init(); + Common::Schedule::NextTime execute() override; + + void enable() override; + void disable() override; + void notify_up_button() override; + void notify_middle_button() override; + void notify_down_button() override; + +private: + + void refresh(); + void render_time(); + void render_selection(); + void display_number(uint32_t *x, uint32_t y, uint32_t tens, uint32_t ones, const font &f); + void draw_line(uint32_t x, uint32_t y, uint32_t width); + enum class SetState { + HOURS, + MINUTES, + SECONDS, + AM_PM, + ACK_NACK + }; + + BSP::DisplayDriver &m_display; + ScreenManager &m_manager; + + SetState m_state; + bool m_is_acked = false; + Common::WallClockTime m_time; +}; diff --git a/Task.h b/Task.h index 78af57b..2963ccc 100644 --- a/Task.h +++ b/Task.h @@ -29,8 +29,8 @@ namespace Common { namespace Schedule { enum class ScheduleType { - AT_TIME, - NEVER, + AT_TIME = 0, + NEVER = 1, }; class NextTime { @@ -63,8 +63,38 @@ public: return { ScheduleType::AT_TIME, time }; } - inline ScheduleType get_type() { return m_type; } - inline time_t get_time() { return m_time; } + inline ScheduleType get_type() const { return m_type; } + inline time_t get_time() const { return m_time; } + + inline bool operator<(const NextTime other) const { + if (m_type < other.m_type) { + return true; + } else if (m_type == other.m_type) { + switch(m_type){ + case ScheduleType::AT_TIME: + return m_time < other.m_time; + case ScheduleType::NEVER: + return false; + } + return false; + } else { + return false; + } + } + + inline bool operator==(const NextTime other) const { + if (m_type != other.m_type) { + return false; + } else { + switch(m_type){ + case ScheduleType::AT_TIME: + return m_time == other.m_time; + case ScheduleType::NEVER: + return true; + } + return false; + } + } private: NextTime(ScheduleType type, time_t time) : diff --git a/TaskScheduler.h b/TaskScheduler.h index 3b0cfc1..a5e3950 100644 --- a/TaskScheduler.h +++ b/TaskScheduler.h @@ -31,7 +31,7 @@ namespace Schedule { class TaskScheduler { public: - virtual void add_task(Task &task, NextTime &time) = 0; + virtual void add_task(Task &task, const NextTime &time) = 0; [[noreturn]] virtual void run() = 0; protected: //virtual ~TaskScheduler() {} diff --git a/Time.h b/Time.h index 7ba97e9..b3377ba 100644 --- a/Time.h +++ b/Time.h @@ -79,4 +79,145 @@ public: } }; +class WallClockTime { + + // TODO: Implement a saturating counter? + +public: + WallClockTime() + : m_hours(0) + , m_minutes(0) + , m_seconds(0) + {} + + WallClockTime(uint8_t hours, uint8_t minutes, uint8_t seconds) + : m_hours(hours) + , m_minutes(minutes) + , m_seconds(seconds) + {} + + static inline uint8_t hour24_to_hour12(uint8_t hour24) { + if (hour24 == 0) { + return 12; + } else if (hour24 > 12) { + return hour24 - 12; + } else { + return hour24; + } + } + + static inline uint8_t hour24_is_am(uint8_t hour24) { + return hour24 >= 12; + } + + inline uint8_t get_hours_12() const { + return hour24_to_hour12(m_hours); + } + + inline uint8_t get_hours_12_tens() const { + return get_hours_12() / 10; + } + + inline uint8_t get_hours_12_ones() const { + return get_hours_12() % 10; + } + + inline bool get_is_pm() const { + return m_hours >= 12; + } + + inline uint8_t get_hours_24() const { + return m_hours; + } + + inline uint8_t get_hours_24_ones() const { + return m_hours % 10; + } + + inline uint8_t get_hours_24_tens() const { + return m_hours / 10; + } + + inline uint8_t get_minutes() const { + return m_minutes; + } + + inline uint8_t get_minutes_ones() const { + return m_minutes % 10; + } + + inline uint8_t get_minutes_tens() const { + return m_minutes / 10; + } + + inline uint8_t get_seconds() const { + return m_seconds; + } + + inline uint8_t get_seconds_ones() const { + return m_seconds % 10; + } + + inline uint8_t get_seconds_tens() const { + return m_seconds / 10; + } + + inline void increment_hours() { + m_hours++; + if (m_hours >= 24) { + m_hours = 0; + } + } + + inline void increment_minutes() { + m_minutes++; + if (m_minutes >= 60) { + m_minutes = 0; + } + } + + inline void increment_seconds() { + m_seconds++; + if (m_seconds >= 60) { + m_seconds = 0; + } + } + + inline void decrement_hours() { + if (m_hours == 0) { + m_hours = 23; + } else { + m_hours--; + } + } + + inline void decrement_minutes() { + if (m_minutes == 0) { + m_minutes = 59; + } else { + m_minutes--; + } + } + + inline void decrement_seconds() { + if (m_seconds == 0) { + m_seconds = 59; + } else { + m_seconds--; + } + } + + inline void toggle_am_pm() { + if (m_hours < 12) { + m_hours += 12; + } else { + m_hours -= 12; + } + } + +private: + uint8_t m_hours; + uint8_t m_minutes; + uint8_t m_seconds; +}; } diff --git a/main.cpp b/main.cpp index e97a62e..ff57028 100644 --- a/main.cpp +++ b/main.cpp @@ -23,10 +23,13 @@ #include "RtcDriver.h" #include "DisplayDriver.h" #include "SpiDriver.h" -#include "DisplayTimeTask.h" #include "LptimPwm.h" #include "ButtonManager.h" +#include "ScreenManager.h" +#include "DisplayTimeScreen.h" +#include "SetTimeScreen.h" + #include "stm32l0xx.h" #include "macros.h" @@ -37,8 +40,11 @@ static Common::Schedule::LowPowerTaskScheduler<10> g_sched; static BSP::SpiDriver g_spi(g_sched); static BSP::DisplayDriver g_display(g_sched, g_spi); static BSP::LptimPwm g_lptim_pwm(LPTIM1); -static BSP::ButtonManager g_btn_manager(0, 1, 3, Time::millis(1)); -static DisplayTimeTask g_display_time(g_display); +static BSP::ButtonManager g_btn_manager(2, 1, 0, Time::millis(1)); + +static ScreenManager g_screen_manager(g_sched, g_display, g_btn_manager); +static SetTimeScreen g_set_time_screen(g_display, g_screen_manager); +static DisplayTimeScreen g_display_time_screen(g_display, g_screen_manager, g_set_time_screen); extern "C" void __cxa_pure_virtual() { while(1) {} } @@ -97,7 +103,6 @@ void SystemInit() CLR(RCC->CFGR, RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL | RCC_CFGR_PLLDIV); - /*!< Disable all interrupts */ RCC->CIER = 0x00000000; @@ -133,45 +138,42 @@ static void _init(void) g_spi.init(); g_btn_manager.init(); g_display.init(); - g_display_time.init(); + g_screen_manager.init(); + g_screen_manager.set_root_screen(g_display_time_screen); // Enqueue each of the tasks Common::Schedule::NextTime asap = Common::Schedule::NextTime::asap(); g_sched.add_task(g_spi, asap); g_sched.add_task(g_btn_manager, asap); g_sched.add_task(g_display, asap); - g_sched.add_task(g_display_time, asap); + g_sched.add_task(g_screen_manager, asap); // And we're off! This will never return g_sched.run(); } -extern "C" void NMI_Handler() {while (1);} -extern "C" void HardFault_Handler() {while (1);} -extern "C" void SVC_Handler() {while (1);} -extern "C" void PendSV_Handler() {while (1);} -extern "C" void SysTick_Handler() {while (1);} +extern "C" void NMI_Handler() {while (1);} +extern "C" void HardFault_Handler() {while (1);} +extern "C" void SVC_Handler() {while (1);} +extern "C" void PendSV_Handler() {while (1);} +extern "C" void SysTick_Handler() {while (1);} -extern "C" void WWDG_IRQHandler() {while (1);} -extern "C" void PVD_IRQHandler() {while (1);} -extern "C" void WDT_IRQHandler() {while (1);} -//extern "C" void RTC_IRQHandler() {while (1);} -extern "C" void FLASH_IRQHandler() {while (1);} -extern "C" void RCC_CRS_IRQHandler() {while (1);} -// extern "C" void EXTI_1_0_IRQHandler() {while (1);} -// extern "C" void EXTI_3_2_IRQHandler() {while (1);} -// extern "C" void EXTI_15_4_IRQHandler() {while (1);} -extern "C" void DMA1_CHANNEL1_IRQHandler() {while (1);} -extern "C" void DMA1_CHANNEL3_2_IRQHandler() {while (1);} -extern "C" void DMA_CHANNEL_7_4_IRQHandler() {while (1);} -extern "C" void ADC_COMP_IRQHandler() {while (1);} -extern "C" void LPTIM1_IRQHandler() {while (1);} -extern "C" void USART4_USART5_IRQHandler() {while (1);} -extern "C" void TIM2_IRQHandler() {while (1);} -extern "C" void TIM3_IRQHandler() {while (1);} -extern "C" void TIM6_IRQHandler() {while (1);} -extern "C" void TIM7_IRQHandler() {while (1);} -extern "C" void TIM21_IRQHandler() {while (1);} -extern "C" void I2C3_IRQHandler() {while (1);} -extern "C" void TIM22_IRQHandler() {while (1);} +extern "C" void WWDG_IRQHandler() {while (1);} +extern "C" void PVD_IRQHandler() {while (1);} +extern "C" void WDT_IRQHandler() {while (1);} +extern "C" void FLASH_IRQHandler() {while (1);} +extern "C" void RCC_CRS_IRQHandler() {while (1);} +extern "C" void DMA1_CHANNEL1_IRQHandler() {while (1);} +extern "C" void DMA1_CHANNEL3_2_IRQHandler() {while (1);} +extern "C" void DMA_CHANNEL_7_4_IRQHandler() {while (1);} +extern "C" void ADC_COMP_IRQHandler() {while (1);} +extern "C" void LPTIM1_IRQHandler() {while (1);} +extern "C" void USART4_USART5_IRQHandler() {while (1);} +extern "C" void TIM2_IRQHandler() {while (1);} +extern "C" void TIM3_IRQHandler() {while (1);} +extern "C" void TIM6_IRQHandler() {while (1);} +extern "C" void TIM7_IRQHandler() {while (1);} +extern "C" void TIM21_IRQHandler() {while (1);} +extern "C" void I2C3_IRQHandler() {while (1);} +extern "C" void TIM22_IRQHandler() {while (1);}