| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "EspGpioInput.h" | ||
| 2 | #include "esp_log.h" | ||
| 3 | #include "esp_timer.h" | ||
| 4 | #include <cstring> | ||
| 5 | #include <atomic> | ||
| 6 | |||
| 7 | static const char* TAG = "EspGpioInput"; | ||
| 8 | |||
| 9 | ✗ | EspGpioInput::EspGpioInput(gpio_num_t pin, uint32_t debounceMs) | |
| 10 | ✗ | : m_pin(pin) | |
| 11 | ✗ | , m_debounceMs(debounceMs) | |
| 12 | ✗ | , m_handler(nullptr) | |
| 13 | ✗ | , m_lastInterruptTime(0) { | |
| 14 | // Configure GPIO: enable internal pull-up by default for stable input | ||
| 15 | ✗ | gpio_config_t io_conf = {}; | |
| 16 | ✗ | io_conf.pin_bit_mask = (1ULL << pin); | |
| 17 | ✗ | io_conf.mode = GPIO_MODE_INPUT; | |
| 18 | ✗ | io_conf.pull_up_en = GPIO_PULLUP_ENABLE; // Use internal pull-up to avoid floating | |
| 19 | ✗ | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // Adjust per sensor if needed | |
| 20 | ✗ | io_conf.intr_type = GPIO_INTR_ANYEDGE; // Interrupt on both edges | |
| 21 | |||
| 22 | ✗ | esp_err_t ret = gpio_config(&io_conf); | |
| 23 | ✗ | if (ret != ESP_OK) { | |
| 24 | ✗ | ESP_LOGE(TAG, "Failed to configure GPIO %d: %s", pin, esp_err_to_name(ret)); | |
| 25 | } else { | ||
| 26 | // Read initial level immediately after configuration | ||
| 27 | ✗ | int initial_level = gpio_get_level(pin); | |
| 28 | ✗ | ESP_LOGW(TAG, "*** GPIO %d configured, pull: PULLUP, initial level: %d ***", pin, initial_level); | |
| 29 | } | ||
| 30 | ✗ | } | |
| 31 | |||
| 32 | ✗ | EspGpioInput::~EspGpioInput() { | |
| 33 | ✗ | disableInterrupt(); | |
| 34 | ✗ | } | |
| 35 | |||
| 36 | ✗ | bool EspGpioInput::getLevel() const { | |
| 37 | ✗ | return gpio_get_level(m_pin) != 0; | |
| 38 | } | ||
| 39 | |||
| 40 | ✗ | void EspGpioInput::setInterruptHandler(std::function<void(bool level)> handler) { | |
| 41 | ✗ | m_handler = std::move(handler); | |
| 42 | ✗ | } | |
| 43 | |||
| 44 | ✗ | void EspGpioInput::enableInterrupt() { | |
| 45 | ✗ | if (!m_handler) { | |
| 46 | ✗ | ESP_LOGW(TAG, "Cannot enable interrupt on GPIO %d: no handler set", m_pin); | |
| 47 | ✗ | return; | |
| 48 | } | ||
| 49 | |||
| 50 | // Install ISR service if not already installed (done once globally) | ||
| 51 | static bool isr_service_installed = false; | ||
| 52 | ✗ | if (!isr_service_installed) { | |
| 53 | ✗ | esp_err_t ret = gpio_install_isr_service(0); | |
| 54 | ✗ | if (ret == ESP_OK) { | |
| 55 | ✗ | isr_service_installed = true; | |
| 56 | ✗ | ESP_LOGI(TAG, "GPIO ISR service installed"); | |
| 57 | ✗ | } else if (ret != ESP_ERR_INVALID_STATE) { // Already installed | |
| 58 | ✗ | ESP_LOGE(TAG, "Failed to install ISR service: %s", esp_err_to_name(ret)); | |
| 59 | ✗ | return; | |
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | // Add ISR handler | ||
| 64 | ✗ | esp_err_t ret = gpio_isr_handler_add(m_pin, gpioIsrHandler, this); | |
| 65 | ✗ | if (ret != ESP_OK) { | |
| 66 | ✗ | ESP_LOGE(TAG, "Failed to add ISR handler for GPIO %d: %s", m_pin, esp_err_to_name(ret)); | |
| 67 | } else { | ||
| 68 | ✗ | int current_level = gpio_get_level(m_pin); | |
| 69 | ✗ | ESP_LOGW(TAG, "*** ISR enabled GPIO %d, level: %d, debounce: %lu ms ***", | |
| 70 | m_pin, current_level, m_debounceMs); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | ✗ | void EspGpioInput::disableInterrupt() { | |
| 75 | ✗ | gpio_isr_handler_remove(m_pin); | |
| 76 | ✗ | ESP_LOGI(TAG, "Interrupt disabled on GPIO %d", m_pin); | |
| 77 | ✗ | } | |
| 78 | |||
| 79 | ✗ | void EspGpioInput::simulateInterrupt(bool level) { | |
| 80 | ✗ | ESP_LOGI(TAG, "Simulating interrupt on GPIO %d with level %d", m_pin, level); | |
| 81 | ✗ | if (m_handler) { | |
| 82 | ✗ | m_handler(level); | |
| 83 | } | ||
| 84 | ✗ | } | |
| 85 | |||
| 86 | ✗ | void IRAM_ATTR EspGpioInput::gpioIsrHandler(void* arg) { | |
| 87 | ✗ | auto* input = static_cast<EspGpioInput*>(arg); | |
| 88 | ✗ | if (input) { | |
| 89 | // Simple debug: count interrupts (visible in debugger) | ||
| 90 | static std::atomic<uint32_t> isr_count{0}; | ||
| 91 | isr_count.fetch_add(1, std::memory_order_relaxed); | ||
| 92 | ✗ | input->handleInterrupt(); | |
| 93 | } | ||
| 94 | ✗ | } | |
| 95 | |||
| 96 | ✗ | void IRAM_ATTR EspGpioInput::handleInterrupt() { | |
| 97 | // Debug counter | ||
| 98 | static std::atomic<uint32_t> handle_count{0}; | ||
| 99 | handle_count.fetch_add(1, std::memory_order_relaxed); | ||
| 100 | |||
| 101 | // Read current level immediately | ||
| 102 | ✗ | bool level = gpio_get_level(m_pin) != 0; | |
| 103 | |||
| 104 | // Debouncing: check if enough time has passed since last interrupt | ||
| 105 | ✗ | if (m_debounceMs > 0) { | |
| 106 | ✗ | int64_t now = esp_timer_get_time(); | |
| 107 | ✗ | int64_t elapsed_ms = (now - m_lastInterruptTime) / 1000; | |
| 108 | |||
| 109 | ✗ | if (elapsed_ms < m_debounceMs) { | |
| 110 | // Debug: count debounced interrupts | ||
| 111 | static std::atomic<uint32_t> debounce_blocked{0}; | ||
| 112 | debounce_blocked.fetch_add(1, std::memory_order_relaxed); | ||
| 113 | ✗ | return; // Ignore this interrupt (debounce period) | |
| 114 | } | ||
| 115 | |||
| 116 | ✗ | m_lastInterruptTime = now; | |
| 117 | } | ||
| 118 | |||
| 119 | // Call handler with current level | ||
| 120 | ✗ | if (m_handler) { | |
| 121 | ✗ | m_handler(level); | |
| 122 | } | ||
| 123 | } | ||
| 124 |