ARTICLE DETAIL

资讯详情

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

树莓派Pico串口通信实战:从硬件特性到MicroPython调试全攻略

树莓派Pico串口通信实战:从硬件特性到MicroPython调试全攻略 做嵌入式开发的朋友对串口通信应该都不陌生。调试日志、传感器数据采集、上位机交互、固件升级串口几乎是每个项目里跑不掉的一环。树莓派Pico这块基于RP2040的小板子虽然定位是入门级MCU但它在串口方面一点都不含糊——两个硬件UART、可灵活映射的引脚、MicroPython和C SDK两套开发方式并存让它成了我这两年在原型验证阶段用得最顺手的小板子。这篇东西就从Pico的串口硬件特性讲起给一套MicroPython下可以直接拿走的收发代码再聊聊调试工具和工程里容易踩的坑。如果你正打算用Pico做上位机通信、接传感器模块或者串口控制舵机这篇应该能帮你省下不少试错时间。网上关于Pico串口的资料挺散的很多帖子只讲了一个点比如怎么开一个UART、怎么print但很少有人把硬件特性、编程方式和调试工具串成一条完整链路。我这篇试着把整条链路讲透从引脚电平一直聊到逻辑分析仪看波形该上代码的地方直接上代码该讲原理的地方也不含糊。1. RP2040串口硬件特性写代码前先摸清底细1.1 两个硬件UART引脚映射到底有多灵活RP2040内部集成了两个硬件UART外设官方命名是UART0和UART1。每个UART在芯片层面都是独立的外设有自己的波特率发生器、发送接收FIFO、中断控制逻辑。和STM32那种外设固定绑定某几个引脚的做法不同RP2040的每一个GPIO引脚都支持功能复用可以通过IO MUX把UART功能映射到几乎任意引脚上。MicroPython里初始化UART时直接指定txPin(0), rxPin(1)底层固件会帮你完成引脚复用配置你不需要像用C SDK那样手动查datasheet去设置功能选择寄存器。从实际选型角度看两个UART对绝大多数个人项目完全够用。一个UART接PC上位机做数据交互另一个UART接GPS模块或者舵机控制板互不干扰。但如果你要做多路RS485总线比如同时挂十几台电表或者多个传感器节点两个UART就紧张了。我的建议是要么换用带更多UART的RP2350系列要么加一颗UART扩展芯片像SC16IS752这种SPI/I2C转双UART的方案这两个路子我都实测过RP2350在软件层面和RP2040几乎兼容迁移成本很低SC16IS752则胜在接什么MCU都行灵活性更高。这里有个新手特别容易踩的坑虽然理论上任意引脚都能做UART但Pico板载的USB口占用了GPIO0和GPIO1对应USB D-和D而且MicroPython虚拟U盘功能也依赖这两个引脚。如果你把UART0的TX配到GPIO0、RX配到GPIO1且同时还想用USB连接电脑轻则通信异常重则板子直接被识别成未知设备。我自己的习惯是避开GPIO0/1UART0固定在GPIO4/5或者GPIO16/17既不影响USB也方便飞线。每个UART外设内置了32字节的发送FIFO和32字节接收FIFO这个容量对于正常的数据收发足够用了。如果上位机突发一大包数据FIFO可以帮你缓冲一部分配合MicroPython的uart.read()读取不容易丢数据。FIFO还有水位线中断功能不过在MicroPython里你一般感知不到这层细节固件已经帮你处理好了。1.2 波特率、帧格式和流控参数不是随便选的RP2040的UART波特率由系统时钟默认125MHz分频而来MicroPython里直接写baudrate115200或者baudrate9600就行底层会自动计算分频系数并写入寄存器。我实测过从1200到921600的波特率表现都很稳。但波特率不是越高越好关键看你用什么线连接、连接距离多长、对端设备支不支持。我根据实际项目整理了三个参考场景第一115200是绝对的主力日志输出、调试帧下发、和绝大多数传感器模块通信都能胜任在杜邦线短接或者PCB走线场景下非常可靠。第二9600到19200适合老式设备和长线传输比如RS485总线距离超过50米时或者对端是电表、老式工控屏这类设备它们多半只支持低速。第三460800以上属于高速通信只有USB转串口芯片直连、线缆很短、对端芯片缓冲能力足够时我才建议使用常见于固件升级或者大批量数据传输。帧格式方面MicroPython的UART构造函数支持bits、parity、stop三个参数默认配置是8位数据位、无校验、1位停止位也就是常说的8N1这个格式和绝大多数设备兼容。如果你的对端设备要求的是7位数据位加偶校验比如某些欧系仪表可以通过参数调整UART(0, baudrate9600, bits7, parity1, stop1)其中parity0表示无校验parity1表示偶校验parity2表示奇校验。这个细节很多人容易搞混我写程序时习惯在初始化后立刻打印一下当前配置确认无误再继续。硬件流控方面RP2040的UART支持RTS和CTS引脚MicroPython初始化时可以用flow0关闭流控、flow1RTS、flow2CTS、flow3RTSCTS来配置。但说句实在话在Pico这种场景下绝大多数项目用不到硬件流控。PC串口工具和Pico之间的数据量一般不大FIFO配合软件处理就足够了。只有对端是高速设备、且数据量很大时硬件流控才有实际价值。1.3 RS232和RS485电平转换工控场景绕不开的坎Pico的UART输出的是3.3V TTL电平也就是0V代表低电平、3.3V代表高电平。但PC串口RS232使用的是正负电压逻辑通常是-12V到12V两者直接对接轻则通信失败重则烧毁引脚。接RS232设备时必须通过MAX3232这类电平转换芯片。反过来RS485是差分信号通过A/B两线的电压差来表示逻辑状态抗干扰能力强适合长距离和总线组网需要MAX485或者SP3485这种转换芯片而且RS485是半双工的收发共用一组差分线。用RS485时有个关键操作发送数据前必须把DE发送使能引脚拉高数据发送完成后拉低这样能让总线释放给其他节点。在MicroPython里可以这样实现from machine import UART, Pin import time de Pin(10, Pin.OUT) # DE/RE引脚 uart UART(0, baudrate9600, txPin(4), rxPin(5)) def send_485(data): de.value(1) # 拉高DE进入发送模式 uart.write(data) uart.flush() # 等待发送完成 de.value(0) # 拉低DE恢复接收模式如果不做flush()就直接拉低DE很可能出现最后一个字节被截断的情况因为write()只是把数据放进FIFO并不代表物理发送完毕。这一点我在多节点RS485总线上吃过亏后来养成了每次发送后都调flush()的习惯。另外需要注意的是RS485总线两端必须接120欧姆的终端匹配电阻否则信号反射会非常严重。我用两个60欧姆串联的接法效果也不错但一颗120欧姆最省事。MicroPython代码本身不需要感知电阻的存在但物理层不接这个电阻通信距离一长就会偶发错误帧。2. MicroPython串口编程实战从第一行代码到能跑的项目2.1 固件准备与最小初始化代码要在Pico上跑MicroPython第一步是烧录固件。去MicroPython官网下载.uf2格式的固件文件按住Pico板子上的BOOTSEL按键用USB线连接到电脑板子会以U盘模式出现把uf2文件拖进去几秒钟后固件烧录完成板子会自动重启。之后用Thonny或者Mu这类编辑器连接板子就能看到REPL交互界面了。最小初始化代码非常简单from machine import UART, Pin uart0 UART(0, baudrate115200, txPin(0), rxPin(1), bits8, parityNone, stop1)这行代码做的事情包括使能UART0外设、配置引脚复用、设置波特率分频、配置帧格式以及初始化FIFO。UART1的初始化方式完全一样只是编号改成1然后根据你接线的引脚指定tx和rx。我用的是UART0配GPIO4/5uart1 UART(1, baudrate115200, txPin(4), rxPin(5))这里有个小细节如果你不确定某个引脚能不能用作UART可以直接看官方pico-sdk的datasheet表格里面有每个GPIO的复用功能对照。MicroPython固件本身在启动时会为内置功能预留部分引脚比如GPIO0/1会被USB占用GPIO24/25/29等引脚也有各自的用途尽量避开它们就好。2.2 收发数据的核心API和三种接收模式MicroPython的UART类提供了一套很简洁的API常用的是这几个uart.write(data)发送数据data可以是字符串或者字节串返回实际发送的字节数uart.read(n)读取最多n个字节如果n省略或不写则读取所有可用字节uart.readline()读取一行直到遇到换行符\n为止uart.any()返回接收缓冲区中当前可读的字节数uart.flush()等待所有数据发送完毕uart.deinit()关闭串口释放资源发送数据的代码很简单uart0.write(bHello Pico\r\n)注意b前缀表示字节串。如果直接写Hello Pico在MicroPython里也能发送但底层会先编码成UTF-8字节串建议养成用字节串的习惯避免中文编码问题。接收数据有几种常见模式我按实际使用频率介绍一下。第一种是轮询模式循环检查any()while True: if uart0.any(): data uart0.read() print(recv:, data)这种模式简单直接适合主循环本来就在不断运行的场景。但如果你在主循环里还要处理其他任务比如读取传感器、控制PWM这种阻塞式的轮询就会显得比较呆。第二种是中断回调模式MicroPython的UART对象本身没有直接暴露中断回调接口但可以用machine的irq机制配合uasyncio或者select实现import select import machine def wait_uart(): select.select([uart0], [], [], 0.1) if uart0.any(): data uart0.read() print(recv:, data) # 在主循环里调用 wait_uart()第三种是异步模式用uasyncio协程处理。这种模式我比较推荐尤其是Pico同时要跑WiFi、控制伺服、处理串口的时候。下面是一段简单的异步串口接收示例import uasyncio from machine import UART, Pin uart0 UART(0, baudrate115200, txPin(0), rxPin(1)) async def echo_task(): while True: if uart0.any(): data uart0.read() print(async recv:, data) await uasyncio.sleep_ms(5) uasyncio.run(echo_task())这里的原因要说清楚Pico的MicroPython没有真正的多线程uasyncio通过协程调度的方式让你可以同时处理多个I/O任务不会因为等待串口数据而卡死其他功能。在实际项目中我通常用select.select搭配uasyncio.sleep_ms既能及时响应串口事件又不会浪费CPU。关于接收超时MicroPython的UART构造函数支持timeout和timeout_char参数单位是毫秒。timeout是读取整个数据块的超时时间timeout_char是字符间超时时间。比如要接收一帧不定长的数据可以设置timeout_char20意思是如果20毫秒内没有新字符到达就认为这一帧接收结束了。这种方式对于解析AT指令这类以时间间隔切分帧的场景很实用。2.3 串口控制舵机协议解析与PWM输出的组合热搜词里有树莓派pico控制舵机串口控制舵机是一个特别典型的Pico应用场景上位机或者另一个MCU通过串口发送角度指令Pico解析后用PWM驱动舵机旋转。这里面的核心难点不是PWM而是如何设计一个稳定、可扩展的协议。我常用的协议格式是一帧包含一个字母指令类型和若干个数字参数以换行符结尾。例如S45\n S180\n其中S表示舵机角度指令后面的数字是目标角度。这个格式比直接用裸数字好得多因为你可以扩展其他指令比如L1\n控制LED、M1 1000\n控制电机转速。解析代码可以写成这样from machine import Pin, PWM, UART import uasyncio servo PWM(Pin(15)) servo.freq(50) # 舵机需要50Hz PWM信号 uart0 UART(0, baudrate115200, txPin(0), rxPin(1)) def set_servo_angle(angle): # 0度对应0.5ms高电平180度对应2.5ms高电平 # 20ms周期下16位占空比对应关系如下 duty int(1638 angle * 36.4) # 0.5ms/20ms*655351638, 2.5ms/20ms*655358192 servo.duty_u16(duty) async def command_handler(): buf b while True: if uart0.any(): buf uart0.read() while b\n in buf: line, buf buf.split(b\n, 1) line line.strip() if line.startswith(bS): try: angle int(line[1:]) angle max(0, min(180, angle)) set_servo_angle(angle) print(servo -, angle) except ValueError: print(invalid angle) await uasyncio.sleep_ms(1) uasyncio.run(command_handler())这个代码里有两个细节值得注意。第一接收缓冲区用的是buf累积再按换行符切分这样即使一条指令被TCP/IP或者USB转串口拆成了两段到达也能正确处理。第二angle被强制限制在0到180之间防止非法数据导致舵机猛转损坏机械结构。PWM频率必须设置为50Hz也就是20毫秒周期这是标准舵机的控制信号频率。有些高速舵机或者数字舵机支持更高的频率但大多数模拟舵机是50Hz设成50Hz最稳妥。duty的计算我直接在注释里写了推导过程如果你从其他地方抄来的代码用的数值不一样可以自己算一遍避免舵机角度偏差。2.4 把REPL搬到硬件串口os.dupterm的高级玩法Pico的MicroPython默认把REPL绑定在USB虚拟串口上你通过USB连接就能交互。但有些项目需要把REPL重定向到硬件UART比如你远程通过无线串口模块连接Pico或者板子装进机箱里不方便插USB。这时候可以使用os.dupterm()函数把指定串口对象作为REPL的额外终端。import os from machine import UART, Pin uart_debug UART(0, baudrate115200, txPin(0), rxPin(1)) os.dupterm(uart_debug, 1)执行这行代码后REPL的输出会同时发送到USB和UART0也就是说你在PC串口助手或者无线串口模块的终端里就能直接进入Python REPL可以运行命令、查看输出。dupterm的第二个参数可以指定重定向的通道序号在不同的固件版本上支持程度略有差异遇到问题先升级到最新固件。这个功能的实际价值在于当你写的程序崩溃或者进入死循环时USB REPL可能无法正常响应但硬件UART重定向仍然可以帮你救回现场。我自己跑舵机控制项目时就靠这块功能在不开盖的情况下动态修改舵机角度参数省了不少拆卸的功夫。3. 调试工具选型与上下位机联调3.1 串口调试软件横向对比哪款适合你的场景串口调试工具我前前后后用了不下十款现在固定下来了几款。Windows下最常用的是SSCOM体积小、免安装、支持定时发送、常用数据格式切换还有简单的图表显示适合快速验证Pico的收发逻辑。MobaXterm也值得推荐它集成了SSH、串口等多种会话模式如果你既需要操作Linux服务器又需要调试串口一个软件就能搞定。PuTTY更纯粹只做串口和SSH界面简陋但稳定。Linux/macOS下我的首选是picocom它的配置文件简洁CtrlA CtrlQ退出CtrlA CtrlX挂断并退出用起来非常顺手。minicom功能更强支持脚本、日志记录但配置相对繁琐。还有一个tio是我最近才发现的支持高亮显示和宏命令轻量好用。我按平台整理了一个表格方便大家对照选择工具平台特点适合场景SSCOMWindows轻量、免安装、支持定时发送和图表快速验证Pico收发、调试帧下发MobaXtermWindows串口SSH集成、多标签同时管理Linux服务器和串口调试PuTTYWindows/Linux经典、稳定、功能朴素简单串口连接和测试minicomLinux功能全面、支持脚本自动化自动化测试、长时间运行日志记录picocomLinux/macOS轻量、配置简单、快捷键友好日常串口调试CoolTermmacOS图形化、支持数据保存和回放macOS下快速调试Serial Studio全平台数据可视化仪表盘传感器数据实时展示选工具的原则很简单日常调试用最顺手的自动化测试用脚本支持好的数据可视化用Serial Studio这类专门工具。有些人喜欢上来就装一堆软件我建议你先选通一款把Pico到PC的收发先跑通再按需增加工具。3.2 Windows宿主机与Linux虚拟机的串口桥接热搜词里有一个很实际的问题宿主机Windows如何通过串口与VMware中Linux通信。这个场景我遇到过很多次——开发板在宿主机上但编译和测试环境在Linux虚拟机里你需要让虚拟机里的程序直接操作宿主机上的USB转串口。VMware Workstation支持把物理串口映射到虚拟机。操作步骤是打开虚拟机设置添加串行端口设备类型选择使用物理串口然后指定宿主机上的COM口比如COM3。注意虚拟机中这个串口通常显示为/dev/ttyS0第一个串口或者/dev/ttyUSB0如果VMware把它模拟成USB设备。实际操作中有个坑宿主机上如果有串口调试工具正在占用COM3虚拟机里是无法开同一个串口的。所以使用前必须把宿主机的SSCOM、串口助手之类全部退出。另外虚拟机里的Linux用户需要加入dialout组才能访问串口设备否则会报权限错误sudo usermod -aG dialout $USER然后重新登录。用minicom测试minicom -D /dev/ttyS0 -b 115200如果物理串口是通过USB转TTL芯片连接的Pico那么在VMware里映射后Linux虚拟机直接访问/dev/ttyS0就能和Pico通信。我还试过用命名管道的方式把串口桥接给其他程序但物理串口直连是最稳定的。这个功能非常适合在虚拟机里跑自己写的Python串口服务端或者QT串口上位机来和Pico联调不用在Windows和Linux之间来回切换。要注意的是VMware对串口的实时性处理得不算极致如果你需要非常精准的时序控制建议还是用真实物理机或者把程序放在Pico里面。3.3 逻辑分析仪、串口监控和QT上位机开发的三板斧当串口数据出现奇怪的问题——明明波特率对、接线也对收到的数据还是错乱——逻辑分析仪就是你最好的朋友。我手上有一台Saleae Logic 8实际上用的是国产DSLogic兼容版测串口非常方便把TX、RX、GND三根线和逻辑分析仪通道对接在软件里添加UART解码器设置好波特率就能看到波形和数据帧。逻辑分析仪能直接告诉你实际波特率是否和预期一致波形宽度可以测算数据位、停止位、校验位的实际配置是否有多余的毛刺干扰信号发送和接收的时序是否正确如果逻辑分析仪解码显示的数据和上位机收到的数据完全一致但程序就是解析不对那问题一定出在程序逻辑。反之如果逻辑分析仪显示的字节都不是你发送的说明物理层有问题检查电平、接线、地线。串口监控类工具比如Windows下的AccessPort适合调试上位机程序——它们能监听某个程序对串口API的调用看到程序到底往串口写了什么、读了什么。这类工具在调试QT串口程序时特别有用因为你可以对比程序以为它发送的和内核实际发出去的找出封装层的问题。说到QT如果你想给Pico做一个上位机界面QT的串口模块QSerialPort是很好的选择。跨平台、API清晰而且网上资料特别多。用QT写一个简单的串口助手大概就是创建QSerialPort对象、配置端口号和波特率、连接readyRead信号读取数据、用write发送数据这几个步骤。需要注意的是QT串口读取一定要在readyRead信号里处理不要在主线程里用waitForReadyRead阻塞等待否则界面会卡死。这个坑我第一次写QT串口程序就踩过界面卡到点关闭按钮都没反应。4. 常见问题与排查技巧实录4.1 乱码和丢字节八成是物理层问题串口乱码是最高频的问题没有之一。我归纳了一下乱码的原因不外乎四种波特率不匹配、电平标准不对、接线错误、信号干扰。波特率不匹配的典型症状是能收到ASCII字符但都是类似ÿÿÿ的乱码或者某些字符正常某些字符错乱。解决方法是确认双方波特率完全一致注意有些模块默认9600有些默认115200不要想当然。如果用逻辑分析仪实测还能发现硬件分频误差导致的微小偏差——大多数时候这个偏差在容错范围内不用太纠结。电平标准不对的症状是通信完全无响应或者偶尔收到一个字节但内容完全不对。检查方法很简单用万用表测RX/TX引脚的电压如果空闲状态电压是负的-5V左右说明对端是RS232电平而Pico这边是TTL需要一个MAX3232转换芯片。接线错误最常见的是TX和RX没有交叉连接。Pico的TX要接对端的RXPico的RX要接对端的TX同时GND必须共地。很多人以为TTL电平不用共地这是大忌不共地时信号没有参考电平必然乱码。信号干扰导致的问题通常是偶发性丢字节。解决思路是缩短线缆长度、使用双绞线或者屏蔽线、降低波特率、必要时改用RS485。如果是USB转串口模块供电不稳导致的换一个质量好的CP2102模块一般能解决。4.2 收不到任何数据先检查引脚冲突和供电如果程序看起来完全正常但uart.any()永远是0那就从这几个方面排查。第一检查GPIO引脚是否被其他外设占用。MicroPython里Pin对象虽然可以在运行时创建和销毁但如果你之前初始化过PWM或者I2C占用了同一个引脚UART是无法正常工作的。这个问题在快速复制粘贴代码时非常容易碰到因为旧代码创建的Pin对象可能还保留在内存里。第二检查供电。Pico的USB供电能力有限如果外接的传感器模块或者舵机需要较大电流会导致3.3V电压跌落UART电平也随之异常。表现就是板子的LED亮着、程序也在跑但串口就是没数据。给Pico外接一个独立5V电源并把GND和Pico的GND连在一起通常能解决。第三检查串口是否被别的程序占用。Windows下如果SSCOM占用着COM3你再打开一个SSCOM去连同一个COM3会提示无法打开串口。关掉其他程序就好。另外USB转串口芯片的驱动出问题也会导致串口无法识别最简单的办法是换一个USB口或者重新安装驱动。还有一个很容易被忽略的原因MicroPython固件版本太老某些UART配置参数不可用。遇到莫名其妙的问题不妨直接升级到最新版MicroPython固件我有一次遇到的timeout_char参数不生效问题升级固件后就好了。4.3 和STM32方案相比Pico串口的差异化优势微博热搜里stm32串口通信也很靠前很多从STM32转过来的朋友会对比这两类平台。我觉得它们的串口方案各有特点选型时主要看项目需求。STM32的UART外设数量更多比如STM32F103C8T6有3个UARTSTM32F407有6个UART适合那些需要同时挂多个串口设备、并发通信量大的场景。STM32的优势在于实时性和底层可控性你可以用DMA、空闲中断、多缓冲等机制实现接近硬实时的数据收发。但代价是开发成本高用HAL库配置一个UART收发需要处理时钟使能、GPIO复用、中断回调、DMA配置等一堆细节代码量比MicroPython大一个数量级。Pico的优势是开发效率和可塑性。MicroPython三行代码就能初始化UART并开始收发非常适合原型验证、教学、个人DIY项目。RP2040的引脚映射灵活性和32字节FIFO虽不如STM32的高端型号但应付绝大多数物联网和自动化项目绰绰有余。我的建议很简单如果你要做的是量产设备、对功耗和实时性有严格要求、团队熟悉C语言选STM32如果你做的是原型验证、快速出demo、给学生做实验或者希望代码好维护Pico的MicroPython是更高效的选择。许多商业项目实际是两者结合先用Pico跑通算法和协议再移植到STM32上做产品化这样开发周期能缩短很多。写到这里基本把Pico串口通信这条链路从硬件到软件再到调试工具都过了一遍。我个人在实际操作中最深的体会是串口问题十有八九不是代码问题而是电平、接线和波特率的物理层问题。做Pico项目时建议先在PC上用串口助手跑通Pico到PC这条链路再接入传感器或控制对象这样出问题时排查范围会大大缩小。最后再分享一个小技巧调试时在Pico代码里尽量用十六进制打印收到的原始字节而不是直接转成字符串比如print(data.hex())这样能看到不可见字符和帧边界很多诡异问题瞬间就浮出水面了。
返回列表