- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
导读
本文围绕 RIOT OS 仓库中的tests/drivers/lpc1768_eth测试应用展开,完整讲解如何为兼容的 LPC1768 开发板构建并烧录该以太网驱动测试程序,深入剖析lpc1768_eth_link_up与lpc1768_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这里可以看到三条重要的依赖规则:
lpc1768_eth_auto隐含lpc1768_eth_link_up——自动协商必须以链路状态检测为前提,这与 README 中 "auto-negotiation (implieslpc1768_eth_link_up)" 的说明完全一致;lpc1768_eth_link_up会引入ztimer_periodic,因为链路状态是通过周期性定时器轮询实现的;lpc1768_eth本身依赖iolist(收发数据描述)、netdev_eth(通用以太网 netdev 层)、netdev_new_api(新版 netdev API)、ztimer_msec(毫秒级定时),并要求periph_eth与periph_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 中,它要求测试应用提供两样东西:
- 设备初始化函数
netdev_eth_minimal_init_devs(netdev_event_cb_t cb):由测试应用实现,负责把被测驱动绑定到 netdev 实例上并完成硬件初始化; - 设备数量宏
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 驱动的标准使用姿势:
- 调用
lpc1768_eth_netdev_setup()把静态netdev_t实例绑定到 LPC1768 EMAC 驱动(注意:setup 阶段不会触碰硬件); - 把工具模块提供的事件回调
cb赋给event_callback,之后驱动的 ISR 事件都会通过它送达测试框架的事件循环; - 调用
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); }这里体现了驱动设计的两个细节:
- 边沿触发:只有当链路状态发生跳变(up→down 或 down→up)时才发出事件,避免每次轮询都向上层重复上报;
- 自动协商的完成时机:当检测到链路从 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_MS | 5000U | PHY 自动协商完成的超时时间(毫秒) |
CONFIG_LPC1768_ETH_LINK_POLL_MS | 1000U | 链路状态轮询间隔(毫秒),即 README 所述"link state timer"的周期 |
CONFIG_LPC1768_ETH_PHY_TIMEOUT_MS | 1U | PHY 操作超时(毫秒),属于绝对误差上限,实际 PHY 操作通常只需数微秒 |
CONFIG_LPC1768_ETH_RX_BUF_NUMOF | 4U | 接收缓冲区数量(static_assert要求至少为 1) |
CONFIG_LPC1768_ETH_TX_BUF_NUMOF | 4U | 发送缓冲区数量(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.h与netdev_eth_minimal_init_devs()中的绑定逻辑,即可快速将测试迁移到新的以太网设备上。
- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
相关推荐
RIOT OS 中 ADT7310 温度传感器驱动的测试应用实践:编译、运行与源码解析
RIOT OS 中 ADT7310 温度传感器驱动的测试应用实践:编译、运行与源码解析 本篇技术指南围绕 RIOT 仓库中的 tests/drivers/adt
物联网嵌入式操作系统实时系统RIOT 中 SCD30 CO2/温湿度传感器驱动测试应用深度解析:从编译运行到源码原理
RIOT 中 SCD30 CO2/温湿度传感器驱动测试应用深度解析:从编译运行到源码原理 导读 本文围绕 RIOT 仓库中 tests/drivers/scd3
物联网嵌入式操作系统实时系统Binci完全指南:如何用Docker容器化你的开发工作流,告别环境配置烦恼
Binci完全指南:如何用Docker容器化你的开发工作流,告别环境配置烦恼 Binci是一款强大的开发工作流容器化工具,它能够帮助开发者将开发环境和任务流程通
物联网嵌入式操作系统实时系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考