208 lines
5.4 KiB
C
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");
|
|
} |