switch to event based input system (untested)

This commit is contained in:
Mitchell Marino 2026-04-10 07:58:31 -05:00
parent 2f1ff23678
commit f5c938d61f
5 changed files with 1127 additions and 1 deletions

View File

@ -4,7 +4,9 @@ set(SOURCES
"TM1640/TM1640.cpp" "TM1640/TM1640.cpp"
"SparkFunBQ27441/SparkFunBQ27441.cpp" "SparkFunBQ27441/SparkFunBQ27441.cpp"
"esp_lcd_ili9488/esp_lcd_ili9488.c" "esp_lcd_ili9488/esp_lcd_ili9488.c"
"bottom_half.cpp" # "bottom_half.cpp"
"event_based_bottom_half.cpp"
"inputs.cpp"
"char_lcd.cpp" "char_lcd.cpp"
"game_timer.cpp" "game_timer.cpp"
"i2c_lcd_pcf8574.c" "i2c_lcd_pcf8574.c"

View File

@ -0,0 +1,326 @@
#include "bottom_half.h"
#include "inputs.hpp"
#include <array>
static uint8_t reverse_4_bits(uint8_t value) {
return static_cast<uint8_t>(((value & 0x1) << 3) |
((value & 0x2) << 1) |
((value & 0x4) >> 1) |
((value & 0x8) >> 3));
}
static KeypadKey map_input_keypad_key(InputKeypadKey key) {
switch (key) {
case InputKeypadKey::K0: return KeypadKey::k0;
case InputKeypadKey::K1: return KeypadKey::k1;
case InputKeypadKey::K2: return KeypadKey::k2;
case InputKeypadKey::K3: return KeypadKey::k3;
case InputKeypadKey::K4: return KeypadKey::k4;
case InputKeypadKey::K5: return KeypadKey::k5;
case InputKeypadKey::K6: return KeypadKey::k6;
case InputKeypadKey::K7: return KeypadKey::k7;
case InputKeypadKey::K8: return KeypadKey::k8;
case InputKeypadKey::K9: return KeypadKey::k9;
case InputKeypadKey::A: return KeypadKey::ka;
case InputKeypadKey::B: return KeypadKey::kb;
case InputKeypadKey::C: return KeypadKey::kc;
case InputKeypadKey::D: return KeypadKey::kd;
case InputKeypadKey::STAR: return KeypadKey::star;
case InputKeypadKey::POUND: return KeypadKey::pound;
default: return KeypadKey::k0;
}
}
static uint8_t get_fingerprint_touch_state() {
InputsState current = InputsController::get_input_state();
return static_cast<uint8_t>((current.touch_state >> 4) & 0x1);
}
static bool touch_state_initialized = false;
static bool touch_state_last = false;
static bool update_fingerprint_transition(bool want_pressed) {
bool current = get_fingerprint_touch_state();
if (!touch_state_initialized) {
touch_state_last = current;
touch_state_initialized = true;
return false;
}
bool transition = want_pressed ? (current && !touch_state_last)
: (!current && touch_state_last);
touch_state_last = current;
return transition;
}
static std::array<SwitchFlip, 8> pending_switch_flips;
static size_t pending_switch_flip_count = 0;
static void push_pending_switch_flip(const SwitchFlip& event) {
if (pending_switch_flip_count < pending_switch_flips.size()) {
pending_switch_flips[pending_switch_flip_count++] = event;
return;
}
// Drop the oldest event if the buffer is full.
for (size_t i = 1; i < pending_switch_flips.size(); ++i) {
pending_switch_flips[i - 1] = pending_switch_flips[i];
}
pending_switch_flips.back() = event;
}
static bool pop_pending_switch_flip(bool want_up, SwitchFlip& out) {
for (size_t i = 0; i < pending_switch_flip_count; ++i) {
if (pending_switch_flips[i].is_up() == want_up) {
out = pending_switch_flips[i];
for (size_t j = i + 1; j < pending_switch_flip_count; ++j) {
pending_switch_flips[j - 1] = pending_switch_flips[j];
}
--pending_switch_flip_count;
return true;
}
}
return false;
}
static void clear_pending_switch_flips() {
pending_switch_flip_count = 0;
}
static std::array<SwitchTouch, 8> pending_switch_touches;
static size_t pending_switch_touch_count = 0;
static void push_pending_switch_touch(const SwitchTouch& event) {
if (pending_switch_touch_count < pending_switch_touches.size()) {
pending_switch_touches[pending_switch_touch_count++] = event;
return;
}
for (size_t i = 1; i < pending_switch_touches.size(); ++i) {
pending_switch_touches[i - 1] = pending_switch_touches[i];
}
pending_switch_touches.back() = event;
}
static bool pop_pending_switch_touch(bool want_touched, SwitchTouch& out) {
for (size_t i = 0; i < pending_switch_touch_count; ++i) {
if (pending_switch_touches[i].is_touched() == want_touched) {
out = pending_switch_touches[i];
for (size_t j = i + 1; j < pending_switch_touch_count; ++j) {
pending_switch_touches[j - 1] = pending_switch_touches[j];
}
--pending_switch_touch_count;
return true;
}
}
return false;
}
static void clear_pending_switch_touches() {
pending_switch_touch_count = 0;
}
void init_bottom_half() {
init_expander();
clear_all_pressed_released();
}
void clear_all_pressed_released() {
InputsController::clear_all_events();
clear_pending_switch_flips();
clear_pending_switch_touches();
touch_state_initialized = false;
}
bool get_keypad_pressed(KeypadKey* kp) {
auto opt = InputsController::get_keypad_press();
if (!opt.has_value()) {
return false;
}
if (kp != nullptr) {
*kp = map_input_keypad_key(opt.value());
}
return true;
}
bool get_keypad_released(KeypadKey* kp) {
auto opt = InputsController::get_keypad_release();
if (!opt.has_value()) {
return false;
}
if (kp != nullptr) {
*kp = map_input_keypad_key(opt.value());
}
return true;
}
char char_of_keypad_key(KeypadKey kp) {
switch (kp) {
case KeypadKey::k1: return '1';
case KeypadKey::k2: return '2';
case KeypadKey::k3: return '3';
case KeypadKey::k4: return '4';
case KeypadKey::k5: return '5';
case KeypadKey::k6: return '6';
case KeypadKey::k7: return '7';
case KeypadKey::k8: return '8';
case KeypadKey::k9: return '9';
case KeypadKey::k0: return '0';
case KeypadKey::ka: return 'A';
case KeypadKey::kb: return 'B';
case KeypadKey::kc: return 'C';
case KeypadKey::kd: return 'D';
case KeypadKey::star: return '*';
case KeypadKey::pound: return '#';
default: return ' ';
}
}
static bool take_button(ButtonKey* button, std::optional<Button> opt) {
if (!opt.has_value()) {
return false;
}
if (button != nullptr) {
*button = static_cast<ButtonKey>(static_cast<uint8_t>(opt.value()));
}
return true;
}
bool get_button_pressed(ButtonKey* button) {
return take_button(button, InputsController::get_button_press());
}
bool get_button_released(ButtonKey* button) {
return take_button(button, InputsController::get_button_release());
}
uint8_t get_button_state() {
return reverse_4_bits(InputsController::button_state() & 0xF);
}
static bool take_switch_key(SwitchKey* switch_, const SwitchFlip& event) {
if (switch_ != nullptr) {
*switch_ = static_cast<SwitchKey>(static_cast<uint8_t>(event.get_switch()));
}
return true;
}
bool get_switch_flipped_up(SwitchKey* switch_) {
SwitchFlip event;
if (pop_pending_switch_flip(true, event)) {
return take_switch_key(switch_, event);
}
while (true) {
auto opt = InputsController::get_switch_flip();
if (!opt.has_value()) {
return false;
}
if (opt->is_up()) {
return take_switch_key(switch_, *opt);
}
push_pending_switch_flip(*opt);
}
}
bool get_switch_flipped_down(SwitchKey* switch_) {
SwitchFlip event;
if (pop_pending_switch_flip(false, event)) {
return take_switch_key(switch_, event);
}
while (true) {
auto opt = InputsController::get_switch_flip();
if (!opt.has_value()) {
return false;
}
if (!opt->is_up()) {
return take_switch_key(switch_, *opt);
}
push_pending_switch_flip(*opt);
}
}
bool get_switch_flipped(SwitchKey* switch_) {
if (pending_switch_flip_count > 0) {
SwitchFlip event = pending_switch_flips[0];
for (size_t i = 1; i < pending_switch_flip_count; ++i) {
pending_switch_flips[i - 1] = pending_switch_flips[i];
}
--pending_switch_flip_count;
return take_switch_key(switch_, event);
}
auto opt = InputsController::get_switch_flip();
if (!opt.has_value()) {
return false;
}
return take_switch_key(switch_, *opt);
}
uint8_t get_switch_state() {
return reverse_4_bits(InputsController::switch_state() & 0xF);
}
static bool take_switch_touch(SwitchKey* switch_, const SwitchTouch& event) {
if (switch_ != nullptr) {
*switch_ = static_cast<SwitchKey>(static_cast<uint8_t>(event.get_switch()));
}
return true;
}
bool get_switch_touch_pressed(SwitchKey* switch_) {
SwitchTouch event;
if (pop_pending_switch_touch(true, event)) {
return take_switch_touch(switch_, event);
}
while (true) {
auto opt = InputsController::get_switch_touch();
if (!opt.has_value()) {
return false;
}
if (opt->is_touched()) {
return take_switch_touch(switch_, *opt);
}
push_pending_switch_touch(*opt);
}
}
bool get_switch_touch_released(SwitchKey* switch_) {
SwitchTouch event;
if (pop_pending_switch_touch(false, event)) {
return take_switch_touch(switch_, event);
}
while (true) {
auto opt = InputsController::get_switch_touch();
if (!opt.has_value()) {
return false;
}
if (!opt->is_touched()) {
return take_switch_touch(switch_, *opt);
}
push_pending_switch_touch(*opt);
}
}
uint8_t get_switch_touch_state() {
return reverse_4_bits(InputsController::switch_touch_state() & 0xF);
}
bool get_touch_state() {
return get_fingerprint_touch_state() != 0;
}
bool get_touch_pressed() {
return update_fingerprint_transition(true);
}
bool get_touch_released() {
return update_fingerprint_transition(false);
}

475
main/drivers/inputs.cpp Normal file
View File

@ -0,0 +1,475 @@
#include "inputs.hpp"
#include "pins.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
static const char *TAG = "INPUTS";
static TaskHandle_t expander_task_handle = NULL;
const static uint8_t REG_WHOAMI = 0x01;
const static uint8_t REG_SW_VERSION = 0x02;
const static uint8_t REG_EVENT_QUEUE_POP = 0x10;
const static uint8_t REG_EVENT_QUEUE_LEN = 0x11;
const static uint8_t REG_STATE_BUTTONS = 0x20;
const static uint8_t REG_STATE_SWITCHES = 0x21;
const static uint8_t REG_STATE_KEYPAD = 0x22;
const static uint8_t REG_STATE_TOUCH = 0x23;
const static uint8_t REG_STATE_RFID = 0x24;
const static uint8_t REG_STATE_HALL = 0x25;
const static uint8_t REG_STATE_CLOSE = 0x26;
const static uint8_t REG_RESET = 0x30;
const static uint8_t REG_HALL_SENSITIVITY = 0x31;
const static uint8_t REG_CLOSE_SENSITIVITY = 0x32;
const static uint8_t REG_SWITCH_TOUCH_EVENT = 0x33;
/// The global data for the expander peripheral.
class ExpanderPeripheral {
// TODO: change these to private
// or even make this class hidden
public:
SemaphoreHandle_t state_mutex;
InputsState state;
// channels
QueueHandle_t button_press_events;
QueueHandle_t button_release_events;
QueueHandle_t switch_flip_events;
QueueHandle_t switch_touch_events;
QueueHandle_t touch_events;
QueueHandle_t keypad_press_events;
QueueHandle_t keypad_release_events;
};
ExpanderPeripheral expander_peripheral_singleton;
// forward declarations
static void get_events();
static void handle_event(uint8_t event);
static void handle_button_switch_event(uint8_t event);
static void handle_keypad_event(uint8_t event);
static void handle_touch_event(uint8_t event);
static void handle_rfid_event(uint8_t event);
static void handle_close_hal_event(uint8_t event);
static void expander_task(void *arg);
// ISR handler
static void IRAM_ATTR expander_isr_handler(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (expander_task_handle != NULL) {
vTaskNotifyGiveFromISR(expander_task_handle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void init_expander() {
ESP_LOGI(TAG, "Initializing expander...");
// legacy I2C driver: use the shared I2C_NUM_0 bus already configured elsewhere.
// TODO: replace all these ESP_ERROR_CHECK with proper error handling that doesn't just crash the program
// setup interrupt on PIN_EXPANDER_INT
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << PIN_EXPANDER_INT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_NEGEDGE
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
// Install ISR service (only call once in your program)
ESP_ERROR_CHECK(gpio_install_isr_service(0));
// Attach the ISR to the expander pin
ESP_ERROR_CHECK(gpio_isr_handler_add(PIN_EXPANDER_INT, expander_isr_handler, NULL));
// verify the expander connection status by reading the WHOAMI register
uint8_t read_buf[2] = {0};
ESP_ERROR_CHECK(i2c_master_write_read_device(I2C_NUM_0, EXPANDER_I2C_ADDR, &REG_WHOAMI, 1, read_buf, 1, pdMS_TO_TICKS(EXPANDER_TIMEOUT_MS)));
if (read_buf[0] != EXPANDER_WHOAMI_VALUE) {
ESP_LOGE(TAG, "WHOAMI mismatch, expected 0x%02X, got 0x%02X", EXPANDER_WHOAMI_VALUE, read_buf[0]);
return;
}
ESP_LOGD(TAG, "Expander WHOAMI check passed");
ESP_ERROR_CHECK(i2c_master_write_read_device(I2C_NUM_0, EXPANDER_I2C_ADDR, &REG_SW_VERSION, 1, read_buf, 2, pdMS_TO_TICKS(EXPANDER_TIMEOUT_MS)));
// init the peripheral struct
expander_peripheral_singleton.state_mutex = xSemaphoreCreateMutex();
expander_peripheral_singleton.button_press_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(Button));
expander_peripheral_singleton.button_release_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(Button));
expander_peripheral_singleton.switch_flip_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(SwitchFlip));
expander_peripheral_singleton.switch_touch_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(SwitchTouch));
expander_peripheral_singleton.touch_events= xQueueCreate(EXPANDER_EVENT_QUEUE_SIZE, sizeof(TouchedReleased));
expander_peripheral_singleton.keypad_press_events= xQueueCreate(EXPANDER_KEYPAD_QUEUE_SIZE, sizeof(InputKeypadKey));
expander_peripheral_singleton.keypad_release_events= xQueueCreate(EXPANDER_KEYPAD_QUEUE_SIZE, sizeof(InputKeypadKey));
ESP_LOGI(TAG, "Expander initialized! SW version: v%d.%d", read_buf[0], read_buf[1]);
// Create the expander background worker task
BaseType_t task_created = xTaskCreate(
expander_task,
"expander_task",
4096,
NULL,
tskIDLE_PRIORITY + 1,
&expander_task_handle
);
if (task_created != pdPASS) {
ESP_LOGE(TAG, "Failed to create expander task");
}
}
static void expander_task(void *arg) {
(void)arg;
while (true) {
get_events();
// Wait for interrupt notification (signal is sent when INT falls)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
}
static void get_events() {
uint8_t recv;
while (gpio_get_level(PIN_EXPANDER_INT) == 0) {
ESP_ERROR_CHECK(i2c_master_write_read_device(I2C_NUM_0, EXPANDER_I2C_ADDR, &REG_EVENT_QUEUE_POP, 1, &recv, 1, pdMS_TO_TICKS(EXPANDER_TIMEOUT_MS)));
handle_event(recv);
}
}
static void handle_event(uint8_t event) {
const uint8_t BUTTON_SWITCH = 0b000;
const uint8_t KEYPAD = 0b001;
const uint8_t TOUCH = 0b010;
const uint8_t RFID = 0b011;
ESP_LOGD(TAG, "Expander event: 0b%08b (0x%02X)", event, event);
if (event == 0) {
ESP_LOGE(TAG, "We read from event queue while it was empty!");
return;
}
uint8_t type_bits = event >> 5;
switch (type_bits) {
case BUTTON_SWITCH:
handle_button_switch_event(event);
break;
case KEYPAD:
handle_keypad_event(event);
break;
case TOUCH:
handle_touch_event(event);
break;
case RFID:
handle_rfid_event(event);
break;
default:
handle_close_hal_event(event);
break;
}
}
static void handle_button_switch_event(uint8_t event) {
const uint8_t PRESSED_NOT_RELEASED_BIT = 0b10000;
const uint8_t SWITCH_NOT_BUTTON_BIT = 0b01000;
const uint8_t SWITCH_UP_NOT_DOWN_BIT = 0b00100;
const uint8_t NUMBER_MASK = 0b00011;
bool pressed = (event & PRESSED_NOT_RELEASED_BIT) != 0;
uint8_t number = event & NUMBER_MASK;
if ((event & SWITCH_NOT_BUTTON_BIT) != 0) {
// For now, we support two position switches by only looking at the switch up events
bool switch_up = (event & SWITCH_UP_NOT_DOWN_BIT) != 0;
if (!switch_up) {
return;
}
Switch sw = static_cast<Switch>(number);
SwitchFlip sw_flip = SwitchFlip(sw, pressed);
xQueueSendToBack(expander_peripheral_singleton.switch_flip_events, &sw_flip, 0);
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
if (pressed) {
// set
expander_peripheral_singleton.state.switch_state |= 1 << number;
} else {
// clear
expander_peripheral_singleton.state.switch_state &= ~(1 << number);
}
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
} else {
// button
Button button = static_cast<Button>(number);
if (pressed) {
xQueueSendToBack(expander_peripheral_singleton.button_press_events, &button, 0);
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
expander_peripheral_singleton.state.button_state |= 1 << number;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
} else {
xQueueSendToBack(expander_peripheral_singleton.button_release_events, &button, 0);
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
expander_peripheral_singleton.state.button_state &= ~(1 << number);
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
}
}
}
static void handle_keypad_event(uint8_t event) {
const uint8_t PRESSED_NOT_RELEASED_BIT = 0b10000;
const uint8_t KEY_MASK = 0b1111;
bool pressed = (event & PRESSED_NOT_RELEASED_BIT) != 0;
uint8_t number = event & KEY_MASK;
InputKeypadKey key = static_cast<InputKeypadKey>(number);
// starcode system gets first dibs
// TODO: do starcode inbetweener
// if starcode_handle_keypad(key, pressed).await {
// return;
// }
if (pressed) {
xQueueSendToBack(expander_peripheral_singleton.keypad_press_events, &key, 0);
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
expander_peripheral_singleton.state.keypad_state |= 1 << number;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
} else {
xQueueSendToBack(expander_peripheral_singleton.keypad_release_events, &key, 0);
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
expander_peripheral_singleton.state.keypad_state &= ~(1 << number);
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
}
}
static void handle_touch_event(uint8_t event) {
const uint8_t TOUCHED_NOT_UNTOUCHED_BIT = 0b10000;
const uint8_t SENSOR_MASK = 0b0111;
const uint8_t FINGERPRINT_BIT = 0b0100;
bool touched = (event & TOUCHED_NOT_UNTOUCHED_BIT) != 0;
uint8_t sensor = event & SENSOR_MASK;
if ((sensor & FINGERPRINT_BIT) != 0) {
TouchedReleased touch_state = static_cast<TouchedReleased>(touched);
xQueueSendToBack(expander_peripheral_singleton.touch_events, &touch_state, 0);
} else {
Switch sw = static_cast<Switch>(sensor);
SwitchTouch sw_touch = SwitchTouch(sw, touched);
xQueueSendToBack(expander_peripheral_singleton.switch_touch_events, &sw_touch, 0);
}
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
if (touched) {
expander_peripheral_singleton.state.touch_state |= 1 << sensor;
} else {
expander_peripheral_singleton.state.touch_state &= ~(1 << sensor);
}
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
}
static void handle_rfid_event(uint8_t event) {
// TODO: impl
(void)event;
}
static void handle_close_hal_event(uint8_t event) {
// TODO: impl
(void)event;
}
// InputsController implementations
/// Clears all events waiting in the queues.
void InputsController::clear_all_events() {
xQueueReset(expander_peripheral_singleton.button_press_events);
xQueueReset(expander_peripheral_singleton.button_release_events);
xQueueReset(expander_peripheral_singleton.switch_flip_events);
xQueueReset(expander_peripheral_singleton.switch_touch_events);
xQueueReset(expander_peripheral_singleton.touch_events);
xQueueReset(expander_peripheral_singleton.keypad_press_events);
xQueueReset(expander_peripheral_singleton.keypad_release_events);
}
InputsState InputsController::get_input_state() {
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
InputsState state_copy = expander_peripheral_singleton.state;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
return state_copy;
}
/// Returns `true` iff there is a button press event waiting.
bool InputsController::has_button_press() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.button_press_events) > 0;
}
/// Gets the next button press event (if any).
std::optional<Button> InputsController::get_button_press() {
Button b;
if (xQueueReceive(expander_peripheral_singleton.button_press_events, &b, 0) == pdTRUE) {
return b;
}
return std::nullopt;
}
/// Gets the next button press event, waiting if neccesary.
Button InputsController::wait_button_press() {
Button b;
xQueueReceive(expander_peripheral_singleton.button_press_events, &b, portMAX_DELAY);
return b;
}
/// Gets the current state of the buttons.
uint8_t InputsController::button_state() {
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
uint8_t value = expander_peripheral_singleton.state.button_state;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
return value;
}
/// Returns `true` iff there is a button release event waiting.
bool InputsController::has_button_release() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.button_release_events) > 0;
}
/// Gets the next button release event (if any).
std::optional<Button> InputsController::get_button_release() {
Button b;
if (xQueueReceive(expander_peripheral_singleton.button_release_events, &b, 0) == pdTRUE) {
return b;
}
return std::nullopt;
}
/// Gets the next button release event, waiting if neccesary.
Button InputsController::wait_button_release() {
Button b;
xQueueReceive(expander_peripheral_singleton.button_release_events, &b, portMAX_DELAY);
return b;
}
/// Returns `true` iff there is a switch flip event waiting.
bool InputsController::has_switch_flip() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.switch_flip_events) > 0;
}
/// Gets the next switch flip event (if any).
std::optional<SwitchFlip> InputsController::get_switch_flip() {
SwitchFlip s;
if (xQueueReceive(expander_peripheral_singleton.switch_flip_events, &s, 0) == pdTRUE) {
return s;
}
return std::nullopt;
}
/// Gets the next switch flip event, waiting if neccesary.
SwitchFlip InputsController::wait_switch_flip() {
SwitchFlip s;
xQueueReceive(expander_peripheral_singleton.switch_flip_events, &s, portMAX_DELAY);
return s;
}
/// Gets the current state of the switches.
uint8_t InputsController::switch_state() {
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
uint8_t value = expander_peripheral_singleton.state.switch_state;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
return value;
}
/// Returns `true` iff there is a switch touch event waiting.
bool InputsController::has_switch_touch() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.switch_touch_events) > 0;
}
/// Gets the next switch touch event (if any).
std::optional<SwitchTouch> InputsController::get_switch_touch() {
SwitchTouch s;
if (xQueueReceive(expander_peripheral_singleton.switch_touch_events, &s, 0) == pdTRUE) {
return s;
}
return std::nullopt;
}
/// Gets the next switch touch event, waiting if neccesary.
SwitchTouch InputsController::wait_switch_touch() {
SwitchTouch s;
xQueueReceive(expander_peripheral_singleton.switch_touch_events, &s, portMAX_DELAY);
return s;
}
/// Gets the current state of the touch sensors.
uint8_t InputsController::switch_touch_state() {
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
uint8_t value = expander_peripheral_singleton.state.touch_state;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
return value;
}
/// Returns `true` iff there is a keypad press event waiting.
bool InputsController::has_keypad_press() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.keypad_press_events) > 0;
}
/// Gets the next keypad press event (if any).
std::optional<InputKeypadKey> InputsController::get_keypad_press() {
InputKeypadKey k;
if (xQueueReceive(expander_peripheral_singleton.keypad_press_events, &k, 0) == pdTRUE) {
return k;
}
return std::nullopt;
}
/// Gets the next keypad press event, waiting if neccesary.
InputKeypadKey InputsController::wait_keypad_press() {
InputKeypadKey k;
xQueueReceive(expander_peripheral_singleton.keypad_press_events, &k, portMAX_DELAY);
return k;
}
/// Returns `true` iff there is a keypad release event waiting.
bool InputsController::has_keypad_release() {
return uxQueueMessagesWaiting(expander_peripheral_singleton.keypad_release_events) > 0;
}
/// Gets the next keypad release event (if any).
std::optional<InputKeypadKey> InputsController::get_keypad_release() {
InputKeypadKey k;
if (xQueueReceive(expander_peripheral_singleton.keypad_release_events, &k, 0) == pdTRUE) {
return k;
}
return std::nullopt;
}
/// Gets the next keypad release event, waiting if neccesary.
InputKeypadKey InputsController::wait_keypad_release() {
InputKeypadKey k;
xQueueReceive(expander_peripheral_singleton.keypad_release_events, &k, portMAX_DELAY);
return k;
}
/// Gets the current state of the keypad.
uint16_t InputsController::keypad_state() {
xSemaphoreTake(expander_peripheral_singleton.state_mutex, portMAX_DELAY);
uint16_t value = expander_peripheral_singleton.state.keypad_state;
xSemaphoreGive(expander_peripheral_singleton.state_mutex);
return value;
}

275
main/drivers/inputs.hpp Normal file
View File

@ -0,0 +1,275 @@
#ifndef INPUTS_H
#define INPUTS_H
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/semphr.h>
#include <optional>
#define EXPANDER_I2C_ADDR (0x7E)
#define EXPANDER_I2C_SPEED (400000)
// the actual transaction takes ~0.3ms, but for some reason a timout of ~10 or lower causes issues.
#define EXPANDER_TIMEOUT_MS (100)
#define EXPANDER_WHOAMI_VALUE (0x85)
// queue sizes
#define EXPANDER_EVENT_QUEUE_SIZE 4
#define EXPANDER_KEYPAD_QUEUE_SIZE 16
void init_expander();
/// The four buttons on the bottom half.
enum class Button: uint8_t {
B1 = 0,
B2 = 1,
B3 = 2,
B4 = 3,
GREEN = 0,
YELLOW = 1,
RED = 2,
BLUE = 3,
};
constexpr uint8_t raw_value(Button v) { return static_cast<uint8_t>(v); }
constexpr Button button_from_raw(uint8_t raw) { return static_cast<Button>(raw & 0b11); }
/// The four switches on the bottom half.
enum class Switch: uint8_t {
S1 = 0,
S2 = 1,
S3 = 2,
S4 = 3,
};
constexpr uint8_t raw_value(Switch v) { return static_cast<uint8_t>(v); }
constexpr Switch switch_from_raw(uint8_t raw) { return static_cast<Switch>(raw & 0b11); }
enum class TouchedReleased: uint8_t {
Released = 0,
Touched = 1,
};
constexpr uint8_t raw_value(TouchedReleased v) { return static_cast<uint8_t>(v); }
constexpr TouchedReleased touched_released_from_raw(uint8_t raw) { return static_cast<TouchedReleased>(raw & 0b1); }
/// One of the keys on the keypad.
enum class InputKeypadKey: uint8_t {
K0 = 0,
K1 = 1,
K2 = 2,
K3 = 3,
K4 = 4,
K5 = 5,
K6 = 6,
K7 = 7,
K8 = 8,
K9 = 9,
A = 10,
B = 11,
C = 12,
D = 13,
STAR = 14,
POUND = 15,
};
constexpr uint8_t raw_value(InputKeypadKey v) { return static_cast<uint8_t>(v); }
constexpr InputKeypadKey keypad_key_from_raw(uint8_t raw) { return static_cast<InputKeypadKey>(raw & 0b1111); }
constexpr char keypad_key_to_char(InputKeypadKey key) {
static constexpr char lookup[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', '*', '#'
};
return lookup[static_cast<uint8_t>(key) & 0b1111];
}
struct SwitchFlip {
private:
// [bit2: up] [bit1-0: switch]
uint8_t data;
public:
// Constructor
SwitchFlip(Switch sw, bool up)
: data((static_cast<uint8_t>(sw) & 0b11) |
((up ? 1 : 0) << 2)) {}
// Default constructor
SwitchFlip() : data(0) {}
// Raw value constructor
explicit SwitchFlip(uint8_t raw_data) : data(raw_data) {}
// Raw value getter
uint8_t raw() const { return data; }
// Getters
Switch get_switch() const {
return static_cast<Switch>(data & 0b11);
}
bool is_up() const {
return (data >> 2) & 1;
}
// Setters
void set_switch(Switch sw) {
data = (data & ~0b11) | (static_cast<uint8_t>(sw) & 0b11);
}
void set_up(bool up) {
data = (data & ~(1 << 2)) | ((up ? 1 : 0) << 2);
}
};
static_assert(sizeof(SwitchFlip) == 1);
struct SwitchTouch {
private:
// [bit2: touched] [bit1-0: switch]
uint8_t data;
public:
// Constructor
SwitchTouch(Switch sw, bool touched)
: data((static_cast<uint8_t>(sw) & 0b11) |
((touched ? 1 : 0) << 2)) {}
// Default constructor
SwitchTouch() : data(0) {}
// Raw value constructor
explicit SwitchTouch(uint8_t raw_data) : data(raw_data) {}
// Raw value getter
uint8_t raw() const { return data; }
// Getters
Switch get_switch() const {
return static_cast<Switch>(data & 0b11);
}
bool is_touched() const {
return (data >> 2) & 1;
}
// Setters
void set_switch(Switch sw) {
data = (data & ~0b11) | (static_cast<uint8_t>(sw) & 0b11);
}
void set_touched(bool touched) {
data = (data & ~(1 << 2)) | ((touched ? 1 : 0) << 2);
}
};
static_assert(sizeof(SwitchTouch) == 1);
struct ButtonOrSwitch {
private:
// [bit2: is_switch] [bit1-0: number]
uint8_t data;
public:
// Constructor
ButtonOrSwitch(uint8_t number, bool is_switch)
: data((number & 0b11) |
((is_switch ? 1 : 0) << 2)) {}
ButtonOrSwitch() : data(0) {}
// Raw value constructor
explicit ButtonOrSwitch(uint8_t raw_data) : data(raw_data) {}
// Raw value getter
uint8_t raw() const { return data; }
// Getters
uint8_t number() const {
return data & 0b11;
}
bool is_switch() const {
return (data >> 2) & 1;
}
// Setters
void set_number(uint8_t number) {
data = (data & ~0b11) | (number & 0b11);
}
void set_is_switch(bool is_switch) {
data = (data & ~(1 << 2)) | ((is_switch ? 1 : 0) << 2);
}
};
static_assert(sizeof(ButtonOrSwitch) == 1);
/// @brief The state of the bottom half of the box.
struct InputsState {
/// The touch state of the switches in the lower 4 bits.
/// The touch pad state in bit 4.
uint8_t touch_state;
/// The current state of the buttons in the lower 4 bits.
uint8_t button_state;
/// The current state of the switches. Up switches are stored
/// in the lower 4 bits, switches that are down are stored in
/// the upper 4 bits. If switches are in the middle, the
/// corresponding bit will be `0` in the upper and lower 4.
uint8_t switch_state;
/// The state of the keypad.
uint16_t keypad_state;
/// The sensitivity of the `hal` value to auto update.
uint16_t hal_sense;
/// The sensitivity of the `close_hal` value to auto update.
uint16_t close_hal_sense;
/// A non-exact hal value reading.
/// This only gets updated when it changes by `hal_sense`
uint16_t hal;
/// A non-exact hal value reading.
/// This only gets updated when it changes by `close_hal_sense`
uint16_t close_hal;
/// The RFID card that was presented last.
uint32_t rfid_state;
InputsState() : touch_state(0), button_state(0), switch_state(0), keypad_state(0), hal_sense(0), close_hal_sense(0), hal(0), close_hal(0), rfid_state(0) {}
};
class InputsController {
public:
static void clear_all_events();
static InputsState get_input_state();
static bool has_button_press();
static std::optional<Button> get_button_press();
static Button wait_button_press();
static uint8_t button_state();
static bool has_button_release();
static std::optional<Button> get_button_release();
static Button wait_button_release();
static bool has_switch_flip();
static std::optional<SwitchFlip> get_switch_flip();
static SwitchFlip wait_switch_flip();
static uint8_t switch_state();
static bool has_switch_touch();
static std::optional<SwitchTouch> get_switch_touch();
static SwitchTouch wait_switch_touch();
static uint8_t switch_touch_state();
static bool has_keypad_press();
static std::optional<InputKeypadKey> get_keypad_press();
static InputKeypadKey wait_keypad_press();
static bool has_keypad_release();
static std::optional<InputKeypadKey> get_keypad_release();
static InputKeypadKey wait_keypad_release();
static uint16_t keypad_state();
// TODO: impl and add the hal and RFID stuff
};
#endif // INPUTS_H

48
main/drivers/pins.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef PINS_H
#define PINS_H
#include "driver/gpio.h"
// ONLY INPUTS.HPP uses this file
#define PIN_SDA (GPIO_NUM_7)
#define PIN_SCL (GPIO_NUM_15)
#define PIN_LCD_MISO (GPIO_NUM_16)
#define PIN_LCD_MOSI (GPIO_NUM_17)
#define PIN_LCD_CLK (GPIO_NUM_18)
#define PIN_LCD_RS (GPIO_NUM_8)
#define PIN_LCD_RST (GPIO_NUM_9)
#define PIN_USB_DM (GPIO_NUM_19)
#define PIN_USB_DP (GPIO_NUM_20)
#define PIN_I2S_DAT (GPIO_NUM_3)
#define PIN_I2S_BCLK (GPIO_NUM_11)
#define PIN_I2S_LRCLK (GPIO_NUM_12)
#define PIN_SSEG_DAT (GPIO_NUM_46)
#define PIN_SSEG_CLK (GPIO_NUM_48)
#define PIN_MPU_INT (GPIO_NUM_10)
#define PIN_EXPANDER_INT (GPIO_NUM_13)
#define PIN_IR_RCV (GPIO_NUM_14)
// #define PIN_NEOPIXEL (GPIO_NUM_21) // Rev 2.1
#define PIN_NEOPIXEL (GPIO_NUM_0) // Rev 2.0
#define PIN_SD_DAT0 (GPIO_NUM_38)
#define PIN_SD_DAT1 (GPIO_NUM_47)
#define PIN_SD_DAT2 (GPIO_NUM_42)
#define PIN_SD_DAT3 (GPIO_NUM_41)
#define PIN_SD_CMD (GPIO_NUM_40)
#define PIN_SD_CLK (GPIO_NUM_39)
#define PIN_PERH0 (GPIO_NUM_6)
#define PIN_PERH1 (GPIO_NUM_5)
#define PIN_PERH2 (GPIO_NUM_4)
#define PIN_PERH3 (GPIO_NUM_2)
#define PIN_PERH4 (GPIO_NUM_1)
#endif // PINS_H