在这里插入图片描述

前言

ESP32-P4 是乐鑫推出的高性能 RISC-V SoC,内置以太网 MAC(EMAC),支持有线网络连接。本文记录在 Windows 环境下,基于 Apache NuttX 实时操作系统,完成 ESP32-P4-Function-Ev-Board 以太网功能的编译、烧录和测试全过程。

最终效果:ESP32-P4 通过网线连接路由器,自动获取 IP,成功 Ping 通外网,并完成 TCP 连接测试。


1. 硬件准备

  • ESP32-P4-Function-Ev-Board 1.6
  • USB 数据线(用于烧录和串口调试)
  • 网线(连接路由器或交换机)
  • 电源(USB 供电即可)

接线说明

ESP32-P4-Function-Ev-Board 自带以太网 RJ45 接口,只需插上网线即可。USB 口同时用于烧录和串口控制台(USB CDC-ACM)。


2. 开发环境概述

本文复用已有的 ESP-IDF 工具链,只补齐 NuttX 编译所需的额外组件。

工具链版本

工具 版本 路径
RISC-V GCC 15.2.0 C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204
CMake 4.0.3 C:\Espressif\tools\cmake\4.0.3
Ninja 1.12.1 C:\Espressif\tools\ninja\1.12.1
esptool 5.3.0 C:\Espressif\tools\python\master\venv\Scripts
Git Bash C:\Program Files\Git

NuttX 源码

cd D:\esp32p4-nuttx
git clone --depth 1 https://github.com/apache/nuttx.git nuttx
git clone --depth 1 https://github.com/apache/nuttx-apps.git apps

3. 关键配置步骤

3.1 Python 虚拟环境

NuttX 的 Kconfig 解析需要 kconfiglib,建议单独创建 venv:

cd D:\esp32p4-nuttx
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install kconfiglib==14.1.0

3.2 工具链前缀适配

NuttX CMake 期望的 RISC-V 工具链前缀是 riscv64-unknown-elf-,而 ESP-IDF 提供的是 riscv32-esp-elf-。解决方案是创建符号链接或复制重命名:

# 创建工具目录
mkdir D:\esp32p4-nuttx\tools\bin

# 复制并重命名工具链
$src = "C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204\riscv32-esp-elf\bin"
$dst = "D:\esp32p4-nuttx\tools\bin"
@("gcc","g++","ld","ar","objcopy","objdump","nm","size","strip") | ForEach-Object {
    Copy-Item "$src\riscv32-esp-elf-$_.exe" "$dst\riscv64-unknown-elf-$_.exe"
}

同时设置环境变量:

$env:GCC_EXEC_PREFIX = "C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204\riscv32-esp-elf\libexec\gcc\"

3.3 环境脚本

创建 env-esp32p4-nuttx.ps1 一键加载环境:

$Root = $PSScriptRoot
$toolPaths = @(
    (Join-Path $Root ".venv\Scripts"),
    "C:\Espressif\tools\python\master\venv\Scripts",
    (Join-Path $Root "tools\bin"),
    (Join-Path $Root "mingw64\bin"),
    "C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204\riscv32-esp-elf\bin",
    "C:\Espressif\tools\cmake\4.0.3\bin",
    "C:\Espressif\tools\ninja\1.12.1",
    "C:\Program Files\Git\usr\bin",
    "C:\Program Files\Git\bin"
)
$existingToolPaths = $toolPaths | Where-Object { Test-Path $_ }
$env:Path = ($existingToolPaths -join ";") + ";" + $env:Path
$env:GCC_EXEC_PREFIX = "C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204\riscv32-esp-elf\libexec\gcc\"
Write-Host "ESP32-P4 NuttX environment ready at $Root"

4. 以太网 defconfig 定制

NuttX 为 ESP32-P4-Function-Ev-Board 提供了多个预置配置。以太网功能对应的配置是 esp32p4-function-ev-board:ethernet

4.1 原始 ethernet 配置分析

查看 nuttx\boards\risc-v\esp32p4\esp32p4-function-ev-board\configs\ethernet\defconfig,关键配置项:

CONFIG_ESPRESSIF_EMAC=y            # 启用以太网 MAC
CONFIG_ESPRESSIF_ETH_DMA_BUFFER_SIZE=512
CONFIG_NETINIT_DHCPC=y             # DHCP 客户端
CONFIG_NETDB_DNSCLIENT=y           # DNS 解析
CONFIG_NET_TCP=y                   # TCP 协议
CONFIG_NET_UDP=y                   # UDP 协议
CONFIG_NET_ICMP_SOCKET=y           # ICMP (Ping)
CONFIG_SYSTEM_PING=y               # ping 命令
CONFIG_UART0_SERIAL_CONSOLE=y      # 默认用 UART0 作控制台

4.2 修改为 USB 串口控制台

默认配置使用 UART0(GPIO37/38)作为控制台,需要外接 USB-TTL 转接器。改为 USB 串口控制台后,烧录和调试都用同一根 USB 线:

- CONFIG_UART0_SERIAL_CONSOLE=y
+ # CONFIG_ESPRESSIF_UART0 is not set
+ CONFIG_ESPRESSIF_USBSERIAL=y

4.3 添加自定义测试程序

apps\examples\ 下创建 ethdemo 测试应用,用于验证网络连通性:

apps/examples/ethdemo/ethdemo_main.c

#include <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "netutils/netlib.h"

#define ETHDEMO_BUF_SIZE  512
#define ETHDEMO_TIMEOUT   5

static void print_ifinfo(FAR const char *ifname)
{
    struct in_addr addr, mask, gw;
    uint8_t mac[IFHWADDRLEN];

    printf("\n=== Network Interface: %s ===\n", ifname);

    if (netlib_get_ipv4addr(ifname, &addr) >= 0)
        printf("  IP Address:  %s\n", inet_ntoa(addr));
    if (netlib_get_ipv4netmask(ifname, &mask) >= 0)
        printf("  Netmask:     %s\n", inet_ntoa(mask));
    if (netlib_get_dripv4addr(ifname, &gw) >= 0)
        printf("  Gateway:     %s\n", inet_ntoa(gw));
    if (netlib_getmacaddr(ifname, mac) >= 0)
        printf("  MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n",
               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

static int tcp_connect_test(FAR const char *host, uint16_t port)
{
    struct sockaddr_in server;
    struct timeval tv;
    int sockfd, ret;
    char buf[ETHDEMO_BUF_SIZE];
    const char *http_request = "GET / HTTP/1.0\r\nHost: test\r\n\r\n";
    ssize_t nread;

    printf("\n=== TCP Connect Test ===\n");
    printf("Connecting to %s:%u...\n", host, port);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { printf("ERROR: socket() failed\n"); return -errno; }

    tv.tv_sec = ETHDEMO_TIMEOUT; tv.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    inet_pton(AF_INET, host, &server.sin_addr);

    ret = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (ret < 0) { printf("ERROR: connect() failed\n"); close(sockfd); return -errno; }

    printf("Connected successfully!\n");
    send(sockfd, http_request, strlen(http_request), 0);
    printf("Sent HTTP request, waiting for response...\n");

    nread = recv(sockfd, buf, sizeof(buf) - 1, 0);
    if (nread > 0) {
        buf[nread > 200 ? 200 : nread] = '\0';
        printf("Received %zd bytes:\n%s\n", nread, buf);
    }

    close(sockfd);
    return 0;
}

int main(int argc, FAR char *argv[])
{
    FAR const char *host = (argc > 1) ? argv[1] : "192.168.1.1";
    uint16_t port = (argc > 2) ? (uint16_t)atoi(argv[2]) : 80;

    printf("\n============================================\n");
    printf("  ESP32-P4 Ethernet Network Test\n");
    printf("============================================\n");

    print_ifinfo("eth0");
    tcp_connect_test(host, port);

    printf("\n============================================\n");
    printf("  Test Complete\n");
    printf("============================================\n");
    return 0;
}

配套的构建文件(Kconfig、Makefile、Make.defs、CMakeLists.txt)遵循 NuttX 应用标准结构,详见文末附录。

在 defconfig 中启用:

CONFIG_EXAMPLES_ETHDEMO=y
CONFIG_EXAMPLES_ETHDEMO_HOST="192.168.1.1"
CONFIG_EXAMPLES_ETHDEMO_PORT=80

5. 编译

5.1 CMake 配置

cd D:\esp32p4-nuttx
. .\env-esp32p4-nuttx.ps1

cmake -S nuttx -B build-ethernet -G Ninja `
  -DCMAKE_MAKE_PROGRAM="C:\Espressif\tools\ninja\1.12.1\ninja.exe" `
  -DBOARD_CONFIG="esp32p4-function-ev-board:ethernet" `
  -DNUTTX_APPS_DIR="D:\esp32p4-nuttx\apps" `
  -DPython3_EXECUTABLE="D:\esp32p4-nuttx\.venv\Scripts\python.exe" `
  -DEXTRA_FLAGS="-std=gnu11"

5.2 处理兼容性问题

stdatomic.h 兼容头:ESP HAL 部分代码与当前工具链的 <stdatomic.h> 不兼容,需要在以下路径放置兼容包装头:

build-ethernet\arch\risc-v\src\common\espressif\esp-hal-3rdparty\nuttx\src\components\esp_libc\platform_include\stdatomic.h

核心内容:

#pragma once
#include_next <stdatomic.h>
#ifdef ATOMIC_VAR_INIT
# undef ATOMIC_VAR_INIT
#endif
#define ATOMIC_VAR_INIT(val) (val)
#ifdef atomic_init
# undef atomic_init
#endif
#define atomic_init(ptr, val) __atomic_store_n((ptr), (val), __ATOMIC_RELAXED)

libgcc.a:复制到构建目录:

Copy-Item "C:\Espressif\tools\riscv32-esp-elf\esp-15.2.0_20251204\riscv32-esp-elf\lib\gcc\riscv32-esp-elf\15.2.0\libgcc.a" `
  -Destination "D:\esp32p4-nuttx\build-ethernet\libgcc.a"

5.3 编译

cmake --build build-ethernet --parallel 4

成功输出:

[1666/1666] Running utility command for nuttx_post_build
esptool v5.3.0
Creating ESP32-P4 image...
Successfully created ESP32-P4 image.
-- Generated: nuttx.bin

编译产物:

文件 大小 说明
nuttx 771 KB ELF 可执行文件
nuttx.bin 339 KB ESP32-P4 烧录镜像
nuttx.hex 812 KB Intel HEX 格式
nuttx.map 1.9 MB 链接 Map 文件

6. 烧录

ESP32-P4 使用 simple boot 模式,烧录地址为 0x2000

esptool --chip esp32p4 -p COM4 -b 460800 write-flash -fs 4MB -fm dio -ff 80m 0x2000 .\build-ethernet\nuttx.bin

烧录日志:

Connected to ESP32-P4 on COM4
Wrote 339068 bytes at 0x00002000
Hash of data verified.
Hard resetting via RTS pin...

7. 测试验证

7.1 打开串口

python -m serial.tools.miniterm COM4 115200

按 RESET 或敲回车,看到 nsh> 提示符。

7.2 查看网络接口

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr e8:f6:0a:e3:a6:46 at RUNNING mtu 1500
        inet addr:0.0.0.0 DRaddr:10.0.0.1 Mask:255.255.255.0

此时已获取到 MAC 地址,但还没有 IP。

7.3 DHCP 获取 IP

nsh> renew eth0
nsh> ifconfig
eth0    Link encap:Ethernet HWaddr e8:f6:0a:e3:a6:46 at RUNNING mtu 1500
        inet addr:192.168.1.135 DRaddr:192.168.1.1 Mask:255.255.255.0

成功通过 DHCP 获取到 IP 192.168.1.135

7.4 Ping 外网

nsh> ping www.baidu.com
PING 110.242.69.21 56 bytes of data
56 bytes from 110.242.69.21: icmp_seq=0 time=10.0 ms
56 bytes from 110.242.69.21: icmp_seq=1 time=10.0 ms
56 bytes from 110.242.69.21: icmp_seq=2 time=10.0 ms
...
10 packets transmitted, 10 received, 0% packet loss, time 10100 ms
rtt min/avg/max/mdev = 0.000/9.000/10.000/3.000 ms

DNS 解析和 ICMP 全部正常,10 包 0% 丢包。

7.5 ethdemo TCP 测试

nsh> ethdemo

============================================
  ESP32-P4 Ethernet Network Test
============================================

=== Network Interface: eth0 ===
  IP Address:  192.168.1.135
  Netmask:     255.255.255.0
  Gateway:     192.168.1.1
  MAC Address: e8:f6:0a:e3:a6:46

=== TCP Connect Test ===
Connecting to 192.168.1.1:80...
Connected successfully!
Sent HTTP request, waiting for response...
Received 183 bytes:
HTTP/1.1 200 OK
Cache-control:no-cache, no-store, max-age=0
Content-Type:text/html
...

============================================
  Test Complete
============================================

TCP 连接、HTTP 请求、数据收发全部正常。


8. SPI Flash 文件系统(可选)

首次使用 SPI Flash 需要格式化:

nsh> mksmartfs /dev/smart0
nsh> mount -t smartfs /dev/smart0 /mnt
nsh> echo "Hello ESP32-P4" > /mnt/test.txt
nsh> cat /mnt/test.txt
Hello ESP32-P4

9. 已启用的网络功能一览

功能 配置项 说明
以太网 MAC CONFIG_ESPRESSIF_EMAC=y 内部 EMAC + 外部 PHY
USB 串口 CONFIG_ESPRESSIF_USBSERIAL=y USB CDC-ACM 控制台
DHCP CONFIG_NETINIT_DHCPC=y 自动获取 IP
DNS CONFIG_NETDB_DNSCLIENT=y 域名解析
TCP CONFIG_NET_TCP=y TCP 协议栈
UDP CONFIG_NET_UDP=y UDP 协议栈
ICMP CONFIG_NET_ICMP_SOCKET=y Ping 支持
SPI Flash CONFIG_ESPRESSIF_SPIFLASH=y 文件系统存储
PSRAM CONFIG_ESPRESSIF_SPIRAM=y 扩展内存

10. 常见问题

Q: ping 不通外网但能 ping 通网关

A: 检查 DNS 配置,手动设置:

nsh> echo "nameserver 8.8.8.8" > /etc/resolv.conf

Q: DHCP 获取不到 IP

A: 检查网线连接,确认路由器 DHCP 服务开启。也可以手动设置 IP:

nsh> ifconfig eth0 192.168.1.200 netmask 255.255.255.0
nsh> ifconfig eth0 gw 192.168.1.1

Q: 编译时报 stdatomic.h 相关错误

A: 参考第 5.2 节,放置兼容包装头。

Q: esptool 烧录失败

A: 确认 esptool 在 PATH 中,确认串口号正确。按住 BOOT 按键再插 USB 可进入下载模式。


11. 总结

ESP32-P4 内置以太网 MAC,配合 NuttX 操作系统可以快速实现有线网络功能。整个流程的关键点:

  1. 使用 esp32p4-function-ev-board:ethernet 配置启用 EMAC
  2. 使用 USB 串口控制台避免外接转接器
  3. 处理工具链前缀和 stdatomic.h 兼容性
  4. 通过 DHCP 自动获取 IP,DNS 解析域名
  5. ping 和自定义 ethdemo 验证网络连通性

以太网相比 WiFi 具有更低的延迟和更高的稳定性,适合工业控制、网关设备等场景。ESP32-P4 + NuttX 的组合为这类应用提供了良好的基础。


附录:ethdemo 构建文件

Kconfig

config EXAMPLES_ETHDEMO
    tristate "Ethernet demo application"
    default n
    depends on NET_TCP
    depends on NET_UDP

if EXAMPLES_ETHDEMO

config EXAMPLES_ETHDEMO_HOST
    string "HTTP test host (IP address)"
    default "192.168.1.1"

config EXAMPLES_ETHDEMO_PORT
    int "HTTP test port"
    default 80

config EXAMPLES_ETHDEMO_PROGNAME
    string "Program name"
    default "ethdemo"

config EXAMPLES_ETHDEMO_STACKSIZE
    int "Stack size"
    default DEFAULT_TASK_STACKSIZE

config EXAMPLES_ETHDEMO_PRIORITY
    int "Priority"
    default 100

endif

Makefile

include $(APPDIR)/Make.defs
PRIORITY  = $(CONFIG_EXAMPLES_ETHDEMO_PRIORITY)
STACKSIZE = $(CONFIG_EXAMPLES_ETHDEMO_STACKSIZE)
PROGNAME  = $(CONFIG_EXAMPLES_ETHDEMO_PROGNAME)
MAINSRC = ethdemo_main.c
include $(APPDIR)/Application.mk

Make.defs

ifneq ($(CONFIG_EXAMPLES_ETHDEMO),)
CONFIGURED_APPS += $(APPDIR)/examples/ethdemo
endif

CMakeLists.txt

if(CONFIG_EXAMPLES_ETHDEMO)
  nuttx_add_application(
    NAME ${CONFIG_EXAMPLES_ETHDEMO_PROGNAME}
    SRCS ethdemo_main.c
    STACKSIZE ${CONFIG_EXAMPLES_ETHDEMO_STACKSIZE}
    PRIORITY ${CONFIG_EXAMPLES_ETHDEMO_PRIORITY})
endif()
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐