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.
This commit is contained in:
2019-04-17 21:51:35 -07:00
parent 6747d6c831
commit a7f1ffc1b5
22 changed files with 1051 additions and 231 deletions

View File

@@ -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

147
ButtonManager.cpp Normal file
View File

@@ -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();
}
}

100
ButtonManager.h Normal file
View File

@@ -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; /*<! The last state the button was in when it the callback was called */
ButtonState m_state; /*<! The state the button was in during the last iteration */
Common::time_t m_state_change_ts; /*<! The system time when the button entered its current state */
ChangeCallback m_callback; /*<! The callback to call when the button has changed states (post-debounce) */
};
button_state m_buttons[Button::Count];
static void nop_callback(ButtonState) {};
};
}

View File

@@ -40,20 +40,7 @@ DisplayDriver::DisplayDriver(Common::Schedule::TaskScheduler &scheduler, SpiDriv
ReturnCode DisplayDriver::init()
{
/** Enable Port A,B clock */
SET(RCC->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;

View File

@@ -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;

43
DisplayManager.h Normal file
View File

@@ -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;
};

30
DisplayScreen.h Normal file
View File

@@ -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;
};

View File

@@ -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));
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -26,14 +26,15 @@
#include "TaskScheduler.h"
#include "SystemTime.h"
#include "LowPower.h"
#include "RtcDriver.h"
namespace Common {
namespace Schedule {
template <uint32_t MAX_TASKS>
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++;
}

111
LptimPwm.cpp Normal file
View File

@@ -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;
}
}

42
LptimPwm.h Normal file
View File

@@ -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;
};
}

View File

@@ -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:

249
RtcDriver.cpp Normal file
View File

@@ -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()
{
/*<! Disable write protection */
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
}
void RtcDriver::disable_rtc_write()
{
/*<! Disable write protection */
RTC->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);
/*<! Set RTC input clock to the LSE (low-speed external 32.768kHz) clock */
if (!(RCC->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)) {}
/*<! Set the Clock Prescalers (32.768kHz / 1 / 32768 = 1Hz */
/*<! Set the Async prescaler to the Maximum (divide the clock by 128) */
SET_TO(RTC->PRER, RTC_PRER_PREDIV_A, 0);
/*<! Set the Syncronous scaler so the RTC updates at 1Hz */
SET_TO(RTC->PRER, RTC_PRER_PREDIV_S, (LSE_CLOCK_FREQ - 1));
/*<! Load initial date and time */
// 12-Hour format
SET(RTC->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)
{
/*<! The value of TR in the shadow register is locked when SSR is
read (by the system timer), until the date register is read. We're
not using the date here, but we do need to clear the stale value. */
(void) RTC->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)
{
/*<! 2^64 / (1000000 * 32768) / 60 / 60 / 24 / 365 = ~17.85 This
value will only overflow for wakeup_delays > 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();
/*<! If there is an ongoing wakeup, disable it */
if (RTC->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);
}
}
}

93
RtcDriver.h Normal file
View File

@@ -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 <stdint.h>
#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;
};
}

View File

@@ -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()
{
/*<! Disable write protection */
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
}
void SystemTimer::disable_rtc_write()
{
/*<! Disable write protection */
RTC->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);
/*<! Set RTC input clock to the LSI (low-speed internal 32.768kHz) clock */
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)) {}
// FIXME: Make this use the minimum prescaler value
/*<! Set the Clock Prescalers (32.768kHz / 128 / 256 = 1Hz */
/*<! Set the Async prescaler to the Maximum (divide the clock by 128) */
SET_TO(RTC->PRER, RTC_PRER_PREDIV_A, 0);
/*<! Set the Syncronous scaler (divide the clock by 255 + 1) */
SET_TO(RTC->PRER, RTC_PRER_PREDIV_S, (LSE_CLOCK_FREQ - 1));
/*<! Load initial date and time */
// TODO
/* uint32_t time = 0; */
/* uint32_t date = 0; */
/*<! Set the date and time format */
// TODO: currently defaults to 24hr
// 12-Hour format
SET(RTC->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);
}
}

View File

@@ -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;
};
}

20
Time.h
View File

@@ -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;
}
};
}

View File

@@ -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); \

View File

@@ -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);}

View File

@@ -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.