mirror of
https://github.com/esphome/esphome.git
synced 2026-09-14 00:28:39 +00:00
[socket] Add socket wake support for RP2040
Enable instant wake on socket activity for RP2040 using ARM WFE/SEV hardware instructions, matching ESP8266's esp_delay/esp_schedule pattern. Previously, RP2040 used plain delay() in the main loop which could not be interrupted by incoming socket data. Now LWIP recv/accept callbacks call socket_wake() which sets a flag and sends a hardware event (__sev) to wake the main loop from __wfe() sleep immediately. The implementation uses a one-shot pico-sdk timer alarm for timeout (same pattern as ESP8266's os_timer_arm) combined with __wfe() for true hardware sleep between events.
This commit is contained in:
@@ -13,6 +13,11 @@
|
||||
#include <coredecls.h> // For esp_schedule()
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <hardware/sync.h> // For __sev(), __wfe()
|
||||
#include <pico/time.h> // For add_alarm_in_ms(), cancel_alarm()
|
||||
#endif
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -42,6 +47,58 @@ void IRAM_ATTR socket_wake() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
// RP2040 (non-FreeRTOS) socket wake using hardware WFE/SEV instructions.
|
||||
//
|
||||
// Same pattern as ESP8266's esp_delay()/esp_schedule(): set a one-shot timer,
|
||||
// then sleep with __wfe(). Wake on either:
|
||||
// - Timer alarm fires → callback calls __sev() → __wfe() returns → timeout
|
||||
// - Socket data arrives → LWIP callback calls socket_wake() → __sev() → __wfe() returns → early wake
|
||||
//
|
||||
// CYW43 WiFi chip communicates via SPI interrupts on core 0. When data arrives,
|
||||
// the GPIO interrupt fires → async_context pendsv processes CYW43/LWIP → recv/accept
|
||||
// callbacks call socket_wake() → __sev() wakes the main loop from __wfe() sleep.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static volatile bool s_socket_woke = false;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static volatile bool s_delay_expired = false;
|
||||
|
||||
static int64_t alarm_callback_(alarm_id_t id, void *user_data) {
|
||||
(void) id;
|
||||
(void) user_data;
|
||||
s_delay_expired = true;
|
||||
// Wake the main loop from __wfe() sleep — timeout expired.
|
||||
__sev();
|
||||
// Return 0 = don't reschedule (one-shot)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void socket_delay(uint32_t ms) {
|
||||
if (ms == 0)
|
||||
return;
|
||||
s_socket_woke = false;
|
||||
s_delay_expired = false;
|
||||
// Set a one-shot timer to wake us after the timeout
|
||||
alarm_id_t alarm = add_alarm_in_ms(ms, alarm_callback_, nullptr, true);
|
||||
// Sleep until woken by either the timer alarm or socket_wake().
|
||||
// __wfe() may return spuriously (stale event register, other interrupts),
|
||||
// so we loop checking both flags.
|
||||
while (!s_socket_woke && !s_delay_expired) {
|
||||
__wfe();
|
||||
}
|
||||
// Cancel timer if we woke early (socket data arrived before timeout)
|
||||
if (alarm > 0 && !s_delay_expired)
|
||||
cancel_alarm(alarm);
|
||||
}
|
||||
|
||||
void socket_wake() {
|
||||
s_socket_woke = true;
|
||||
// Wake the main loop from __wfe() sleep. __sev() is a global event that
|
||||
// wakes any core sleeping in __wfe(). This is ISR-safe.
|
||||
__sev();
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *const TAG = "socket.lwip";
|
||||
|
||||
// set to 1 to enable verbose lwip logging
|
||||
@@ -371,7 +428,7 @@ err_t LWIPRawImpl::recv_fn(struct pbuf *pb, err_t err) {
|
||||
} else {
|
||||
pbuf_cat(this->rx_buf_, pb);
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040))
|
||||
// Wake the main loop immediately so it can process the received data.
|
||||
socket_wake();
|
||||
#endif
|
||||
@@ -650,7 +707,7 @@ err_t LWIPRawListenImpl::accept_fn_(struct tcp_pcb *newpcb, err_t err) {
|
||||
sock->init();
|
||||
this->accepted_sockets_[this->accepted_socket_count_++] = std::move(sock);
|
||||
LWIP_LOG("Accepted connection, queue size: %d", this->accepted_socket_count_);
|
||||
#ifdef USE_ESP8266
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040))
|
||||
// Wake the main loop immediately so it can accept the new connection.
|
||||
socket_wake();
|
||||
#endif
|
||||
|
||||
@@ -120,13 +120,17 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po
|
||||
/// Format sockaddr into caller-provided buffer, returns length written (excluding null)
|
||||
size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::span<char, SOCKADDR_STR_LEN> buf);
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
/// Delay that can be woken early by socket activity.
|
||||
/// On ESP8266, lwip callbacks set a flag and call esp_schedule() to wake the delay.
|
||||
/// On ESP8266, uses esp_delay() with a callback that checks socket activity.
|
||||
/// On RP2040, uses __wfe() (Wait For Event) to truly sleep until an interrupt
|
||||
/// (CYW43 GPIO, timer alarm) fires, then processes pending async_context work.
|
||||
void socket_delay(uint32_t ms);
|
||||
|
||||
/// Signal socket/IO activity and wake the main loop from esp_delay() early.
|
||||
/// ISR-safe: uses IRAM_ATTR internally and only sets a volatile flag + esp_schedule().
|
||||
/// Signal socket/IO activity and wake the main loop early.
|
||||
/// On ESP8266: sets flag + esp_schedule().
|
||||
/// On RP2040: sets flag + __sev() (Send Event) to wake from __wfe().
|
||||
/// ISR-safe on both platforms.
|
||||
void socket_wake(); // NOLINT(readability-redundant-declaration)
|
||||
#endif
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "esphome/components/status_led/status_led.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#endif
|
||||
|
||||
@@ -713,8 +713,10 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
}
|
||||
// No sockets registered or select() failed - use regular delay
|
||||
delay(delay_ms);
|
||||
#elif defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// No select support but can wake on socket activity via esp_schedule()
|
||||
#elif (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// No select support but can wake on socket activity
|
||||
// ESP8266: via esp_schedule()
|
||||
// RP2040: via __sev()/__wfe() hardware sleep/wake
|
||||
socket::socket_delay(delay_ms);
|
||||
#else
|
||||
// No select support, use regular delay
|
||||
|
||||
Reference in New Issue
Block a user