diff --git a/src/helpers/esp32/TEthEliteBoard.cpp b/src/helpers/esp32/TEthEliteBoard.cpp index 81af5bf7..b4797e07 100644 --- a/src/helpers/esp32/TEthEliteBoard.cpp +++ b/src/helpers/esp32/TEthEliteBoard.cpp @@ -1,22 +1,67 @@ #if defined(T_ETH_ELITE_SX1262) || defined(T_ETH_ELITE_SX1276) #include -#include -#include "driver/spi_common.h" +#include +#include "esp_eth.h" +#include "esp_netif.h" +#include "esp_netif_defaults.h" +#include "esp_event.h" +#include "esp_mac.h" +#include "driver/spi_master.h" #include "driver/gpio.h" -#include #include "TEthEliteBoard.h" #include "target.h" #include "helpers/ui/MomentaryButton.h" #include +// Build IP addresses in lwIP's internal format (network byte order stored as LE uint32_t). +// Using a variadic macro so that comma-expanded build flags like ETH_STATIC_IP work: +// IP4_NBO(ETH_STATIC_IP) expands to _ip4_make(192,168,254,21) after arg expansion. +static inline uint32_t _ip4_make(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return ((uint32_t)d << 24) | ((uint32_t)c << 16) | ((uint32_t)b << 8) | (uint32_t)a; +} +#define IP4_NBO(...) _ip4_make(__VA_ARGS__) + extern MomentaryButton user_btn; uint32_t deviceOnline = 0x00; -static SPIClass spi_eth(FSPI); -static ETHClass2 ETH; String eth_local_ip; // global, accessible from ESP32Board.cpp via extern +#ifdef USE_ETHERNET +static esp_netif_t *eth_netif = NULL; +static esp_eth_handle_t eth_handle = NULL; + +// Refresh eth_local_ip from the current netif state. +static void updateLocalIpString() { + esp_netif_ip_info_t info = {}; + esp_netif_get_ip_info(eth_netif, &info); + char buf[16]; + esp_ip4addr_ntoa(&info.ip, buf, sizeof(buf)); + eth_local_ip = String(buf); +} + +// Apply static IP config. All uint32_t params are already in network byte order +// (ie. in the format accepted by esp_ip4_addr_t.addr). Pass dns_nbo=0 to skip DNS. +static void applyStaticIp(uint32_t ip_nbo, uint32_t gw_nbo, uint32_t mask_nbo, uint32_t dns_nbo) { + esp_netif_dhcpc_stop(eth_netif); + + esp_netif_ip_info_t info = {}; + info.ip.addr = ip_nbo; + info.gw.addr = gw_nbo; + info.netmask.addr = mask_nbo; + esp_netif_set_ip_info(eth_netif, &info); + + if (dns_nbo != 0) { + esp_netif_dns_info_t dns = {}; + dns.ip.u_addr.ip4.addr = dns_nbo; + dns.ip.type = ESP_IPADDR_TYPE_V4; + esp_netif_set_dns_info(eth_netif, ESP_NETIF_DNS_MAIN, &dns); + } + + updateLocalIpString(); +} +#endif + void TEthEliteBoard::begin() { ESP32Board::begin(); user_btn.begin(); @@ -33,23 +78,13 @@ void TEthEliteBoard::begin() { pinMode(P_LORA_TX_LED, OUTPUT); digitalWrite(P_LORA_TX_LED, LOW); - esp_reset_reason_t reason = esp_reset_reason(); - if (reason == ESP_RST_DEEPSLEEP) { -#if defined(T_ETH_ELITE_SX1262) - long wakeup_source = esp_sleep_get_ext1_wakeup_status(); - if (wakeup_source & (1 << P_LORA_DIO_1)) { + if (esp_reset_reason() == ESP_RST_DEEPSLEEP) { + uint64_t wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1ULL << P_LORA_WAKE_DIO)) { startup_reason = BD_STARTUP_RX_PACKET; } rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); - rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); -#elif defined(T_ETH_ELITE_SX1276) - long wakeup_source = esp_sleep_get_ext1_wakeup_status(); - if (wakeup_source & (1 << P_LORA_DIO_0)) { - startup_reason = BD_STARTUP_RX_PACKET; - } - rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); - rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_0); -#endif + rtc_gpio_deinit((gpio_num_t)P_LORA_WAKE_DIO); } } @@ -125,76 +160,131 @@ void TEthEliteBoard::startNetwork() { } void TEthEliteBoard::startEthernet() { - pinMode(ETH_CS, OUTPUT); - digitalWrite(ETH_CS, HIGH); +#ifdef USE_ETHERNET + // Arduino-ESP32 3.x does not initialize these until WiFi is actively used. + // The native Ethernet path needs them up-front. Each returns ESP_ERR_INVALID_STATE + // on second call — treat that as success. + esp_err_t err = esp_netif_init(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) ESP_ERROR_CHECK(err); + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) ESP_ERROR_CHECK(err); + // W5500 driver calls gpio_isr_handler_add() on ETH_INT; needs ISR service running. + err = gpio_install_isr_service(0); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) ESP_ERROR_CHECK(err); - ETH.begin(ETH_PHY_W5500, ETH_ADDR, ETH_CS, ETH_INT, -1, SPI2_HOST, ETH_SCLK, ETH_MISO, ETH_MOSI); - delay(100); + // Initialize SPI2 (FSPI) bus with W5500 pins. The LoRa radio uses the + // default Arduino SPIClass, which on ESP32-S3 is HSPI → SPI3 — so W5500 + // must stay on SPI2 to keep the two drivers on separate buses. + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = ETH_MOSI; + buscfg.miso_io_num = ETH_MISO; + buscfg.sclk_io_num = ETH_SCLK; + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + // W5500 SPI device: 16-bit address phase + 8-bit control byte + spi_device_interface_config_t spi_devcfg = {}; + spi_devcfg.command_bits = 16; + spi_devcfg.address_bits = 8; + spi_devcfg.mode = 0; + spi_devcfg.clock_speed_hz = 20 * 1000 * 1000; + spi_devcfg.spics_io_num = ETH_CS; + spi_devcfg.queue_size = 20; + + spi_device_handle_t spi_handle = NULL; + ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &spi_devcfg, &spi_handle)); + + // W5500 MAC + PHY via native ESP-IDF driver. Bump the receive task stack: + // the default 2048 bytes overflows under load (observed w5500_tsk stack canary). + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + mac_config.rx_task_stack_size = 4096; + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = ETH_ADDR; + phy_config.reset_gpio_num = ETH_RST; + + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); + w5500_config.int_gpio_num = ETH_INT; + + esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); + esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config); + + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); + + // W5500 has no factory MAC — derive one from the ESP32 efuse base MAC and + // program it into the driver. Without this, DHCP never completes and the + // ETH interface transmits with MAC 00:00:00:00:00:00. + uint8_t mac_addr[6]; + ESP_ERROR_CHECK(esp_read_mac(mac_addr, ESP_MAC_ETH)); + ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr)); + + // Create default ETH netif and attach driver glue + esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); + eth_netif = esp_netif_new(&netif_cfg); + ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); + + ESP_ERROR_CHECK(esp_eth_start(eth_handle)); + + // Wait for link up (netif up means PHY link is established) unsigned long t0 = millis(); - while (!ETH.linkUp() && millis() - t0 < 5000) { + while (!esp_netif_is_netif_up(eth_netif) && millis() - t0 < 5000) { esp_task_wdt_reset(); delay(100); } + // Wait for DHCP IP assignment + esp_netif_ip_info_t ip_info = {}; t0 = millis(); - while (ETH.localIP() == IPAddress(0, 0, 0, 0) && millis() - t0 < 5000) { + while (ip_info.ip.addr == 0 && millis() - t0 < 5000) { esp_task_wdt_reset(); + esp_netif_get_ip_info(eth_netif, &ip_info); delay(100); } - if (ETH.localIP() == IPAddress(0, 0, 0, 0)) { + if (ip_info.ip.addr == 0) { #ifdef ETH_STATIC_IP Serial.println("DHCP timeout, using static IP from build flags"); - ETH.config(IPAddress(ETH_STATIC_IP), IPAddress(ETH_GATEWAY), IPAddress(ETH_SUBNET), IPAddress(ETH_DNS)); + #ifdef ETH_DNS + applyStaticIp(IP4_NBO(ETH_STATIC_IP), IP4_NBO(ETH_GATEWAY), + IP4_NBO(ETH_SUBNET), IP4_NBO(ETH_DNS)); + #else + applyStaticIp(IP4_NBO(ETH_STATIC_IP), IP4_NBO(ETH_GATEWAY), + IP4_NBO(ETH_SUBNET), 0); + #endif #else Serial.println("DHCP timeout, using fallback IP"); - ETH.config(IPAddress(192, 168, 4, 2), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); + applyStaticIp(IP4_NBO(192, 168, 4, 2), IP4_NBO(192, 168, 4, 1), + IP4_NBO(255, 255, 255, 0), 0); #endif - } - - eth_local_ip = ETH.localIP().toString(); // save IP for OTA use - - uint8_t mac[6]; - ETH.macAddress(mac); - Serial.printf("ETH MAC %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - Serial.print("ETH IP "); Serial.println(ETH.localIP()); - Serial.println(ETH.linkUp() ? "ETH LINK UP" : "ETH LINK DOWN"); -} -void TEthEliteBoard::reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1) { - if (ip != 0) { - uint8_t b1 = (ip >> 24) & 0xFF; - uint8_t b2 = (ip >> 16) & 0xFF; - uint8_t b3 = (ip >> 8) & 0xFF; - uint8_t b4 = ip & 0xFF; - - uint8_t gw1 = (gw >> 24) & 0xFF; - uint8_t gw2 = (gw >> 16) & 0xFF; - uint8_t gw3 = (gw >> 8) & 0xFF; - uint8_t gw4 = gw & 0xFF; - - uint8_t sub1 = (subnet >> 24) & 0xFF; - uint8_t sub2 = (subnet >> 16) & 0xFF; - uint8_t sub3 = (subnet >> 8) & 0xFF; - uint8_t sub4 = subnet & 0xFF; - - uint8_t dns_1 = (dns1 >> 24) & 0xFF; - uint8_t dns_2 = (dns1 >> 16) & 0xFF; - uint8_t dns_3 = (dns1 >> 8) & 0xFF; - uint8_t dns_4 = dns1 & 0xFF; - - bool ok = ETH.config( - IPAddress(b1, b2, b3, b4), - IPAddress(gw1, gw2, gw3, gw4), - IPAddress(sub1, sub2, sub3, sub4), - IPAddress(dns_1, dns_2, dns_3, dns_4) - ); - if (ok) { - Serial.printf("ETH reconfigured to %d.%d.%d.%d\n", b1, b2, b3, b4); } else { - Serial.println("ETH reconfigure failed"); + updateLocalIpString(); } - eth_local_ip = ETH.localIP().toString(); - } + + Serial.printf("ETH MAC %02X:%02X:%02X:%02X:%02X:%02X\n", + mac_addr[0], mac_addr[1], mac_addr[2], + mac_addr[3], mac_addr[4], mac_addr[5]); + Serial.printf("ETH IP %s\n", eth_local_ip.c_str()); + Serial.println(esp_netif_is_netif_up(eth_netif) ? "ETH LINK UP" : "ETH LINK DOWN"); +#endif +} + +#ifdef USE_ETHERNET +// Packed big-endian uint32 (b1 in MSB) → network-byte-order as stored in ip4_addr_t.addr +// on little-endian ESP32. Host→net byte swap achieves exactly this layout. +static inline uint32_t packedToNbo(uint32_t packed) { + return __builtin_bswap32(packed); } #endif + +void TEthEliteBoard::reconfigureEthernet(uint32_t ip, uint32_t gw, uint32_t subnet, uint32_t dns1) { +#ifdef USE_ETHERNET + if (ip == 0 || eth_netif == NULL) return; + + applyStaticIp(packedToNbo(ip), packedToNbo(gw), + packedToNbo(subnet), dns1 ? packedToNbo(dns1) : 0); + Serial.printf("ETH reconfigured to %s\n", eth_local_ip.c_str()); +#endif +} + +#endif diff --git a/src/helpers/esp32/TEthEliteBoard_SX1262.h b/src/helpers/esp32/TEthEliteBoard_SX1262.h index fda5c176..7e1a5291 100644 --- a/src/helpers/esp32/TEthEliteBoard_SX1262.h +++ b/src/helpers/esp32/TEthEliteBoard_SX1262.h @@ -5,15 +5,16 @@ // Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_1 // LoRa SX1262 pins (T-ETH Elite LoRa Shield) -#define P_LORA_NSS 40 // CS -#define P_LORA_RESET 46 // RESET -#define P_LORA_BUSY 16 // BUSY -#define P_LORA_DIO_0 -1 // NC -#define P_LORA_DIO_1 8 // IRQ -#define P_LORA_TX_LED 38 -#define P_LORA_SCLK 10 -#define P_LORA_MISO 9 -#define P_LORA_MOSI 11 +#define P_LORA_NSS 40 // CS +#define P_LORA_RESET 46 // RESET +#define P_LORA_BUSY 16 // BUSY +#define P_LORA_DIO_0 -1 // NC +#define P_LORA_DIO_1 8 // IRQ +#define P_LORA_TX_LED 38 +#define P_LORA_SCLK 10 +#define P_LORA_MISO 9 +#define P_LORA_MOSI 11 +#define P_LORA_WAKE_DIO P_LORA_DIO_1 // DIO used as deep-sleep wake source // ETH W5500 pins (T-ETH Elite main board) #define ETH_MISO 47 @@ -78,15 +79,13 @@ public: void enterDeepSleep(uint32_t secs, int pin_wake_btn) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + rtc_gpio_set_direction((gpio_num_t)P_LORA_WAKE_DIO, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_WAKE_DIO); rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); - } else { - esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); - } + uint64_t wake_mask = 1ULL << P_LORA_WAKE_DIO; + if (pin_wake_btn >= 0) wake_mask |= 1ULL << pin_wake_btn; + esp_sleep_enable_ext1_wakeup(wake_mask, ESP_EXT1_WAKEUP_ANY_HIGH); if (secs > 0) { esp_sleep_enable_timer_wakeup(secs * 1000000); diff --git a/src/helpers/esp32/TEthEliteBoard_SX1276.h b/src/helpers/esp32/TEthEliteBoard_SX1276.h index d6473cef..17617a3b 100644 --- a/src/helpers/esp32/TEthEliteBoard_SX1276.h +++ b/src/helpers/esp32/TEthEliteBoard_SX1276.h @@ -5,15 +5,16 @@ // Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_0 // LoRa SX1276 pins (T-ETH Elite LoRa Shield) -#define P_LORA_DIO_0 8 // IRQ (DIO0 on SX1276) -#define P_LORA_DIO_1 16 // DIO1 -#define P_LORA_NSS 40 // CS -#define P_LORA_RESET 46 // RESET -#define P_LORA_BUSY -1 // SX1276 has no BUSY pin -#define P_LORA_TX_LED 38 -#define P_LORA_SCLK 10 -#define P_LORA_MISO 9 -#define P_LORA_MOSI 11 +#define P_LORA_DIO_0 8 // IRQ (DIO0 on SX1276) +#define P_LORA_DIO_1 16 // DIO1 +#define P_LORA_NSS 40 // CS +#define P_LORA_RESET 46 // RESET +#define P_LORA_BUSY -1 // SX1276 has no BUSY pin +#define P_LORA_TX_LED 38 +#define P_LORA_SCLK 10 +#define P_LORA_MISO 9 +#define P_LORA_MOSI 11 +#define P_LORA_WAKE_DIO P_LORA_DIO_0 // DIO used as deep-sleep wake source // ETH W5500 pins (T-ETH Elite main board) #define ETH_MISO 47 @@ -78,15 +79,13 @@ public: void enterDeepSleep(uint32_t secs, int pin_wake_btn) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0); + rtc_gpio_set_direction((gpio_num_t)P_LORA_WAKE_DIO, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_WAKE_DIO); rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); - } else { - esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); - } + uint64_t wake_mask = 1ULL << P_LORA_WAKE_DIO; + if (pin_wake_btn >= 0) wake_mask |= 1ULL << pin_wake_btn; + esp_sleep_enable_ext1_wakeup(wake_mask, ESP_EXT1_WAKEUP_ANY_HIGH); if (secs > 0) { esp_sleep_enable_timer_wakeup(secs * 1000000);