diff --git a/main/drivers/CMakeLists.txt b/main/drivers/CMakeLists.txt index c734a66..15bf99e 100644 --- a/main/drivers/CMakeLists.txt +++ b/main/drivers/CMakeLists.txt @@ -4,12 +4,13 @@ set(SOURCES "TM1640/TM1640.cpp" "SparkFunBQ27441/SparkFunBQ27441.cpp" "esp_lcd_ili9488/esp_lcd_ili9488.c" - "bottom_half.cpp" + # "bottom_half.cpp" "char_lcd.cpp" "game_info.cpp" "game_timer.cpp" "i2c_lcd_pcf8574.c" "i2c.cpp" + "inputs.cpp" "leds.cpp" "perh.cpp" "power.cpp" diff --git a/main/drivers/inputs.cpp b/main/drivers/inputs.cpp new file mode 100644 index 0000000..1ce81ed --- /dev/null +++ b/main/drivers/inputs.cpp @@ -0,0 +1,486 @@ +#include "blk_box_drivers/inputs.hpp" + +#include "bottom_half.h" +#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; + +static i2c_master_dev_handle_t expander_i2c_dev_handle; + +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..."); + + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = EXPANDER_I2C_ADDR, + .scl_speed_hz = EXPANDER_I2C_SPEED, + .scl_wait_us = 0, // default + .flags = { + .disable_ack_check = 0, + } + }; + + // setup interrupt on BOTTOM_PIN_INTERUPT + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << BOTTOM_PIN_INTERUPT), + .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(BOTTOM_PIN_INTERUPT, expander_isr_handler, NULL)); + + // verify the expander connection status by reading the WHOAMI register + uint8_t read_buf[2] = {0}; + + i2c_master_write_read_device(I2C_NUM_0, BOTTOM_I2C_ADDR, ®_WHOAMI, 1, read_buf, 1, 1000); + + 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_transmit_receive(expander_i2c_dev_handle, ®_SW_VERSION, 1, read_buf, 2, 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(KeypadKey)); + expander_peripheral_singleton.keypad_release_events= xQueueCreate(EXPANDER_KEYPAD_QUEUE_SIZE, sizeof(KeypadKey)); + + 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_transmit_receive(expander_i2c_dev_handle, ®_EVENT_QUEUE_POP, 1, &recv, 1, 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(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