From a7f1ffc1b5e9f51433d46f60d7a012b2cbcdf9ae Mon Sep 17 00:00:00 2001 From: Max Regan Date: Wed, 17 Apr 2019 21:51:35 -0700 Subject: [PATCH] Huge refactoring for C++ and low-power mode The display currently shows the time, with hours and minutes, and is capable of receiving input with buttons (though does nothing). It sleeps during intervals where nothing is happening. The display task runs once per second, and RTC alarm A is used for periodic alarms to update the system time. --- .gdbinit | 5 +- ButtonManager.cpp | 147 +++++++++++ ButtonManager.h | 100 +++++++ DisplayDriver.cpp | 38 ++- DisplayDriver.h | 13 +- DisplayManager.h | 43 +++ DisplayScreen.h | 30 +++ DisplayTimeTask.cpp | 90 +++++-- DisplayTimeTask.h | 11 +- LowPower.cpp | 15 +- ...TaskScheduler.h => LowPowerTaskScheduler.h | 27 +- LptimPwm.cpp | 111 ++++++++ LptimPwm.h | 42 +++ Makefile | 25 +- RtcDriver.cpp | 249 ++++++++++++++++++ RtcDriver.h | 93 +++++++ SystemTime.cpp | 134 +--------- SystemTime.h | 26 +- Time.h | 20 ++ macros.h | 2 - main.cpp | 59 ++++- stm32l031k6.ld | 2 +- 22 files changed, 1051 insertions(+), 231 deletions(-) create mode 100644 ButtonManager.cpp create mode 100644 ButtonManager.h create mode 100644 DisplayManager.h create mode 100644 DisplayScreen.h rename ConcreteTaskScheduler.h => LowPowerTaskScheduler.h (86%) create mode 100644 LptimPwm.cpp create mode 100644 LptimPwm.h create mode 100644 RtcDriver.cpp create mode 100644 RtcDriver.h diff --git a/.gdbinit b/.gdbinit index bbeee75..def27ca 100644 --- a/.gdbinit +++ b/.gdbinit @@ -1,6 +1,5 @@ -tar rem :4242 file watch.elf -load watch.elf +tar rem :4242 set $PERIPH_BASE = (uint32_t)0x40000000U set $APBPERIPH_BASE = $PERIPH_BASE @@ -11,6 +10,8 @@ set $RCC = (RCC_TypeDef *)(0x40000000 + 0x00020000U + 0x1000U) set $RTC = (RTC_TypeDef *)(0x40000000 + 0x00002800U) set $PWR = (PWR_TypeDef *)(0x40000000 + 0x00007000U) set $EXTI = (EXTI_TypeDef *) ($APBPERIPH_BASE + 0x00010400U) +set $GPIOA = (GPIO_TypeDef *)($IOPPERIPH_BASE + 0x00000000U) +set $LPTIM1 = (LPTIM_TypeDef *)($APBPERIPH_BASE + 0x00007C00U) set history filename .gdb_history set history save on diff --git a/ButtonManager.cpp b/ButtonManager.cpp new file mode 100644 index 0000000..78edf38 --- /dev/null +++ b/ButtonManager.cpp @@ -0,0 +1,147 @@ +/* + * 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 "ButtonManager.h" +#include "SystemTime.h" +#include "macros.h" +#include "stm32l0xx.h" + +namespace BSP { + +using Common::ReturnCode; +using Common::Schedule::NextTime; + +ButtonManager *ButtonManager::m_instance = nullptr; + +ReturnCode ButtonManager::init() +{ + for (auto &btn: m_buttons) { + /** Enable pin for input (pulled up) */ + CLR(GPIOA->MODER, 3u << (2 * btn.m_gpio_idx)); + CLR(GPIOA->OTYPER, 1u << btn.m_gpio_idx); + SET_TO(GPIOA->PUPDR, 3u << (2 * btn.m_gpio_idx), 1u << (2 * btn.m_gpio_idx)); + + // Unmask this interrupt + SET(EXTI->IMR, 1u << btn.m_gpio_idx); + // Enable this interrupt + SET(EXTI->EMR, 1u << btn.m_gpio_idx); + // Enable interrupt for rising edge + SET(EXTI->RTSR, 1u << btn.m_gpio_idx); + // Enable interrupt for falling edge + SET(EXTI->FTSR, 1u << btn.m_gpio_idx); + } + + NVIC_EnableIRQ(EXTI0_1_IRQn); + NVIC_EnableIRQ(EXTI2_3_IRQn); + NVIC_EnableIRQ(EXTI4_15_IRQn); + + NVIC_SetPriority(EXTI0_1_IRQn, 1); + NVIC_SetPriority(EXTI2_3_IRQn, 1); + NVIC_SetPriority(EXTI4_15_IRQn, 1); + + return ReturnCode::OK; +} + +NextTime ButtonManager::execute() +{ + // TODO: is this call too expensive for an interrupt handler? + Common::time_t systime; + BSP::SystemTimer::get_time(systime); + + + for (auto &btn: m_buttons) { + // Has the state changed? + if (btn.m_prev_call_state != btn.m_state) { + // Have we 'debounced' this state? + if (btn.m_state_change_ts + btn.m_debounce_time < systime) { + // It's tiiiiime + if (btn.m_callback != nullptr) { + btn.m_callback(btn.m_state); + } + btn.m_prev_call_state = btn.m_state; + } + } + } + + // TODO: Call less frequently, and let the buttonmanager re-add itself to the task list on interrupts + return NextTime::in(Common::Time::millis(1000)); +} + +void ButtonManager::set_callback(Button btn, ChangeCallback callback) +{ + if (btn == Button::Count) { + return; + } + + m_buttons[btn].m_callback = callback; +} + +void ButtonManager::remove_callback(Button btn) +{ + if (btn == Button::Count) { + return; + } + + m_buttons[btn].m_callback = nop_callback; +} + +void ButtonManager::irq() +{ + uint32_t idr = GPIOA->IDR; + Common::time_t systime; + + // TODO: is this call too expensive for an interrupt handler? + BSP::SystemTimer::get_time(systime); + + if (!m_instance) { + return; + } + + for (auto &btn: m_instance->m_buttons) { + 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)) { + btn.m_state = ButtonState::PRESSED; + btn.m_state_change_ts = systime; + } else if (!is_pressed && (btn.m_state == ButtonState::PRESSED)) { + btn.m_state = ButtonState::NOT_PRESSED; + btn.m_state_change_ts = systime; + } + + // Clear the event + SET(EXTI->PR, 1u << btn.m_gpio_idx); + } +} + +extern "C" void EXTI_1_0_IRQHandler() { + ButtonManager::irq(); +} + +extern "C" void EXTI_3_2_IRQHandler() { + ButtonManager::irq(); +} + +extern "C" void EXTI_15_4_IRQHandler() { + ButtonManager::irq(); +} + +} diff --git a/ButtonManager.h b/ButtonManager.h new file mode 100644 index 0000000..d017f65 --- /dev/null +++ b/ButtonManager.h @@ -0,0 +1,100 @@ +/* + * 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 "Task.h" +#include "ReturnCode.h" + +namespace BSP { + +class ButtonManager : public Common::Schedule::Task { +public: + ButtonManager(uint8_t up_gpio_idx, + uint8_t mid_gpio_idx, + uint8_t down_gpio_idx, + Common::time_t debounce_time) + : m_buttons + { + button_state(up_gpio_idx, debounce_time, ButtonManager::nop_callback), + button_state(mid_gpio_idx, debounce_time, ButtonManager::nop_callback), + button_state(down_gpio_idx, debounce_time, ButtonManager::nop_callback), + } + { + m_instance = this; + } + + ButtonManager() = delete; + + // TODO: Make a real singleton instead + static ButtonManager *m_instance; + + enum Button { + UP = 0, + MID, + DOWN, + Count + }; + + enum class ButtonState { + PRESSED, + NOT_PRESSED + }; + + typedef void (*ChangeCallback)(ButtonState); + + Common::Schedule::NextTime execute() override; + + Common::ReturnCode init(); + void set_callback(Button btn, ChangeCallback callback); + void remove_callback(Button btn); + + static void irq(); + +private: + + struct button_state { + + button_state(uint8_t gpio_index, + Common::time_t debounce_time, + ChangeCallback callback) + : m_gpio_idx(gpio_index) + , m_debounce_time(debounce_time) + , m_state(ButtonState::NOT_PRESSED) + , m_state_change_ts(0) + , m_callback(callback) + {} + + uint8_t const m_gpio_idx; + Common::time_t const m_debounce_time; + + ButtonState m_prev_call_state; /*IOPENR, RCC_IOPENR_IOPBEN); - /** Enable pin P3 for output */ - SET_TO(GPIOB->MODER, - GPIO_MODER_MODE3, - GPIO_MODER_MODE3_0); - - CLR(GPIOB->OTYPER, GPIO_OTYPER_OT_3); - CLR(GPIOB->PUPDR, GPIO_PUPDR_PUPD3); - - return Common::ReturnCode::OK; - - - return ReturnCode::OK; + return Common::ReturnCode::OK; } NextTime DisplayDriver::execute() @@ -66,7 +53,7 @@ void DisplayDriver::buffer_init() for (size_t i = 0; i < ARRAY_SIZE(m_buffer.lines); i++) { struct display_line *line = &m_buffer.lines[i]; line->mode = 1; // Update display - line->line = i; + line->line = i + 1; // Line numbers start at 1 for (size_t j = 0; j < ARRAY_SIZE(line->data); j++) { line->data[j] = 0xFF; } @@ -126,21 +113,32 @@ void DisplayDriver::char_at(int *x_off, int y_off, char c, const struct font *fo return; } - // TODO: Don't hardcode this - int byte_cols = (g->cols / 8); + int byte_cols = g->cols / 8; if (g->cols & 7) { byte_cols++; } if (byte_cols & 1) { byte_cols++; } + + for (size_t x = 0; x < g->left; x++) { + for (size_t y = 0; y < g->rows; y++) { + set_bit(*x_off + x, y_off + y + font->size - g->top, 0); + } + } + + for (size_t x = g->left + g->cols; x < g->advance; x++) { + for (size_t y = 0; y < g->rows; y++) { + set_bit(*x_off + x, y_off + y + font->size - g->top, 0); + } + } + for (size_t x = 0; x < g->cols; x++) { for (size_t y = 0; y < g->rows; y++) { - int byte_x = x >> 3; + int byte_x = x / 8; int byte_y = y; uint8_t bit = (g->bitmap[byte_y * byte_cols + byte_x] >> (7 - (x & 7))) & 1; - /* 16 is font max height */ - set_bit(g->left + *x_off + x, y_off + y + 16 - g->top, bit); + set_bit(g->left + *x_off + x, y_off + y + font->size - g->top, bit); } } *x_off += g->advance; diff --git a/DisplayDriver.h b/DisplayDriver.h index 29614be..fa5040a 100644 --- a/DisplayDriver.h +++ b/DisplayDriver.h @@ -31,8 +31,6 @@ class DisplayDriver : public Common::Schedule::Task { public: DisplayDriver(Common::Schedule::TaskScheduler &scheduler, SpiDriver &spi); - static constexpr uint8_t DISPLAY_WIDTH = 144; - static constexpr uint8_t DISPLAY_HEIGHT = 168; /** @@ -51,11 +49,22 @@ public: void refresh(); void clear(); + inline uint32_t get_width() { + return DISPLAY_WIDTH; + } + + inline uint32_t get_height() { + return DISPLAY_HEIGHT; + } + private: void buffer_init(); void set_dirty(unsigned int y); const struct glyph *glyph_for_char(const struct font *font, char c); + static constexpr uint32_t DISPLAY_WIDTH = 144; + static constexpr uint32_t DISPLAY_HEIGHT = 168; + struct display_line { uint8_t mode; diff --git a/DisplayManager.h b/DisplayManager.h new file mode 100644 index 0000000..3d86ebb --- /dev/null +++ b/DisplayManager.h @@ -0,0 +1,43 @@ +/* + * 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 "DisplayDriver.h" +#include "ReturnCode.h" +#include "Task.h" + +class ScreenManager : public Common::Schedule::Task { +public: + + ScreenManager(BSP::DisplayDriver &display); + + Common::Schedule::NextTime execute(); + +private: + + void display_menu(); + + BSP::DisplayDriver &m_driver; + bool m_displayed; +}; diff --git a/DisplayScreen.h b/DisplayScreen.h new file mode 100644 index 0000000..1b9253d --- /dev/null +++ b/DisplayScreen.h @@ -0,0 +1,30 @@ +/* + * 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 "Task.h" + +class Screen : Common::Task::Schedule { +public: + virtual void init() = 0; + virtual void cleanup() = 0; +}; diff --git a/DisplayTimeTask.cpp b/DisplayTimeTask.cpp index dcd557d..ead4e12 100644 --- a/DisplayTimeTask.cpp +++ b/DisplayTimeTask.cpp @@ -20,38 +20,98 @@ */ #include "DisplayTimeTask.h" -#include "LowPower.h" - -#include "font-notomono-10.h" +#include "SystemTime.h" +#include "font-notomono-24.h" +#include "font-notomono-64.h" using Common::ReturnCode; using Common::Time; using Common::Schedule::NextTime; +static const font &font_large = font_notomono_64; +static const font &font_default = font_notomono_24; + DisplayTimeTask::DisplayTimeTask(BSP::DisplayDriver &driver) : m_driver(driver) - , m_y_pos(0) + , m_has_cleared(false) + , m_last_time() + , m_display_seconds(true) {} -ReturnCode DisplayTimeTask::init() { +static char get_char_for_digit(uint8_t bcd_digit) +{ + if (bcd_digit > 9) { + return '0'; + } + return bcd_digit + '0'; +} + + +ReturnCode DisplayTimeTask::init() +{ return ReturnCode::OK; } -NextTime DisplayTimeTask::execute() { - //static const char msg_str[] = "Hello world!"; +void DisplayTimeTask::display_number(uint32_t x, uint32_t y, uint32_t tens, uint32_t ones, const font &f) +{ + char time_str[3] = { 0 }; - int x = 20; + time_str[0] = get_char_for_digit(tens); + time_str[1] = get_char_for_digit(ones); + time_str[2] = '\0'; - m_driver.clear(); - m_driver.string_at(x, m_y_pos++, "Hello world!", &font_notomono_10); - m_driver.refresh(); + m_driver.string_at(x, y, time_str, &f); +} - if (m_y_pos > 160) { - m_y_pos = 0; +void DisplayTimeTask::display_time() +{ + uint32_t width = m_driver.get_width(); + uint32_t height = m_driver.get_height(); + + BSP::time_bcd time; + BSP::RtcDriver::get_time(time); + + if (!m_has_cleared) { + m_driver.clear(); } - BSP::LowPower::stop(); + int i = 0; - return NextTime::asap(); + if (m_last_time.hour_tens != time.hour_tens || + m_last_time.hour_ones != time.hour_ones || + !m_has_cleared) { + display_number(0, (i * font_large.size) + (i + 1) * 4, time.hour_tens, time.hour_ones, font_large); + } + + i++; + if (m_last_time.minute_tens != time.minute_tens || + m_last_time.minute_ones != time.minute_ones || + !m_has_cleared) { + display_number(0, (i * font_large.size) + (i + 1) * 4, time.minute_tens, time.minute_ones, font_large); + } + + 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(0, (i * font_large.size) + (i + 1) * 4, time.second_tens, time.second_ones, font_default); + } + } + + m_has_cleared = true; + m_last_time = time; + + m_driver.refresh(); +} + +NextTime DisplayTimeTask::execute() +{ + display_time(); + + Common::time_t now; + BSP::SystemTimer::get_time(now); + uint32_t next_secs = Time::to_seconds(now) + 1; + return NextTime::at(Time::seconds(next_secs)); } diff --git a/DisplayTimeTask.h b/DisplayTimeTask.h index 022adb1..106c2fa 100644 --- a/DisplayTimeTask.h +++ b/DisplayTimeTask.h @@ -26,6 +26,7 @@ #include "DisplayDriver.h" #include "ReturnCode.h" #include "Task.h" +#include "RtcDriver.h" class DisplayTimeTask : public Common::Schedule::Task { public: @@ -36,6 +37,14 @@ public: Common::Schedule::NextTime execute(); private: + + void display_time(); + void display_number(uint32_t x, uint32_t y, uint32_t tens, uint32_t ones, const font &f); + BSP::DisplayDriver &m_driver; - unsigned int m_y_pos; + bool m_has_cleared; + BSP::time_bcd m_last_time; + + const bool m_display_seconds; + }; diff --git a/LowPower.cpp b/LowPower.cpp index 6a68ff1..c30bb8b 100644 --- a/LowPower.cpp +++ b/LowPower.cpp @@ -43,25 +43,20 @@ ReturnCode LowPower::init() ReturnCode LowPower::sleep() { + // TODO: unimplemented + return ReturnCode::FAIL; } ReturnCode LowPower::stop() { - - /* Prepare to enter stop mode */ - SET(PWR->CR, PWR_CR_CWUF); // clear WUF + SET(PWR->CR, PWR_CR_CWUF); // clear wakeup flag while(PWR->CSR & PWR_CSR_WUF) {}; + CLR(PWR->CR, PWR_CR_PDDS); // Enter stop mode when the CPU enters deepsleep CLR(RCC->CFGR, RCC_CFGR_STOPWUCK); // MSI oscillator is wake-up from stop clock SET(SCB->SCR, SCB_SCR_SLEEPDEEP_Msk); // low-power mode = stop mode - // Common::time_t time0 = ~0; - // SystemTimer::get_time(time0); - __WFI(); // enter low-power mode - // Common::time_t time1 = ~0; - // SystemTimer::get_time(time1); - // Common::time_t timediff = time1 - time0; - // volatile Common::time_t timediff2 = timediff; + __WFI(); // enter low-power mode (Wake from interrupt) return ReturnCode::OK; } diff --git a/ConcreteTaskScheduler.h b/LowPowerTaskScheduler.h similarity index 86% rename from ConcreteTaskScheduler.h rename to LowPowerTaskScheduler.h index 9e5f2ed..28bdb62 100644 --- a/ConcreteTaskScheduler.h +++ b/LowPowerTaskScheduler.h @@ -26,14 +26,15 @@ #include "TaskScheduler.h" #include "SystemTime.h" #include "LowPower.h" +#include "RtcDriver.h" namespace Common { namespace Schedule { template -class ConcreteTaskScheduler : public TaskScheduler { +class LowPowerTaskScheduler : public TaskScheduler { public: - ConcreteTaskScheduler() : + LowPowerTaskScheduler() : m_tasks(), m_task_count(0), m_cycle_count(0) @@ -56,7 +57,7 @@ public: m_tasks[m_task_count++] = TaskEvent(task, time); } - // ~ConcreteTaskScheduler() {} + // ~LowPowerTaskScheduler() {} private: struct TaskEvent { @@ -92,33 +93,35 @@ private: bool task_died = false; /* Keep state for when the next task will execute. */ - // bool execed = false; - // Common::time_t next_time = ~0; + bool execed = false; + Common::time_t next_time = ~0; for (size_t i = 0; i < m_task_count; i++) { TaskEvent &event = m_tasks[i]; if (event.m_time.get_type() == ScheduleType::AT_TIME) { if (time >= event.m_time.get_time()) { - // execed = true; + execed = true; call_task(event); } else { - // next_time = MIN(next_time, event.m_time.get_time()); + next_time = MIN(next_time, event.m_time.get_time()); } } else if (event.m_time.get_type() == ScheduleType::NEVER) { task_died = true; } } - /* If nothing happened this cycle, and nothing will happen for - awhile, go to sleep */ - //if (!execed && (next_time - time > Time::millis(10))) { - //BSP::LowPower::stop(); - //} if (task_died) { remove_dead_tasks(); } + 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(); + } + } + m_cycle_count++; } diff --git a/LptimPwm.cpp b/LptimPwm.cpp new file mode 100644 index 0000000..0ee4eee --- /dev/null +++ b/LptimPwm.cpp @@ -0,0 +1,111 @@ +/* + * 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 "LptimPwm.h" +#include "macros.h" + +namespace BSP { + +using Common::ReturnCode; + +LptimPwm::LptimPwm(LPTIM_TypeDef *lptim) + : m_lptim(lptim) +{} + + +void LptimPwm::init_lptim() +{ + /* Enable LPTIM in APB1 */ + SET(RCC->APB1ENR, + RCC_APB1ENR_LPTIM1EN); + + // Enable low-speed internal + RCC->CSR |= RCC_CSR_LSION; + while (!(RCC->CSR & RCC_CSR_LSIRDY)) {}; + + /*!< Set the LSE clock to be the source of the LPTIM */ + SET_TO(RCC->CCIPR, + RCC_CCIPR_LPTIM1SEL, + RCC_CCIPR_LPTIM1SEL_0); + + /** Write CR CFGR and IER while LPTIM is disabled (LPTIM_CR_ENABLE not yet set) */ + /*!< Disable Interrupts (not needed, this is the default */ + LPTIM1->IER = 0; + + /*!< Reset + * ENC (Disable encoder mode) + * TIMOUT (disable timeout mode) + * TRIGEN (Trigger count start with software only) + * PRELOAD (Update ARR and CMP registers immediately after write) + * CKSEL (LPTIM is not using an input clock) + * COUNTMODE (LPTIM counter updated on every clock pulse) + * TRGFLT (Do not debounce triggers) + */ + CLR(LPTIM1->CFGR, + LPTIM_CFGR_ENC | LPTIM_CFGR_TIMOUT | LPTIM_CFGR_TRIGEN | + LPTIM_CFGR_TRIGSEL | LPTIM_CFGR_PRELOAD | LPTIM_CFGR_CKSEL | + LPTIM_CFGR_COUNTMODE); + + /*!< Set + * PRESC (Set prescaler to 128. Using 32kHz LSE as input, this should + * correspond to 250Hz counting. + */ + CLR(LPTIM1->CFGR, LPTIM_CFGR_PRESC); + + SET(LPTIM1->CR, LPTIM_CR_ENABLE); + + /*!< Do not modify ARR and CMP until after ENABLE bit is set */ + /*!< Produce a 60Hz, signal with minimal "high" time. The display + only needs 2us of "high" time on EXTCOMM, and it draws a fair + amount of power. */ + LPTIM1->ARR = 0x4FF; + LPTIM1->CMP = 0x4FE; + while(!(LPTIM1->ISR & LPTIM_ISR_ARROK)) {} + while(!(LPTIM1->ISR & LPTIM_ISR_CMPOK)) {} + + /*!< Enable and start the timer */ + SET(LPTIM1->CR, LPTIM_CR_CNTSTRT); +} + + +ReturnCode LptimPwm::init() +{ + init_lptim(); + + /* Enable GPIO port A */ + SET(RCC->IOPENR, RCC_IOPENR_IOPAEN); + + /* Assign LPTIM1_OUT to PA7 */ + SET_TO(GPIOA->AFR[0], + GPIO_AFRL_AFRL7, + 1u << GPIO_AFRL_AFRL7_Pos); + + SET_TO(GPIOA->MODER, + GPIO_MODER_MODE7, + 2u << GPIO_MODER_MODE7_Pos); + + CLR(GPIOA->OTYPER, GPIO_OTYPER_OT_7); + CLR(GPIOA->PUPDR, GPIO_PUPDR_PUPD7); + + return ReturnCode::OK; +} + +} diff --git a/LptimPwm.h b/LptimPwm.h new file mode 100644 index 0000000..3fea77c --- /dev/null +++ b/LptimPwm.h @@ -0,0 +1,42 @@ +/* + * 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 "ReturnCode.h" +#include "stm32l0xx.h" + +namespace BSP { + +class LptimPwm { + +public: + LptimPwm() = delete; + LptimPwm(LPTIM_TypeDef *lptim); + + Common::ReturnCode init(); + +private: + void init_lptim(); + const LPTIM_TypeDef *m_lptim; +}; + +} diff --git a/Makefile b/Makefile index e58e2fd..c068f63 100644 --- a/Makefile +++ b/Makefile @@ -24,14 +24,14 @@ # Tools # -TOOL_PREFIX ?= arm-eabi- +TOOL_PREFIX ?= arm-none-eabi- CC = $(TOOL_PREFIX)gcc CXX = $(TOOL_PREFIX)g++ CPP = $(TOOL_PREFIX)cpp AS = $(TOOL_PREFIX)as -LD = $(TOOL_PREFIX)gcc +LD = $(TOOL_PREFIX)g++ OBJCOPY = $(TOOL_PREFIX)objcopy - +STM32_PROG = STM32_Programmer.sh # # Device Variables # @@ -73,13 +73,15 @@ 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 + # C pedantism CFLAGS = -Wall -Wextra -Wpedantic # Debug/optimization -CFLAGS += -ggdb -g3 +CFLAGS += -ggdb -g3 -Os CFLAGS += -fdata-sections -ffunction-sections # Architecture -CFLAGS += -mthumb -mcpu=cortex-m0plus +CFLAGS += $(CPU_FLAGS) CFLAGS += -ffreestanding # Defines CFLAGS += -D$(DEVICE_DEFINE) @@ -91,12 +93,14 @@ CFLAGS += -I./lib/fonts/ CXX_FLAGS = -std=c++14 -fno-exceptions -fno-rtti # Startup Definitions +ASFLAGS += $(CPU_FLAGS) ASFLAGS += -D__STARTUP_CLEAR_BSS ASFLAGS += -D__HEAP_SIZE=0 # No heap- let the linker decide it all -LDFLAGS += -lc -lstdc++ -nostdinc -lnosys -Wl,--gc-sections -Wl,--build-id=None -LDFLAGS += -Wl,--wrap=malloc -Wl,--wrap=free # Fail to compile if dynamic allocation is sneaking through -LDFLAGS += -mthumb -mcpu=cortex-m0plus +LDFLAGS += $(CPU_FLAGS) +LDFLAGS += -Wl,--gc-sections -Wl,--build-id=none -static +LDFLAGS += -Wl,--wrap=malloc -Wl,--wrap=free # Fail to link if dynamic allocation is sneaking through +LDFLAGS += -Wl,-print-memory-usage # # Default Target @@ -126,7 +130,7 @@ $(OUTPUT_BIN): $(OUTPUT_ELF) $(OUTPUT_ELF): $(LINKER_SCRIPT) $(OBJS) @echo "LD $@" - @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $(OUTPUT_ELF) $(OBJS) + $(LD) -T $(LINKER_SCRIPT) $(LDFLAGS) -o $(OUTPUT_ELF) $(OBJS) # # Utilities @@ -137,8 +141,7 @@ STM32FLASH_DEVICE = /dev/ttyUSB0 .PHONY: flash flash: $(OUTPUT_BIN) @echo "FLASH $(OUTPUT_BIN)" - @st-flash write $(OUTPUT_BIN) 0x8000000 - + $(STM32_PROG) --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go .PHONY: clean clean: diff --git a/RtcDriver.cpp b/RtcDriver.cpp new file mode 100644 index 0000000..2cb7371 --- /dev/null +++ b/RtcDriver.cpp @@ -0,0 +1,249 @@ +/* + * 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 "RtcDriver.h" +#include "macros.h" + +namespace BSP { + +using Common::ReturnCode; +using Common::time_t; + +RtcDriver::RtcSystemTimer RtcDriver::m_sys_timer; + +void RtcDriver::enable_rtc_write() +{ + /*WPR = 0xCA; + RTC->WPR = 0x53; +} + +void RtcDriver::disable_rtc_write() +{ + /*WPR = 0x00; +} + +void RtcDriver::enable_periodic_alarm() +{ + + SET(RTC->ALRMAR, RTC_ALRMAR_MSK4 | RTC_ALRMAR_MSK3 | RTC_ALRMAR_MSK2 | RTC_ALRMAR_MSK1); + + SET(RTC->ALRMASSR, RTC_ALRMASSR_MASKSS); + CLR(RTC->ALRMASSR, RTC_ALRMASSR_SS); + + SET(RTC->CR, RTC_CR_ALRAE); +} + +ReturnCode RtcDriver::init_hw() +{ + uint32_t temp = RCC->CSR; + + SET(RCC->CSR, RCC_CSR_RTCRST); + SET(RCC->APB1ENR, RCC_APB1ENR_PWREN); + SET(PWR->CR, PWR_CR_DBP); + + /*CSR & RCC_CSR_LSERDY)) { + // TODO: Does this help? + SET(temp, RCC_CSR_LSEON); + } + + SET_TO(temp, RCC_CSR_RTCSEL, RCC_CSR_RTCSEL_0); + SET(temp, RCC_CSR_RTCEN); + RCC->CSR = temp; + + while (!(RCC->CSR & RCC_CSR_LSERDY)) {} + + enable_rtc_write(); + + RTC->ISR = RTC_ISR_INIT; + while (!(RTC->ISR & RTC_ISR_INITF)) {} + + /*PRER, RTC_PRER_PREDIV_A, 0); + /*PRER, RTC_PRER_PREDIV_S, (LSE_CLOCK_FREQ - 1)); + + /*CR, RTC_CR_FMT); + + uint32_t time = 0; + SET(time, RTC_TR_PM); + SET_TO(time, RTC_TR_HT, 1 << RTC_TR_HT_Pos); + SET_TO(time, RTC_TR_HU, 2 << RTC_TR_HU_Pos); + SET_TO(time, RTC_TR_MNT, 5 << RTC_TR_MNT_Pos); + SET_TO(time, RTC_TR_MNU, 9 << RTC_TR_MNU_Pos); + SET_TO(time, RTC_TR_ST, 0 << RTC_TR_ST_Pos); + SET_TO(time, RTC_TR_SU, 0 << RTC_TR_SU_Pos); + RTC->TR = time; + + CLR(RTC->ISR, RTC_ISR_INIT); + + SET(EXTI->IMR, EXTI_IMR_IM20); + SET(EXTI->EMR, EXTI_EMR_EM20); + SET(EXTI->RTSR, EXTI_RTSR_RT20); + + // Enable Wakeup interrupts, we may/will use them later + SET(RTC->CR, RTC_CR_WUTIE); + NVIC_EnableIRQ(RTC_IRQn); + NVIC_SetPriority(RTC_IRQn, 0); + + enable_periodic_alarm(); + + disable_rtc_write(); + + return ReturnCode::OK; +} + +ReturnCode RtcDriver::get_time(time_bcd &tm_bcd) +{ + /*DR; + + uint32_t time = RTC->TR; + + tm_bcd.hour_tens = STM32_GET_FIELD(time, RTC_TR_HT); + tm_bcd.hour_ones = 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); + + tm_bcd.second_tens = STM32_GET_FIELD(time, RTC_TR_ST); + tm_bcd.second_ones = STM32_GET_FIELD(time, RTC_TR_SU); + + tm_bcd.pm = STM32_GET_FIELD(time, RTC_TR_PM); + + return ReturnCode::OK; +} + +ReturnCode RtcDriver::init() +{ + init_hw(); + + return ReturnCode::OK; +} + +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; + + enable_rtc_write(); + + /*CR & RTC_CR_WUTE) { + CLR(RTC->CR, RTC_CR_WUTE); + while (!(RTC->ISR & RTC_ISR_WUTWF)) {} + } + + uint32_t wucksel = 0; + + if (delay_cycles == 0) { + return ReturnCode::FAIL; + } else if (delay_cycles < 0x10000) { + delay_cycles /= 2; + wucksel = 3; + } else if (delay_cycles <= 0x20000) { + delay_cycles /= 4; + wucksel = 2; + } else if (delay_cycles <= 0x40000) { + delay_cycles /= 8; + wucksel = 1; + } else if (delay_cycles <= 0x80000) { + delay_cycles /= 16; + wucksel = 0; + } else { +#if 0 + // TODO: implement longer delays using ck_spre as clock source + // TODO: the datasheet text and block diagram disagree- is it using clock_spre or clock_apre? + wucksel = 4; + delay_cycles >>= async_prediv; // +#else + return ReturnCode::FAIL; +#endif + } + + SET_TO(RTC->WUTR, RTC_WUTR_WUT, delay_cycles - 1); + SET_TO(RTC->CR, RTC_CR_WUCKSEL, wucksel << RTC_CR_WUCKSEL_Pos); + + SET(RTC->CR, RTC_CR_WUTE); + + disable_rtc_write(); + + 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); + + 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() +{ + m_seconds++; +} + +void RtcDriver::increment_seconds() +{ + m_sys_timer.increment_seconds(); +} + +extern "C" void RTC_IRQHandler() +{ + // Clear the interrupt in the EXTI + SET(EXTI->PR, EXTI_PR_PIF20); + + if (RTC->ISR & RTC_ISR_ALRAF) { + RtcDriver::increment_seconds(); + + CLR(RTC->ISR, RTC_ISR_ALRAF); + + } + + if (RTC->ISR & RTC_ISR_WUTF) { + // Clear the interrupt in the RTC + CLR(RTC->ISR, RTC_ISR_WUTF); + // Disable the Wakeup timer (its periodic, but we use it as a + // one-shot timer + CLR(RTC->CR, RTC_CR_WUTE); + } + +} + +} diff --git a/RtcDriver.h b/RtcDriver.h new file mode 100644 index 0000000..0a8b742 --- /dev/null +++ b/RtcDriver.h @@ -0,0 +1,93 @@ +/* + * 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 "SystemTime.h" +#include "ReturnCode.h" +#include "Time.h" + +#include "stm32l0xx.h" + +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: + + RtcDriver() = delete; + ~RtcDriver() = delete; + + static SystemTimerImpl& get_system_timer() { + return m_sys_timer; + }; + static Common::ReturnCode init(); + static void increment_seconds(); + static Common::ReturnCode get_time(time_bcd &tm_bcd); + static Common::ReturnCode set_wakeup_in(Common::time_t wakeup_delay); + +private: + + static Common::ReturnCode init_hw(); + static void enable_rtc_write(); + static void disable_rtc_write(); + static void enable_rtc_wakeup_interrupt(); + static void enable_periodic_alarm(); + + static constexpr uint32_t LSE_CLOCK_FREQ = 32768; + + static RTC_TypeDef *m_rtc; + + class RtcSystemTimer : public BSP::SystemTimerImpl { + public: + + RtcSystemTimer() + : m_seconds(0) + {} + + ~RtcSystemTimer() {} + + Common::time_t get_time() override; + void increment_seconds(); + + private: + /** I'll be dead before this rolls over */ + /** FIXME FIXME FIXME: XXX This should be an atomic */ + uint32_t m_seconds; + + static constexpr uint32_t LSE_CLOCK_FREQ = 32768; + }; + + static RtcSystemTimer m_sys_timer; +}; + +} diff --git a/SystemTime.cpp b/SystemTime.cpp index b2cee6f..b1cee50 100644 --- a/SystemTime.cpp +++ b/SystemTime.cpp @@ -27,145 +27,21 @@ namespace BSP { using Common::ReturnCode; using Common::time_t; -uint32_t SystemTimer::m_seconds(0); -RTC_TypeDef *SystemTimer::m_rtc = nullptr; +SystemTimerImpl *SystemTimer::m_impl = nullptr; -void SystemTimer::enable_rtc_write() -{ - /*WPR = 0xCA; - RTC->WPR = 0x53; -} - -void SystemTimer::disable_rtc_write() -{ - /*WPR = 0x00; -} - -void SystemTimer::enable_rtc_wakeup_interrupt() -{ - CLR(RTC->CR, RTC_CR_WUTE); - while (!(RTC->ISR & RTC_ISR_WUTWF)) {} - SET_TO(RTC->WUTR, RTC_WUTR_WUT, 0); - SET_TO(RTC->CR, RTC_CR_WUCKSEL, RTC_CR_WUCKSEL_2); - - SET(EXTI->IMR, EXTI_IMR_IM20); - SET(EXTI->EMR, EXTI_EMR_EM20); - SET(EXTI->RTSR, EXTI_RTSR_RT20); - - NVIC_EnableIRQ(RTC_IRQn); - NVIC_SetPriority(RTC_IRQn, 0); - - SET(RTC->CR, RTC_CR_WUTE | RTC_CR_WUTIE); -} - -ReturnCode SystemTimer::init_hw() -{ - uint32_t temp = RCC->CSR; - - SET(RCC->CSR, RCC_CSR_RTCRST); - SET(RCC->APB1ENR, RCC_APB1ENR_PWREN); - SET(PWR->CR, PWR_CR_DBP); - - /*CSR = temp; - - while (!(RCC->CSR & RCC_CSR_LSERDY)) {} - - enable_rtc_write(); - - RTC->ISR = RTC_ISR_INIT; - while (!(RTC->ISR & RTC_ISR_INITF)) {} - - // FIXME: Make this use the minimum prescaler value - /*PRER, RTC_PRER_PREDIV_A, 0); - /*PRER, RTC_PRER_PREDIV_S, (LSE_CLOCK_FREQ - 1)); - - /*CR, RTC_CR_FMT); - - uint32_t time = 0; - SET(time, RTC_TR_PM); - SET_TO(time, RTC_TR_HT, 1 << RTC_TR_HT_Pos); - SET_TO(time, RTC_TR_HU, 2 << RTC_TR_HU_Pos); - SET_TO(time, RTC_TR_MNT, 5 << RTC_TR_MNT_Pos); - SET_TO(time, RTC_TR_MNU, 9 << RTC_TR_MNU_Pos); - SET_TO(time, RTC_TR_ST, 0 << RTC_TR_ST_Pos); - SET_TO(time, RTC_TR_SU, 0 << RTC_TR_SU_Pos); - RTC->TR = time; - - - CLR(RTC->ISR, RTC_ISR_INIT); - - enable_rtc_wakeup_interrupt(); - - disable_rtc_write(); - - return ReturnCode::OK; -} - -ReturnCode SystemTimer::init(RTC_TypeDef *rtc) -{ - if (rtc == nullptr) { - return ReturnCode::FAIL; - } - - m_rtc = rtc; - m_seconds = 0; - - init_hw(); - - return ReturnCode::OK; +void SystemTimer::set_timer(SystemTimerImpl &timer) { + m_impl = &timer; } ReturnCode SystemTimer::get_time(time_t &time) { - if (m_rtc == nullptr) { + if (m_impl == nullptr) { return ReturnCode::FAIL; } - uint32_t new_secs, old_secs, ssr, subsecond; - do { - old_secs = m_seconds; - ssr = m_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; - time += Common::Time::millis(subsecond); + time = m_impl->get_time(); return ReturnCode::OK; } -void SystemTimer::increment_seconds() -{ - m_seconds++; -} - -extern "C" void RTC_IRQHandler(void); - -void RTC_IRQHandler() { - SystemTimer::increment_seconds(); - // Clear the interrupt in the EXTI - SET(EXTI->PR, EXTI_PR_PIF20); - // Clear the interrupt in the RTC - CLR(RTC->ISR, RTC_ISR_WUTF); -} - } diff --git a/SystemTime.h b/SystemTime.h index 22d5f25..29e96d7 100644 --- a/SystemTime.h +++ b/SystemTime.h @@ -30,24 +30,20 @@ namespace BSP { -class SystemTimer { +class SystemTimerImpl { public: - static Common::ReturnCode init(RTC_TypeDef *rtc); + virtual Common::time_t get_time() = 0; +}; + +class SystemTimer final { +public: + SystemTimer() = delete; + ~SystemTimer() = delete; + static Common::ReturnCode get_time(Common::time_t &time); - static void increment_seconds(); + static void set_timer(SystemTimerImpl& timer); private: - - static Common::ReturnCode init_hw(); - static void enable_rtc_write(); - static void disable_rtc_write(); - static void enable_rtc_wakeup_interrupt(); - - static constexpr uint32_t LSE_CLOCK_FREQ = 32768; - - /** I'll be dead before this rolls over */ - /** FIXME FIXME FIXME: XXX This should be an atomic */ - static uint32_t m_seconds; - static RTC_TypeDef *m_rtc; + static SystemTimerImpl *m_impl; }; } diff --git a/Time.h b/Time.h index 3857f88..7ba97e9 100644 --- a/Time.h +++ b/Time.h @@ -57,6 +57,26 @@ public: { return value * MICROS_PER_SEC; } + + static inline uint64_t to_nanos(time_t value) + { + return value * NANOS_PER_MICRO; + } + + static inline uint64_t to_micros(time_t value) + { + return value; + } + + static inline uint64_t to_millis(time_t value) + { + return value / MICROS_PER_MILLI; + } + + static inline uint64_t to_seconds(time_t value) + { + return value / MICROS_PER_SEC; + } }; } diff --git a/macros.h b/macros.h index bf322a8..397a928 100644 --- a/macros.h +++ b/macros.h @@ -78,8 +78,6 @@ } while (0) #define SET_TO(x, clear_mask, val) \ - static_assert((clear_mask & val) == val, \ - "'value' in SET_TO() has bits set that are not in clear_mask"); \ do { \ CLR(x, clear_mask); \ SET(x, val); \ diff --git a/main.cpp b/main.cpp index b142193..e97a62e 100644 --- a/main.cpp +++ b/main.cpp @@ -19,12 +19,13 @@ * THE SOFTWARE. */ -#include "ConcreteTaskScheduler.h" +#include "LowPowerTaskScheduler.h" +#include "RtcDriver.h" #include "DisplayDriver.h" #include "SpiDriver.h" -#include "BlinkTask.h" -#include "LowPowerDelay.h" #include "DisplayTimeTask.h" +#include "LptimPwm.h" +#include "ButtonManager.h" #include "stm32l0xx.h" @@ -32,12 +33,12 @@ using Common::Time; -static Common::Schedule::ConcreteTaskScheduler<10> g_sched; +static Common::Schedule::LowPowerTaskScheduler<10> g_sched; static BSP::SpiDriver g_spi(g_sched); static BSP::DisplayDriver g_display(g_sched, g_spi); -//static BlinkTask g_blink(Common::Time::seconds(2)); +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 LowPowerDelay g_lp_delay; extern "C" void __cxa_pure_virtual() { while(1) {} } @@ -116,25 +117,61 @@ static void _init(void) (*func)(); } } - } [[noreturn]] void main() { _init(); - BSP::SystemTimer::init(RTC); + // Set up the system clock + BSP::RtcDriver::init(); + BSP::SystemTimer::set_timer(BSP::RtcDriver::get_system_timer()); + BSP::LowPower::init(); + + // Initialize the tasks + g_lptim_pwm.init(); g_spi.init(); + g_btn_manager.init(); g_display.init(); - //g_blink.init(); g_display_time.init(); + // Enqueue each of the tasks Common::Schedule::NextTime asap = Common::Schedule::NextTime::asap(); - //g_sched.add_task(g_blink, asap); - //g_sched.add_task(g_lp_delay, 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); + // 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 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);} diff --git a/stm32l031k6.ld b/stm32l031k6.ld index fad22d1..d344c29 100644 --- a/stm32l031k6.ld +++ b/stm32l031k6.ld @@ -34,7 +34,7 @@ MEMORY } /* Library configurations */ -GROUP(libgcc.a libc.a libm.a libnosys.a) +/* GROUP(libgcc.a libc.a libm.a libnosys.a) */ /* Linker script to place sections and symbol values. Should be used together * with other linker script that defines memory regions FLASH and RAM.