MicroPython pyb.UART 串口编程完全指南:Pyboard 全系串口配置、流式读写与 RTS/CTS 硬件流控深度解析
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
本篇技术指南以 MicroPython 官方文档中pyb.UART类为核心,系统讲解在 Pyboard(含 Pyboard Lite 与 Pyboard D)上如何使用 UART/USART 双工串行通信:从构造器与板级引脚映射、init()全参数配置(波特率、数据位、校验、停止位、超时、流控、接收缓冲),到流式读写 API 与 RTS/CTS 硬件流控的完整行为语义。结合仓库源码 machine_uart.c 与官方测试用例 uart.py,读者将掌握可落地的串口编程方案,并理解底层实现约束(如波特率精度、缓冲阈值、流控引脚极性)。
UART 基础:双线双工、字符宽度与适用场景
pyb.UART实现标准的 UART/USART 双工串行通信协议。物理层面仅由两条信号线构成:
- RX(接收):接收对端数据;
- TX(发送):发送数据到对端。
通信的基本单元是字符(character),注意它不等同于 Python 字符串中的"字符"。一个 UART 字符的宽度可以是8 位或 9 位(结合校验位使用时则支持 7 位或 8 位),这决定了每帧携带的信息量与最高传输速率之间的权衡。
UART 是嵌入式系统中最基础的外设之一,典型应用包括:与 PC 上位机通信(配合 USB 转串口)、连接 GPS/GSM 模块、传感器调试日志输出、板间通信等。在 MicroPython 中,UART 对象实现了标准流(stream)接口,因此可以像操作文件一样进行读写,并可与select、asyncio等高层机制配合。
快速上手:创建与初始化 UART 对象
标准用法如下(与 pyb.UART 文档 保持一致):
from pyb import UART uart = UART(1, 9600) # 以给定波特率创建并初始化 uart.init(9600, bits=8, parity=None, stop=1) # 使用给定参数重新初始化参数取值范围:
- bits:7、8 或 9;
- parity:
None(无校验)、0(偶校验 even)或1(奇校验 odd); - stop(停止位):1 或 2。
重要限制:当
parity=None时,仅支持 8 位和 9 位;启用校验位后,仅支持 7 位和 8 位。换言之,9 位数据帧无法与校验位共存——因为 9 位数据 + 1 位校验会超出帧结构。
构造器与板级引脚映射
UART(bus, ...)在指定总线上构造 UART 对象。若不提供额外参数,对象仅被创建而不初始化(保留该总线上次初始化时的设置,如有);若传入额外参数,则立即按init()的语义初始化。
Pyboard(V1/V1.1)引脚映射
| 总线 | 位置 | (TX, RX) | (PAx, PAx) 映射 |
|---|---|---|---|
UART(4) | XA | (X1, X2) | (PA0, PA1) |
UART(1) | XB | (X9, X10) | (PB6, PB7) |
UART(6) | YA | (Y1, Y2) | (PC6, PC7) |
UART(3) | YB | (Y9, Y10) | (PB10, PB11) |
UART(2) | — | (X3, X4) | (PA2, PA3) |
Pyboard Lite 引脚映射
Pyboard Lite 仅支持UART(1)、UART(2)、UART(6):
| 总线 | 位置 | (TX, RX) | 映射 |
|---|---|---|---|
UART(1) | XB | (X9, X10) | (PB6, PB7) |
UART(6) | YA | (Y1, Y2) | (PC6, PC7) |
UART(2) | — | (X1, X2) | (PA2, PA3) |
Pyboard D 引脚映射
Pyboard D 仅支持UART(1)、UART(2)、UART(3)、UART(4):
| 总线 | 位置 | (TX, RX) | 映射 |
|---|---|---|---|
UART(4) | XA | (X1, X2) | (PA0, PA1) |
UART(1) | YA | (Y1, Y2) | (PA9, PA10) |
UART(3) | YB | (Y9, Y10) | (PB10, PB11) |
UART(2) | — | (X3, X4) | (PA2, PA3) |
特别注意:Pyboard D 的
UART(1)位于YA,与 Pyboard / Pyboard Lite 上UART(1)位于 XB、UART(6)位于 YA 的布局不同。移植代码到 Pyboard D 前务必核对引脚。
构造时传入不存在的总线标识会抛出ValueError,这在官方测试 tests/ports/stm32/uart.py 中得到验证:UART(-1, 9600)、UART(0, 9600)、UART(100, 9600)、UART("doesnotexist", 9600)均触发ValueError。
init() 参数详解:从波特率到接收缓冲
init()的完整签名:
UART.init(baudrate, bits=8, parity=None, stop=1, *, timeout=0, flow=0, timeout_char=0, read_buf_len=64)参数语义:
- baudrate:时钟速率(波特率)。
- bits:每字符位数,7、8 或 9。
- parity:
None、0(偶)、1(奇)。 - stop:停止位数,1 或 2。
- flow:流控类型,可为
0、UART.RTS、UART.CTS或UART.RTS | UART.CTS(见下文"硬件流控"章节)。 - timeout:等待写入/读取第一个字符的超时时间,单位毫秒。
- timeout_char:写入或读取时相邻字符之间的等待超时,单位毫秒。
- read_buf_len:接收缓冲区的字符长度,
0表示禁用缓冲。
波特率精度与最低波特率
init()在无法将波特率设置在目标值5% 误差范围内时会抛出异常。最低可达波特率由总线所在的外设时钟频率决定:
UART(1)与UART(6)挂接在APB2总线上,默认总线频率下最低波特率约1300;- 其余 UART 挂接在APB1总线上,默认频率下最低波特率约650。
若需要更低的波特率,可使用 pyb.freq 降低对应总线时钟频率(注意这会影响同总线上的其他外设)。
超时与缓冲的底层实现细节(源码视角)
从 machine_uart.c 的参数解析代码可以印证文档行为并补充若干实现事实:
read_buf_len默认值为64,且被标记为 legacy 参数;实际分配缓冲时会在用户指定值上+1(size_t len = args.read_buf_len.u_int + 1; // +1 to adjust for usable length of buffer),以保证环形缓冲可用的最大长度,打印输出时也会做-1修正;timeout_char并非任意值都生效:源码强制其不小于13000 / baudrate + 2(毫秒),这是保证两个字符之间中断调度可完成的最短间隔,低于该值会被自动抬高;- 参数表中还可见较新的
rxbuf关键字参数会覆盖 legacy 的read_buf_len; - 在 STM32H7 系列上还额外支持
invert=0及INV_TX/INV_RX信号极性反转(见 machine_uart.c),文档所列 API 之外的进阶能力。
deinit() 与对象状态查看
UART.deinit():关闭 UART 总线,释放外设;- 打印 UART 对象会输出其完整配置,如
UART(1, baudrate=9600, bits=8, parity=None, stop=1, flow=0, timeout=1000, timeout_char=0, rxbuf=63)(实现于 machine_uart.c,实际显示内容取决于该对象是否已启用)。
流式读写 API:read / readline / readinto / write
UART 对象是一个流对象(stream),读写采用标准流方法:
uart.read(10) # 读取 10 个字符,返回 bytes 对象 uart.read() # 读取当前所有可用字符 uart.readline() # 读取一行(以换行符结尾) uart.readinto(buf) # 读取并存入给定缓冲区 uart.write('abc') # 写入 3 个字符各方法语义(严格对应 pyb.UART 文档):
read([nbytes]):指定nbytes时最多读取该字节数;若缓冲区已有足够数据则立即返回,否则等待数据到齐或超时。不指定nbytes时读取尽可能多的数据,在timeout到期后返回。返回bytes对象,超时返回None。readline():读取以换行符结尾的一行;已存在完整行则立即返回,超时后无论是否含换行都返回已收集数据。无数据且超时返回None。readinto(buf[, nbytes]):读入buf,指定nbytes时最多读该字节数,否则最多读len(buf)字节。返回实际读入字节数,超时返回None。write(buf):写入缓冲区。7/8 位字符时每字节即一个字符;9 位字符时每个字符占用两个字节(小端序),buf必须包含偶数个字节。返回写入字节数;超时且未写入任何字节时返回None。
9 位字符的读写对称性:
read时若为 9 位字符,每字符占两字节,nbytes必须为偶数,实际字符数为nbytes/2;write时同样要求偶数字节数。9 位模式常用于需要传输带标志位帧(如 9 位地址帧多机通信)的场景。
单字符与查询方法
uart.readchar() # 读取 1 个字符,以整数形式返回 uart.writechar(42) # 写入 1 个字符(整数) uart.any() # 返回等待读取的字符数readchar():返回读到的字符(整数);超时返回-1。writechar(char):char为要写入的整数,返回None。若启用了 CTS 流控,见下文特殊说明。any():返回缓冲区中等待读取的字节数(可能为 0),可用于轮询式接收,避免阻塞。sendbreak():在总线上发送一个中断条件(break condition)——将总线拉低约13 位时长,返回None。常用于唤醒对端设备或触发线路状态中断。
版本兼容性提醒
流式方法read、write等自 MicroPythonv1.3.4起引入;更早版本使用uart.send与uart.recv,在新版本中已不再适用。
RTS/CTS 硬件流控深度解析
流控常量与可用总线
UART.RTS与UART.CTS常量用于init(flow=...)选择流控类型,可单独或组合使用(UART.RTS | UART.CTS)。在源码中它们被定义为硬件寄存器位值(machine_uart.c 中的UART_HWCONTROL_RTS/UART_HWCONTROL_CTS)。
支持硬件流控的引脚(下文"目标"指与 Pyboard 相连的对端设备):
Pyboard V1 / V1.1:UART(2)与UART(3)支持 RTS/CTS。
UART(2):(TX, RX, nRTS, nCTS) = (X3, X4, X2, X1) = (PA2, PA3, PA1, PA0)UART(3):(TX, RX, nRTS, nCTS) = (Y9, Y10, Y7, Y6) = (PB10, PB11, PB14, PB13)
Pyboard Lite:仅UART(2)支持:
(TX, RX, nRTS, nCTS) = (X1, X2, X4, X3) = (PA2, PA3, PA1, PA0)
连接方式与信号极性
当init(flow=...)指定 RTS 和/或 CTS 时,相关引脚被配置为流控用途:
- nRTS:低电平有效(active low)输出;
- nCTS:低电平有效输入,内部上拉使能。
接线规则是交叉连接:
- Pyboard 的
nCTS连接目标的nRTS; - Pyboard 的
nRTS连接目标的nCTS。
CTS:目标设备控制 Pyboard 的发送
启用 CTS 流控后,write()的行为:
- 调用
UART.write(buf)时,只要nCTS为False(低有效,表示对端繁忙),发送即暂停; - 若在超时时间内整个缓冲区未能发完,则发生超时。方法返回已写入的字节数,用户可据此续发剩余数据;
- 发生超时时,UART 中会残留一个正在等待
nCTS的字符,该字符的字节数包含在返回值中。
writechar()的特殊行为:
- 若在
nCTS为False时调用writechar(),方法会等待,若目标未及时拉高nCTS则超时并抛出OSError 116(ETIMEDOUT); - 一旦目标拉高
nCTS,该字符立即被发送。
RTS:Pyboard 控制目标设备的发送
启用 RTS 流控后的行为取决于是否使用接收缓冲:
使用缓冲(read_buf_len> 0):
- 入站字符进入缓冲区;
- 当缓冲区写满后,下一个到达的字符会使
nRTS变为False(通知目标暂停发送);从缓冲区读取字符后nRTS恢复为True。
文档给出一个精确的计数示例:设缓冲长度为N字节。缓冲满后再到达一个字符,nRTS被置False,此时any()返回计数N。当字符被读出后,这个"多出来"的字符会被放入缓冲区,并在后续any()调用中计入结果。
不使用缓冲(read_buf_len== 0):
- 每到达一个字符,
nRTS立即变为False,直到该字符被读出为止——即逐字符流控,适合对延迟敏感、由应用立即消费的接收场景。
流控使用的时机建议
硬件流控适合两类典型场景:一是与无缓冲/小缓冲传感器或模块通信时防止数据丢失;二是高速率(如 921600 及以上)长距离传输时避免软件层来不及取走数据。若对端不支持 RTS/CTS,务必保持flow=0(默认)。
从源码到测试:pyb.UART 的实现与验证路径
实现位置
- Python 层封装:
pyb模块中的 UART 由 machine_uart.c 实现(STM32 移植版),其头文件与底层 HAL 适配见 uart.h 与 uart.c; - REPL 串口:Pyboard 的 USB VCP 之外的调试串口由 main.c 初始化,例如
pyb_uart_repl_obj使用MICROPY_HW_UART_REPL_BAUD波特率、timeout_char=2,并将 UART 挂接到 REPL(uart_attach_to_repl),这解释了为何某些板载 UART(1) 默认被 REPL 占用(测试 uart.py 中对 STM32WB 平台直接SKIP的原因)。
官方测试要点
tests/ports/stm32/uart.py 覆盖了本文所述核心行为:
- 非法总线标识触发
ValueError; - 构造、
init()变参重初始化、print(uart)输出规范化(波特率取整到偶数以跨 MCU 一致); any()/write()/writechar()/sendbreak()的返回值验证;- 非阻塞模式(
timeout=0)下的写行为与read(100)返回None; rxbuf=8/rxbuf=0与 legacyread_buf_len=4/read_buf_len=0的缓冲配置切换。
对于连接了真实硬件回环(TX-RX 短接)的板子,可自行验证read/readinto/readline的完整读写闭环。
实战要点总结
- 选对总线:优先使用
UART(1)/UART(6)(APB2,默认最低波特率 1300);需要更低波特率时先调低总线时钟(pyb.freq),再验证init()是否在 5% 误差内成功。 - 帧格式匹配:对端为 8N1(8 数据位、无校验、1 停止位)时用
init(9600);9 位地址帧模式记得读写时按"每字符 2 字节"处理且保证nbytes为偶数。 - 超时设计:
timeout管首字符,timeout_char管字符间隔;接收侧推荐轮询any()或使用带rxbuf的缓冲读取,避免read()长时间阻塞。 - 流控接线:交叉连接 nRTS↔nCTS,注意均为低电平有效;CTS 会导致
write()返回部分写入数、writechar()可能抛OSError 116,代码需容忍并处理。 - 兼容性:老代码中的
send/recv需改写为write/read;Pyboard D 的引脚布局与 V1 不同,跨板移植必须重新核对 引脚映射。
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考