diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index af516efc5d..29fc28cd2e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace esphome { namespace esp32_touch { @@ -83,13 +84,16 @@ class ESP32TouchComponent : public Component { static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100; static void touch_isr_handler(void *arg); - QueueHandle_t touch_queue_{nullptr}; + + // Ring buffer handle for FreeRTOS ring buffer + RingbufHandle_t ring_buffer_handle_{nullptr}; + uint32_t ring_buffer_overflow_count_{0}; // Design note: last_touch_time_ does not require synchronization primitives because: // 1. ESP32 guarantees atomic 32-bit aligned reads/writes // 2. ISR only writes timestamps, main loop only reads (except sentinel value 1) // 3. Timing tolerance allows for occasional stale reads (50ms check interval) - // 4. Queue operations provide implicit memory barriers + // 4. Ring buffer operations provide implicit memory barriers // Using atomic/critical sections would add overhead without meaningful benefit uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; uint32_t release_timeout_ms_{1500}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 774ff0b0bf..2f5da4df60 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -12,16 +12,19 @@ #include "hal/touch_sensor_ll.h" // Include for RTC clock frequency #include "soc/rtc.h" +// Include FreeRTOS ring buffer +#include "freertos/ringbuf.h" namespace esphome { namespace esp32_touch { static const char *const TAG = "esp32_touch"; -struct TouchPadEventV1 { - touch_pad_t pad; - uint32_t value; - bool is_touched; +// Structure for a single pad's state in the ring buffer +struct TouchPadState { + uint8_t pad; // touch_pad_t + uint32_t value; // Current reading + bool is_touched; // Touch state }; void ESP32TouchComponent::setup() { @@ -30,14 +33,19 @@ void ESP32TouchComponent::setup() { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - // Create queue for touch events - size_t queue_size = this->children_.size() * 4; - if (queue_size < 8) - queue_size = 8; + // Create ring buffer for touch events + // Size calculation: We need space for multiple snapshots + // Each snapshot contains: array of TouchPadState structures + size_t pad_state_size = sizeof(TouchPadState); + size_t snapshot_size = this->children_.size() * pad_state_size; - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); - if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + // Allow for 4 snapshots in the buffer to handle normal operation and bursts + size_t buffer_size = snapshot_size * 4; + + // Create a byte buffer ring buffer (allows variable sized items) + this->ring_buffer_handle_ = xRingbufferCreate(buffer_size, RINGBUF_TYPE_BYTEBUF); + if (this->ring_buffer_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create ring buffer of size %d", buffer_size); this->mark_failed(); return; } @@ -65,8 +73,8 @@ void ESP32TouchComponent::setup() { esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vQueueDelete(this->touch_queue_); - this->touch_queue_ = nullptr; + vRingbufferDelete(this->ring_buffer_handle_); + this->ring_buffer_handle_ = nullptr; this->mark_failed(); return; } @@ -114,33 +122,44 @@ void ESP32TouchComponent::loop() { this->setup_mode_last_log_print_ = now; } - // Process any queued touch events from interrupts - TouchPadEventV1 event; - while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { - // Find the corresponding sensor - for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - child->value_ = event.value; + // Process ring buffer entries + size_t item_size; + TouchPadState *pad_states; - // The interrupt gives us the touch state directly - bool new_state = event.is_touched; + // Receive all available items from ring buffer (non-blocking) + while ((pad_states = (TouchPadState *) xRingbufferReceive(this->ring_buffer_handle_, &item_size, 0)) != nullptr) { + // Calculate number of pads in this snapshot + size_t num_pads = item_size / sizeof(TouchPadState); - // Track when we last saw this pad as touched - if (new_state) { - this->last_touch_time_[event.pad] = now; + // Process each pad in the snapshot + for (size_t i = 0; i < num_pads; i++) { + const TouchPadState &pad_state = pad_states[i]; + + // Find the corresponding sensor + for (auto *child : this->children_) { + if (child->get_touch_pad() == static_cast(pad_state.pad)) { + child->value_ = pad_state.value; + + // Track when we last saw this pad as touched + if (pad_state.is_touched) { + this->last_touch_time_[pad_state.pad] = now; + } + + // Only publish if state changed + if (pad_state.is_touched != child->last_state_) { + child->last_state_ = pad_state.is_touched; + child->publish_state(pad_state.is_touched); + ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), pad_state.is_touched ? "ON" : "OFF", pad_state.value, + child->get_threshold()); + } + break; } - - // Only publish if state changed - if (new_state != child->last_state_) { - child->last_state_ = new_state; - child->publish_state(new_state); - // Original ESP32: ISR only fires when touched, release is detected by timeout - ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), event.value, child->get_threshold()); - } - break; } } + + // Return item to ring buffer + vRingbufferReturnItem(this->ring_buffer_handle_, (void *) pad_states); } // Check for released pads periodically @@ -184,8 +203,10 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(); touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); + + if (this->ring_buffer_handle_) { + vRingbufferDelete(this->ring_buffer_handle_); + this->ring_buffer_handle_ = nullptr; } bool is_wakeup_source = false; @@ -218,7 +239,23 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_clear_status(); - // Process all configured pads to check their current state + // Calculate size needed for this snapshot + size_t num_pads = component->children_.size(); + size_t snapshot_size = num_pads * sizeof(TouchPadState); + + // Allocate space in ring buffer (ISR-safe version) + void *buffer = xRingbufferSendAcquireFromISR(component->ring_buffer_handle_, snapshot_size); + if (buffer == nullptr) { + // Buffer full - track overflow + component->ring_buffer_overflow_count_++; + return; + } + + // Fill the buffer with pad states + TouchPadState *pad_states = (TouchPadState *) buffer; + + // Process all configured pads + size_t pad_index = 0; for (auto *child : component->children_) { touch_pad_t pad = child->get_touch_pad(); @@ -238,21 +275,24 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { continue; } + // Store pad state + pad_states[pad_index].pad = static_cast(pad); + pad_states[pad_index].value = value; // For original ESP32, lower value means touched - bool is_touched = value < child->get_threshold(); + pad_states[pad_index].is_touched = value < child->get_threshold(); - // Always send the current state - the main loop will filter for changes - TouchPadEventV1 event; - event.pad = pad; - event.value = value; - event.is_touched = is_touched; + pad_index++; + } - // Send to queue from ISR - BaseType_t x_higher_priority_task_woken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); - if (x_higher_priority_task_woken) { - portYIELD_FROM_ISR(); - } + // Adjust size if we skipped any pads + size_t actual_size = pad_index * sizeof(TouchPadState); + + // Send the item + BaseType_t higher_priority_task_woken = pdFALSE; + xRingbufferSendCompleteFromISR(component->ring_buffer_handle_, buffer, actual_size, &higher_priority_task_woken); + + if (higher_priority_task_woken) { + portYIELD_FROM_ISR(); } }