news 2026/9/20 22:40:09

RIOT 外设自检指南:使用 Peripheral Selftest Shield 全面测试 ADC/GPIO/I2C/PWM/SPI/UART

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RIOT 外设自检指南:使用 Peripheral Selftest Shield 全面测试 ADC/GPIO/I2C/PWM/SPI/UART
  • 物联网
  • 嵌入式
  • 操作系统
  • 实时系统

【免费下载链接】RIOT

RIOT - The friendly OS for IoT

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

导读

本文基于 RIOT 操作系统的外设自检测试应用tests/periph/selftest_shield,介绍如何借助一块与 Arduino UNO 兼容的 "Peripheral Selftest Shield" 扩展板,对板载的 ADC、GPIO、GPIO-IRQ、I2C、PWM、SPI、UART 七类外设驱动进行全自动回环自检。读完本文后,你将掌握从硬件准备、DIP 开关配置、编译烧录、结果解读到失败排障的完整闭环流程,并理解该测试应用的底层实现机制(如 R-2R 电阻梯形网络 DAC、PCF8574 GPIO 扩展器回环、基于定时器的波特率/时钟频率校验等),从而快速定位 BSP 或外设驱动中的问题。

⚠️重要安全警告:在给板卡上电之前,务必确认 Shield 上的 VCC 电压选择开关与板卡的逻辑电平匹配。例如,在逻辑电平为 3.3 V 的板卡上把 VCC 选择为 5 V,且引脚不兼容 5 V 时,可能永久损坏板卡。该警告来自 tests/periph/selftest_shield/README.md,是硬件操作的第一步。


一、快速开始:五步完成外设自检

1. 选择兼容的板卡

  • 待测板卡必须机械与电气兼容 Arduino UNO Shield(即符合 Arduino UNO 排针间距与引脚定义);
  • 板卡需要提供 Arduino GPIO 引脚映射,理想情况下还需要 SPI、I2C、ADC、PWM 的引脚映射。

在 RIOT 中,这类能力由arduino_pinsarduino_shield_uno等特性标识,测试应用通过FEATURES_REQUIRED强制要求它们存在(见 Makefile):

FEATURES_REQUIRED += arduino_pins FEATURES_REQUIRED += arduino_shield_uno

同时,其余外设能力(arduino_analogarduino_i2carduino_pwmarduino_shield_isparduino_spiarduino_uart以及periph_adcperiph_gpioperiph_gpio_irqperiph_i2cperiph_pwmperiph_spiperiph_timerperiph_timer_query_freqsperiph_uart)都被声明为可选特性FEATURES_OPTIONAL),也就是说测试应用会"按板卡能力自动裁剪"测试范围(见 Makefile)。

以 nRF52840DK 为例,其 arduino_iomap.h 中提供了完整的映射:

#define ARDUINO_UART_D0D1 UART_DEV(1) #define ARDUINO_SPI_D11D12D13 SPI_DEV(0) #define ARDUINO_SPI_ISP SPI_DEV(0) #define ARDUINO_I2C_UNO I2C_DEV(0) #define ARDUINO_A0 ADC_LINE(1) #define ARDUINO_A1 ADC_LINE(2) #define ARDUINO_A2 ADC_LINE(4)

2. 断电并连接 Shield

先将板卡完全断电(拔掉电源),再插上 Shield。

3. 设置 VCC 电平选择开关

  • 将 VCC 选择器拨到与板卡逻辑电平一致的位置;
  • 不确定时优先选择 3.3 V
  • 文档特别指出:nRF52840DK 的逻辑电平虽然是 3.0 V,但用 3.3 V 档位工作正常(见 tests/periph/selftest_shield/README.md)。

4. 通过 DIP 开关配置待测外设回环

用 Shield 上的 DIP 开关为要测试的外设闭合回环(拨到ON),未测试的外设保持OFF(开路):

  • 建议一开始先使能除 UART 以外的所有回环
  • 如果 UART 测试实际运行了但失败,再打开 UART 开关;
  • 原因:如果 D0/D1 上的 UART 被用作 stdio(标准输入输出),该 UART 无法回环,也无法测试(见 tests/periph/selftest_shield/README.md)。

这一点在源码中也有体现——UART 测试的启用条件非常严格(见 main.c):

#if defined(ARDUINO_UART_D0D1) && defined(MODULE_PERIPH_UART) # if MODULE_STDIO_UART # define ENABLE_UART_TEST (STDIO_UART_DEV != ARDUINO_UART_D0D1) # else # define ENABLE_UART_TEST 1 # endif # define UART_TEST_DEV ARDUINO_UART_D0D1 #else # define ENABLE_UART_TEST 0 #endif

即:必须提供 D0/D1 的 UART 映射,且该 UART 设备不与 stdio 使用的 UART 冲突时,才真正执行 UART 回环测试。

5. 编译烧录并运行

tests/periph/selftest_shield目录下执行:

make BOARD=<YOUR_BOARD> flash test-with-config
  • flash负责把固件烧录到板卡;
  • test-with-config会结合tests-with-config/目录下的自动化测试脚本运行。

自动化测试脚本 01-run.py 的校验逻辑非常简洁:期望串口输出依次出现self-testing peripheral driversALL TESTS SUCCEEDED两段标志性文本,从而判定测试整体通过:

def testfunc(child): child.expect("self-testing peripheral drivers") child.expect("ALL TESTS SUCCEEDED")

对应地,main.c 在main()末尾根据是否有失败项打印SOME TESTS FAILED(返回 1)或ALL TESTS SUCCEEDED(返回 0)。


二、测试覆盖范围与设计细节

该测试应用会在板卡支持且存在 Arduino I/O 映射的前提下,对以下外设驱动做完整自检:

外设测试方式要点
periph_adc(1)通过 4 位 R-2R 电阻梯形网络产生 ADC 输入电压
periph_gpioD3/D4 引脚回环,覆盖推挽、上拉、下拉、开漏等模式
periph_gpio_irqD3 上升沿/下降沿/双边沿中断计数验证
periph_i2c(2)间接驱动 PCF8574 I2C GPIO 扩展器并观测结果
periph_pwm通过 ADC 采样回读 PWM 占空比
periph_spi(3)MOSI/MISO 回环,多模式、多时钟速率、片选时序验证
periph_uart(4)D0/D1 回环,9600 与 115200 两种速率收发

注 1:ADC 测试与 R-2R 梯形网络

ADC 输入由4 位 R-2R 电阻梯形网络生成,其电压由 I2C 连接的 GPIO 扩展器(PCF8574)和/或 PWM 控制。若两者都不可用,则不做 ADC 测试;若都可用,最多测试映射到 A0、A1、A2 的三个 ADC 通道。

从源码看,R-2R 网络的 4 个 bit 由 PCF8574 的 P0.4~P0.7 驱动(见 main.c):

static void r_2r_dac_init(void) { ASSERT_NO_ERROR(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 4), GPIO_OUT)); ASSERT_NO_ERROR(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 5), GPIO_OUT)); ASSERT_NO_ERROR(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 6), GPIO_OUT)); ASSERT_NO_ERROR(pcf857x_gpio_init(&egpios, PCF857X_GPIO_PIN(0, 7), GPIO_OUT)); }

测试循环 16 个 DAC 值(i从 0 到 15),期望采样值约为i << 6,并允许 10% 误差(delta = 1024 / 10),以容忍 v0.3 板卡电阻精度不足(见 main.c)。

注 2:I2C 的间接测试

I2C 不直接回环,而是通过操作 PCF8574 I2C GPIO 扩展器、再用内部 GPIO 与扩展器 GPIO 互联观测结果来间接验证。该测试仅在periph_i2cperiph_gpio同时可用时启用。

从源码看(main.c),测试双向进行:

  • 内部 GPIO(D9 输出)→ 扩展器 GPIO(P0.1 输入):内部置高/置低,读取扩展器侧电平;
  • 扩展器 GPIO(P0.0 输出)→ 内部 GPIO(D8 输入):扩展器置高/置低,读取内部 GPIO。

每一方向都循环flaky_test_repetitions(默认 100)次。

PCF8574 模块的引入是按需的:只有在板卡同时具备periph_i2carduino_i2c特性时才链接pcf8574模块(见 Makefile.board.dep):

ifneq (,$(filter periph_i2c,$(FEATURES_USED))) ifneq (,$(filter arduino_i2c,$(FEATURES_USED))) USEMODULE += pcf8574 endif endif

注 3:SPI 回环测试的局限

SPI 采用 MOSI 与 MISO 短接的回环测试,因此:

  • 位序(bit order)和时钟相位(clock phase)无法校验(回环方式天然测不出相位差异);
  • 时钟极性(clock polarity)可以校验(在空闲电平下检测);
  • 若实现了periph_timer,时钟频率的正确性可做粗略校验

SPI 测试覆盖两种总线(ARDUINO_SPI_D11D12D13ARDUINO_SPI_ISP,见 main.c),每种总线遍历SPI_CLK_400KHZ / SPI_CLK_1MHZ / SPI_CLK_10MHZ三档时钟,以及 SPI_MODE_0~3 四种模式(见 main.c)。空闲电平通过扩展器引脚 P0.2 / P0.3 读取确认,片选(CS)则由 D10 驱动、D7 观测。

注 4:UART 回环测试的约束

  • 当 D0/D1 的 UART 接口被 stdio 占用时,跳过 UART 自检
  • 符号率(symbol rate,常被误称为 "baudrate")仅在periph_timer可用时做合理性校验
  • 不同的每字符位数、停止位数、校验位等参数理论上可借助periph_timer检测,但当前版本尚未实现

UART 测试使用 8 字节测试数据"Selftest",依次在 9600 与 115200 两种速率下进行(见 main.c)。当启用定时器后,发送耗时会被测量并与理论值比对(允许实际耗时在期望值的 75%~200% 之间,见 main.c)。此外还会验证:

  1. 数据内容与字节数完全一致(回环正确性);
  2. uart_poweroff后不再收到数据(断电有效);
  3. uart_poweron后重新收发正常(上电恢复)。

三、失败排障指南

3.1 增加输出详细程度

编译时加上DETAILED_OUTPUT=1可提高输出详细度,代价是 ROM 占用增加:

make BOARD=<YOUR_BOARD> DETAILED_OUTPUT=1

当出现 ROM 链接失败时,可通过FEATURES_BLACKLIST屏蔽部分无关外设特性来缩小固件体积。但注意:部分外设是借助其他外设测试的,例如屏蔽 ADC 会连带导致 PWM 输出占空比的正确性无法验证(见 tests/periph/selftest_shield/README.md)。从 Makefile 可以看到该宏被直接注入编译选项:

CFLAGS += \ '-DSTOP_ON_FAILURE=$(STOP_ON_FAILURE)' \ '-DDETAILED_OUTPUT=$(DETAILED_OUTPUT)' \ #

DETAILED_OUTPUT开启后,每个测试在启动时会打印更完整的信息(包括测试名与细节描述,见 main.c),SPI 测试还会额外打印当前时钟频率(见 main.c)。

3.2 早期关键失败(Critical Failure)

如果应用在执行任何测试之前就失败,例如输出类似:

START main(): This is RIOT! (Version: 2023.10-devel-348-gf1c68-peripheral-selftest) self-testing peripheral drivers =============================== CRITICAL FAILURE in tests/periph/selftest_shield/main.c:<line-number>

这表明GPIO 扩展器(PCF8574)初始化失败。可能原因按可能性排序:

  1. I2C 配置错误
    • I2C 总线(通常在periph_conf.h或其包含的文件中配置)是否正确配置?是否连接到了 Arduino UNO 的 D14 / D15 引脚?
    • ARDUINO_I2C_UNO宏是否正确定义为连接到 D14 / D15 的 I2C 总线索引?
  2. 缺少上拉电阻
    • Shield 的 I2C 总线上没有上拉电阻
    • 默认情况下 RIOT 的 I2C 驱动会启用 MCU 内部上拉,但部分 MCU 的某些引脚没有内部上拉,或在 I2C 模式下无法启用
    • 若可能,应在periph_conf.h中配置 I2C 驱动/板级配置,使无外部上拉时也能开箱即用地稳定运行;
    • 若板卡必须依赖外部上拉(无合适的内部上拉、板卡在 D14/D15 上也没有外部上拉),最省事的办法是在 J2 的 I2C 引脚插座上插入一块 I2C 传感器/EEPROM 扩展板——这类扩展板通常自带外部上拉电阻。
  3. I2C 驱动 bug
    • I2C 驱动本身的缺陷也可能导致此问题;
    • 此时连接逻辑分析仪到 I2C 排针(J3)、I2C 插座(J2)或 I2C Grove 连接器(J1)会很有帮助。
  4. 硬件问题
    • 这是最不可能的原因。

从源码结构看,pcf857x_init()main()第一个不可失败的操作——扩展器同时服务于 I2C 测试与 ADC 测试,因此只初始化一次(见 main.c):

/* the GPIO extender is used by the I2C test and the ADC test, so only * initialize it once here */ if (IS_USED(MODULE_PCF857X)) { ASSERT_NO_ERROR(pcf857x_init(&egpios, &params)); }

一旦初始化返回错误,ASSERT_NO_ERROR会打印错误码(通过tiny_strerror转为可读字符串)并触发ARCHITECTURE_BREAKPOINT(1)挂起(见 main.c),这正是"CRITICAL FAILURE"输出的来源。扩展器的 I2C 地址与设备类型在 main.c 中通过pcf857x_params_t固定为PCF857X_EXP_PCF8574

3.3 同一错误反复出现

许多测试在循环中执行,例如部分"碰运气也可能过"的测试会重复flaky_test_repetitions次(默认 100次,见 main.c),以提高对间歇性问题的检出置信度;部分测试还会遍历不同配置(如多种时钟速度)并对每种配置重复测试。

若想减少噪声、快速定位首个失败点,可在编译时传入STOP_ON_FAILURE=1(环境变量或make参数均可),让测试在第一个失败用例处立即中止

make BOARD=<YOUR_BOARD> STOP_ON_FAILURE=1 flash test-with-config

源码中对应逻辑位于 main.c:do_test()在测试失败时打印失败行号,若STOP_ON_FAILURE==1则打印提示并触发断点挂起,否则继续执行后续测试:

static bool do_test(bool failed, uint16_t line) { if (failed) { print_test_failed(line); if (STOP_ON_FAILURE) { printf("Stopping, as STOP_ON_FAILURE==1\n"); ARCHITECTURE_BREAKPOINT(1); while (1) { /* stop */ } } } return failed; }

另外注意:为节省 RAM,失败信息刻意只打印__FILE__与行号,而不保存冗长错误文本——代码注释明确指出,行号加上代码中的注释足以提供等价的排查信息(见 main.c)。因此排查时应"按行号回查源码"。


四、配置说明:默认全测 + 按需裁剪

该测试默认无需任何配置,会自动测试所有存在 Arduino I/O 映射的外设驱动。

如需屏蔽某类测试(例如为低端板卡瘦身以节省 RAM/ROM),可通过FEATURES_BLACKLIST关闭对应特性:

FEATURES_BLACKLIST += periph_<foobar>

例如:

make BOARD=<YOUR_BOARD> FEATURES_BLACKLIST="periph_i2c periph_pwm" flash test-with-config

各测试的自动启用条件汇总如下(均可在 main.c 中查到):

  • ADC 测试periph_adc+ARDUINO_A0映射 +pcf857x模块(即同时具备periph_i2carduino_i2c);
  • GPIO 测试periph_gpio
  • GPIO-IRQ 测试periph_gpio_irq
  • I2C 测试periph_i2c+periph_gpio(间接经 PCF8574);
  • PWM 测试periph_pwm+periph_adc,且满足以下任一组合:
    • D5 的 PWM 映射A2 的 ADC 映射;
    • D6 的 PWM 映射A1 的 ADC 映射;
  • SPI 测试periph_spi(需ARDUINO_SPI_D11D12D13ARDUINO_SPI_ISP之一);
  • UART 测试periph_uart+ARDUINO_UART_D0D1映射,且 D0/D1 不被 stdio 占用。

内存占用的额外细节:为保证尽可能多的板卡可运行,非 ESP32 平台会强制-DMAXTHREADS=2(仅需 1 个线程 + 部分平台的 Idle 线程),ESP32x 平台则使用-DMAXTHREADS=3(因esp_timer需要额外线程)——见 Makefile。若你使用的是极小内存的板卡,这一点值得留意。

关于定时器的使用

测试会使用一个独立定时器来校验 SPI 时钟频率与 UART 符号率的合理性,且刻意避开 ztimer 占用的定时器(见 main.c):

#ifndef TIMER # if IS_USED(MODULE_ZTIMER_PERIPH_TIMER) && CONFIG_ZTIMER_USEC_DEV == TIMER_DEV(0) # define TIMER TIMER_DEV(1) # else # define TIMER TIMER_DEV(0) # endif #endif #if IS_USED(MODULE_ZTIMER_PERIPH_TIMER) # if CONFIG_ZTIMER_USEC_DEV == TIMER # error "Same timer used for ztimer and test" # endif #endif

不同 MCU 族的测试定时器频率也做了针对性选择(见 main.c):

  • SPI 测试希望频率尽量高:SAM3/STM32 用CLOCK_CORECLOCK / 4,NRF51/NRF52 用 16 MHz,其余默认直接用核心时钟CLOCK_CORECLOCK
  • UART 测试希望频率较低:AVR 用CLOCK_CORECLOCK / 64,其余用 1 MHz(避免 16 位定时器溢出);
  • 支持periph_timer_query_freqs的定时器会自动选择最接近且不低于目标值的真实可用频率(见 main.c)。

五、结果解读

  • 每个单项测试以[OK][FAILED]结尾(见 main.c),不支持的能力则打印(skipped)
  • 全部结束后输出汇总结论:
ALL TESTS SUCCEEDED # 退出码 0

或:

SOME TESTS FAILED # 退出码 1

main()中的最终判断逻辑见 main.c。由于执行顺序为 GPIO → GPIO-IRQ → I2C → UART → SPI → PWM → ADC(见 main.c),当使用STOP_ON_FAILURE=1时,首个失败用例的位置即可直接对应到上述顺序中的某一阶段。

小结

tests/periph/selftest_shield是 RIOT 生态中一个"零配置、按能力裁剪"的外设驱动回归测试工具:只要板卡兼容 Arduino UNO Shield 并提供 Arduino I/O 映射,就能在数分钟内完成七大外设驱动的自动回环自检。它通过 R-2R 梯形网络测 ADC、PCF8574 扩展器间接测 I2C、定时器辅助校验 SPI 时钟与 UART 符号率等精巧设计,将"驱动是否工作"转化为可重复、可自动化的断言。配合DETAILED_OUTPUTSTOP_ON_FAILURE两个编译期开关,它既可以作为新板卡 BSP 合入前的验收测试,也可以作为驱动开发时的回归测试基线。

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

【免费下载链接】RIOT

RIOT - The friendly OS for IoT

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

相关推荐

上一篇:AdGuardHome高并发场景优化:应对流量峰值
下一篇:拯救混乱的.zshrc:Oh My Zsh模块化配置完全指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 22:39:19

WorkBuddy实战:用AI智能体搭建每日工作自动化流程

每天打开电脑&#xff0c;有多少时间是花在重复劳动上的&#xff1f;整理日报、汇总数据、定时签到、抓取网页信息、回复固定格式的邮件……这些事情不复杂&#xff0c;但就是消耗时间。我一直在找一款能把这类“琐碎但必须做”的工作真正自动化的工具&#xff0c;试过不少脚本…

作者头像 李华
网站建设 2026/9/20 22:38:05

专科生必学:10款抗AI工具提升职业竞争力

1. 专科生如何应对AI时代的工具选择困境2026年的专科生正面临前所未有的就业压力——AI自动化正在快速取代传统岗位。根据最新行业调研&#xff0c;未来3年内约有47%的现有岗位将受到AI影响。作为专科生&#xff0c;掌握能降低被AI替代风险&#xff08;简称"降AI率"&…

作者头像 李华
网站建设 2026/9/20 22:30:56

Claude Code 实战:从安装到深度配置,打造你的 AI 编程队友

说实话&#xff0c;我实际用 Claude Code 做日常开发已经一个多月了&#xff0c;最初只是抱着“试一下”的心态装上&#xff0c;结果它逐渐变成了我工作流里绕不开的一环。与其说它是一个 AI 聊天框&#xff0c;不如说它更像一支可以塞进终端、跟着你一起读代码、写代码、跑测试…

作者头像 李华
网站建设 2026/9/20 22:30:27

UWB超宽带技术解析:从测距定位到雷达感知的工程实践

简介&#xff1a;《超宽带基础知识&#xff08;UWB&#xff09;》是一份面向通信工程、物联网定位及智能硬件开发人员的白皮书式PDF文档&#xff0c;系统讲解UWB这一IEEE 802.15.4a/z标准技术的核心原理与落地应用。文档从飞行时间&#xff08;ToF&#xff09;测距机制入手&…

作者头像 李华