1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-14 17:22:20 +01:00
This commit is contained in:
J. Nick Koston
2025-06-12 16:58:24 -05:00
parent a18374e1ad
commit 866eaed73d
2 changed files with 96 additions and 52 deletions

View File

@@ -11,6 +11,7 @@
#include <driver/touch_sensor.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/ringbuf.h>
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};

View File

@@ -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<touch_pad_t>(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<uint8_t>(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();
}
}