/** * @file rcsignal.c * @brief RC PWM signal reading implementation using edge capture */ #include "rcsignal.h" #include "driver/gpio.h" #include "esp_timer.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include static const char *TAG = "RCSIGNAL"; #define PULSE_THRESHOLD_US 1500 #define SIGNAL_TIMEOUT_MS 100 static struct { int8_t gpio_pin; volatile uint32_t pulse_width_us; volatile int64_t last_edge_time; volatile int64_t pulse_start_time; volatile bool last_level; volatile bool signal_active; volatile bool pull_detected; uint8_t current_mode; rcsignal_mode_change_callback_t callback; bool initialized; TaskHandle_t monitor_task; } rcsignal = { .gpio_pin = -1, .pulse_width_us = 0, .last_edge_time = 0, .pulse_start_time = 0, .last_level = false, .signal_active = false, .pull_detected = false, .current_mode = 0, .callback = NULL, .initialized = false, .monitor_task = NULL, }; static void IRAM_ATTR gpio_isr_handler(void *arg) { int64_t now = esp_timer_get_time(); bool level = gpio_get_level(rcsignal.gpio_pin); if (level && !rcsignal.last_level) { // Rising edge - start of pulse rcsignal.pulse_start_time = now; } else if (!level && rcsignal.last_level) { // Falling edge - end of pulse if (rcsignal.pulse_start_time > 0) { rcsignal.pulse_width_us = (uint32_t)(now - rcsignal.pulse_start_time); rcsignal.last_edge_time = now; rcsignal.signal_active = true; } } rcsignal.last_level = level; } static void monitor_task(void *arg) { uint32_t last_pulse_width = 0; while (1) { vTaskDelay(pdMS_TO_TICKS(10)); // Check for signal timeout int64_t now = esp_timer_get_time(); if (rcsignal.signal_active && (now - rcsignal.last_edge_time) > (SIGNAL_TIMEOUT_MS * 1000)) { rcsignal.signal_active = false; rcsignal.pulse_width_us = 0; } // Detect mode change (rising edge on PWM signal > 1500us) if (rcsignal.pulse_width_us != last_pulse_width) { last_pulse_width = rcsignal.pulse_width_us; if (rcsignal.pulse_width_us < PULSE_THRESHOLD_US) { rcsignal.pull_detected = true; } if (rcsignal.pulse_width_us > PULSE_THRESHOLD_US && rcsignal.pull_detected) { // Mode change detected rcsignal.pull_detected = false; if (rcsignal.callback) { rcsignal.callback(); } } } } } esp_err_t rcsignal_init(int8_t pin) { if (pin < 0) { ESP_LOGI(TAG, "RC signal disabled (no pin configured)"); return ESP_OK; } rcsignal.gpio_pin = pin; // Configure GPIO gpio_config_t io_conf = { .pin_bit_mask = (1ULL << pin), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_ANYEDGE, }; ESP_ERROR_CHECK(gpio_config(&io_conf)); // Install ISR service ESP_ERROR_CHECK(gpio_install_isr_service(0)); ESP_ERROR_CHECK(gpio_isr_handler_add(pin, gpio_isr_handler, NULL)); // Create monitor task BaseType_t ret = xTaskCreate(monitor_task, "rcsignal_monitor", 2048, NULL, 5, &rcsignal.monitor_task); if (ret != pdPASS) { gpio_isr_handler_remove(pin); gpio_uninstall_isr_service(); return ESP_FAIL; } rcsignal.initialized = true; ESP_LOGI(TAG, "RC signal initialized on GPIO%d", pin); return ESP_OK; } void rcsignal_deinit(void) { if (!rcsignal.initialized) return; if (rcsignal.monitor_task) { vTaskDelete(rcsignal.monitor_task); rcsignal.monitor_task = NULL; } if (rcsignal.gpio_pin >= 0) { gpio_isr_handler_remove(rcsignal.gpio_pin); } rcsignal.initialized = false; } void rcsignal_register_callback(rcsignal_mode_change_callback_t callback) { rcsignal.callback = callback; } uint32_t rcsignal_get_pulse_width(void) { return rcsignal.pulse_width_us; } bool rcsignal_is_active(void) { return rcsignal.signal_active; } uint8_t rcsignal_get_current_mode(void) { return rcsignal.current_mode; }