#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 #define LIPO_ADC_FULL_SCALE_MV 3300 #define LIPO_ADC_MAX_RAW 4095 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 uint32_t raw_to_mv(int raw) { if (raw < 0) { return 0; } return (uint32_t)((raw * LIPO_ADC_FULL_SCALE_MV) / LIPO_ADC_MAX_RAW); } static void sample_one_channel(adc_channel_t ch, bool ok, uint32_t *mv_out, bool *valid_out) { *valid_out = false; *mv_out = 0; if (!ok || s_adc == NULL) { return; } int raw = 0; if (adc_oneshot_read(s_adc, ch, &raw) == ESP_OK) { *valid_out = true; *mv_out = raw_to_mv(raw); } } void board_input_read_lipo(board_lipo_reading_t *out) { if (out == NULL) { return; } memset(out, 0, sizeof(*out)); sample_one_channel(s_lipo1_ch, s_lipo1_ok, &out->lipo1_mv, &out->lipo1_valid); sample_one_channel(s_lipo2_ch, s_lipo2_ok, &out->lipo2_mv, &out->lipo2_valid); } static void lipo_monitor_task(void *param) { (void)param; ESP_LOGI(TAG_LIPO, "monitor task (interval %d ms)", LIPO_SAMPLE_INTERVAL_MS); while (1) { board_lipo_reading_t reading; board_input_read_lipo(&reading); ESP_LOGI(TAG_LIPO, "LIPO1 GPIO%d %s %lu mV LIPO2 GPIO%d %s %lu mV", V_LIPO_1_GPIO, reading.lipo1_valid ? "ok" : "n/a", (unsigned long)reading.lipo1_mv, V_LIPO_2_GPIO, reading.lipo2_valid ? "ok" : "n/a", (unsigned long)reading.lipo2_mv); 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; }