- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
本指南深入讲解 RIOT OS 测试应用tests/periph/uart_locate_pins(源码位于 tests/periph/uart_locate_pins):它通过软件 UART(soft_uart模块)在每一个 GPIO 引脚上以 9600 Bd 的符号率逐位发送该引脚自身的名字,从而帮助开发者在没有板卡原理图或引脚标注不明时,仅凭一个 UART 适配器逐脚探测即可确认 GPIO 编号。读完本文,你将掌握该工具的工作原理、消息编码格式、构建与使用步骤,以及如何定制引脚探测列表、修改波特率,并从源码层面理解软件 UART 的位时序实现。
这个工具解决什么问题
在实际嵌入式开发中,经常遇到以下场景:
- 拿到了没有原理图、或丝印不清的评估板,无法确认某个物理引脚对应的
GPIO_PIN(port, pin)编号; - 芯片的硬件 UART 引脚被复用、或者硬件 UART 资源不足,想临时用任意 GPIO 输出调试信息;
- 需要确认某个引脚是否可用作输出(例如确认其没有被连接到晶体、复位电路等特殊外设)。
uart_locate_pins正是为这种"盲探引脚"场景设计的:它在每个 GPIO 上通过软件模拟的 UART 反复发送一行文本,文本内容就是当前引脚的名字。只要把 UART 适配器依次接到各个引脚上,从串口终端读到的内容就能立刻告诉你"你现在接的是哪个引脚"。
工作原理:软件 UART 逐引脚广播引脚名
该应用的思路非常直接(见 main.c):
- 维护一个待探测引脚的列表(
pins[]数组); - 循环遍历该列表,对每个引脚调用
soft_uart_init()初始化软件 UART; - 构造一行包含引脚编号与引脚字母名的文本;
- 用
soft_uart_write()把该文本逐位(bit-bang)发送出去; - 发送完成后调用
soft_uart_poweroff()关闭 UART,再继续下一个引脚,如此无限循环。
它没有使用芯片的硬件 UART 外设,而是完全依靠soft_uart驱动——一个纯软件实现的 UART,由通用定时器(timer)+ 普通 GPIO 配合产生串口波形,因此任何支持periph/gpio与periph/timer的 RIOT 板卡都可以运行,不依赖硬件 UART 引脚。根据 drivers/include/soft_uart.h 的说明,该模块正是用于"硬件 UART 不可用"的场合;需要注意的是当前实现收发不能同时进行,因此本工具只使用发送方向(TX)。
引脚消息的编码格式
main.c中发送的每行消息由两段组成(对应fmt模块的格式化函数,见 main.c):
P<port>.<pin> / P<字母><pin>\n其中:
P<port>.<pin>:以数字形式给出端口号与引脚号,例如P0.0表示 port 0、pin 0;P<字母><pin>:以字母形式给出端口('A' + port_num)与引脚号,例如PA0,即 GPIO A 的第 0 脚;- 行尾是
\n换行符,便于串口终端逐行显示。
两种表示法同时输出,方便与芯片数据手册中的命名(如PA0)以及 RIOT 的GPIO_PIN(0, 0)写法互相印证。消息通过fmt_u16_dec()把无符号整数转成十进制字符串,再逐字节送入soft_uart_write(),整个过程不依赖printf和 stdio——这正是该测试在 Makefile 中引入stdio_null模块的原因(见下文)。
源码剖析:从引脚表到发送循环
1. 待探测引脚列表
所有被探测的引脚集中定义在main.c顶部的静态数组(main.c):
static const struct { uint8_t port_num; uint8_t pin_num; } pins[] = { { .port_num = 0, .pin_num = 0, }, };源码注释(main.c)特别提醒:请根据实际需要调整这个列表,不一定要包含所有引脚——例如连接到外部晶体的引脚被强制驱动为输出时可能表现异常,应当从列表中排除。默认模板只探测P0.0(PA0),实际使用前需要把它展开成目标芯片的全部 GPIO。
2. 软件 UART 配置
应用为唯一的软件 UART 实例定义了配置(main.c):
soft_uart_conf_t soft_uart_config[] = { { .tx_pin = GPIO_UNDEF, .rx_pin = GPIO_UNDEF, .tx_timer = TIMER_DEV(0), .timer_freq = MHZ(1), }, };tx_pin/rx_pin初始为GPIO_UNDEF,因为发送引脚会在主循环里被动态切换为当前探测的引脚;tx_timer使用定时器 0(TIMER_DEV(0));timer_freq = MHZ(1),即定时器以 1 MHz 工作。该值必须能被波特率整除才有良好的时序精度(soft_uart驱动对此有专门的校验,见下文"底层实现"一节)。
需要说明的是,这个配置文件覆盖了soft_uart模块自带的默认配置。驱动默认参数位于 drivers/soft_uart/include/soft_uart_params.h:默认rx_pin/tx_pin均为GPIO_UNDEF、RX 定时器为 timer 0、TX 定时器为 timer 1、频率MHZ(1)、标志为 0。而本应用通过自定义的 include/soft_uart_params.h 重定义了soft_uart_config数组(extern声明)并固定SOFT_UART_NUMOF为 1,正如其文件头注释所说:这是为了在"每次切换引脚配置"时节省内存、避免使用驱动默认的静态参数数组。
3. 主循环:切换引脚 → 初始化 → 发送 → 关闭
主循环(main.c)核心逻辑如下:
while (1) { for (unsigned i = 0; i < ARRAY_SIZE(pins); i++) { gpio_t pin = GPIO_PIN(pins[i].port_num, pins[i].pin_num); soft_uart_config[0].tx_pin = pin; soft_uart_init(0, UART_SYMBOL_RATE, NULL, NULL); /* ...构造 "P0.0 / PA0\n" 消息... */ soft_uart_write(0, (void *)buf, (size_t)pos - (size_t)buf); soft_uart_poweroff(0); } }关键点:
GPIO_PIN(port, pin)把 (端口号, 引脚号) 组合成 RIOT 的gpio_t句柄;soft_uart_init(0, UART_SYMBOL_RATE, NULL, NULL)传入NULL回调,表示只初始化TX 发送模式(见 drivers/include/soft_uart.h 的说明);soft_uart_write()是阻塞发送——它会等到全部字节发送完毕才返回(drivers/include/soft_uart.h),保证当前引脚发完消息后才切换到下一个;- 发送完毕调用
soft_uart_poweroff(),把该引脚释放,接着探测下一个引脚,外层while(1)让整个扫描无限循环,因此你可以在任意时刻接入 UART 适配器,等待当前周期轮询到该引脚即可收到消息。
4. 波特率可配置
波特率通过编译期宏UART_SYMBOL_RATE提供,默认 9600(main.c):
#ifndef UART_SYMBOL_RATE # define UART_SYMBOL_RATE 9600 #endif也就是说,即使不修改源码,也可以用CFLAGS += -DUART_SYMBOL_RATE=...或make CFLAGS+=-DUART_SYMBOL_RATE=115200调整符号率。9600 Bd 是 UART 适配器与串口工具最通用的默认档位,绝大多数 USB-TTL 转换器无需额外配置即可使用。
构建与运行
Makefile 要点
应用的构建配置位于 Makefile,值得注意的有几点:
BOARD ?= nucleo-f767zi # 让本应用的 soft_uart_params.h 优先于驱动自带的版本被找到 INCLUDES += -I$(abspath $(CURDIR))/include include ../Makefile.periph_common USEMODULE += soft_uart USEMODULE += fmt # 不需要 stdio USEMODULE += stdio_null # 本应用不需要多线程 DISBALE_MODULE += core_thread- 默认板卡为
nucleo-f767zi(Nucleo-144 上的 STM32F767ZI),可通过BOARD=...覆盖; - 头文件路径注入:
INCLUDES += -I.../include保证链接器优先找到本应用自定义的 soft_uart_params.h,而不是驱动目录下的默认版本(Makefile 注释明确说明了这一目的); - 模块选择:启用
soft_uart与fmt(轻量整数格式化),并通过stdio_null关闭标准输出——因为本应用不需要 stdio; - 裁剪内核:
DISBALE_MODULE += core_thread移除了多线程支持,进一步减小镜像体积(注意此处是 Makefile 中实际的拼写,RIOT 的模块裁剪机制会据此跳过对应模块); - 同时继承 tests/periph/Makefile.periph_common 与 tests/Makefile.tests_common(后者默认
BOARD ?= native、默认开启DEVELHELP,并给自动化测试注入交互同步与栈使用统计模块)。
编译、烧录与使用步骤
在 RIOT 仓库根目录下,按以下步骤操作:
# 1. 进入测试目录 cd tests/periph/uart_locate_pins # 2. 编译(默认板卡 nucleo-f767zi,也可显式指定其他板卡) make BOARD=nucleo-f767zi # 3. 烧录到目标板 make BOARD=nucleo-f767zi flash # 4. 打开串口终端(波特率 9600,8N1) # 例如:minicom -D /dev/ttyUSB0 -b 9600然后:
- 把 USB-TTL UART 适配器的RX引脚连接到目标板上的任意一个 GPIO;
- 适配器的GND必须与目标板共地;
- 观察串口终端输出,例如收到
P0.0 / PA0,就说明当前接的正是PA0; - 依次把适配器换到其他引脚,逐一记录每个引脚对应的编号;
- 若某引脚始终无输出,则该引脚可能是特殊用途引脚(如晶体/复位/电源),或已被从
pins[]列表中排除。
每次换脚无需重新烧录——应用会无限循环轮询整个引脚表。
软件 UART 底层原理:位时序如何生成
要理解为什么这个工具能在任意 GPIO 上"模拟"出 9600 Bd 的串口,需要深入soft_uart驱动的实现(drivers/soft_uart/soft_uart.c)。
波特率到定时器节拍的换算
初始化时,驱动根据配置的定时器频率计算每一位对应的定时器 tick 数(soft_uart.c):
ctx->bit_time = (cfg->timer_freq + baudrate / 2) / baudrate; unsigned accuracy = (100 * cfg->timer_freq / ctx->bit_time) / baudrate; if (accuracy > 110 || accuracy < 90) { return UART_NOBAUD; }bit_time= 每个数据位的定时器计数个数(四舍五入取整);- 驱动还校验换算精度:若
(100 * timer_freq / bit_time) / baudrate落在 90%~110% 区间之外,则直接返回UART_NOBAUD拒绝该波特率。这正是配置里要求timer_freq取MHZ(1)(1 MHz)的原因——1 MHz 对 9600 属于整除关系(1,000,000 / 9600 ≈ 104.17 tick/位),精度足够通过校验。
发送:定时器中断驱动逐位翻转 GPIO
发送一字节的逻辑在soft_uart_write_byte()(soft_uart.c)与定时器回调_tx_timer_cb()(soft_uart.c)中:
- 把待发字节左移 1 位,空出的最低位作为起始位(低电平);
- 按 8 数据位 + 1 停止位的帧结构,把 8 个数据位和停止位拼进
byte_tx; - 用
timer_set_periodic()让定时器以bit_time为周期周期性触发中断; - 每次定时器回调把
byte_tx的最低位写进gpio_write(cfg->tx_pin, ...),然后右移一位,直到整帧发完,清定时器并释放同步信号量; soft_uart_write()(soft_uart.c)在持有互斥锁的前提下逐字节调用上述过程,实现整条消息的阻塞发送。
因此,每个引脚的"bit-bang"本质上是:GPIO 被配置为普通推挽输出,由定时器中断在每个位时刻精确翻转电平,从而在引脚上产生标准的 UART 帧波形。这也解释了为什么soft_uart_config里必须指定一个可用的硬件定时器,以及为什么tx_timer/timer_freq的选取直接决定能否在目标波特率下稳定工作。
与periph/uart的接口一致性
从 drivers/include/soft_uart.h 可以看到,soft_uart_init、soft_uart_mode、soft_uart_write、soft_uart_poweron/poweroff的签名刻意与periph/uart保持一致,并复用uart_rx_cb_t等类型。这意味着soft_uart可以作为硬件 UART 的即插即用替代品,本测试应用正是利用了这一设计,把"任意 GPIO"临时变成串口 TX。
定制指南
根据你的目标板,通常需要改动三处:
| 定制项 | 位置 | 说明 |
|---|---|---|
| 探测引脚列表 | main.c 的pins[] | 展开为芯片全部(或目标)GPIO 的 (port, pin) 对;剔除晶体等特殊引脚 |
| 符号率 | main.c 的UART_SYMBOL_RATE,或编译期-DUART_SYMBOL_RATE=... | 默认 9600;改大需同步调整串口终端设置,并注意soft_uart的精度校验 |
| 软件 UART 定时器/频率 | main.c 的soft_uart_config[] | 更换tx_timer、timer_freq,频率应为波特率的倍数 |
此外,include/soft_uart_params.h 通过SOFT_UART_NUMOF 1固定实例数量、以extern复用main.c中可变的配置数组,这一模式在你想把本应用改造成"动态引脚输出调试口"时同样可以复用。
总结
tests/periph/uart_locate_pins是 RIOT 生态中一个非常实用的小工具:它把soft_uart驱动的"任意 GPIO 可变身为串口 TX"能力发挥到极致,用逐引脚 bit-bang 引脚名的方式,把未知板卡的引脚映射问题转化为"一根 UART 适配器 + 一个串口终端"就能解决的简单探测过程。其核心价值在于:
- 零硬件 UART 依赖:完全基于软件 UART,任何支持 timer + GPIO 的板卡可用;
- 消息自解释:同时输出
P0.0与PA0两种格式,方便对照数据手册; - 即改即用:通过
pins[]数组、UART_SYMBOL_RATE宏与自定义soft_uart_config[]即可适配任意目标板。
如果你正在为一款引脚标注不明的开发板编写板级支持,或者想快速确认某颗新芯片的 GPIO 编号,这个工具可以让你在几分钟内拿到完整的引脚对照表。
- 物联网
- 嵌入式
- 操作系统
- 实时系统
【免费下载链接】RIOT
RIOT - The friendly OS for IoT
相关推荐
安全找回比特币钱包:btcrecover离线模式与数据提取脚本详解
安全找回比特币钱包:btcrecover离线模式与数据提取脚本详解 比特币钱包密码或助记词遗忘是加密货币用户常见的痛点,而 btcrecover 作为一款开源的
应用安全区块链CLIRIOT OS 在 STM32L496G-DISCO 开发板上的外设支持与引脚映射全解析
RIOT OS 在 STM32L496G DISCO 开发板上的外设支持与引脚映射全解析 导读 本文以 RIOT 操作系统中 stm32l496g disco
物联网嵌入式操作系统实时系统RIOT OS 中的 Adafruit Feather M0 板级支持:从引脚映射到 WiFi/LoRa 无线实战
RIOT OS 中的 Adafruit Feather M0 板级支持:从引脚映射到 WiFi/LoRa 无线实战 Adafruit Feather M0 是
物联网嵌入式操作系统实时系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考