Add persistent settings store

In preparation for on-screen menu.
This commit is contained in:
Frederik Walk 2023-07-06 19:37:57 +02:00
parent b440a35630
commit 0f1fd87a07
6 changed files with 239 additions and 12 deletions

View File

@ -1,6 +1,9 @@
#ifndef _PERIPHERALS_DISPLAY_H_
#define _PERIPHERALS_DISPLAY_H_
#include "usb/usb_driver.h"
#include "utils/InputState.h"
#include <ssd1306/ssd1306.h>
#include "hardware/i2c.h"
@ -26,6 +29,10 @@ class Display {
Config m_config;
State m_state;
Utils::InputState m_input_state;
usb_mode_t m_usb_mode;
uint8_t m_player_id;
ssd1306_t m_display;
void drawIdleScreen();
@ -34,6 +41,10 @@ class Display {
public:
Display(const Config &config);
void setInputState(const Utils::InputState &state);
void setUsbMode(usb_mode_t mode);
void setPlayerId(uint8_t player_id);
void showIdle();
void showMenu();

View File

@ -11,6 +11,13 @@ namespace Doncon::Peripherals {
class Drum {
public:
struct Config {
struct Thresholds {
uint16_t don_left;
uint16_t ka_left;
uint16_t don_right;
uint16_t ka_right;
};
struct {
uint8_t don_left;
uint8_t ka_left;
@ -18,13 +25,7 @@ class Drum {
uint8_t ka_right;
} pins;
struct {
uint16_t don_left;
uint16_t ka_left;
uint16_t don_right;
uint16_t ka_right;
} trigger_thresholds;
Thresholds trigger_thresholds;
uint8_t sample_count;
uint16_t debounce_delay_ms;
};

View File

@ -0,0 +1,58 @@
#ifndef _UTILS_SETTINGSSTORE_H_
#define _UTILS_SETTINGSSTORE_H_
#include "peripherals/Drum.h"
#include "usb/usb_driver.h"
#include "hardware/flash.h"
namespace Doncon::Utils {
class SettingsStore {
private:
const static uint32_t m_flash_size = FLASH_SECTOR_SIZE;
const static uint32_t m_flash_offset = PICO_FLASH_SIZE_BYTES - m_flash_size;
const static uint32_t m_store_size = FLASH_PAGE_SIZE;
const static uint32_t m_store_pages = m_flash_size / m_store_size;
const static uint8_t m_magic_byte = 0x39;
struct __attribute((packed, aligned(1))) Storecache {
uint8_t in_use;
usb_mode_t usb_mode;
Peripherals::Drum::Config::Thresholds trigger_thresholds;
uint8_t _padding[m_store_size - sizeof(uint8_t) - sizeof(usb_mode_t) -
sizeof(Peripherals::Drum::Config::Thresholds)];
};
static_assert(sizeof(Storecache) == m_store_size);
enum class RebootType {
None,
Normal,
Bootsel,
};
Storecache m_store_cache;
bool m_dirty;
RebootType m_scheduled_reboot;
private:
Storecache read();
public:
SettingsStore();
void setUsbMode(usb_mode_t mode);
usb_mode_t getUsbMode();
void setTriggerThresholds(Peripherals::Drum::Config::Thresholds thresholds);
Peripherals::Drum::Config::Thresholds getTriggerThresholds();
void scheduleReboot(bool bootsel = false);
void store();
};
} // namespace Doncon::Utils
#endif // _UTILS_SETTINGSSTORE_H_

View File

@ -3,6 +3,7 @@
#include "peripherals/Drum.h"
#include "peripherals/StatusLed.h"
#include "usb/usb_driver.h"
#include "utils/SettingsStore.h"
#include "GlobalConfiguration.h"
@ -14,9 +15,23 @@
using namespace Doncon;
queue_t control_queue;
queue_t drum_input_queue;
queue_t controller_input_queue;
enum class ControlCommand {
SetUsbMode,
SetPlayerLed,
};
struct ControlMessage {
ControlCommand command;
union {
usb_mode_t usb_mode;
usb_player_led_t player_led;
} data;
};
void core1_task() {
multicore_lockout_victim_init();
@ -26,12 +41,28 @@ void core1_task() {
gpio_pull_up(Config::Default::i2c_config.scl_pin);
i2c_init(Config::Default::i2c_config.block, Config::Default::i2c_config.speed_hz);
Utils::InputState input_state;
Peripherals::Buttons buttons(Config::Default::button_config);
Peripherals::StatusLed led(Config::Default::led_config);
Peripherals::Display display(Config::Default::display_config);
Utils::InputState input_state;
ControlMessage control_msg;
while (true) {
if (queue_try_remove(&control_queue, &control_msg)) {
switch (control_msg.command) {
case ControlCommand::SetUsbMode:
display.setUsbMode(control_msg.data.usb_mode);
break;
case ControlCommand::SetPlayerLed:
if (control_msg.data.player_led.type == USB_PLAYER_LED_ID) {
display.setPlayerId(control_msg.data.player_led.id);
} else if (control_msg.data.player_led.type == USB_PLAYER_LED_COLOR) {
}
break;
}
}
buttons.updateInputState(input_state);
queue_try_add(&controller_input_queue, &input_state.controller);
@ -48,18 +79,34 @@ void core1_task() {
}
int main() {
queue_init(&control_queue, sizeof(ControlMessage), 1);
queue_init(&drum_input_queue, sizeof(Utils::InputState::Drum), 1);
queue_init(&controller_input_queue, sizeof(Utils::InputState::Controller), 1);
multicore_launch_core1(core1_task);
Utils::InputState input_state;
Peripherals::Drum drum(Config::Default::drum_config);
usb_mode_t mode = Config::Default::usb_mode;
auto settings_store = std::make_shared<Utils::SettingsStore>();
Peripherals::Drum drum(Config::Default::drum_config);
auto mode = settings_store->getUsbMode();
usb_driver_init(mode);
usb_driver_set_player_led_cb([](usb_player_led_t player_led) {
auto ctrl_message = ControlMessage{ControlCommand::SetPlayerLed, {.player_led = player_led}};
queue_add_blocking(&control_queue, &ctrl_message);
});
stdio_init_all();
multicore_launch_core1(core1_task);
auto readSettings = [&]() {
ControlMessage ctrl_message;
ctrl_message = {ControlCommand::SetUsbMode, {.usb_mode = mode}};
queue_add_blocking(&control_queue, &ctrl_message);
};
readSettings();
while (true) {
drum.updateInputState(input_state);

View File

@ -165,12 +165,17 @@ static const std::array<uint8_t, 546> ka_r_bmp = {
0x00, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xf0, 0x00, 0xff, 0xff,
0xff, 0xc1, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00};
Display::Display(const Config &config) : m_config(config), m_state(State::Idle) {
Display::Display(const Config &config)
: m_config(config), m_state(State::Idle), m_input_state({}), m_usb_mode(USB_MODE_DEBUG), m_player_id(0) {
m_display.external_vcc = false;
ssd1306_init(&m_display, 128, 64, m_config.i2c_address, m_config.i2c_block);
ssd1306_clear(&m_display);
}
void Display::setInputState(const Utils::InputState &state) { m_input_state = state; }
void Display::setUsbMode(usb_mode_t mode) { m_usb_mode = mode; };
void Display::setPlayerId(uint8_t player_id) { m_player_id = player_id; };
void Display::showIdle() { m_state = State::Idle; }
void Display::showMenu() { m_state = State::Menu; }

105
src/utils/SettingsStore.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "utils/SettingsStore.h"
#include "GlobalConfiguration.h"
#include "hardware/watchdog.h"
#include "pico/bootrom.h"
#include "pico/multicore.h"
namespace Doncon::Utils {
static uint8_t read_byte(uint32_t offset) { return *(reinterpret_cast<uint8_t *>(XIP_BASE + offset)); }
SettingsStore::SettingsStore()
: m_store_cache({m_magic_byte, Config::Default::usb_mode, Config::Default::drum_config.trigger_thresholds, {}}),
m_dirty(true), m_scheduled_reboot(RebootType::None) {
uint32_t current_page = m_flash_offset + m_flash_size - m_store_size;
bool found_valid = false;
for (uint8_t i = 0; i < m_store_pages; ++i) {
if (read_byte(current_page) == m_magic_byte) {
found_valid = true;
break;
} else {
current_page -= m_store_size;
}
}
if (found_valid) {
m_store_cache = *(reinterpret_cast<Storecache *>(XIP_BASE + current_page));
m_dirty = false;
}
}
void SettingsStore::setUsbMode(usb_mode_t mode) {
if (mode != m_store_cache.usb_mode) {
m_store_cache.usb_mode = mode;
m_dirty = true;
scheduleReboot();
}
}
usb_mode_t SettingsStore::getUsbMode() { return m_store_cache.usb_mode; }
void SettingsStore::setTriggerThresholds(Peripherals::Drum::Config::Thresholds thresholds) {
if (m_store_cache.trigger_thresholds.don_left != thresholds.don_left ||
m_store_cache.trigger_thresholds.don_right != thresholds.don_right ||
m_store_cache.trigger_thresholds.ka_left != thresholds.ka_left ||
m_store_cache.trigger_thresholds.ka_right != thresholds.ka_right) {
m_store_cache.trigger_thresholds = thresholds;
m_dirty = true;
}
}
Peripherals::Drum::Config::Thresholds SettingsStore::getTriggerThresholds() { return m_store_cache.trigger_thresholds; }
void SettingsStore::store() {
if (m_dirty) {
multicore_lockout_start_blocking();
uint32_t interrupts = save_and_disable_interrupts();
uint32_t current_page = m_flash_offset;
bool do_erase = true;
for (uint8_t i = 0; i < m_store_pages; ++i) {
if (read_byte(current_page) == 0xFF) {
do_erase = false;
break;
} else {
current_page += m_store_size;
}
}
if (do_erase) {
flash_range_erase(m_flash_offset, m_flash_size);
current_page = m_flash_offset;
}
flash_range_program(current_page, reinterpret_cast<uint8_t *>(&m_store_cache), sizeof(m_store_cache));
m_dirty = false;
restore_interrupts(interrupts);
multicore_lockout_end_blocking();
}
switch (m_scheduled_reboot) {
case RebootType::Normal:
watchdog_enable(1, 1);
while (1)
;
break;
case RebootType::Bootsel:
reset_usb_boot(0, 0);
break;
case RebootType::None:
break;
}
}
void SettingsStore::scheduleReboot(bool bootsel) {
if (m_scheduled_reboot != RebootType::Bootsel) {
m_scheduled_reboot = (bootsel ? RebootType::Bootsel : RebootType::Normal);
}
}
} // namespace Doncon::Utils