深度解析:从运行方法到源码级判定逻辑)
RIOT 外设定时器测试应用tests/periph/timer深度解析从运行方法到源码级判定逻辑【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT本文以 RIOT 仓库中tests/periph/timer测试应用为核心系统讲解 RIOT 如何对目标平台所有已配置的外设定时器peripheral timer进行功能验证每个通道以递增的超时时间5ms、10ms、15ms……依次触发并通过回调记录的软件计数差值判定通道是否均匀触发。读完本文你将掌握该测试应用的运行方法、预期结果判定标准、main.c的完整测试流程与底层实现原理以及如何借助periph_timer_query_freqs等特性做更深入的定时器频率与伪中断验证。一、测试应用概述tests/periph/timer是 RIOT 的periph外设测试套件之一其定位在 tests/periph/timer/README.md 中写得很明确This application will test all configured peripheral timers of the target platform.即对目标平台所有已配置的外设定时器逐一进行测试。所谓已配置指的是在板级配置文件periph_conf.h中通过TIMER_x宏TIMER_0、TIMER_1……声明并在构建时启用了的定时器设备其数量由TIMER_NUMOF给出。核心测试思路是对于每个定时器将其每一个可用通道都设置一个递增的超时时间——CH0 设为 5msCH1 设为 10msCH2 设为 15ms依此类推。随后启动定时器并等待所有通道触发通过统计回调触发时的软件计数差值来判断各个通道是否按预期均匀触发。二、编译、烧录与运行该应用是标准的 RIOT 测试应用使用常规的构建流程即可。在仓库根目录下执行# 以 native宿主机模拟或任意支持 periph_timer 的板卡为例 make -C tests/periph/timer BOARDnative flash term也可以先单独编译再分别烧录/连接终端make -C tests/periph/timer BOARDyour-board # 编译 make -C tests/periph/timer BOARDyour-board flash # 烧录 make -C tests/periph/timer BOARDyour-board term # 打开串口终端构建层面的约束在 tests/periph/timer/Makefile 中include ../Makefile.periph_common FEATURES_REQUIRED periph_timer FEATURES_OPTIONAL periph_timer_query_freqsFEATURES_REQUIRED periph_timer目标板卡必须提供外设定时器否则该应用无法编译构建系统会直接报错FEATURES_OPTIONAL periph_timer_query_freqs频率查询特性是可选的。若板卡支持测试将额外执行频率查询与最近频率搜索的验证详见第六节若不支持相关代码路径会自动退化为回退实现。tests/periph/Makefile.periph_common中通过include $(CURDIR)/../../Makefile.tests_common引入测试公共配置而 tests/periph/timer/Makefile.ci 则声明了 CI 环境下内存不足、不参与自动化测试的板卡BOARD_INSUFFICIENT_MEMORY : \ atmega8 \ #三、预期结果与判定标准README 中给出了明确的通过标准Expected ResultThe output should show that every channel fired after an evenly distributed amount of time, i.e. the diff values should be equal (with some jitter...).也就是说串口输出中每个通道记录的触发时刻软件计数应当是均匀分布的相邻通道之间的差值diff应当相等允许存在少量抖动jitter。程序运行结束后若所有定时器全部通过会打印TEST SUCCEEDED任一环节失败则打印TEST FAILED以 1MHz 的TIMER_SPEED为例理论上各通道 diff 应稳定在5ms × 1MHz 5000 ticks附近。四、测试流程的源码级拆解测试逻辑全部集中在 tests/periph/timer/main.c 中。下面按执行顺序逐段剖析。4.1 关键常量与全局状态#define CHAN_OFFSET_MS 5U /* fire channels with 5 ms offset */ #define MINIMUM_TIMEOUT_MS 2 #define COOKIE (100U) /* for checking if arg is passed */ #define MINIMUM_TICKS 2 static uint8_t fired; static uint32_t sw_count; static uint32_t timeouts[TIMER_CHANNEL_NUMOF]; static unsigned args[TIMER_CHANNEL_NUMOF];CHAN_OFFSET_MS 5相邻通道的超时时间步进为 5msMINIMUM_TIMEOUT_MS 2清除定时器验证时使用的最小超时时间取值保守避免因超时太短导致误判失败test 多花几毫秒比误报失败更可接受MINIMUM_TICKS 2milliseconds_to_ticks()换算结果的下限防止设置少于两个 tick 的定时器时因定时器恰好即将 tick 而立即触发COOKIE 100作为回调参数校验的签名值用于确认中断上下文正确传回了初始化时传入的arg。4.2 回调函数static void cb(void *arg, int chan) { timeouts[chan] sw_count; args[chan] (uintptr_t)arg chan; fired; }cb在定时器中断上下文中被调用对应periph/timer.h中timer_cb_t的定义void (*timer_cb_t)(void *arg, int channel)见 drivers/include/periph/timer.h。它记录timeouts[chan] sw_count当前软件计数快照用于后续计算触发时刻args[chan] (uintptr_t)arg chan校验中断回调是否拿到了正确的arg初始化时传入的COOKIE * num与通道号之和fired触发计数供主循环判断是否所有已设置通道都已完成触发。另一个回调cb_not_to_be_executed则用于伪中断验证它一旦被调用就把fired置 1从而让测试失败见 4.6 节。4.3 毫秒到 tick 的换算static unsigned milliseconds_to_ticks(uint32_t timer_freq, unsigned millisecs) { unsigned result ((uint64_t)millisecs * US_PER_MS * timer_freq) / US_PER_SEC; return (result MINIMUM_TICKS) ? result : MINIMUM_TICKS; }公式为ticks millisecs × 1000 × freq / 1000000 millisecs × freq / 1000。实现特意使用64 位中间运算以避免高频率如 32MHz 以上下乘法溢出换算结果还会被夹到MINIMUM_TICKS2 tick以上。4.4 单个定时器的完整测试流程test_timer()test_timer(unsigned num, uint32_t timer_freq)是核心测试函数流程如下状态复位用atomic_store_u32/atomic_store_u8清零sw_count与fired并初始化timeouts[]与args[]args[]置为UINT_MAX便于检测未更新情况。初始化定时器并立即停止if (timer_init(TIMER_DEV(num), timer_freq, cb, (void *)(uintptr_t)(COOKIE * num)) ! 0) { printf(ERROR: timer_init() failed\n\n); return 0; } timer_stop(TIMER_DEV(num));timer_init()要求定时器当前未激活未初始化或已被timer_stop随后timer_stop()使定时器完全停止部分平台还会关闭外设时钟门控以省电见 drivers/include/periph/timer.h 的电源语义说明。为每个可用通道设置递增超时unsigned chan_offset_ticks milliseconds_to_ticks(timer_freq, CHAN_OFFSET_MS); for (unsigned i 0; i query_channel_numof(TIMER_DEV(num)); i) { unsigned timeout ((i 1) * chan_offset_ticks); if (timer_set(TIMER_DEV(num), i, timeout) 0) { ... } }这里体现了 README 描述的递增超时通道 0 超时为1 × chan_offset_ticks5ms 换算值通道 1 为2 × chan_offset_ticks10ms通道 2 为3 × chan_offset_ticks15ms依次类推。若某一通道设置失败支持periph_timer_query_freqs的定时器会被要求如实上报支持通道数故直接返回失败否则break跳出。若所有通道都设置失败则直接判失败。启动定时器并等待全部触发timer_start(TIMER_DEV(num)); do { semi_atomic_fetch_add_u32(sw_count, 1); } while (atomic_load_u8(fired) ! set);主循环通过semi_atomic_fetch_add_u32不断递增软件计数器sw_count直到触发次数达到已成功设置的通道数。回调在中断上下文记录触发瞬间的sw_count快照。收集并打印结果for (int i 0; i set; i) { if (args[i] ! ((COOKIE * num) i)) { printf( ERROR: Callback for channel %u on timer %u has incorrect argument\n, ...); return 0; } printf( - channel %i fired at SW count %8u, ...); if (i 0) { printf( - init: %8 PRIu32 \n, ...); } else { printf( - diff: %8 PRIu32 \n, timeouts[i] - timeouts[i - 1]); } }首先校验每个回调的arg是否等于COOKIE * num i确认中断上下文参数传递正确打印每个通道触发时的软件计数通道 0 输出init其余通道输出与上一通道的差值diff。正是这些diff值构成了 README 所说的均匀分布判定依据——理想情况下全部相等允许抖动。4.5 伪中断spurious IRQ验证在通道触发测试之后test_timer()还会验证清除超时后定时器不会产生伪中断expect(0 timer_init(TIMER_DEV(num), timer_freq, cb_not_to_be_executed, NULL)); const unsigned duration milliseconds_to_ticks(timer_freq, MINIMUM_TIMEOUT_MS); unsigned target timer_read(TIMER_DEV(num)) duration; expect(0 timer_set_absolute(TIMER_DEV(num), 0, target)); expect(0 timer_clear(TIMER_DEV(num), 0)); atomic_store_u8(fired, 0); while ((target - timer_read(TIMER_DEV(num))) duration) { /* busy waiting for the timer to reach it timeout. Timer must not fire, * it was cleared */ }关键点是先用timer_set_absolute()设置一个绝对比较值target随即用timer_clear()清除该通道然后忙等待到target时刻之后。由于通道已被清除回调cb_not_to_be_executed绝不应被触发。这一验证会连续执行两次1/2 与 2/2第二次是为了确保任何可能刚被掩码的 IRQ 挂起位不会在下一次timer_set时误触发中断。两次均无触发则打印[OK] (no spurious IRQs)。4.6 频率查询验证test_querying()当板卡支持periph_timer_query_freqs特性时main()会额外调用test_querying()对timer_get_closest_freq()的最近频率搜索逻辑做边界验证对应 drivers/include/periph/timer.h 中声明的二分查找语义查询一个恰好支持的频率应返回其本身expect(freq timer_get_closest_freq(dev, freq))查询freq - 1结果允许在freq - 2、freq - 1、freq三者之一考虑相邻频率也受支持的边界情形查询freq 1结果允许在freq、freq 1、freq 2三者之一越界索引timer_query_freqs(dev, last 1)必须返回 0。五、构建配置TIMER_SPEED 与板卡默认频率tests/periph/timer/Makefile 按板卡族为TIMER_SPEED提供了默认值再通过CFLAGS -DTIMER_SPEED$(TIMER_SPEED)注入编译板卡/板卡族默认 TIMER_SPEEDatxmega 系列a1-xplained / a1u-xpro / a3bu-xplained500000500 kHzarduino-duemilanove / leonardo / mega2560 / uno、atmega328p 等 AVR 板250000250 kHze180-zg120b-tb、hifive1/hifive1b、ikea-tradfri、%-kw41z、frdm-k64f/k22f、SL 系列等3276832.768 kHzcc2538dk、openmote-b、openmote-cc2538、remote-reva/revbcoreclk()系统内核时钟其余板卡10000001 MHz默认回退值main.c中query_freq()在不支持periph_timer_query_freqs时返回TIMER_SPEED作为唯一测试频率支持时则通过timer_query_freqs(dev, index)枚举硬件实际支持的频率但main()只测试最快的前 3 个频率end MIN(end, 3)并在代码注释中说明原因部分定时器支持极低频率若全部测试会耗时过久。六、自动化测试脚本测试脚本 tests/periph/timer/tests/01-run.py 基于 RIOT 的 testrunner 框架逻辑非常精简def testfunc(child): child.expect(Test for peripheral TIMERs) child.expect(TEST SUCCEEDED)它只做两件事确认应用已启动匹配Test for peripheral TIMERs然后等待TEST SUCCEEDED。脚本注释点明了设计哲学——C 应用本身已经严谨地评估了各项测试结果Python 侧无需重复实现判定逻辑只需检查最终结论。这也是该测试可无缝接入 RIOT CImurdock的原因。七、注意事项本测试的边界README 在 Note 一节中特别强调了本测试的局限This test does howeverNOTshow whether the timeouts and diffs were correct in relation to the expected real-time; use e.g. tests/xtimer_msg for this.本测试只验证各通道触发时刻的相对均匀性diff 是否相等并不验证绝对超时是否与真实时间吻合。软件计数sw_count的增量速率取决于主循环的忙等待节奏而非硬件时钟精度因此init/diff的绝对值不具备真实时间意义。若要验证超时相对真实时间的正确性应使用tests/sys/xtimer_msg/main.c基于xtimer的msg消息超时测试tests/sys/ztimer_msg/README.md基于ztimer的对应测试ztimer是 RIOT 中 xtimer 的现代替代实现。八、关联的底层定时器接口本测试是 drivers/include/periph/timer.h 所定义外设定时器接口的直接使用者。测试中涉及的 API 及其语义如下API作用测试中的用途timer_init(dev, freq, cb, arg)以指定频率初始化定时器并启动中断使能每个频率下初始化并注册回调timer_stop(dev)/timer_start(dev)停止 / 启动定时器初始化后立即停止设置好通道后再启动timer_set(dev, channel, timeout)设置相对超时ticks为各通道设置 5ms 递增的超时timer_set_absolute(dev, channel, value)设置绝对比较值伪中断验证中设置绝对目标timer_clear(dev, channel)清除通道验证清除后不产生伪中断timer_read(dev)读取当前计数值忙等待到目标时刻timer_query_freqs(dev, i)/timer_query_freqs_numof(dev)枚举支持频率打印与遍历测试频率timer_get_closest_freq(dev, target)返回最接近目标的支持频率最近频率搜索正确性验证timer_query_channel_numof(dev)查询通道数弱实现默认返回TIMER_CHANNEL_NUMOF遍历通道此外该接口还定义了timer_set_periodic()周期性触发需periph_timer_periodic特性与timer_poll_channel()轮询匹配状态需periph_timer_poll特性见 makefiles/features_existing.inc.mk 中列出的相关特性本测试未覆盖需要时可参考对应驱动与文档。结语tests/periph/timer虽然 README 只有寥寥数段但其背后是一套相当严谨的验证矩阵多通道递增超时触发、回调参数完整性校验、伪中断检测、频率查询与最近频率搜索的边界验证并且通过FEATURES_REQUIRED/FEATURES_OPTIONAL机制优雅地适配不同能力等级的板卡。理解这份测试应用既能帮你快速判断一块板卡的定时器外设是否工作正常TEST SUCCEEDED/TEST FAILED也能让你洞悉 RIOT 外设定时器驱动接口的完整语义与正确性边界——这正是从会跑测试到读懂外设驱动的关键一步。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考