From 1ad527119d9d1e1cf3336d9857190c0b4014ede4 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 19 May 2026 00:16:40 +0200 Subject: [PATCH] Add board input driver for button and LiPo ADC logging. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: Hardware pinning in powerpod.h (TASTER_GPIO, V_LIPO_1/2_GPIO) does not match final hardware yet — verify against schematic before production. Co-authored-by: Cursor --- main/CMakeLists.txt | 2 + main/README.md | 9 ++- main/board_input.c | 193 ++++++++++++++++++++++++++++++++++++++++++++ main/board_input.h | 12 +++ main/powerpod.c | 3 + main/powerpod.h | 9 +++ 6 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 main/board_input.c create mode 100644 main/board_input.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 45f2e6f..a31a3ae 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -23,6 +23,7 @@ idf_component_register( "esp_now_comm.c" "esp_now_proto.c" "bosch456.c" + "board_input.c" "proto/uart_messages.pb.c" "proto/esp_now_messages.pb.c" "proto/pb_encode.c" @@ -40,6 +41,7 @@ idf_component_register( esp_driver_gpio esp_driver_uart esp_driver_i2c + esp_adc app_update bma456) diff --git a/main/README.md b/main/README.md index b61832a..2a00d56 100644 --- a/main/README.md +++ b/main/README.md @@ -51,6 +51,11 @@ Pins (`powerpod.h`): | UART RX | 2 | | LED ring | 7 | | BMA456 INT | 10 | +| Button (Taster) | 12 | +| LiPo sense 1 (ADC) | 1 | +| LiPo sense 2 (ADC) | 12 (skipped if same as button) | + +> **TODO:** GPIO assignments above are provisional; confirm pinning against the real board before release. Startup order: @@ -58,7 +63,8 @@ Startup order: 2. **I2C bus** — IO expander `0x20`; optional **BMA456H** (`init_bma456`, same bus) 3. `esp_now_comm_init(&app_config)` — WiFi + ESP-NOW 4. `led_ring_init()` -5. **Master only:** command queue, UART, registered commands (e.g. VERSION) +5. `board_input_init()` — button press logs, LiPo ADC logs every **10 s** +6. **Master only:** command queue, UART, registered commands (e.g. VERSION) ## BMA456 accelerometer (`bosch456.c`) @@ -310,6 +316,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running ` | `cmd_client_info.c/h` | CLIENT_INFO handler | | `client_registry.c/h` | Registered slave table | | `bosch456.c/h` | BMA456H I2C driver, accel poll, tap INT, deadzone filter | +| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO12 | | `led_ring.c/h` | LED digit display | | `proto/uart_messages.proto` | UART protocol schema | | `proto/esp_now_messages.proto` | ESP-NOW protocol schema | diff --git a/main/board_input.c b/main/board_input.c new file mode 100644 index 0000000..4518a5c --- /dev/null +++ b/main/board_input.c @@ -0,0 +1,193 @@ +#include "board_input.h" +#include "powerpod.h" +#include "driver/gpio.h" +#include "esp_adc/adc_oneshot.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/queue.h" +#include + +static const char *TAG_BTN = "[BTN]"; +static const char *TAG_LIPO = "[LIPO]"; + +#define LIPO_SAMPLE_INTERVAL_MS 10000 +#define BUTTON_QUEUE_LEN 4 +#define BUTTON_DEBOUNCE_MS 80 + +static QueueHandle_t s_button_queue; +static adc_oneshot_unit_handle_t s_adc; +static bool s_lipo1_ok; +static bool s_lipo2_ok; +static adc_channel_t s_lipo1_ch; +static adc_channel_t s_lipo2_ch; + +static esp_err_t adc_init_channel(int gpio, adc_channel_t *out_ch, bool *out_ok) { + adc_unit_t unit; + esp_err_t err = adc_oneshot_io_to_channel(gpio, &unit, out_ch); + if (err != ESP_OK) { + ESP_LOGW(TAG_LIPO, "GPIO%d not an ADC channel: %s", gpio, esp_err_to_name(err)); + *out_ok = false; + return err; + } + if (unit != ADC_UNIT_1) { + ESP_LOGW(TAG_LIPO, "GPIO%d on ADC unit %d (expected ADC1)", gpio, (int)unit); + } + adc_oneshot_chan_cfg_t chan_cfg = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + err = adc_oneshot_config_channel(s_adc, *out_ch, &chan_cfg); + if (err != ESP_OK) { + ESP_LOGW(TAG_LIPO, "ADC config GPIO%d failed: %s", gpio, esp_err_to_name(err)); + *out_ok = false; + return err; + } + *out_ok = true; + return ESP_OK; +} + +static void lipo_monitor_task(void *param) { + (void)param; + + ESP_LOGI(TAG_LIPO, "monitor task (interval %d ms)", LIPO_SAMPLE_INTERVAL_MS); + + while (1) { + int raw1 = -1; + int raw2 = -1; + int mv1 = -1; + int mv2 = -1; + + if (s_lipo1_ok) { + raw1 = 0; + if (adc_oneshot_read(s_adc, s_lipo1_ch, &raw1) == ESP_OK) { + mv1 = (raw1 * 3300) / 4095; + } + } + if (s_lipo2_ok) { + raw2 = 0; + if (adc_oneshot_read(s_adc, s_lipo2_ch, &raw2) == ESP_OK) { + mv2 = (raw2 * 3300) / 4095; + } + } + + ESP_LOGI(TAG_LIPO, + "LIPO1 GPIO%d raw=%d (~%d mV) LIPO2 GPIO%d raw=%d (~%d mV)", + V_LIPO_1_GPIO, raw1, mv1, V_LIPO_2_GPIO, raw2, mv2); + + vTaskDelay(pdMS_TO_TICKS(LIPO_SAMPLE_INTERVAL_MS)); + } +} + +static void IRAM_ATTR button_isr(void *arg) { + (void)arg; + uint8_t one = 1; + BaseType_t wake = pdFALSE; + if (s_button_queue != NULL) { + xQueueSendFromISR(s_button_queue, &one, &wake); + if (wake) { + portYIELD_FROM_ISR(); + } + } +} + +static void button_task(void *param) { + (void)param; + uint8_t evt; + + while (1) { + if (xQueueReceive(s_button_queue, &evt, portMAX_DELAY) != pdTRUE) { + continue; + } + vTaskDelay(pdMS_TO_TICKS(BUTTON_DEBOUNCE_MS)); + if (gpio_get_level(TASTER_GPIO) == 0) { + ESP_LOGI(TAG_BTN, "pressed (GPIO%d)", TASTER_GPIO); + } + } +} + +static esp_err_t init_button(void) { + if (V_LIPO_2_GPIO == TASTER_GPIO) { + ESP_LOGW(TAG_BTN, + "GPIO%d shared with V_LIPO_2 — button only, no ADC on that pin", + TASTER_GPIO); + } + + s_button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(uint8_t)); + if (s_button_queue == NULL) { + return ESP_ERR_NO_MEM; + } + + gpio_config_t cfg = { + .pin_bit_mask = 1ULL << TASTER_GPIO, + .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(&cfg)); + + esp_err_t err = gpio_install_isr_service(0); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + return err; + } + + err = gpio_isr_handler_add(TASTER_GPIO, button_isr, NULL); + if (err != ESP_OK) { + return err; + } + + if (xTaskCreate(button_task, "btn", 2048, NULL, 2, NULL) != pdPASS) { + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG_BTN, "ready GPIO%d (active low)", TASTER_GPIO); + return ESP_OK; +} + +static esp_err_t init_lipo_adc(void) { + adc_oneshot_unit_init_cfg_t init_cfg = { + .unit_id = ADC_UNIT_1, + }; + esp_err_t err = adc_oneshot_new_unit(&init_cfg, &s_adc); + if (err != ESP_OK) { + ESP_LOGW(TAG_LIPO, "ADC init failed: %s", esp_err_to_name(err)); + return err; + } + + adc_init_channel(V_LIPO_1_GPIO, &s_lipo1_ch, &s_lipo1_ok); + + if (V_LIPO_2_GPIO == TASTER_GPIO) { + ESP_LOGW(TAG_LIPO, "LIPO2 on GPIO%d skipped (button uses same pin)", + V_LIPO_2_GPIO); + s_lipo2_ok = false; + } else { + adc_init_channel(V_LIPO_2_GPIO, &s_lipo2_ch, &s_lipo2_ok); + } + + if (!s_lipo1_ok && !s_lipo2_ok) { + adc_oneshot_del_unit(s_adc); + s_adc = NULL; + return ESP_FAIL; + } + + if (xTaskCreate(lipo_monitor_task, "lipo_mon", 3072, NULL, 1, NULL) != pdPASS) { + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t board_input_init(void) { + esp_err_t err = init_button(); + if (err != ESP_OK) { + ESP_LOGW(TAG_BTN, "init failed: %s", esp_err_to_name(err)); + } + + err = init_lipo_adc(); + if (err != ESP_OK) { + ESP_LOGW(TAG_LIPO, "init failed: %s", esp_err_to_name(err)); + } + + return ESP_OK; +} diff --git a/main/board_input.h b/main/board_input.h new file mode 100644 index 0000000..ffc670a --- /dev/null +++ b/main/board_input.h @@ -0,0 +1,12 @@ +#ifndef BOARD_INPUT_H +#define BOARD_INPUT_H + +#include "esp_err.h" + +/** + * Button (log on press) and LiPo ADC sampling (log every 10 s). + * TODO: Pin assignments come from powerpod.h and may not match final hardware yet. + */ +esp_err_t board_input_init(void); + +#endif diff --git a/main/powerpod.c b/main/powerpod.c index d6e3815..e8ac655 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -15,6 +15,7 @@ #include "esp_ota_ops.h" #include "freertos/FreeRTOS.h" #include "freertos/idf_additions.h" +#include "board_input.h" #include "bosch456.h" #include "led_ring.h" #include "uart.h" @@ -140,6 +141,8 @@ void app_main(void) { led_ring_init(); + board_input_init(); + if (app_config.master) { cmd_queue = xQueueCreate(10, sizeof(generic_msg_t)); init_cmdHandler(cmd_queue); diff --git a/main/powerpod.h b/main/powerpod.h index db65854..6cba94d 100644 --- a/main/powerpod.h +++ b/main/powerpod.h @@ -7,4 +7,13 @@ #define I2C_PORT 0 #define IO_EXPANDER_ADDRESS 0x20 +/* TODO: Hardware pinning not finalized — verify against schematic before production. */ +/** Front-panel button (active low, internal pull-up). */ +#define TASTER_GPIO 12 + +/** LiPo voltage sense inputs (ADC1-capable GPIOs). */ +#define V_LIPO_1_GPIO 1 +/** Shares GPIO with TASTER on current bench wiring; second ADC is skipped in board_input.c. */ +#define V_LIPO_2_GPIO 12 + #endif