ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

树莓派Pico串口通信实战:从UART原理到MicroPython调试全攻略

树莓派Pico串口通信实战:从UART原理到MicroPython调试全攻略 1. 项目概述与方案选型最近在调一个用树莓派 Pico 采集传感器数据、再把数据通过串口上报给上位机的项目前前后后折腾了几天踩了不少坑。这篇文章就把 Pico 串口通信这件事一次性讲透——从硬件引脚到 MicroPython 编程再到调试工具怎么选怎么用全部按我实际走过的流程来写。如果你正准备用 Pico 做串口相关的开发或者已经被串口通信的“玄学问题”折磨过这篇应该能帮你少走很多弯路。先说结论Pico 的串口通信本质上就是 UARTUniversal Asynchronous Receiver/Transmitter通用异步收发器这是嵌入式领域最基础也最常用的通信方式之一。它不需要时钟线只需要两根数据线发送 TX 和接收 RX就能实现两个设备之间的双向通信非常适合单片机之间、单片机与 PC 之间、单片机与 GPS/蓝牙/传感器模块之间的数据交互。在正式进入细节之前我先说说为什么选择 Pico 作为串口通信的开发平台以及它在这个场景下有哪些不可替代的优势。1.1 为什么选 Pico不只是“便宜”这么简单很多人一提起树莓派 Pico第一反应就是“这玩意儿才十几块钱”但真正做项目的时候你会发现Pico 的价值远不止价格。首先是它的主控芯片 RP2040 采用双核 ARM Cortex-M0 架构主频可以跑到 133MHz对于串口通信这种对算力要求不高的任务来说性能绰绰有余甚至可以同时处理多路 UART 加 PWM 输出。更重要的是RP2040 内置了 2 个硬件 UART 控制器分别对应 UART0 和 UART1。这意味着你可以在同一块 Pico 上同时跑两路独立的串口通信比如一路接 GPS 模块一路接上位机互不干扰。我实际测试过在 115200 波特率下双路 UART 同时收发数据没有任何丢包或数据错乱的情况。另外Pico 对 MicroPython 的支持非常完善。官方固件直接集成了machine.UART模块写串口程序就像写 Python 一样简单不需要像 STM32 那样去翻寄存器手册、配置中断优先级这对快速原型验证来说简直是神器。当然如果你追求极致的实时性和稳定性也可以用 C/C SDK 开发但作为日常调试和小型项目MicroPython 完全够用。还有一个容易被忽略的点Pico 的引脚兼容 3.3V 逻辑电平而大多数常见的传感器模块如 GPS、蓝牙、LoRa 等也都是 3.3V 电平直接相连无需电平转换。相比之下老派的 5V 单片机如 Arduino Uno和 3.3V 模块连接时还需要考虑电平匹配问题Pico 在这个层面省了不少事。1.2 串口通信的知识铺垫一次说清 UART 的原理在配置 Pico 之前有必要花点时间把 UART 通信的基本原理梳理一遍。虽然 MicroPython 封装得已经很好了但如果你不理解底层的机制遇到问题时会非常被动。UART 通信的核心是异步传输也就是说通信双方不需要共享时钟信号而是通过约定的波特率来同步数据位。所谓波特率就是每秒传输的比特数常用的有 9600、57600、115200 等。通信双方必须设置完全相同的波特率否则接收端采样的位置会偏移导致收到乱码。数据帧的结构是空闲时数据线保持高电平发送数据时先拉低一个位宽作为起始位Start Bit然后依次发送 5~8 个数据位通常为 8 位接着是可选的奇偶校验位Parity Bit最后是 1 或 2 个停止位Stop Bit通常为 1 位。接收端检测到下降沿高电平跳变为低电平就认为是起始位开始然后按照波特率逐个采样数据位。这里有一个很多新手会犯迷糊的地方UART 的发送引脚TX要连接到对端设备的接收引脚RX反之亦然。也就是说Pico 的 TX 接外部设备的 RXPico 的 RX 接外部设备的 TX这是交叉连接的。我见过不少人因为这个问题折腾了半天最后发现只是 TX/RX 接反了。还有一个概念需要区分UART 和 USB 转串口是两回事。UART 是芯片级别的通信协议而 USB 转串口是一个硬件桥接方案。Pico 板载的 Micro-USB 接口连接的是 RP2040 的 USB 控制器默认用于供电和程序下载它并不直接等同于一个串口。要让 Pico 通过 USB 和电脑通信需要借助 MicroPython 的machine.UART配合板载 USB 转串口芯片或者使用额外的 USB 转 TTL 模块。这一点在后面的实操部分会详细讲。提示Pico 上的 UART 是 3.3V TTL 电平如果你要用它和 RS232 电平的设备通信必须加 MAX3232 之类的电平转换芯片。不要直接连不然大概率烧引脚。2. Pico 硬件特性与串口引脚规划搞清楚了原理接下来就要动手接线了。Pico 的引脚虽然多但真正和 UART 相关的就那么几个规划不好容易踩坑。我先带你把 Pico 的硬件特性过一遍再给出具体的引脚分配方案。2.1 RP2040 的 UART 资源与引脚分布RP2040 芯片内部有 2 个 UART 外设每个 UART 支持多个引脚映射选项这意味着你可以在不同 GPIO 上复用 UART 功能。官方数据手册给出的映射关系如下UART 编号TX 可选引脚RX 可选引脚UART0GP0、GP12、GP16GP1、GP13、GP17UART1GP4、GP8、GP20GP5、GP9、GP21默认情况下MicroPython 的UART(0)使用 GP0 作为 TX、GP1 作为 RXUART(1)使用 GP4 作为 TX、GP5 作为 RX。这是最常用的配置如果你没有特殊需求建议就用默认引脚因为 Pico 的板子上丝印标识很清楚不容易接错。不过在规划引脚时有个容易忽略的细节有些 GPIO 同时具备其他复用功能比如 I2C、SPI、PWM、ADC 等。如果你的项目同时要用到这些外设就要合理分配引脚避免冲突。举个我踩过的例子我之前想把 UART0 放在 GP12/GP13 上因为 GP0/GP1 被 I2C 占用了结果调了半天才发现 GP12/GP13 在有些 Pico 板子版本上和板载 LED 有冲突虽然也能用但会导致 LED 状态异常排查起来非常费劲。2.2 实际接线方案从 Pico 到 USB 转 TTL 模块在调通板间串口之前我最推荐的第一步是让 Pico 和电脑通信。这需要用到 USB 转 TTL 模块常见的有 CH340、CP2102、FT232 这几类芯片的方案。我个人比较推荐 CP2102 模块驱动兼容性好在 Windows、macOS、Linux 下基本上即插即用。CH340 便宜但驱动在某些系统上偶尔会有兼容问题。接线方式如下Pico 的 GP0TX → USB 转 TTL 模块的 RXPico 的 GP1RX → USB 转 TTL 模块的 TXPico 的 GND → USB 转 TTL 模块的 GND注意不要把 USB 转 TTL 模块的 5V 接到 Pico 上。Pico 的 VBUS 引脚确实支持 5V 输入但大多数 USB 转 TTL 模块在 5V 模式下输出的逻辑电平也是 5V直接接到 Pico 的 3.3V GPIO 上有烧毁风险。正确做法是如果需要给 Pico 供电单独用 USB 线接电脑或充电头如果模块支持 3.3V 跳线把跳线帽拔到 3.3V 档然后用它的 3.3V 输出给 Pico 供电也可以但要确保模块的输出电流足够Pico 正常工作电流约 20~50mA模块一般都能满足。还有一个容易忽视的点共地。串口通信虽然只有 TX 和 RX 两根数据线但通信双方必须有共同的参考地也就是 GND 必须相连。如果你只是接了 TX/RX 而忘记接地数据会完全乱掉甚至完全收不到。这是我调试过程中犯过的最基础的错误提醒大家务必记住。2.3 供电与电平相关的注意事项Pico 的 GPIO 是 3.3V 逻辑但有一个特殊情况它的 ADC 输入电压范围是 0~3.3V而板载的 VSYS 引脚可以接受 1.8~5.5V 的宽范围供电。这意味着如果你要测量外部电压不能直接用 ADC 引脚去量 5V否则会损坏 ADC。这和串口通信相关的地方在于很多传感器模块会同时提供 3.3V 和 5V 供电选择如果你用了 5V 供电但信号线是 3.3V 逻辑的通常是可以直连的5V 供电的模块内部一般会做电平转换但反过来就很危险。另外Pico 的每个 GPIO 引脚的最大注入电流和输出电流都有严格限制官方给出的极限值是单引脚 50mA但长期稳定运行建议控制在 20mA 以内。串口通信本身电流很小一般只有几毫安所以不用太担心但如果你把 GPIO 同时用于驱动 LED 或蜂鸣器就要注意总电流不要超过芯片的承受范围。3. MicroPython 环境搭建与串口编程实战硬件接线准备就绪后接下来进入软件层面。这一部分我会从 MicroPython 固件的烧录开始逐步带你完成串口通信的完整代码编写并解释每一段代码背后的设计思路。3.1 固件烧录与开发环境选择如果你的 Pico 还没烧录过 MicroPython 固件需要先按住 Pico 板子上的 BOOTSEL 按钮然后用 USB 线连接到电脑。这时候电脑上会弹出一个名为“RPI-RP2”的 U 盘把下载好的 MicroPython UF2 固件文件直接拖进去Pico 会自动重启固件就烧录完成了。开发环境方面Thonny 是我用下来最顺手的选择。它内置了 MicroPython 的 REPL 环境可以直接在编辑器里写代码、按 F5 运行还能在下方看到串口输出和交互式解释器。如果你更喜欢命令行也可以用mpremote工具连接 Pico 操作但我个人觉得 Thonny 对新手更友好。连接上之后先做一个简单的串口测试。打开 Thonny在 REPL 窗口输入from machine import UART uart UART(0, baudrate115200) uart.write(Hello Pico UART\n)如果这时候你还没有接 USB 转 TTL 模块这段代码会把数据发送到 GP0 引脚上你暂时看不到任何输出。但如果你按照上一节的接线方式连好了 USB 转 TTL 模块并在电脑上打开一个串口助手比如 PuTTY 或 minicom把波特率设为 115200就能收到“Hello Pico UART”这串字符。3.2 最基础的 UART 收发示例与逐行解释串口通信最基本的功能就是两个方向的数据收发。下面是一个完整的收发示例我把每一行的作用都标注清楚from machine import UART, Pin import time # 初始化 UART0波特率 1152008 位数据位无校验1 位停止位 # 这是最常用的 8N1 格式 uart UART(0, baudrate115200, bits8, parityNone, stop1, txPin(0), rxPin(1)) def send_data(data): 发送数据传入 bytes 或 str 类型 if isinstance(data, str): data data.encode(utf-8) uart.write(data) print(发送:, data) def receive_data(timeout_ms100): 接收数据返回收到的 bytes超时返回 None start time.ticks_ms() buffer b while time.ticks_diff(time.ticks_ms(), start) timeout_ms: if uart.any(): buffer uart.read(1) # 逐字节读取确保不丢失 return buffer if buffer else None # 主循环每 2 秒发送一次数据并等待接收 while True: send_data(Hello from Pico\n) response receive_data(200) if response: print(收到:, response) time.sleep(2)这段代码里有几个值得深挖的点第一uart.any()方法返回接收缓冲区中等待读取的字节数。这是判断“有没有数据到达”的常用手段比直接read()更可靠因为read()在没有数据时会阻塞等待如果设置了超时还好没设置超时可能会卡死整个程序。第二uart.read(1)每次只读一个字节然后拼接到缓冲区里。这种方式在数据量小的时候没问题但如果数据量很大逐字节读取会很慢。更好的做法是先用any()确认缓冲区大小再一次性read(n)读出来。我给出的代码是一个平衡方案既保证了不丢数据又不会太复杂。第三发送端的编码问题。uart.write()需要接收bytes类型的数据如果你传入的是字符串需要先.encode(utf-8)。很多新手在这个地方踩坑因为 MicroPython 的write()并不像 Python 3 的文件操作那样自动编码它默认要求 bytes。3.3 参数设置的“为什么”波特率、数据位、校验位、停止位前面代码里设置了baudrate115200, bits8, parityNone, stop1这就是 UART 通信中最常见的“8N1”格式。你可能好奇这些参数到底怎么选我来逐一解释。波特率115200 是绝大多数 USB 转 TTL 模块和传感器模块的默认波特率选择它最不容易出错。9600 在要求不高的场景下也常用但传输速度慢。理论上波特率越高传输越快但误码率也会增加。对于 Pico 这种 133MHz 主频的芯片来说115200 在短距离1 米内的稳定性和速度之间是最平衡的。如果你的项目涉及长距离布线超过 2 米建议降到 9600 或 38400否则信号衰减容易导致乱码。数据位通常选 8 位。早期的 UART 有 7 位数据位模式那是为了传输 ASCII 字符7 位够用但现在 8 位已经是事实标准因为 8 位正好是一个字节。校验位None 表示无校验。奇偶校验用来检测数据在传输过程中是否发生错误但它只能检测错误不能纠正错误。对于大多数传感器数据传输和调试场景基本用不到校验位。如果你和设备端的协议里明确要求有校验位比如 Modbus 协议默认就有 CRC 校验那就按协议来但在裸串口通信中None 是最稳妥的。停止位选 1 位。停止位的作用是标识一个数据帧的结束给接收端一个“缓冲”的时间。理论上 2 位停止位更可靠但会降低有效传输速率。实际测试中1 位停止位在 115200 波特率下完全够用。3.4 进阶用法用中断或轮询处理不定长数据包在真实项目中你收到的往往不是固定的“Hello”而是不定长的协议数据包。比如 GPS 模块输出的 NMEA 语句每条以$开头、以换行符结尾长度不固定。这时候逐字节读取并判断结束标志是最常见的处理方式。我写一个 NMEA 数据解析的例子展示如何用一个状态机处理不定长数据from machine import UART, Pin import time uart UART(1, baudrate9600, txPin(4), rxPin(5)) BUFFER_MAX 128 # 防止缓冲区溢出 data_buffer b def parse_nmea(line): 简单解析 NMEA 语句的示例 if line.startswith(b$GPGGA): parts line.split(b,) if len(parts) 6: lat parts[2] lon parts[4] return f纬度: {lat}, 经度: {lon} return None while True: if uart.any(): char uart.read(1) if char b\n: # 遇到换行符说明一条完整语句结束 result parse_nmea(data_buffer) if result: print(result) data_buffer b # 清空缓冲区 else: data_buffer char if len(data_buffer) BUFFER_MAX: data_buffer b # 防止异常数据导致缓冲区溢出这种“逐字节读取 结束标志判断”的模式非常实用几乎可以处理所有串口协议。你需要根据实际协议调整结束标志可能是\n、\r\n、;或者自定义字节。如果你需要更高效率的数据处理可以考虑使用中断。MicroPython 的UART对象不支持直接注册外部中断回调但你可以写一个定时器中断周期性检查uart.any()。不过对于大多数应用主循环轮询的延迟完全够用不建议一开始就把复杂度提上去。3.5 双机通信两块 Pico 之间的 UART 直连串口通信的经典场景是两块单片机之间互相通信。我这里用两块 Pico 示例Pico A 定时发送传感器数据Pico B 接收并处理。接线方式如下Pico A 的 GP0TX → Pico B 的 GP1RXPico A 的 GP1RX → Pico B 的 GP0TXPico A 的 GND → Pico B 的 GND两块板子的 GND 必须相连否则参考电平不同数据会完全错乱。这一点再次强调非常重要。发送端代码from machine import UART, Pin import time uart UART(0, baudrate115200, txPin(0), rxPin(1)) counter 0 while True: message fpacket_{counter} uart.write(message \n) counter 1 time.sleep(1)接收端代码from machine import UART, Pin import time uart UART(0, baudrate115200, txPin(0), rxPin(1)) buffer b while True: if uart.any(): char uart.read(1) if char b\n: print(收到完整数据包:, buffer) buffer b else: buffer char这里有个容易踩的坑两块 Pico 使用相同的默认引脚GP0/GP1但它们是交叉连接的所以发送方接的是接收方的 RX接收方接的是发送方的 TX。很多人会误以为“同样的引脚编号直接连起来就行”结果 TX 对 TX、RX 对 RX当然通信失败。4. 调试工具与实战排查技巧最后这部分是我最想分享的内容因为在实际开发中代码写错还好调整最怕的是串口连上了但数据是乱的、或者是收到一半就断了。这类问题的排查主要靠合适的调试工具和方法论。4.1 常用串口调试工具横评与选择建议市面上的串口调试工具五花八门我按场景给你推荐几个工具适用系统特点适用场景PuTTYWindows/Linux轻量、稳定、支持串口快速查看串口原始输出minicomLinux经典命令行工具功能全面嵌入式 Linux 开发板交互串口助手Windows 软件Windows图形界面支持 HEX 显示和自动发送协议调试、报文分析Thonny内置全平台集成 REPL 和串口监控MicroPython 开发调试逻辑分析仪全平台硬件级信号采样能看到波形排查电平/时序问题Saleae Logic全平台软件强大支持 UART 协议解码深层次协议分析对于快速验证我用得最多的是 PuTTY 和串口助手。PuTTY 适合打开一个串口就一直盯着输出串口助手适合做双向交互——一边发指令一边看响应。但如果你的项目涉及多个设备、多路 UART 同时工作这种软件工具就不够用了。这时候强烈建议入手一个逻辑分析仪价格从几十到几百不等但能给你带来“开天眼”级别的调试体验。它会直接采样引脚上的电平变化通过软件解码出 UART 的数据帧你可以直观地看到波特率是否匹配、数据位是否正确、有没有干扰毛刺。4.2 调试前必须做的五个基础检查在打开任何调试工具之前建议按照下面五个步骤过一遍可以省去很多“排查半小时、结果发现线没插好”的尴尬第一检查接线是否交叉。Pico 的 TX 必须接对端的 RXPico 的 RX 必须接对端的 TX。用万用表蜂鸣档测一下通路或者直接目视确认别凭记忆。第二检查共地。确认通信双方的 GND 引脚已经相连。这是 UART 通信的前提条件。第三检查波特率是否一致。发送端和接收端的波特率必须完全一致差一点点都会导致乱码。有些模块标称 115200但实际因为晶振误差导致有偏差如果出现间歇性乱码可以尝试把波特率微调一下比如改成 115200 附近的其他值试试。第四检查引脚是否被占用。MicroPython 初始化 UART 时如果指定的 TX/RX 引脚已经被其他对象如 I2C、SPI初始化过会引发异常或导致通信失败。可以在初始化 UART 之前用Pin(0, Pin.IN)把引脚重置一下或者干脆换一对引脚。第五检查电压逻辑电平。确认两端的逻辑电平都是 3.3VPico 和大多数模块或者经过电平转换。如果一端是 3.3V、另一端是 5V最好加电平转换模块或分压电路。4.3 常见问题排查实录与速查表我在实际调试中遇到过各种奇奇怪怪的问题挑几个最有代表性的分享一下。现象一完全收不到任何数据。排查思路先用逻辑分析仪或示波器看发送端 TX 引脚有没有波形输出。如果没波形说明发送端代码根本没在发数据如果有波形再看波形幅度是不是 3.3V如果只有 1V 左右说明后级负载太重或者接线有问题如果波形正常再检查接收端 RX 引脚有没有收到波形。我用这个方法定位过一次问题Pico 代码没问题但 USB 转 TTL 模块坏了导致数据完全传不出去。现象二收到的是乱码。这个问题 90% 是波特率不匹配。但也可能是数据位/停止位配置不一致比如发送端是 8N1接收端配成了 7E1就会错乱。还有一个隐蔽原因如果你用的是 USB 转 TTL 模块模块内部的晶振精度也有影响但通常不会严重到产生乱码。如果排除了这些可以试试把波特率降低到 9600看是否恢复正常这能区分是线路干扰还是配置错误。现象三数据能收到但偶尔会多出几个字符或者丢字符。这种情况通常是电平干扰或者供电不足。如果是长导线超过 20cm推荐用带屏蔽的双绞线或者降低波特率。如果 Pico 供电不稳可以外接一个稳定的 3.3V 电源。此外看看发送端和接收端是否共用了同一个电源但有压降这也是常见干扰源。现象四上位机收不到 Pico 的数据但 Pico 能正常打印。这可能不是串口通信的问题而是你用错了串口通道。Pico 的板载 USB 串口通过 REPL 打印和你用 GP0/GP1 配置的 UART 是两条完全独立的通道。如果你把 print() 的输出当成要发给上位机的数据那就大错特错了。print() 只会发到 REPL 的 USB 串口不会出现在 GP0 引脚上。要用 GP0/GP1 发送数据必须用uart.write()。我把上述问题整理成一个速查表方便你排查时对照问题可能原因排查方法完全收不到数据接线错误检查 TX/RX 是否交叉、GND 是否相连波特率不匹配确认双方波特率一致引脚被占用初始化前重置引脚或换引脚模块故障用逻辑分析仪看 TX 输出波形收到乱码波特率不匹配降低波特率尝试数据位/校验位不一致检查两端 bits/parity/stop 配置线路干扰缩短线缆、使用屏蔽线间歇性丢数据供电不稳定使用稳定的外部电源缓冲溢出增加接收缓冲或提高读取频率上位机收不到数据print() 和 uart.write() 混淆确认使用的是 uart 对象发送4.4 独家经验善用回环测试与虚拟串口最后分享几个我觉得非常实用的小技巧。第一个是回环测试。把 Pico 的 TX 引脚直接短接到 RX 引脚用一根杜邦线连起来然后跑一段代码发送数据的同时读取同样的数据如果收到的和发送的一致说明 UART 外设和 MicroPython 配置都没问题。这个测试只要几秒钟能帮你把问题范围缩小一大半——如果是通的说明问题出在外部设备或线路上如果不通问题在 Pico 自身的软件配置。第二个是虚拟串口。如果你没有实体串口设备有些工具可以直接创建一对虚拟串口比如 Windows 的 com0com 工具、Linux 的 socat一端让 Pico 连一端让上位机软件连可以在没有真实硬件的情况下调试协议逻辑。我经常用它来模拟一个假的传感器设备测试上位机的数据处理逻辑非常方便。第三个是加时间戳。在调试不定长数据包时给每一条收到的数据前面加上time.ticks_ms()的时间戳能帮助你分析数据是否连续、间隔是否稳定。如果两条数据之间的时间间隔忽大忽小说明发送端可能有不稳定的延迟需要从发送逻辑上排查。第四个是用 JSON 打包数据。如果传输的数据结构比较复杂比如包含多种传感器数值建议在发送端用 JSON 序列化、接收端用 JSON 解析。虽然 JSON 比二进制协议有额外开销但在开发阶段能极大提高可读性和排错效率。Pico 的 MicroPython 内置了ujson模块用法和标准 Python 的json几乎一样。import ujson # 发送端 payload ujson.dumps({temp: 25.6, humidity: 60.2, id: 1}) uart.write(payload \n) # 接收端 data uart.readline().strip() obj ujson.loads(data) print(obj[temp])提示用readline()会比逐字节读取更高效前提是发送端在每条数据的末尾加了换行符。MicroPython 的 UART 模块内置了按行读取的逻辑在大多数场景下都可以直接用。根据我个人这段时间折腾 Pico 串口通信的经验最核心的收获就是串口通信其实不难难的是把每一层的细节都做好。硬件上注意电平匹配、共地、交叉接线软件上注意波特率一致、数据格式统一、缓冲管理再用逻辑分析仪这把“照妖镜”辅助定位大部分问题都能在十分钟内解决。最后再提醒一句拿到任何一个新的串口模块先跑一遍回环测试再接到你的 Pico 上这个习惯能帮你避免无数个无语的下午。
返回列表