Files
WS2812B-LED-RC-Controller/main/localbtn.c
2026-01-06 18:15:53 +01:00

208 lines
5.4 KiB
C

/**
* @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 <string.h>
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");
}