Enable buttons and the display.

Kind-of-sort-of usable-ish.
This commit is contained in:
2019-06-06 22:30:27 -07:00
parent 1cc2f7adf4
commit 125ddfb687
19 changed files with 937 additions and 196 deletions

View File

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

View File

@@ -21,6 +21,7 @@
#pragma once
#include <functional>
#include "Task.h"
#include "ReturnCode.h"
@@ -59,7 +60,7 @@ public:
NOT_PRESSED
};
typedef void (*ChangeCallback)(ButtonState);
using ChangeCallback = std::function<void(ButtonState)>;
Common::Schedule::NextTime execute() override;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@
#include "RtcDriver.h"
#include "macros.h"
#include <new>
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()
{
/*<! Disable write protection */
@@ -48,7 +56,7 @@ void RtcDriver::enable_periodic_alarm()
SET(RTC->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,6 +92,7 @@ ReturnCode RtcDriver::init_hw()
/*<! Set the Clock Prescalers (32.768kHz / 1 / 32768 = 1Hz */
/*<! Set the Async prescaler to the Maximum (divide the clock by 128) */
// XXX reset to 0, this is to enable easier debugging
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));
@@ -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)
{
/*<! 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;
// 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)) {}
/*<! Load initial date and time */
// 12-Hour format
SET(RTC->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)
/*<! 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;
uint64_t delay_cycles = Common::Time::to_micros(wakeup_delay) * LSE_CLOCK_FREQ /
Common::Time::MICROS_PER_SEC;
enable_rtc_write();
@@ -203,8 +241,8 @@ ReturnCode RtcDriver::set_wakeup_in(Common::time_t wakeup_delay)
return ReturnCode::OK;
}
Common::time_t RtcDriver::RtcSystemTimer::get_time()
{
Common::time_t RtcDriver::RtcSystemTimer::get_time()
{
uint32_t new_secs, old_secs, ssr, subsecond;
do {
old_secs = m_seconds;
@@ -220,6 +258,7 @@ Common::time_t RtcDriver::RtcSystemTimer::get_time()
void RtcDriver::RtcSystemTimer::increment_seconds()
{
/** TODO: Atomic increment */
m_seconds++;
}

View File

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

View File

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

129
ScreenManager.cpp Normal file
View File

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

68
ScreenManager.h Normal file
View File

@@ -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 <array>
#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<Screen *, MAX_SCREEN_STACK> m_screen_stack;
std::size_t m_screen_stack_depth;
BSP::DisplayDriver &m_display;
BSP::ButtonManager &m_buttons;
};

219
SetTimeScreen.cpp Normal file
View File

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

69
SetTimeScreen.h Normal file
View File

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

38
Task.h
View File

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

View File

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

141
Time.h
View File

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

View File

@@ -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,14 +138,15 @@ 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();
@@ -156,12 +162,8 @@ 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);}