Add button tests

This is implemented by connecting the DTR pin of the serial device to the BTN_UP pin of the watch.

Also, make it possible to flash different applications with the Makefile.

Resolves #4
This commit is contained in:
2020-06-11 16:39:51 +00:00
parent cdf0f4ffc9
commit 573504547c
9 changed files with 260 additions and 65 deletions

View File

@@ -54,9 +54,9 @@ public:
static ButtonManager *m_instance; static ButtonManager *m_instance;
enum Button { enum Button {
UP = 0, DOWN = 0,
MID, MID,
DOWN, UP,
Count Count
}; };

View File

@@ -27,10 +27,13 @@
using BSP::Schedule::Task; using BSP::Schedule::Task;
using BSP::Schedule::NextTime; using BSP::Schedule::NextTime;
using BSP::ReturnCode; using BSP::ReturnCode;
using BSP::ButtonManager;
using Button = ButtonManager::Button;
using ButtonState = ButtonManager::ButtonState;
ScreenManager::ScreenManager(BSP::Schedule::TaskScheduler &scheduler, ScreenManager::ScreenManager(BSP::Schedule::TaskScheduler &scheduler,
BSP::DisplayDriver &display, BSP::DisplayDriver &display,
BSP::ButtonManager &buttons) ButtonManager &buttons)
: m_scheduler(scheduler) : m_scheduler(scheduler)
, m_screen_stack{nullptr} , m_screen_stack{nullptr}
, m_screen_stack_depth(0) , m_screen_stack_depth(0)
@@ -43,23 +46,23 @@ ScreenManager::ScreenManager(BSP::Schedule::TaskScheduler &scheduler,
ReturnCode ScreenManager::init() ReturnCode ScreenManager::init()
{ {
m_buttons.set_callback( m_buttons.set_callback(
BSP::ButtonManager::Button::UP, Button::UP,
[this](BSP::ButtonManager::ButtonState state) { [this](ButtonState state) {
if (state == BSP::ButtonManager::ButtonState::PRESSED) { if (state == ButtonState::PRESSED) {
current_screen()->notify_up_button(); current_screen()->notify_up_button();
}}); }});
m_buttons.set_callback( m_buttons.set_callback(
BSP::ButtonManager::Button::MID, Button::MID,
[this](BSP::ButtonManager::ButtonState state) { [this](ButtonState state) {
if (state == BSP::ButtonManager::ButtonState::PRESSED) { if (state == ButtonState::PRESSED) {
return current_screen()->notify_middle_button(); return current_screen()->notify_middle_button();
}}); }});
m_buttons.set_callback( m_buttons.set_callback(
BSP::ButtonManager::Button::DOWN, Button::DOWN,
[this](BSP::ButtonManager::ButtonState state) { [this](ButtonState state) {
if (state == BSP::ButtonManager::ButtonState::PRESSED) { if (state == ButtonState::PRESSED) {
current_screen()->notify_down_button(); current_screen()->notify_down_button();
}}); }});

View File

@@ -40,47 +40,47 @@
#include "Mcu.h" #include "Mcu.h"
using BSP::Time; using namespace BSP;
// GPIOs // GPIOs
static BSP::GpioDriver g_gpioa(GPIOA); static GpioDriver g_gpioa(GPIOA);
static BSP::GpioPin g_dbg0(g_gpioa, 3); static GpioPin g_dbg0(g_gpioa, 3);
static BSP::GpioPin g_dbg1(g_gpioa, 6); static GpioPin g_dbg1(g_gpioa, 6);
static BSP::GpioPin g_tx(g_gpioa, 9); static GpioPin g_tx(g_gpioa, 9);
static BSP::GpioPin g_rx(g_gpioa, 10); static GpioPin g_rx(g_gpioa, 10);
static BSP::GpioPin g_btn_down(g_gpioa, 0); static GpioPin g_btn_down(g_gpioa, 0);
static BSP::GpioPin g_btn_mid(g_gpioa, 1); static GpioPin g_btn_mid(g_gpioa, 1);
static BSP::GpioPin g_btn_up(g_gpioa, 2); static GpioPin g_btn_up(g_gpioa, 2);
static BSP::GpioPin g_nss(g_gpioa, 4); static GpioPin g_nss(g_gpioa, 4);
static BSP::GpioPin g_sck(g_gpioa, 5); static GpioPin g_sck(g_gpioa, 5);
static BSP::GpioPin g_mosi(g_gpioa, 12); static GpioPin g_mosi(g_gpioa, 12);
static BSP::GpioPin g_extcomm(g_gpioa, 7); static GpioPin g_extcomm(g_gpioa, 7);
// Scheduler and Tasks // Scheduler and Tasks
static BSP::Schedule::LowPowerTaskScheduler<5> g_sched; static Schedule::LowPowerTaskScheduler<5> g_sched;
static BSP::SpiDriver g_spi(g_sched, g_nss); static SpiDriver g_spi(g_sched, g_nss);
static BSP::DisplayDriver g_display(g_sched, g_spi); static DisplayDriver g_display(g_sched, g_spi);
static BSP::LptimPwm g_lptim_pwm(LPTIM1); static LptimPwm g_lptim_pwm(LPTIM1);
static BSP::ButtonManager g_btn_manager( static ButtonManager g_btn_mgr(
g_sched, g_btn_up, g_btn_mid, g_btn_down, Time::millis(200)); g_sched, g_btn_up, g_btn_mid, g_btn_down, Time::millis(200));
// Screens- contexts for the display // Screens- contexts for the display
static ScreenManager g_screen_manager(g_sched, g_display, g_btn_manager); static ScreenManager g_screen_mgr(g_sched, g_display, g_btn_mgr);
static SetTimeScreen g_set_time_screen(g_display, g_screen_manager); static SetTimeScreen g_set_time_screen(g_display, g_screen_mgr);
static SetTimeScreen g_set_date_screen(g_display, g_screen_manager); static SetTimeScreen g_set_date_screen(g_display, g_screen_mgr);
static StopwatchScreen g_stopwatch_screen(g_display, g_screen_manager); static StopwatchScreen g_stopwatch_screen(g_display, g_screen_mgr);
static MenuScreen g_set_face_screen(g_display, static MenuScreen g_set_face_screen(g_display,
g_screen_manager, g_screen_mgr,
"Face", "Face",
std::initializer_list<MenuScreenItem>()); std::initializer_list<MenuScreenItem>());
static MenuScreen g_settings_menu_screen(g_display, static MenuScreen g_settings_menu_screen(g_display,
g_screen_manager, g_screen_mgr,
"Settings", "Settings",
std::initializer_list<MenuScreenItem>( std::initializer_list<MenuScreenItem>(
{ {
@@ -89,45 +89,45 @@ static MenuScreen g_settings_menu_screen(g_display,
MenuScreenItem("Set Face", g_set_face_screen) MenuScreenItem("Set Face", g_set_face_screen)
})); }));
static MenuScreen g_apps_menu_screen(g_display, static MenuScreen g_apps_menu_screen(g_display,
g_screen_manager, g_screen_mgr,
"Apps", std::initializer_list<MenuScreenItem>({MenuScreenItem("Stopwatch", g_stopwatch_screen)})); "Apps", std::initializer_list<MenuScreenItem>({MenuScreenItem("Stopwatch", g_stopwatch_screen)}));
static MenuScreen g_main_menu_screen(g_display, static MenuScreen g_main_menu_screen(g_display,
g_screen_manager, g_screen_mgr,
"Main Menu", "Main Menu",
std::initializer_list<MenuScreenItem>( std::initializer_list<MenuScreenItem>(
{ {
MenuScreenItem("Apps", g_apps_menu_screen), MenuScreenItem("Apps", g_apps_menu_screen),
MenuScreenItem("Settings", g_settings_menu_screen) MenuScreenItem("Settings", g_settings_menu_screen)
})); }));
static AnalogTimeScreen g_analog_time_screen(g_display, g_screen_manager, g_main_menu_screen); static AnalogTimeScreen g_analog_time_screen(g_display, g_screen_mgr, g_main_menu_screen);
static BigDigitalTimeScreen g_digital_time_screen(g_display, g_screen_manager, g_main_menu_screen); static BigDigitalTimeScreen g_digital_time_screen(g_display, g_screen_mgr, g_main_menu_screen);
[[noreturn]] void main() { [[noreturn]] void main() {
// Set up the system clock // Set up the system clock
BSP::RtcDriver::init(); RtcDriver::init();
BSP::SystemTimer::set_timer(BSP::RtcDriver::get_system_timer()); SystemTimer::set_timer(RtcDriver::get_system_timer());
BSP::LowPower::init(); LowPower::init();
// Initialize the tasks // Initialize the tasks
g_lptim_pwm.init(); g_lptim_pwm.init();
g_spi.init(); g_spi.init();
g_btn_manager.init(); g_btn_mgr.init();
g_display.init(); g_display.init();
g_screen_manager.init(); g_screen_mgr.init();
g_screen_manager.set_root_screen(g_analog_time_screen); g_screen_mgr.set_root_screen(g_analog_time_screen);
g_set_face_screen.add_item(MenuScreenItem("Analog", g_set_face_screen.add_item(MenuScreenItem("Analog",
[]() { g_screen_manager.set_root_screen(g_analog_time_screen); })); []() { g_screen_mgr.set_root_screen(g_analog_time_screen); }));
g_set_face_screen.add_item(MenuScreenItem("Digital", g_set_face_screen.add_item(MenuScreenItem("Digital",
[]() { g_screen_manager.set_root_screen(g_digital_time_screen); })); []() { g_screen_mgr.set_root_screen(g_digital_time_screen); }));
// Enqueue each of the tasks // Enqueue each of the tasks
BSP::Schedule::NextTime asap = BSP::Schedule::NextTime::asap(); Schedule::NextTime asap = Schedule::NextTime::asap();
g_sched.add_task(g_spi, asap); g_sched.add_task(g_spi, asap);
g_sched.add_task(g_btn_manager, asap); g_sched.add_task(g_btn_mgr, asap);
g_sched.add_task(g_display, asap); g_sched.add_task(g_display, asap);
g_sched.add_task(g_screen_manager, asap); g_sched.add_task(g_screen_mgr, asap);
// And we're off! This will never return // And we're off! This will never return
g_sched.run(); g_sched.run();

View File

@@ -83,7 +83,7 @@ S_SOURCES := $(call find_important, $(SOURCEDIR), '*.s')
SPP_SOURCES := Bsp/Mcu/$(DEVICE_TYPE).S SPP_SOURCES := Bsp/Mcu/$(DEVICE_TYPE).S
SOURCES = $(C_SOURCES) $(S_SOURCES) $(SPP_SOURCES) $(CPP_SOURCES) SOURCES = $(C_SOURCES) $(S_SOURCES) $(SPP_SOURCES) $(CPP_SOURCES)
APPS := ./Application/main ./Test/pass ./Test/fail ./Test/timeout ./Test/clock ./Test/stop ./Test/no_start ./Test/lptim ./Test/set_time ./Test/periodic_alarms ./Test/wakeup_irq APPS := ./Application/main ./Test/pass ./Test/fail ./Test/timeout ./Test/clock ./Test/stop ./Test/no_start ./Test/lptim ./Test/set_time ./Test/periodic_alarms ./Test/wakeup_irq ./Test/button
APP_ELFS = $(addsuffix .elf, $(APPS)) APP_ELFS = $(addsuffix .elf, $(APPS))
APP_MAPS = $(addsuffix .map, $(APPS)) APP_MAPS = $(addsuffix .map, $(APPS))
APP_BINS = $(addsuffix .bin, $(APPS)) APP_BINS = $(addsuffix .bin, $(APPS))
@@ -223,22 +223,22 @@ $(FONT_GEN_DIR)/large_digits.h $(FONT_GEN_DIR)/large_digits.c: Gen/fixedfont-to-
# #
STM32FLASH_DEVICE = /dev/ttyUSB0 STM32FLASH_DEVICE = /dev/ttyUSB0
FLASH_BIN ?= $(OUTPUT_BIN)
.PHONY: flash .PHONY: flash
flash: $(OUTPUT_BIN) flash: $(OUTPUT_BIN)
@echo "FLASH $(OUTPUT_BIN)" @echo "FLASH $(OUTPUT_BIN)"
$(STM32_PROG) --connect port=SWD reset=Hwrst -w $(OUTPUT_BIN) 0x8000000 -v --go $(STM32_PROG) --connect port=SWD reset=Hwrst -w $(FLASH_BIN) 0x8000000 -v --go
.PHONY: jlink .PHONY: jlink
jlink: $(OUTPUT_BIN) jlink: $(OUTPUT_BIN)
@echo "FLASH $(OUTPUT_BIN)" @echo "FLASH $(OUTPUT_BIN)"
JLinkExe -device $$(echo $(DEVICE_TYPE) | tr '[:lower:]' '[:upper:]') -if SWD \ ./jlink.sh $(DEVICE_TYPE) $(FLASH_BIN)
-speed auto -autoconnect 1 -CommanderScript cmd.jlink
.PHONY: clean .PHONY: clean
clean: clean:
rm -f $(OBJS) $(OUTPUT_BIN) $(OUTPUT_ELF) $(FONT_C_FILES) $(FONT_H_FILES) $(OUTPUT_MAP) $(addsuffix .su,$(basename $(OBJS))) rm -f $(OBJS) $(OUTPUT_BIN) $(OUTPUT_ELF) $(FONT_C_FILES) $(FONT_H_FILES) $(OUTPUT_MAP) $(APPS) $(APP_ELFS) $(APP_MAPS) $(APP_BINS) $(APP_OBJS) $(addsuffix .su,$(basename $(ALL_OBJS)))
# Please do not delete my files. # Please do not delete my files.
.SECONDARY: $(ALL_OBJS) $(APP_ELFS) .SECONDARY: $(ALL_OBJS) $(APP_ELFS)

111
firmware/Test/button.cpp Normal file
View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2020 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 "Bsp/Drivers/GpioDriver.h"
#include "Bsp/Drivers/RtcDriver.h"
#include "Bsp/Drivers/UsartDriver.h"
#include "Bsp/LowPowerTaskScheduler.h"
#include "Bsp/macros.h"
#include "Application/ButtonManager.h"
#include "printf.h"
#include "test.h"
#include "Mcu.h"
using namespace BSP;
using Button = ButtonManager::Button;
using ButtonState = ButtonManager::ButtonState;
static Schedule::LowPowerTaskScheduler<5> g_sched;
static GpioDriver g_gpioa(GPIOA);
#if defined(BOARD_WATCH)
static UsartDriver g_test_uart(USART2, g_sched);
static GpioPin g_tx_pin(g_gpioa, 9);
#elif defined(BOARD_DEVBOARD)
static UsartDriver g_test_uart(USART1, g_sched);
#endif
static GpioPin g_btn_down(g_gpioa, 0);
static GpioPin g_btn_mid(g_gpioa, 1);
static GpioPin g_btn_up(g_gpioa, 2);
static ButtonManager g_btn_mgr(g_sched,
g_btn_down,
g_btn_mid,
g_btn_up,
Time::millis(50));
class IdleTask : public BSP::Schedule::Task {
public:
IdleTask()
{}
BSP::Schedule::NextTime execute() override {
return BSP::Schedule::NextTime::asap();
}
};
static IdleTask g_idle;
[[noreturn]] void main() {
g_gpioa.enable();
#if defined(BOARD_WATCH)
g_tx_pin.configure_alternate_function(4);
#endif
g_test_uart.init();
g_test_uart.tx_blocking(test_start_text);
RtcDriver::init();
SystemTimer::set_timer(RtcDriver::get_system_timer());
LowPower::init();
g_btn_down.configure_input(GpioDriver::input_pull_t::PULL_UP);
g_btn_mid.configure_input(GpioDriver::input_pull_t::PULL_UP);
g_btn_up.configure_input(GpioDriver::input_pull_t::PULL_UP);
g_btn_mgr.init();
ButtonManager::ChangeCallback callback =
[&](ButtonState state) {
g_test_uart.tx_blocking("up:");
if (state == ButtonState::PRESSED) {
g_test_uart.tx_blocking("pressed\r\n");
} else {
g_test_uart.tx_blocking("released\r\n");
}
};
g_btn_mgr.set_callback(Button::UP, callback);
g_test_uart.tx_blocking("Waiting for button press...\r\n");
Schedule::NextTime asap = Schedule::NextTime::asap();
g_sched.add_task(g_test_uart, asap);
g_sched.add_task(g_btn_mgr, asap);
g_sched.add_task(g_idle, asap);
g_sched.run();
TEST_SPIN();
}

View File

@@ -1,6 +0,0 @@
exitonerror 1
h
r
loadbin Application/main.bin 0x8000000
g
q

41
firmware/jlink.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Copyright (C) 2020 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.
set -e
DEVICE_TYPE=$1
APP_BIN="$2"
ADDR=0x8000000
FLASH_SCRIPT="\
exitonerror 1
h
r
loadbin ${APP_BIN} ${ADDR}
g
q"
flash_script="$(mktemp)"
echo "$FLASH_SCRIPT" > "$flash_script"
JLinkExe -device "$DEVICE_TYPE" -if SWD \
-speed auto -autoconnect 1 -CommanderScript "$flash_script"
rm "$flash_script"

View File

@@ -150,7 +150,7 @@ def test_periodic_alarms(context_factory, logger):
def test_clock(context_factory, logger): def test_clock(context_factory, logger):
serial_dev, jlink = context_factory("Test/clock.bin") serial_dev, jlink = context_factory("Test/clock.bin")
EXPECTED_RUNTIME = 10 EXPECTED_RUNTIME = 10
TOLERANCE = 0.1 TOLERANCE = 0.2
serial_dev.timeout = EXPECTED_RUNTIME * 1.2 serial_dev.timeout = EXPECTED_RUNTIME * 1.2
@@ -297,6 +297,52 @@ def test_lptim(context_factory, logger):
assert max_f < 51 assert max_f < 51
def test_button_slow(context_factory, logger):
serial_dev, jlink = context_factory("Test/button.bin")
serial_dev.timeout = 0.3
ASSERTED = True
serial_dev.dtr = not ASSERTED
while (line := serial_dev.readline()) is not None and len(line) > 0:
pass
for _ in range(5):
serial_dev.dtr = ASSERTED
press_line = serial_dev.readline()
serial_dev.dtr = not ASSERTED
release_line = serial_dev.readline()
assert press_line == b"up:pressed\r\n"
assert release_line == b"up:released\r\n"
def test_button_fast(context_factory, logger):
serial_dev, jlink = context_factory("Test/button.bin")
serial_dev.timeout = 0.3
ASSERTED = True
serial_dev.dtr = not ASSERTED
time.sleep(0.3)
serial_dev.timeout = 0
while (line := serial_dev.readline()) is not None and len(line) > 0:
pass
for _ in range(25):
serial_dev.dtr = ASSERTED
time.sleep(0.075)
serial_dev.dtr = not ASSERTED
time.sleep(0.075)
serial_dev.timeout = 0.3
for _ in range(25):
press_line = serial_dev.readline()
release_line = serial_dev.readline()
assert press_line == b"up:pressed\r\n"
assert release_line == b"up:released\r\n"
serial_dev.timeout = 0
assert serial_dev.readline() == b""
def main(): def main():
pytest.main(sys.argv) pytest.main(sys.argv)