Enable buttons and the display.
Kind-of-sort-of usable-ish.
This commit is contained in:
@@ -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++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "DisplayTimeTask.h"
|
||||
#include "DisplayTimeScreen.h"
|
||||
#include "RtcDriver.h"
|
||||
#include "SystemTime.h"
|
||||
|
||||
#include "font-notomono-29.h"
|
||||
@@ -29,14 +30,16 @@ using Common::ReturnCode;
|
||||
using Common::Time;
|
||||
using Common::Schedule::NextTime;
|
||||
|
||||
static const font &font_large = font_notomono_68;
|
||||
static const font &font_default = font_notomono_29;
|
||||
static const struct font &font = font_notomono_68;
|
||||
|
||||
DisplayTimeTask::DisplayTimeTask(BSP::DisplayDriver &driver)
|
||||
DisplayTimeScreen::DisplayTimeScreen(BSP::DisplayDriver &driver,
|
||||
ScreenManager &manager,
|
||||
Screen &menu_screen)
|
||||
: m_driver(driver)
|
||||
, m_has_cleared(false)
|
||||
, m_last_time()
|
||||
, m_display_seconds(true)
|
||||
, m_manager(manager)
|
||||
, m_menu_screen(menu_screen)
|
||||
, m_display_seconds(false)
|
||||
{}
|
||||
|
||||
static char get_char_for_digit(uint8_t bcd_digit)
|
||||
@@ -47,10 +50,8 @@ static char get_char_for_digit(uint8_t bcd_digit)
|
||||
return bcd_digit + '0';
|
||||
}
|
||||
|
||||
|
||||
ReturnCode DisplayTimeTask::init()
|
||||
ReturnCode DisplayTimeScreen::init()
|
||||
{
|
||||
|
||||
SET_TO(GPIOA->MODER, GPIO_MODER_MODE0, 1u << GPIO_MODER_MODE1_Pos);
|
||||
|
||||
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1;
|
||||
@@ -60,7 +61,7 @@ ReturnCode DisplayTimeTask::init()
|
||||
return ReturnCode::OK;
|
||||
}
|
||||
|
||||
void DisplayTimeTask::display_number(uint32_t x, uint32_t y, uint32_t tens, uint32_t ones, const font &f)
|
||||
void DisplayTimeScreen::display_number(uint32_t *x, uint32_t y, uint32_t tens, uint32_t ones, const struct font &f)
|
||||
{
|
||||
char time_str[3] = { 0 };
|
||||
|
||||
@@ -71,68 +72,69 @@ void DisplayTimeTask::display_number(uint32_t x, uint32_t y, uint32_t tens, uint
|
||||
m_driver.string_at(x, y, time_str, &f);
|
||||
}
|
||||
|
||||
void DisplayTimeTask::display_time()
|
||||
void DisplayTimeScreen::display_time()
|
||||
{
|
||||
BSP::time_bcd time;
|
||||
Common::WallClockTime time;
|
||||
BSP::RtcDriver::get_time(time);
|
||||
|
||||
if (!m_has_cleared) {
|
||||
m_driver.clear();
|
||||
// FIXME: Don't clear every redraw. Something is broken on screen
|
||||
// switching. enable/disable not called?
|
||||
m_driver.clear();
|
||||
|
||||
uint32_t x = 0;
|
||||
const uint32_t y_space = (m_driver.get_height() - (2 * font.size)) / 3;
|
||||
const uint32_t x_space = (m_driver.get_width() - (2 * 54)) / 2;
|
||||
|
||||
if (m_last_time.get_hours_24() != time.get_hours_24()) {
|
||||
x = x_space;
|
||||
display_number(&x, y_space,
|
||||
time.get_hours_12_tens(), time.get_hours_12_ones(), font);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
SET(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
CLR(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
|
||||
if (m_last_time.hour_tens != time.hour_tens ||
|
||||
m_last_time.hour_ones != time.hour_ones ||
|
||||
!m_has_cleared) {
|
||||
display_number(8, (i * font_default.size) + (i + 1) * 4, time.hour_tens, time.hour_ones, font_default);
|
||||
if (m_last_time.get_minutes() != time.get_minutes()) {
|
||||
x = x_space;
|
||||
display_number(&x, y_space * 2 + font.size,
|
||||
time.get_minutes_tens(), time.get_minutes_ones(), font);
|
||||
}
|
||||
|
||||
SET(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
CLR(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
|
||||
i++;
|
||||
if (m_last_time.minute_tens != time.minute_tens ||
|
||||
m_last_time.minute_ones != time.minute_ones ||
|
||||
!m_has_cleared) {
|
||||
display_number(8, (i * font_default.size) + (i + 1) * 4, time.minute_tens, time.minute_ones, font_default);
|
||||
}
|
||||
|
||||
SET(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
CLR(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
|
||||
i++;
|
||||
if (m_display_seconds) {
|
||||
if (m_last_time.second_tens != time.second_tens ||
|
||||
m_last_time.second_ones != time.second_ones ||
|
||||
!m_has_cleared) {
|
||||
display_number(8, (i * font_default.size) + (i + 1) * 4, time.second_tens, time.second_ones, font_default);
|
||||
}
|
||||
// display_number(&x, y,
|
||||
// time.get_seconds_tens(), time.get_seconds_ones(), font_default);
|
||||
}
|
||||
|
||||
// m_has_cleared = true;
|
||||
m_last_time = time;
|
||||
|
||||
SET(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
CLR(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
|
||||
m_driver.refresh();
|
||||
|
||||
SET(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
CLR(GPIOA->ODR, GPIO_ODR_OD1);
|
||||
}
|
||||
|
||||
NextTime DisplayTimeTask::execute()
|
||||
NextTime DisplayTimeScreen::execute()
|
||||
{
|
||||
display_time();
|
||||
|
||||
Common::time_t now;
|
||||
BSP::SystemTimer::get_time(now);
|
||||
uint32_t next_secs = Time::to_seconds(now) + 1;
|
||||
|
||||
display_time();
|
||||
|
||||
uint32_t next_secs = Time::to_seconds(now) + (m_display_seconds ? 1 : 60);
|
||||
return NextTime::at(Time::seconds(next_secs));
|
||||
}
|
||||
|
||||
|
||||
void DisplayTimeScreen::enable() {
|
||||
m_driver.clear();
|
||||
m_last_time = {};
|
||||
}
|
||||
|
||||
void DisplayTimeScreen::disable() {
|
||||
m_driver.clear();
|
||||
}
|
||||
|
||||
void DisplayTimeScreen::notify_up_button() {
|
||||
/* TODO: This should open a menu first */
|
||||
m_manager.push_screen(m_menu_screen);
|
||||
}
|
||||
|
||||
void DisplayTimeScreen::notify_middle_button() {
|
||||
|
||||
}
|
||||
|
||||
void DisplayTimeScreen::notify_down_button() {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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++;
|
||||
|
||||
11
Makefile
11
Makefile
@@ -73,10 +73,11 @@ OUTPUT_ELF ?= $(OUTPUT_NAME).elf
|
||||
|
||||
DEVICE_DEFINE = $(subst XX,xx,$(shell echo $(DEVICE_FAMILY) | tr '[:lower:]' '[:upper:]'))
|
||||
|
||||
CPU_FLAGS = -mthumb -mcpu=cortex-m0 -mfloat-abi=soft
|
||||
CPU_FLAGS = -mthumb -mcpu=cortex-m0plus -mfloat-abi=soft
|
||||
|
||||
# C pedantism
|
||||
CFLAGS = -Wall -Wextra -Wpedantic
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -Werror
|
||||
CXX_FLAGS = -Wsuggest-override -Wsuggest-final-methods -Wsuggest-final-types
|
||||
# Debug/optimization
|
||||
CFLAGS += -Os -ggdb -g3
|
||||
CFLAGS += -fdata-sections -ffunction-sections
|
||||
@@ -90,7 +91,7 @@ CFLAGS += -I./lib/stm32/$(DEVICE_LINE)/Include
|
||||
CFLAGS += -I./lib/CMSIS/Core/Include
|
||||
CFLAGS += -I./lib/fonts/
|
||||
|
||||
CXX_FLAGS = -std=c++14 -fno-exceptions -fno-rtti
|
||||
CXX_FLAGS += -std=c++14 -fno-exceptions -fno-rtti
|
||||
|
||||
# Startup Definitions
|
||||
ASFLAGS += $(CPU_FLAGS)
|
||||
@@ -130,7 +131,7 @@ $(OUTPUT_BIN): $(OUTPUT_ELF)
|
||||
|
||||
$(OUTPUT_ELF): $(LINKER_SCRIPT) $(OBJS)
|
||||
@echo "LD $@"
|
||||
$(LD) -T $(LINKER_SCRIPT) $(LDFLAGS) -o $(OUTPUT_ELF) $(OBJS)
|
||||
@$(LD) -T $(LINKER_SCRIPT) $(LDFLAGS) -o $(OUTPUT_ELF) $(OBJS)
|
||||
|
||||
#
|
||||
# Utilities
|
||||
@@ -141,7 +142,7 @@ STM32FLASH_DEVICE = /dev/ttyUSB0
|
||||
.PHONY: flash
|
||||
flash: $(OUTPUT_BIN)
|
||||
@echo "FLASH $(OUTPUT_BIN)"
|
||||
$(STM32_PROG) -vb 3 --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go
|
||||
$(STM32_PROG) --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@@ -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,7 +92,8 @@ 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) */
|
||||
SET_TO(RTC->PRER, RTC_PRER_PREDIV_A, 0);
|
||||
// XXX reset to 0, this is to enable easier debugging
|
||||
SET_TO(RTC->PRER, RTC_PRER_PREDIV_A, 0);
|
||||
/*<! 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,23 +241,24 @@ ReturnCode RtcDriver::set_wakeup_in(Common::time_t wakeup_delay)
|
||||
return ReturnCode::OK;
|
||||
}
|
||||
|
||||
Common::time_t RtcDriver::RtcSystemTimer::get_time()
|
||||
{
|
||||
uint32_t new_secs, old_secs, ssr, subsecond;
|
||||
do {
|
||||
old_secs = m_seconds;
|
||||
ssr = RTC->SSR & 0xFFFF;
|
||||
new_secs = m_seconds;
|
||||
} while (new_secs != old_secs);
|
||||
Common::time_t RtcDriver::RtcSystemTimer::get_time()
|
||||
{
|
||||
uint32_t new_secs, old_secs, ssr, subsecond;
|
||||
do {
|
||||
old_secs = m_seconds;
|
||||
ssr = RTC->SSR & 0xFFFF;
|
||||
new_secs = m_seconds;
|
||||
} while (new_secs != old_secs);
|
||||
|
||||
new_secs = new_secs * LSE_CLOCK_FREQ;
|
||||
/** SSR is a countdown register */
|
||||
subsecond = (new_secs + LSE_CLOCK_FREQ - 1 - ssr) * Common::Time::MILLIS_PER_SEC / LSE_CLOCK_FREQ;
|
||||
return Common::Time::millis(subsecond);
|
||||
new_secs = new_secs * LSE_CLOCK_FREQ;
|
||||
/** SSR is a countdown register */
|
||||
subsecond = (new_secs + LSE_CLOCK_FREQ - 1 - ssr) * Common::Time::MILLIS_PER_SEC / LSE_CLOCK_FREQ;
|
||||
return Common::Time::millis(subsecond);
|
||||
}
|
||||
|
||||
void RtcDriver::RtcSystemTimer::increment_seconds()
|
||||
{
|
||||
/** TODO: Atomic increment */
|
||||
m_seconds++;
|
||||
}
|
||||
|
||||
|
||||
16
RtcDriver.h
16
RtcDriver.h
@@ -31,16 +31,6 @@
|
||||
|
||||
namespace BSP {
|
||||
|
||||
struct time_bcd {
|
||||
uint8_t hour_tens;
|
||||
uint8_t hour_ones;
|
||||
uint8_t minute_tens;
|
||||
uint8_t minute_ones;
|
||||
uint8_t second_tens;
|
||||
uint8_t second_ones;
|
||||
bool pm;
|
||||
};
|
||||
|
||||
class RtcDriver {
|
||||
public:
|
||||
|
||||
@@ -52,7 +42,8 @@ public:
|
||||
};
|
||||
static Common::ReturnCode init();
|
||||
static void increment_seconds();
|
||||
static Common::ReturnCode get_time(time_bcd &tm_bcd);
|
||||
static Common::ReturnCode get_time(Common::WallClockTime &tm_bcd);
|
||||
static Common::ReturnCode set_time(const Common::WallClockTime &tm_bcd);
|
||||
static Common::ReturnCode set_wakeup_in(Common::time_t wakeup_delay);
|
||||
|
||||
private:
|
||||
@@ -80,8 +71,7 @@ private:
|
||||
void increment_seconds();
|
||||
|
||||
private:
|
||||
/** I'll be dead before this rolls over */
|
||||
/** FIXME FIXME FIXME: XXX This should be an atomic */
|
||||
/** ~136 years. Good enough for me. */
|
||||
uint32_t m_seconds;
|
||||
|
||||
static constexpr uint32_t LSE_CLOCK_FREQ = 32768;
|
||||
|
||||
@@ -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
129
ScreenManager.cpp
Normal 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
68
ScreenManager.h
Normal 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
219
SetTimeScreen.cpp
Normal 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
69
SetTimeScreen.h
Normal 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
38
Task.h
@@ -29,8 +29,8 @@ namespace Common {
|
||||
namespace Schedule {
|
||||
|
||||
enum class ScheduleType {
|
||||
AT_TIME,
|
||||
NEVER,
|
||||
AT_TIME = 0,
|
||||
NEVER = 1,
|
||||
};
|
||||
|
||||
class NextTime {
|
||||
@@ -63,8 +63,38 @@ public:
|
||||
return { ScheduleType::AT_TIME, time };
|
||||
}
|
||||
|
||||
inline ScheduleType get_type() { return m_type; }
|
||||
inline time_t get_time() { return m_time; }
|
||||
inline ScheduleType get_type() const { return m_type; }
|
||||
inline time_t get_time() const { return m_time; }
|
||||
|
||||
inline bool operator<(const NextTime other) const {
|
||||
if (m_type < other.m_type) {
|
||||
return true;
|
||||
} else if (m_type == other.m_type) {
|
||||
switch(m_type){
|
||||
case ScheduleType::AT_TIME:
|
||||
return m_time < other.m_time;
|
||||
case ScheduleType::NEVER:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool operator==(const NextTime other) const {
|
||||
if (m_type != other.m_type) {
|
||||
return false;
|
||||
} else {
|
||||
switch(m_type){
|
||||
case ScheduleType::AT_TIME:
|
||||
return m_time == other.m_time;
|
||||
case ScheduleType::NEVER:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NextTime(ScheduleType type, time_t time) :
|
||||
|
||||
@@ -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
141
Time.h
@@ -79,4 +79,145 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class WallClockTime {
|
||||
|
||||
// TODO: Implement a saturating counter?
|
||||
|
||||
public:
|
||||
WallClockTime()
|
||||
: m_hours(0)
|
||||
, m_minutes(0)
|
||||
, m_seconds(0)
|
||||
{}
|
||||
|
||||
WallClockTime(uint8_t hours, uint8_t minutes, uint8_t seconds)
|
||||
: m_hours(hours)
|
||||
, m_minutes(minutes)
|
||||
, m_seconds(seconds)
|
||||
{}
|
||||
|
||||
static inline uint8_t hour24_to_hour12(uint8_t hour24) {
|
||||
if (hour24 == 0) {
|
||||
return 12;
|
||||
} else if (hour24 > 12) {
|
||||
return hour24 - 12;
|
||||
} else {
|
||||
return hour24;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t hour24_is_am(uint8_t hour24) {
|
||||
return hour24 >= 12;
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_12() const {
|
||||
return hour24_to_hour12(m_hours);
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_12_tens() const {
|
||||
return get_hours_12() / 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_12_ones() const {
|
||||
return get_hours_12() % 10;
|
||||
}
|
||||
|
||||
inline bool get_is_pm() const {
|
||||
return m_hours >= 12;
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_24() const {
|
||||
return m_hours;
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_24_ones() const {
|
||||
return m_hours % 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_hours_24_tens() const {
|
||||
return m_hours / 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_minutes() const {
|
||||
return m_minutes;
|
||||
}
|
||||
|
||||
inline uint8_t get_minutes_ones() const {
|
||||
return m_minutes % 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_minutes_tens() const {
|
||||
return m_minutes / 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_seconds() const {
|
||||
return m_seconds;
|
||||
}
|
||||
|
||||
inline uint8_t get_seconds_ones() const {
|
||||
return m_seconds % 10;
|
||||
}
|
||||
|
||||
inline uint8_t get_seconds_tens() const {
|
||||
return m_seconds / 10;
|
||||
}
|
||||
|
||||
inline void increment_hours() {
|
||||
m_hours++;
|
||||
if (m_hours >= 24) {
|
||||
m_hours = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void increment_minutes() {
|
||||
m_minutes++;
|
||||
if (m_minutes >= 60) {
|
||||
m_minutes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void increment_seconds() {
|
||||
m_seconds++;
|
||||
if (m_seconds >= 60) {
|
||||
m_seconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void decrement_hours() {
|
||||
if (m_hours == 0) {
|
||||
m_hours = 23;
|
||||
} else {
|
||||
m_hours--;
|
||||
}
|
||||
}
|
||||
|
||||
inline void decrement_minutes() {
|
||||
if (m_minutes == 0) {
|
||||
m_minutes = 59;
|
||||
} else {
|
||||
m_minutes--;
|
||||
}
|
||||
}
|
||||
|
||||
inline void decrement_seconds() {
|
||||
if (m_seconds == 0) {
|
||||
m_seconds = 59;
|
||||
} else {
|
||||
m_seconds--;
|
||||
}
|
||||
}
|
||||
|
||||
inline void toggle_am_pm() {
|
||||
if (m_hours < 12) {
|
||||
m_hours += 12;
|
||||
} else {
|
||||
m_hours -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t m_hours;
|
||||
uint8_t m_minutes;
|
||||
uint8_t m_seconds;
|
||||
};
|
||||
}
|
||||
|
||||
68
main.cpp
68
main.cpp
@@ -23,10 +23,13 @@
|
||||
#include "RtcDriver.h"
|
||||
#include "DisplayDriver.h"
|
||||
#include "SpiDriver.h"
|
||||
#include "DisplayTimeTask.h"
|
||||
#include "LptimPwm.h"
|
||||
#include "ButtonManager.h"
|
||||
|
||||
#include "ScreenManager.h"
|
||||
#include "DisplayTimeScreen.h"
|
||||
#include "SetTimeScreen.h"
|
||||
|
||||
#include "stm32l0xx.h"
|
||||
|
||||
#include "macros.h"
|
||||
@@ -37,8 +40,11 @@ static Common::Schedule::LowPowerTaskScheduler<10> g_sched;
|
||||
static BSP::SpiDriver g_spi(g_sched);
|
||||
static BSP::DisplayDriver g_display(g_sched, g_spi);
|
||||
static BSP::LptimPwm g_lptim_pwm(LPTIM1);
|
||||
static BSP::ButtonManager g_btn_manager(0, 1, 3, Time::millis(1));
|
||||
static DisplayTimeTask g_display_time(g_display);
|
||||
static BSP::ButtonManager g_btn_manager(2, 1, 0, Time::millis(1));
|
||||
|
||||
static ScreenManager g_screen_manager(g_sched, g_display, g_btn_manager);
|
||||
static SetTimeScreen g_set_time_screen(g_display, g_screen_manager);
|
||||
static DisplayTimeScreen g_display_time_screen(g_display, g_screen_manager, g_set_time_screen);
|
||||
|
||||
extern "C" void __cxa_pure_virtual() { while(1) {} }
|
||||
|
||||
@@ -97,7 +103,6 @@ void SystemInit()
|
||||
CLR(RCC->CFGR,
|
||||
RCC_CFGR_PLLSRC | RCC_CFGR_PLLMUL | RCC_CFGR_PLLDIV);
|
||||
|
||||
|
||||
/*!< Disable all interrupts */
|
||||
RCC->CIER = 0x00000000;
|
||||
|
||||
@@ -133,45 +138,42 @@ static void _init(void)
|
||||
g_spi.init();
|
||||
g_btn_manager.init();
|
||||
g_display.init();
|
||||
g_display_time.init();
|
||||
g_screen_manager.init();
|
||||
g_screen_manager.set_root_screen(g_display_time_screen);
|
||||
|
||||
// Enqueue each of the tasks
|
||||
Common::Schedule::NextTime asap = Common::Schedule::NextTime::asap();
|
||||
g_sched.add_task(g_spi, asap);
|
||||
g_sched.add_task(g_btn_manager, asap);
|
||||
g_sched.add_task(g_display, asap);
|
||||
g_sched.add_task(g_display_time, asap);
|
||||
g_sched.add_task(g_screen_manager, asap);
|
||||
|
||||
// And we're off! This will never return
|
||||
g_sched.run();
|
||||
}
|
||||
|
||||
|
||||
extern "C" void NMI_Handler() {while (1);}
|
||||
extern "C" void HardFault_Handler() {while (1);}
|
||||
extern "C" void SVC_Handler() {while (1);}
|
||||
extern "C" void PendSV_Handler() {while (1);}
|
||||
extern "C" void SysTick_Handler() {while (1);}
|
||||
extern "C" void NMI_Handler() {while (1);}
|
||||
extern "C" void HardFault_Handler() {while (1);}
|
||||
extern "C" void SVC_Handler() {while (1);}
|
||||
extern "C" void PendSV_Handler() {while (1);}
|
||||
extern "C" void SysTick_Handler() {while (1);}
|
||||
|
||||
extern "C" void WWDG_IRQHandler() {while (1);}
|
||||
extern "C" void PVD_IRQHandler() {while (1);}
|
||||
extern "C" void WDT_IRQHandler() {while (1);}
|
||||
//extern "C" void RTC_IRQHandler() {while (1);}
|
||||
extern "C" void FLASH_IRQHandler() {while (1);}
|
||||
extern "C" void RCC_CRS_IRQHandler() {while (1);}
|
||||
// extern "C" void EXTI_1_0_IRQHandler() {while (1);}
|
||||
// extern "C" void EXTI_3_2_IRQHandler() {while (1);}
|
||||
// extern "C" void EXTI_15_4_IRQHandler() {while (1);}
|
||||
extern "C" void DMA1_CHANNEL1_IRQHandler() {while (1);}
|
||||
extern "C" void DMA1_CHANNEL3_2_IRQHandler() {while (1);}
|
||||
extern "C" void DMA_CHANNEL_7_4_IRQHandler() {while (1);}
|
||||
extern "C" void ADC_COMP_IRQHandler() {while (1);}
|
||||
extern "C" void LPTIM1_IRQHandler() {while (1);}
|
||||
extern "C" void USART4_USART5_IRQHandler() {while (1);}
|
||||
extern "C" void TIM2_IRQHandler() {while (1);}
|
||||
extern "C" void TIM3_IRQHandler() {while (1);}
|
||||
extern "C" void TIM6_IRQHandler() {while (1);}
|
||||
extern "C" void TIM7_IRQHandler() {while (1);}
|
||||
extern "C" void TIM21_IRQHandler() {while (1);}
|
||||
extern "C" void I2C3_IRQHandler() {while (1);}
|
||||
extern "C" void TIM22_IRQHandler() {while (1);}
|
||||
extern "C" void WWDG_IRQHandler() {while (1);}
|
||||
extern "C" void PVD_IRQHandler() {while (1);}
|
||||
extern "C" void WDT_IRQHandler() {while (1);}
|
||||
extern "C" void FLASH_IRQHandler() {while (1);}
|
||||
extern "C" void RCC_CRS_IRQHandler() {while (1);}
|
||||
extern "C" void DMA1_CHANNEL1_IRQHandler() {while (1);}
|
||||
extern "C" void DMA1_CHANNEL3_2_IRQHandler() {while (1);}
|
||||
extern "C" void DMA_CHANNEL_7_4_IRQHandler() {while (1);}
|
||||
extern "C" void ADC_COMP_IRQHandler() {while (1);}
|
||||
extern "C" void LPTIM1_IRQHandler() {while (1);}
|
||||
extern "C" void USART4_USART5_IRQHandler() {while (1);}
|
||||
extern "C" void TIM2_IRQHandler() {while (1);}
|
||||
extern "C" void TIM3_IRQHandler() {while (1);}
|
||||
extern "C" void TIM6_IRQHandler() {while (1);}
|
||||
extern "C" void TIM7_IRQHandler() {while (1);}
|
||||
extern "C" void TIM21_IRQHandler() {while (1);}
|
||||
extern "C" void I2C3_IRQHandler() {while (1);}
|
||||
extern "C" void TIM22_IRQHandler() {while (1);}
|
||||
|
||||
Reference in New Issue
Block a user