/** * @file localbtn.c * @brief Local GPIO button reading using interrupt-based edge detection */ #include "localbtn.h" #include "driver/gpio.h" #include "esp_timer.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include static const char *TAG = "LOCALBTN"; #define DEBOUNCE_TIME_MS 50 // Debounce time in milliseconds // Button state static struct { int8_t gpio_pin; bool initialized; TaskHandle_t task_handle; QueueHandle_t event_queue; localbtn_mode_change_callback_t callback; int64_t last_press_time; // For debouncing } button_state = { .gpio_pin = -1, .initialized = false, .task_handle = NULL, .event_queue = NULL, .callback = NULL, .last_press_time = 0}; /** * @brief GPIO interrupt handler (ISR) * Minimal work in ISR - just send event to task */ static void IRAM_ATTR gpio_isr_handler(void *arg) { int64_t now = esp_timer_get_time(); // Send timestamp to queue for debouncing in task BaseType_t high_priority_task_woken = pdFALSE; xQueueSendFromISR(button_state.event_queue, &now, &high_priority_task_woken); if (high_priority_task_woken) { portYIELD_FROM_ISR(); } } /** * @brief Button handling task * Handles debouncing and callback execution */ static void localbtn_task(void *arg) { int64_t event_time; ESP_LOGI(TAG, "Button task started, monitoring GPIO%d", button_state.gpio_pin); while (1) { // Wait for button press event from ISR if (xQueueReceive(button_state.event_queue, &event_time, portMAX_DELAY)) { // Debouncing: Check if enough time has passed since last press int64_t time_since_last_press = (event_time - button_state.last_press_time) / 1000; // Convert to ms if (time_since_last_press >= DEBOUNCE_TIME_MS) { // Valid button press - verify button is still pressed vTaskDelay(pdMS_TO_TICKS(10)); // Small delay to ensure stable state if (gpio_get_level(button_state.gpio_pin) == 0) { ESP_LOGI(TAG, "Button press detected on GPIO%d", button_state.gpio_pin); button_state.last_press_time = event_time; // Execute callback if (button_state.callback) { button_state.callback(); } } } } } } esp_err_t localbtn_init(int8_t pin_localbtn) { if (pin_localbtn < 0) { ESP_LOGW(TAG, "Button disabled (invalid pin: %d)", pin_localbtn); return ESP_ERR_NOT_SUPPORTED; } if (button_state.initialized) { ESP_LOGW(TAG, "Button already initialized"); return ESP_ERR_INVALID_STATE; } button_state.gpio_pin = pin_localbtn; button_state.last_press_time = 0U; // Create event queue for ISR->Task communication button_state.event_queue = xQueueCreate(10, sizeof(int64_t)); if (button_state.event_queue == NULL) { ESP_LOGE(TAG, "Failed to create event queue"); return ESP_ERR_NO_MEM; } // Configure GPIO gpio_config_t io_conf = { .pin_bit_mask = (1ULL << pin_localbtn), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, // Enable internal pull-up (safe even with external) .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_NEGEDGE // Interrupt on falling edge (button press) }; esp_err_t ret = gpio_config(&io_conf); if (ret != ESP_OK) { ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret)); vQueueDelete(button_state.event_queue); return ret; } // Add ISR handler for this GPIO ret = gpio_isr_handler_add(pin_localbtn, gpio_isr_handler, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "ISR handler add failed: %s", esp_err_to_name(ret)); vQueueDelete(button_state.event_queue); return ret; } // Create button handling task BaseType_t task_ret = xTaskCreate( localbtn_task, "localbtn_task", 2048, NULL, 5, // Priority 5 (same as other tasks) &button_state.task_handle); if (task_ret != pdPASS) { ESP_LOGE(TAG, "Failed to create button task"); gpio_isr_handler_remove(pin_localbtn); vQueueDelete(button_state.event_queue); return ESP_FAIL; } button_state.initialized = true; ESP_LOGI(TAG, "Button initialized on GPIO%d with interrupt-based detection", pin_localbtn); ESP_LOGI(TAG, "Debounce time: %d ms", DEBOUNCE_TIME_MS); return ESP_OK; } void localbtn_deinit(void) { if (!button_state.initialized) { return; } // Remove ISR handler if (button_state.gpio_pin >= 0) { gpio_isr_handler_remove(button_state.gpio_pin); } // Delete task if (button_state.task_handle) { vTaskDelete(button_state.task_handle); button_state.task_handle = NULL; } // Delete queue if (button_state.event_queue) { vQueueDelete(button_state.event_queue); button_state.event_queue = NULL; } button_state.initialized = false; button_state.callback = NULL; ESP_LOGI(TAG, "Button deinitialized"); } void localbtn_register_callback(localbtn_mode_change_callback_t cb) { button_state.callback = cb; ESP_LOGI(TAG, "Callback registered"); }