RIOT OS 中 LPC1768 以太网驱动测试应用深度解析:构建、运行与源码级原理
2026/9/20 3:03:42 网站建设 项目流程
  • 物联网
  • 嵌入式
  • 操作系统
  • 实时系统

【免费下载链接】RIOT

RIOT - The friendly OS for IoT

项目地址:https://gitcode.com/GitHub_Trending/riot/RIOT
点击查看免费下载

导读

本文围绕 RIOT OS 仓库中的tests/drivers/lpc1768_eth测试应用展开,完整讲解如何为兼容的 LPC1768 开发板构建并烧录该以太网驱动测试程序,深入剖析lpc1768_eth_link_uplpc1768_eth_auto两个可选伪模块的作用与依赖关系,并结合 eth_netdev.c 等源码揭示 netdev 驱动的初始化、收发、链路状态轮询与自动协商的底层实现,以及如何通过ifconfig命令验证以太网接口是否正常就绪。

测试应用定位:它到底在测什么

tests/drivers/lpc1768_eth/README.md将该目录明确定义为LPC1768 以太网驱动(ethernet driver)的测试应用(Test application for the LPC1768 ethernet driver)。它的核心目的不是演示完整的网络协议栈应用,而是验证 LPC1768 片上 EMAC(Ethernet Media Access Controller)外设经 RIOT 的 netdev 抽象层封装后,能否完成以下关键工作:

  • 驱动模块能否被正确编译链接并自动初始化;
  • 网卡接口能否成功注册到网络栈,并通过ifconfig观察到接口处于 up 且已配置状态;
  • 可选地,链路状态检测(link state)与 PHY 自动协商(auto-negotiation)功能能否按预期工作。

整个测试应用构建在 RIOT 提供的通用测试工具模块test_utils_netdev_eth_minimal之上,因此不需要为测试编写繁重的网络协议处理逻辑,只需要提供设备初始化入口即可。

构建与烧录:Makefile 逐行解读

测试应用的 Makefile 结构非常精简,其默认目标板为 Seeed Studio Arch Pro:

BOARD ?= seeeduino_arch-pro include ../Makefile.drivers_common USEMODULE += test_utils_netdev_eth_minimal # the driver to test USEMODULE += lpc1768_eth # uncomment to enable additional features # USEMODULE += lpc1768_eth_link_up # USEMODULE += lpc1768_eth_auto FEATURES_REQUIRED += periph_eth FEATURES_REQUIRED += cpu_lpc1768 INCLUDES += -I$(APPDIR) include $(RIOTBASE)/Makefile.include

各部分的含义如下:

配置项作用
BOARD ?= seeeduino_arch-pro默认目标板为 seeeduino_arch-pro,该板在 Makefile.features 中声明了FEATURES_PROVIDED += periph_eth;可通过命令行BOARD=xxx覆盖
USEMODULE += test_utils_netdev_eth_minimal引入最小化 netdev 以太网测试工具模块,负责事件循环与接收帧打印
USEMODULE += lpc1768_eth被测驱动模块本身,编译 cpu/lpc1768/drivers/eth 下的驱动源码
USEMODULE += lpc1768_eth_link_up/lpc1768_eth_auto可选增强特性,默认被注释掉,按需取消注释
FEATURES_REQUIRED += periph_eth强制要求目标板提供以太网外设支持
FEATURES_REQUIRED += cpu_lpc1768强制要求 CPU 为 LPC1768,从构建系统层面确保驱动与外设匹配

构建与烧录命令沿用 RIOT 标准流程:

# 在 tests/drivers/lpc1768_eth 目录下 make BOARD=seeeduino_arch-pro flash

也可以只编译不烧录:

make BOARD=seeeduino_arch-pro

由于测试应用依赖test_utils_netdev_eth_minimal,正式运行前建议查阅该模块的 API 文档(见 netdev_eth_minimal.h)了解其接口约定。

驱动模块的依赖自动解析

lpc1768_eth并不是一个孤立的模块,RIOT 的构建系统会在 cpu/lpc1768/Makefile.dep 中自动补齐它的运行依赖:

ifneq (,$(filter lpc1768_eth_auto,$(USEMODULE))) USEMODULE += lpc1768_eth_link_up endif ifneq (,$(filter lpc1768_eth_link_up,$(USEMODULE))) USEMODULE += ztimer_periodic endif ifneq (,$(filter lpc1768_eth,$(USEMODULE))) FEATURES_REQUIRED += periph_eth FEATURES_REQUIRED += periph_gpio USEMODULE += iolist USEMODULE += netdev_eth USEMODULE += netdev_new_api USEMODULE += ztimer_msec endif

这里可以看到三条重要的依赖规则:

  1. lpc1768_eth_auto隐含lpc1768_eth_link_up——自动协商必须以链路状态检测为前提,这与 README 中 "auto-negotiation (implieslpc1768_eth_link_up)" 的说明完全一致;
  2. lpc1768_eth_link_up会引入ztimer_periodic,因为链路状态是通过周期性定时器轮询实现的;
  3. lpc1768_eth本身依赖iolist(收发数据描述)、netdev_eth(通用以太网 netdev 层)、netdev_new_api(新版 netdev API)、ztimer_msec(毫秒级定时),并要求periph_ethperiph_gpio特性。

两个可选伪模块:链路检测与自动协商

README 明确指出测试应用可以通过 Makefile 启用两个伪模块(pseudo-modules),二者都属于可选项,默认不启用:

伪模块功能依赖
lpc1768_eth_link_up启用链路状态定时器(link state timer),周期性轮询 PHY 链路状态,链路 up/down 时发出事件ztimer_periodic
lpc1768_eth_auto启用自动协商(auto-negotiation),隐含lpc1768_eth_link_up依赖链路状态事件特性

在 lpc1768_eth_netdev.h 的驱动文档中,对这两个特性有更详细的说明:

  • 链路状态事件(Link State Events):启用lpc1768_eth_link_up后,驱动会持续监测链路状态,并在链路建立或断开时向上层发出NETDEV_EVENT_LINK_UP/NETDEV_EVENT_LINK_DOWN事件;
  • 自动协商(Link Auto Negotiation):启用lpc1768_eth_auto后,PHY 会自动协商链路速率与双工模式。驱动文档明确建议:强烈推荐使用自动协商,因为它可以避免由于链路双方配置不匹配而在 PHY 层引发各类通信问题;同时该特性依赖链路状态事件特性。

换言之,lpc1768_eth_link_up解决的是"链路通没通"的感知问题,lpc1768_eth_auto解决的是"以什么速率和双工模式通信"的适配问题。二者在测试中往往组合使用。

运行与预期输出:ifconfig 验证

README 的 "Expected output" 一节描述了测试成功的验收标准:

应用运行后,应自动完成以太网驱动的初始化。请使用ifconfig命令验证以太网接口已 up 且已配置。

也就是说,程序启动后不需要人工干预驱动初始化,RIOT 的网络接口会自动初始化,随后进入 shell。此时执行:

> ifconfig

如果初始化成功,输出中应能看到名为lpc1768_eth的接口条目(接口名来自 auto_init_lpc1768_eth.c 中gnrc_netif_ethernet_create传入的"lpc1768_eth"),其状态应显示接口已启用(UP),并且携带 MAC 地址等配置信息。若接口没有出现或状态异常,则说明驱动初始化链路存在问题。

从 main.c 可以看出程序启动时的完整流程与打印信息:

int main(void) { puts("Test application for LPC1768 ethernet peripheral"); int res = netdev_eth_minimal_init(); if (res) { puts("Error initializing devices"); return 1; } /* start the shell */ puts("Initialization successful - starting the shell now"); char line_buf[SHELL_DEFAULT_BUFSIZE]; shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); return 0; }

启动时依次输出 "Test application for LPC1768 ethernet peripheral" 与 "Initialization successful - starting the shell now",随后进入交互式 shell。如果设备初始化失败,则会打印 "Error initializing devices" 并返回错误码。

测试框架底层:test_utils_netdev_eth_minimal 的协作契约

README 提到"本测试应用使用了test_utils_netdev_eth_minimal,更多信息请参阅 API 文档"。该工具模块的接口契约定义在 netdev_eth_minimal.h 中,它要求测试应用提供两样东西:

  1. 设备初始化函数netdev_eth_minimal_init_devs(netdev_event_cb_t cb):由测试应用实现,负责把被测驱动绑定到 netdev 实例上并完成硬件初始化;
  2. 设备数量宏NETDEV_ETH_MINIMAL_NUMOF:定义在init_dev.h中。

测试应用侧的实现位于 init_dev.h 与 main.c:

/* init_dev.h */ #define NETDEV_ETH_MINIMAL_NUMOF 1 /* main.c */ static netdev_t lpc1768_eth_netdev; int netdev_eth_minimal_init_devs(netdev_event_cb_t cb) { /* setup the specific driver */ lpc1768_eth_netdev_setup(&lpc1768_eth_netdev); /* set the application-provided callback */ lpc1768_eth_netdev.event_callback = cb; /* initialize the device driver */ int res = lpc1768_eth_netdev.driver->init(&lpc1768_eth_netdev); expect(!res); return 0; }

这段代码演示了 RIOT netdev 驱动的标准使用姿势:

  1. 调用lpc1768_eth_netdev_setup()把静态netdev_t实例绑定到 LPC1768 EMAC 驱动(注意:setup 阶段不会触碰硬件);
  2. 把工具模块提供的事件回调cb赋给event_callback,之后驱动的 ISR 事件都会通过它送达测试框架的事件循环;
  3. 调用driver->init()真正初始化外设硬件,并用expect()断言初始化成功。

工具模块侧的实现(netdev_eth_minimal.c)则负责:注册事件线程、把驱动 ISR 桥接到事件循环、并在收到以太网帧时打印帧的目的地址、源地址以及 payload 的十六进制转储,方便在 shell 中直接观察收到的原始以太网帧。

驱动源码深度解读:从 setup 到 ISR 的完整链路

理解了测试框架后,我们再下沉到驱动层。LPC1768 以太网驱动分为两层:底层外设封装(cpu/lpc1768/periph/eth.c,对外暴露lpc1768_eth_*系列 API)与 netdev 适配层(eth_netdev.c)。后者是本次测试真正驱动的对象。

netdev 驱动方法表

eth_netdev.c 末尾定义了完整的 netdev 驱动回调表:

const netdev_driver_t lpc1768_eth_driver = { .send = _send, .confirm_send = _confirm_send, .recv = _recv, .init = _init, .isr = _isr, .get = _get, .set = _set, };

init:初始化与链路策略的分支

_init()的核心逻辑体现了两个伪模块对行为的影响(见 eth_netdev.c):

static int _init(netdev_t *netdev) { eui48_t hwaddr; netdev_eui48_get(netdev, &hwaddr); int res = lpc1768_eth_init(hwaddr.uint8); if (res < 0) { return res; } if (IS_USED(MODULE_LPC1768_ETH_LINK_UP)) { if (IS_USED(MODULE_LPC1768_ETH_AUTO)) { /* start auto-negotiation of the link speed */ lpc1768_eth_start_auto_negotiation(); } /* periodically wake the event loop to poll the link state */ ztimer_periodic_init(ZTIMER_MSEC, &_link_timer, _link_timer_cb, netdev, CONFIG_LPC1768_ETH_LINK_POLL_MS); ztimer_periodic_start(&_link_timer); } else { /* assume link is up */ netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP); } return 0; }

由此可以清晰看到:

  • 硬件初始化前会通过netdev_eui48_get()获取/生成 EUI-48 格式的 MAC 地址;
  • 未启用lpc1768_eth_link_up,驱动不做链路检测,直接假设链路已 up,立即发送NETDEV_EVENT_LINK_UP事件——这是最简运行模式;
  • 启用后,若同时启用了lpc1768_eth_auto,会先调用lpc1768_eth_start_auto_negotiation()启动 PHY 自动协商(异步执行),然后启动一个周期为CONFIG_LPC1768_ETH_LINK_POLL_MS(默认 1000ms)的 ztimer 定时器,周期性唤醒事件循环去轮询链路状态。

ISR:链路状态轮询与接收事件

链路状态轮询并不直接在定时器回调里完成(那样会阻塞中断上下文),而是通过_link_timer_cb设置_link_check标志并调用netdev_trigger_event_isr(),把实际检测推迟到事件循环(线程上下文)中执行。在_isr()中:

if (IS_USED(MODULE_LPC1768_ETH_LINK_UP) && _link_check) { _link_check = false; bool up = lpc1768_eth_link_up(); if (up != _link_state) { _link_state = up; if (up) { if (IS_USED(MODULE_LPC1768_ETH_AUTO)) { /* complete auto-negotiation of the link */ lpc1768_eth_complete_auto_negotiation(); } netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP); } else { netdev->event_callback(netdev, NETDEV_EVENT_LINK_DOWN); } } } /* receive if necessary */ if (lpc1768_eth_rx_pending()) { netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); }

这里体现了驱动设计的两个细节:

  1. 边沿触发:只有当链路状态发生跳变(up→down 或 down→up)时才发出事件,避免每次轮询都向上层重复上报;
  2. 自动协商的完成时机:当检测到链路从 down 变为 up 时,若启用了自动协商,会先调用lpc1768_eth_complete_auto_negotiation()把协商出的速率/双工参数落实到 MAC 寄存器,再通知上层链路已就绪。

底层 PHY 接口的语义定义在 periph/eth.h 中,包括:

  • lpc1768_eth_link_up():读取 PHY 的 BMSR.LINK 位判断链路是否 up;
  • lpc1768_eth_start_auto_negotiation():通告 10/100 Mbps 半双工/全双工全部能力并重启 PHY 自动协商(异步,成功返回 0,PHY 不支持时返回-EOPNOTSUPP);
  • lpc1768_eth_complete_auto_negotiation():等待 PHY 报告协商完成,根据双方能力寄存器推导速率与双工,并编程 SUPP、MAC2、Command、IPGT 等寄存器;
  • lpc1768_eth_tx_status():查询最近一次发送的状态,返回已发送字节数,或-EAGAIN(仍在发送)、-EBUSY(碰撞中止)、-EIO(其他失败);
  • lpc1768_eth_set_link_speed():把静态配置或协商结果写入 MAC。

get/set:面向上层网络的选项接口

_get()/_set()实现了上层网络栈通过netopt_t查询/配置驱动的标准途径(见 eth_netdev.c):

  • NETOPT_ADDRESS:读取/设置 MAC 地址(6 字节);
  • NETOPT_LINK:查询当前链路状态,返回NETOPT_ENABLE/NETOPT_DISABLE
  • NETOPT_PROMISCUOUSMODE:读取/开启混杂模式;
  • 其他选项透传给netdev_eth_get/netdev_eth_set通用以太网层处理。

这正是ifconfig命令能够读到接口 MAC、链路状态等信息所依赖的底层支撑。

关键配置参数一览

驱动相关的可调参数集中定义在 cpu/lpc1768/include/config.h 中,均可通过 CFLAGS 覆盖:

配置宏默认值含义
CONFIG_LPC1768_ETH_AN_TIMEOUT_MS5000UPHY 自动协商完成的超时时间(毫秒)
CONFIG_LPC1768_ETH_LINK_POLL_MS1000U链路状态轮询间隔(毫秒),即 README 所述"link state timer"的周期
CONFIG_LPC1768_ETH_PHY_TIMEOUT_MS1UPHY 操作超时(毫秒),属于绝对误差上限,实际 PHY 操作通常只需数微秒
CONFIG_LPC1768_ETH_RX_BUF_NUMOF4U接收缓冲区数量(static_assert要求至少为 1)
CONFIG_LPC1768_ETH_TX_BUF_NUMOF4U发送缓冲区数量(static_assert要求至少为 1)

例如,若希望把链路轮询周期改为 500ms,可在构建时传入:

make BOARD=seeeduino_arch-pro CFLAGS="-DCONFIG_LPC1768_ETH_LINK_POLL_MS=500U"

从测试到产品:驱动在系统中的自动初始化路径

测试应用通过手动编写netdev_eth_minimal_init_devs()来完成设备注册,而真实产品中,只要在应用 Makefile 中声明USEMODULE += lpc1768_eth,RIOT 的自动初始化机制(auto_init)就会代为完成同样的工作。auto_init_lpc1768_eth.c 展示了完整的注册流程:

void auto_init_lpc1768_eth(void) { LOG_DEBUG("[auto_init_netif] initializing lpc1768_eth\n"); /* setup netdev device */ lpc1768_eth_netdev_setup(&_netdev); /* initialize netdev <-> gnrc adapter state */ int res = gnrc_netif_ethernet_create(&_netif, _stack, GNRC_NETIF_STACKSIZE_DEFAULT, GNRC_NETIF_PRIO, "lpc1768_eth", &_netdev); if (res < 0) { LOG_ERROR("[auto_init_netif] Failed to create netif for lpc1768_eth device: %d\n", res); return; } }

可以看到,自动初始化同样调用lpc1768_eth_netdev_setup()完成 netdev 绑定,然后通过gnrc_netif_ethernet_create()创建名为lpc1768_eth的 GNRC 网络接口并启动其线程。这也解释了为什么测试应用中netdev_eth_minimal_init_devs()能够"只绑定、不实现完整协议栈"——netdev 抽象将外设驱动与上层协议栈解耦,同一份驱动既可服务于轻量测试框架,也可服务于完整的 GNRC 网络栈。

小结

从构建配置、伪模块语义到驱动源码实现,tests/drivers/lpc1768_eth这份测试应用虽然只有三个文件,却完整覆盖了 RIOT 以太网驱动开发与验证的典型路径:通过Makefile声明模块与特性、通过init_dev.h约定设备数量、通过netdev_eth_minimal_init_devs()完成驱动绑定,最后用ifconfig验证接口状态。对于希望为其他 LPC1768 板卡验证以太网功能、或打算在自己的驱动上套用同一套测试框架的开发者而言,它是一份非常合适的参考模板——复制目录、替换init_dev.hnetdev_eth_minimal_init_devs()中的绑定逻辑,即可快速将测试迁移到新的以太网设备上。

  • 物联网
  • 嵌入式
  • 操作系统
  • 实时系统

【免费下载链接】RIOT

RIOT - The friendly OS for IoT

项目地址:https://gitcode.com/GitHub_Trending/riot/RIOT
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询