src.nth.io/

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-13 21:57:47 -0500
committerLuke Hoersten <[email protected]>2026-06-13 21:57:47 -0500
commite2ac22e58901e18b0b567b85d93a8f68335180bc (patch)
tree620b10cd61f92273bd35627b6868c0befd12009b
parentcadff71f2917b1ec8b140ea54a5994b9e438c0c8 (diff)
M1: Ethernet bring-up
net_eth module brings up the Waveshare ESP32-P4-ETH-POE Ethernet interface (internal EMAC + IP101GRI PHY) and waits for a DHCP lease. Pin map confirmed via Waveshare wiki + ESPHome's working config: MDC=31, MDIO=52, REF_CLK=50 (CLK_EXT_IN from PHY) TX_EN=49, TXD0=34, TXD1=35 CRS_DV=28, RXD0=30, RXD1=29 PHY reset/enable=51, PHY addr=1 app_main initializes NVS, netif, the default event loop, then starts the Ethernet driver and waits up to 30s for an IP. On success it logs the IP; on timeout it logs a warning and proceeds (the driver keeps retrying in the background). sdkconfig: declare 16 MB flash so partitions.csv (6.1 MB) fits. main/CMakeLists.txt: explicit REQUIRES esp_eth esp_event esp_netif nvs_flash. Acceptance (per M1 in the impl guide): device gets a DHCP lease over Ethernet and prints its IP. Build is clean against ESP-IDF 5.4 for target esp32p4.
-rw-r--r--main/CMakeLists.txt7
-rw-r--r--main/app_main.c31
-rw-r--r--main/net_eth.c161
-rw-r--r--main/net_eth.h10
-rw-r--r--sdkconfig.defaults4
5 files changed, 201 insertions, 12 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index e088a07..ee5cb24 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,2 +1,5 @@
-idf_component_register(SRC_DIRS "."
- INCLUDE_DIRS ".")
+idf_component_register(
+ SRC_DIRS "."
+ INCLUDE_DIRS "."
+ REQUIRES esp_eth esp_event esp_netif nvs_flash
+)
diff --git a/main/app_main.c b/main/app_main.c
index 3c30497..8b91537 100644
--- a/main/app_main.c
+++ b/main/app_main.c
@@ -1,7 +1,9 @@
-#include <esp_log.h>
-#include <esp_event.h>
-#include <esp_netif.h>
-#include <nvs_flash.h>
+#include "net_eth.h"
+
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_netif.h"
+#include "nvs_flash.h"
static const char *TAG = "viewport";
@@ -13,10 +15,19 @@ void app_main(void)
ESP_LOGI(TAG, "Scrypted Viewport boot");
- // TODO: Ethernet (ESP32-P4 EMAC + Waveshare PoE PHY)
- // TODO: mDNS _scrypted-viewport._tcp.local
- // TODO: HTTP server: /state /config /frame
- // TODO: MIPI-DSI panel init (800x480 IPS, default portrait 480x800)
- // TODO: JPEG decode -> framebuffer
- // TODO: Capacitive touch -> outbound /state POST
+ ESP_ERROR_CHECK(net_eth_init());
+ if (net_eth_wait_for_ip(30 * 1000) == ESP_OK) {
+ ESP_LOGI(TAG, "online at %s", net_eth_get_ip_str());
+ } else {
+ ESP_LOGW(TAG, "no DHCP lease after 30s, will keep retrying in the background");
+ }
+
+ // TODO M2: mDNS _scrypted-viewport._tcp.local
+ // TODO M2: HTTP server: GET /state
+ // TODO M3: MIPI-DSI panel init (800x480 IPS, default portrait 480x800)
+ // TODO M4: /config persistence (NVS)
+ // TODO M5: /frame JPEG decode -> framebuffer
+ // TODO M6: /state POST + idle timer
+ // TODO M7: Capacitive touch -> outbound /state POST
+ // TODO M8: Local screens (IP, loading) + BOOT button
}
diff --git a/main/net_eth.c b/main/net_eth.c
new file mode 100644
index 0000000..640d880
--- /dev/null
+++ b/main/net_eth.c
@@ -0,0 +1,161 @@
+#include "net_eth.h"
+
+#include <string.h>
+
+#include "esp_check.h"
+#include "esp_eth.h"
+#include "esp_eth_mac.h"
+#include "esp_eth_phy.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+
+// Waveshare ESP32-P4-ETH-POE pin map. PHY is IP101GRI at MDIO addr 1.
+// REF_CLK is 50 MHz output from the PHY into GPIO50 (CLK_EXT_IN).
+// Verified against Waveshare wiki and ESPHome's working config.
+#define PIN_PHY_MDC 31
+#define PIN_PHY_MDIO 52
+#define PIN_PHY_REF_CLK 50
+#define PIN_PHY_TX_EN 49
+#define PIN_PHY_TXD0 34
+#define PIN_PHY_TXD1 35
+#define PIN_PHY_CRS_DV 28
+#define PIN_PHY_RXD0 30
+#define PIN_PHY_RXD1 29
+#define PIN_PHY_RESET 51
+#define PHY_ADDR 1
+
+static const char *TAG = "net_eth";
+
+static EventGroupHandle_t s_event_group;
+static const int BIT_GOT_IP = BIT0;
+
+static esp_netif_t *s_eth_netif;
+static esp_eth_handle_t s_eth_handle;
+static char s_ip_str[16] = {0};
+
+static void on_eth_event(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ uint8_t mac[6] = {0};
+ esp_eth_handle_t handle = *(esp_eth_handle_t *)event_data;
+
+ switch (event_id) {
+ case ETHERNET_EVENT_CONNECTED:
+ esp_eth_ioctl(handle, ETH_CMD_G_MAC_ADDR, mac);
+ ESP_LOGI(TAG, "link up, mac %02x:%02x:%02x:%02x:%02x:%02x",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ break;
+ case ETHERNET_EVENT_DISCONNECTED:
+ ESP_LOGW(TAG, "link down");
+ xEventGroupClearBits(s_event_group, BIT_GOT_IP);
+ break;
+ case ETHERNET_EVENT_START:
+ ESP_LOGI(TAG, "ethernet started");
+ break;
+ case ETHERNET_EVENT_STOP:
+ ESP_LOGI(TAG, "ethernet stopped");
+ break;
+ default:
+ break;
+ }
+}
+
+static void on_ip_event(void *arg, esp_event_base_t event_base,
+ int32_t event_id, void *event_data)
+{
+ ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
+ const esp_netif_ip_info_t *ip = &event->ip_info;
+
+ snprintf(s_ip_str, sizeof(s_ip_str), IPSTR, IP2STR(&ip->ip));
+ ESP_LOGI(TAG, "got ip %s gw " IPSTR " netmask " IPSTR,
+ s_ip_str, IP2STR(&ip->gw), IP2STR(&ip->netmask));
+
+ xEventGroupSetBits(s_event_group, BIT_GOT_IP);
+}
+
+esp_err_t net_eth_init(void)
+{
+ s_event_group = xEventGroupCreate();
+ if (!s_event_group) return ESP_ERR_NO_MEM;
+
+ esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_ETH();
+ esp_netif_config_t netif_cfg = {
+ .base = &base_cfg,
+ .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,
+ };
+ s_eth_netif = esp_netif_new(&netif_cfg);
+ if (!s_eth_netif) return ESP_FAIL;
+
+ eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+ eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
+ emac_config.smi_gpio.mdc_num = PIN_PHY_MDC;
+ emac_config.smi_gpio.mdio_num = PIN_PHY_MDIO;
+ emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN;
+ emac_config.clock_config.rmii.clock_gpio = PIN_PHY_REF_CLK;
+ // ESP32-P4 EMAC also needs the data pins routed explicitly.
+ emac_config.emac_dataif_gpio.rmii.tx_en_num = PIN_PHY_TX_EN;
+ emac_config.emac_dataif_gpio.rmii.txd0_num = PIN_PHY_TXD0;
+ emac_config.emac_dataif_gpio.rmii.txd1_num = PIN_PHY_TXD1;
+ emac_config.emac_dataif_gpio.rmii.crs_dv_num = PIN_PHY_CRS_DV;
+ emac_config.emac_dataif_gpio.rmii.rxd0_num = PIN_PHY_RXD0;
+ emac_config.emac_dataif_gpio.rmii.rxd1_num = PIN_PHY_RXD1;
+ emac_config.interface = EMAC_DATA_INTERFACE_RMII;
+
+ esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config);
+ if (!mac) {
+ ESP_LOGE(TAG, "esp_eth_mac_new_esp32 failed");
+ return ESP_FAIL;
+ }
+
+ eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+ phy_config.phy_addr = PHY_ADDR;
+ phy_config.reset_gpio_num = PIN_PHY_RESET;
+ esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
+ if (!phy) {
+ ESP_LOGE(TAG, "esp_eth_phy_new_ip101 failed");
+ return ESP_FAIL;
+ }
+
+ esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
+ ESP_RETURN_ON_ERROR(esp_eth_driver_install(&eth_config, &s_eth_handle),
+ TAG, "driver install failed");
+
+ ESP_RETURN_ON_ERROR(esp_netif_attach(s_eth_netif,
+ esp_eth_new_netif_glue(s_eth_handle)),
+ TAG, "netif attach failed");
+
+ ESP_RETURN_ON_ERROR(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
+ on_eth_event, NULL),
+ TAG, "eth event register failed");
+ ESP_RETURN_ON_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
+ on_ip_event, NULL),
+ TAG, "ip event register failed");
+
+ ESP_RETURN_ON_ERROR(esp_eth_start(s_eth_handle), TAG, "eth start failed");
+ ESP_LOGI(TAG, "ethernet driver started, waiting for link + DHCP");
+ return ESP_OK;
+}
+
+esp_err_t net_eth_wait_for_ip(uint32_t timeout_ms)
+{
+ TickType_t ticks = timeout_ms == UINT32_MAX ? portMAX_DELAY
+ : pdMS_TO_TICKS(timeout_ms);
+ EventBits_t bits = xEventGroupWaitBits(s_event_group, BIT_GOT_IP,
+ pdFALSE, pdTRUE, ticks);
+ return (bits & BIT_GOT_IP) ? ESP_OK : ESP_ERR_TIMEOUT;
+}
+
+bool net_eth_is_up(void)
+{
+ return (xEventGroupGetBits(s_event_group) & BIT_GOT_IP) != 0;
+}
+
+const char *net_eth_get_ip_str(void)
+{
+ return s_ip_str;
+}
diff --git a/main/net_eth.h b/main/net_eth.h
new file mode 100644
index 0000000..23f2450
--- /dev/null
+++ b/main/net_eth.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "esp_err.h"
+
+esp_err_t net_eth_init(void);
+esp_err_t net_eth_wait_for_ip(uint32_t timeout_ms);
+bool net_eth_is_up(void);
+const char *net_eth_get_ip_str(void);
diff --git a/sdkconfig.defaults b/sdkconfig.defaults
index 3f88f72..e612dc8 100644
--- a/sdkconfig.defaults
+++ b/sdkconfig.defaults
@@ -1,5 +1,9 @@
CONFIG_IDF_TARGET="esp32p4"
+# Flash (Waveshare ESP32-P4-ETH-POE ships with 16 MB QSPI flash)
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
+
# Partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"