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

📅 发布时间:2026/9/20 22:40:17
RIOT 外设自检指南:使用 Peripheral Selftest Shield 全面测试 ADC/GPIO/I2C/PWM/SPI/UART
物联网嵌入式操作系统实时系统【免费下载链接】RIOTRIOT - 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_pins、arduino_shield_uno等特性标识测试应用通过FEATURES_REQUIRED强制要求它们存在见 MakefileFEATURES_REQUIRED arduino_pins FEATURES_REQUIRED arduino_shield_uno同时其余外设能力arduino_analog、arduino_i2c、arduino_pwm、arduino_shield_isp、arduino_spi、arduino_uart以及periph_adc、periph_gpio、periph_gpio_irq、periph_i2c、periph_pwm、periph_spi、periph_timer、periph_timer_query_freqs、periph_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 BOARDYOUR_BOARD flash test-with-configflash负责把固件烧录到板卡test-with-config会结合tests-with-config/目录下的自动化测试脚本运行。自动化测试脚本 01-run.py 的校验逻辑非常简洁期望串口输出依次出现self-testing peripheral drivers与ALL 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 两种速率收发注 1ADC 测试与 R-2R 梯形网络ADC 输入由4 位 R-2R 电阻梯形网络生成其电压由 I2C 连接的 GPIO 扩展器PCF8574和/或 PWM 控制。若两者都不可用则不做 ADC 测试若都可用最多测试映射到 A0、A1、A2 的三个 ADC 通道。从源码看R-2R 网络的 4 个 bit 由 PCF8574 的 P0.4P0.7 驱动见 main.cstatic 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。注 2I2C 的间接测试I2C 不直接回环而是通过操作 PCF8574 I2C GPIO 扩展器、再用内部 GPIO 与扩展器 GPIO 互联观测结果来间接验证。该测试仅在periph_i2c和periph_gpio同时可用时启用。从源码看main.c测试双向进行内部 GPIOD9 输出→ 扩展器 GPIOP0.1 输入内部置高/置低读取扩展器侧电平扩展器 GPIOP0.0 输出→ 内部 GPIOD8 输入扩展器置高/置低读取内部 GPIO。每一方向都循环flaky_test_repetitions默认 100次。PCF8574 模块的引入是按需的只有在板卡同时具备periph_i2c与arduino_i2c特性时才链接pcf8574模块见 Makefile.board.depifneq (,$(filter periph_i2c,$(FEATURES_USED))) ifneq (,$(filter arduino_i2c,$(FEATURES_USED))) USEMODULE pcf8574 endif endif注 3SPI 回环测试的局限SPI 采用 MOSI 与 MISO 短接的回环测试因此位序bit order和时钟相位clock phase无法校验回环方式天然测不出相位差异时钟极性clock polarity可以校验在空闲电平下检测若实现了periph_timer时钟频率的正确性可做粗略校验。SPI 测试覆盖两种总线ARDUINO_SPI_D11D12D13与ARDUINO_SPI_ISP见 main.c每种总线遍历SPI_CLK_400KHZ / SPI_CLK_1MHZ / SPI_CLK_10MHZ三档时钟以及 SPI_MODE_03 四种模式见 main.c。空闲电平通过扩展器引脚 P0.2 / P0.3 读取确认片选CS则由 D10 驱动、D7 观测。注 4UART 回环测试的约束当 D0/D1 的 UART 接口被 stdio 占用时跳过 UART 自检符号率symbol rate常被误称为 baudrate仅在periph_timer可用时做合理性校验不同的每字符位数、停止位数、校验位等参数理论上可借助periph_timer检测但当前版本尚未实现。UART 测试使用 8 字节测试数据Selftest依次在 9600 与 115200 两种速率下进行见 main.c。当启用定时器后发送耗时会被测量并与理论值比对允许实际耗时在期望值的 75%200% 之间见 main.c。此外还会验证数据内容与字节数完全一致回环正确性uart_poweroff后不再收到数据断电有效uart_poweron后重新收发正常上电恢复。三、失败排障指南3.1 增加输出详细程度编译时加上DETAILED_OUTPUT1可提高输出详细度代价是 ROM 占用增加make BOARDYOUR_BOARD DETAILED_OUTPUT1当出现 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.cSPI 测试还会额外打印当前时钟频率见 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初始化失败。可能原因按可能性排序I2C 配置错误I2C 总线通常在periph_conf.h或其包含的文件中配置是否正确配置是否连接到了 Arduino UNO 的 D14 / D15 引脚ARDUINO_I2C_UNO宏是否正确定义为连接到 D14 / D15 的 I2C 总线索引缺少上拉电阻Shield 的 I2C 总线上没有上拉电阻默认情况下 RIOT 的 I2C 驱动会启用 MCU 内部上拉但部分 MCU 的某些引脚没有内部上拉或在 I2C 模式下无法启用若可能应在periph_conf.h中配置 I2C 驱动/板级配置使无外部上拉时也能开箱即用地稳定运行若板卡必须依赖外部上拉无合适的内部上拉、板卡在 D14/D15 上也没有外部上拉最省事的办法是在 J2 的 I2C 引脚插座上插入一块 I2C 传感器/EEPROM 扩展板——这类扩展板通常自带外部上拉电阻。I2C 驱动 bugI2C 驱动本身的缺陷也可能导致此问题此时连接逻辑分析仪到 I2C 排针J3、I2C 插座J2或 I2C Grove 连接器J1会很有帮助。硬件问题这是最不可能的原因。从源码结构看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_FAILURE1环境变量或make参数均可让测试在第一个失败用例处立即中止make BOARDYOUR_BOARD STOP_ON_FAILURE1 flash test-with-config源码中对应逻辑位于 main.cdo_test()在测试失败时打印失败行号若STOP_ON_FAILURE1则打印提示并触发断点挂起否则继续执行后续测试static bool do_test(bool failed, uint16_t line) { if (failed) { print_test_failed(line); if (STOP_ON_FAILURE) { printf(Stopping, as STOP_ON_FAILURE1\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 BOARDYOUR_BOARD FEATURES_BLACKLISTperiph_i2c periph_pwm flash test-with-config各测试的自动启用条件汇总如下均可在 main.c 中查到ADC 测试periph_adcARDUINO_A0映射 pcf857x模块即同时具备periph_i2c与arduino_i2cGPIO 测试periph_gpioGPIO-IRQ 测试periph_gpio_irqI2C 测试periph_i2cperiph_gpio间接经 PCF8574PWM 测试periph_pwmperiph_adc且满足以下任一组合D5 的 PWM 映射且A2 的 ADC 映射D6 的 PWM 映射且A1 的 ADC 映射SPI 测试periph_spi需ARDUINO_SPI_D11D12D13或ARDUINO_SPI_ISP之一UART 测试periph_uartARDUINO_UART_D0D1映射且 D0/D1 不被 stdio 占用。内存占用的额外细节为保证尽可能多的板卡可运行非 ESP32 平台会强制-DMAXTHREADS2仅需 1 个线程 部分平台的 Idle 线程ESP32x 平台则使用-DMAXTHREADS3因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.cSPI 测试希望频率尽量高SAM3/STM32 用CLOCK_CORECLOCK / 4NRF51/NRF52 用 16 MHz其余默认直接用核心时钟CLOCK_CORECLOCKUART 测试希望频率较低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 # 退出码 1main()中的最终判断逻辑见 main.c。由于执行顺序为 GPIO → GPIO-IRQ → I2C → UART → SPI → PWM → ADC见 main.c当使用STOP_ON_FAILURE1时首个失败用例的位置即可直接对应到上述顺序中的某一阶段。小结tests/periph/selftest_shield是 RIOT 生态中一个零配置、按能力裁剪的外设驱动回归测试工具只要板卡兼容 Arduino UNO Shield 并提供 Arduino I/O 映射就能在数分钟内完成七大外设驱动的自动回环自检。它通过 R-2R 梯形网络测 ADC、PCF8574 扩展器间接测 I2C、定时器辅助校验 SPI 时钟与 UART 符号率等精巧设计将驱动是否工作转化为可重复、可自动化的断言。配合DETAILED_OUTPUT与STOP_ON_FAILURE两个编译期开关它既可以作为新板卡 BSP 合入前的验收测试也可以作为驱动开发时的回归测试基线。赞分享物联网嵌入式操作系统实时系统【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址https://gitcode.com/GitHub_Trending/riot/RIOT点击查看免费下载相关推荐RIOT-OS 中的 ESP32 SoC 外设资源与引脚配置详解GPIO / ADC / DAC / I2C / PWM / SDMMC / SPI / Timer / UART / JTAGRIOT OS 中的 ESP32 SoC 外设资源与引脚配置详解GPIO / ADC / DAC / I2C / PWM / SDMMC / SPI / Ti物联网嵌入式操作系统实时系统STM32外设调试终极指南stlink实战GPIO、UART、I2C、SPI全解析STM32外设调试终极指南stlink实战GPIO、UART、I2C、SPI全解析 stlink作为开源的STM32 MCU编程工具集为开发者提供了强大的外嵌入式硬件开发开发工具调试器告别外设控制痛点RPPAL全攻略 — 用Rust掌控树莓派GPIO/I2C/PWM/SPI/UART告别外设控制痛点RPPAL全攻略 — 用Rust掌控树莓派GPIO/I2C/PWM/SPI/UART 你是否还在为树莓派外设编程烦恼尝试过Python库却受上一篇AdGuardHome高并发场景优化应对流量峰值下一篇拯救混乱的.zshrcOh My Zsh模块化配置完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考