树莓派4B Ubuntu 22.04串口通信配置与Python编程实战指南
1. 从一次失败的硬件调试说起
去年我接手了一个物联网数据采集项目,核心需求是通过树莓派4B实时读取多个工业传感器数据。传感器通过RS-485总线连接,最终汇总到一个USB转串口适配器上。听起来很简单,对吧?我当时也是这么想的。我熟练地给树莓派4B刷上了最新的Ubuntu 22.04 LTS Server镜像,插上USB转串口线,自信满满地打开终端准备用screen或minicom进行测试。结果,ls /dev/tty*之后,我懵了——设备列表里确实出现了ttyUSB0,但无论我怎么设置波特率、数据位、停止位,发送AT指令都石沉大海,读取到的永远是一片空白,或者偶尔蹦出几个乱码。
折腾了大半天,从怀疑USB线、怀疑传感器、怀疑电源,到最后才意识到问题可能出在系统本身。Ubuntu 22.04,尤其是运行在树莓派这种ARM架构上的版本,其串口管理与我们熟悉的传统x86 Linux桌面版或树莓派官方的Raspberry Pi OS,有着一些微妙但至关重要的差异。这次踩坑经历让我意识到,“树莓派4B + Ubuntu 22.04”这个组合下的串口通信,远不是插上线就能用的。它涉及到底层硬件映射、系统服务管理、权限控制以及驱动兼容性等多个层面,任何一个环节没打通,通信就会失败。
这篇文章,就是把我从那次失败调试中总结出来,并经过后续多个项目验证的一整套“避坑指南”和“最佳实践”梳理出来。无论你是想用树莓派连接Arduino、STM32单片机、GPS模块、4G模块,还是像我一样对接工业PLC或传感器,这篇内容都将帮你绕开我踩过的那些坑,快速建立起稳定可靠的串口通信链路。我们会从最根本的硬件原理和系统配置讲起,一直深入到具体的代码示例和高级调试技巧。
2. 理解树莓派4B的串口硬件布局:不止一个UART
在开始配置之前,我们必须先搞清楚树莓派4B的“家底”——它到底有几个串口,分别对应什么物理接口,以及默认状态下谁被谁占用了。这是所有问题的根源,很多通信失败都是因为连错了“门”。
树莓派4B的SoC(博通BCM2711)内部实际上包含了多个UART(通用异步收发传输器)控制器。对我们来说,最主要的是以下两个:
UART0 (PL011):这是一个功能完整的、高性能的UART,通常支持硬件流控(RTS/CTS)。在树莓派官方的Raspberry Pi OS中,这个UART默认被映射到了GPIO的TXD (GPIO14) 和 RXD (GPIO15)引脚,也就是那个40针扩展排针上的第8和第10脚。同时,这个UART在系统内默认被用作主控制台(Serial Console),也就是说,系统的启动日志和登录终端可以通过它输出和接入。
UART2 (miniUART):这是一个由GPU核心管理的、功能相对简单的UART,其波特率依赖于核心时钟频率,稳定性稍逊于PL011。在树莓派4B上,这个miniUART在硬件层面也可以被路由到GPIO的TXD/RXD引脚。
那么问题来了,当我们安装Ubuntu 22.04 for Raspberry Pi时,情况发生了变化。Ubuntu镜像为了系统的稳定性和兼容性,通常会采用不同的设备树(Device Tree)配置。一个常见且关键的区别是:在Ubuntu 22.04上,默认用于GPIO引脚(物理排针)串口的,可能是UART2 (miniUART),而不是UART0 (PL011)。同时,UART0 (PL011)可能被分配给了蓝牙模块使用。
我们可以通过一个简单的命令来验证当前系统的串口映射关系:
dmesg | grep -E \"tty(AMA|S)\"或者更直接地查看设备树别名:
ls -l /dev/serial*如果输出显示/dev/serial0 -> ttyAMA0且/dev/serial1 -> ttyS0,那么通常ttyAMA0对应PL011,ttyS0对应miniUART。你需要进一步确认哪个对应了物理GPIO引脚。
为什么这个区别如此重要?
- 稳定性:PL011 UART的稳定性和抗干扰能力通常优于miniUART,特别是在高波特率(如115200以上)或长距离通信时。
- 功能:PL011支持硬件流控,这对于某些需要可靠数据传输的场景(如某些4G模块)是必需的。
- 默认配置:如果你按照网上大部分基于Raspberry Pi OS的教程去操作,直接操作
/dev/ttyAMA0,在Ubuntu下很可能发现设备不存在或者操作无响应,因为设备名和映射关系可能已经变了。
因此,我们的第一步配置,往往不是急着去写代码,而是先明确我们想要使用的物理串口(GPIO引脚)在当前的Ubuntu系统下对应的设备文件是哪一个,以及它当前被什么功能占用了。
3. 配置系统:释放GPIO串口并设置权限
假设我们的目标很明确:我要使用树莓派4B GPIO排针上的TXD (GPIO14) / RXD (GPIO15)引脚与外部设备通信。那么我们需要完成以下几步系统级配置。
3.1 步骤一:禁用串口控制台服务
在Ubuntu上,默认可能启用了通过串口登录的控制台服务(serial-getty@.service)。这个服务会独占我们想要使用的串口设备,导致我们的应用程序无法打开它。我们需要先禁用它。
首先,检查该服务是否正在运行以及它绑定到了哪个TTY设备:
sudo systemctl status serial-getty@*如果发现类似serial-getty@ttyAMA0.service或serial-getty@ttyS0.service的服务是active (running)状态,那么它就是我们首先要处理的对象。
禁用并停止该服务:
# 假设服务是 ttyAMA0,请根据上一步的实际情况替换 sudo systemctl stop serial-getty@ttyAMA0.service sudo systemctl disable serial-getty@ttyAMA0.service sudo systemctl mask serial-getty@ttyAMA0.service这里用了三个命令:stop立即停止服务,disable防止下次启动,mask是更彻底的禁用,会创建一个指向/dev/null的符号链接,防止其他服务意外唤醒它。
3.2 步骤二:修改引导配置,切换串口映射(可选但推荐)
如果我们希望将更稳定的PL011 UART(ttyAMA0)分配给GPIO引脚使用,并且确保蓝牙不使用它(蓝牙可能改用miniUART或其它方式),我们需要修改树莓派的引导配置文件/boot/firmware/config.txt(注意:在Ubuntu for Raspberry Pi上,这个路径通常是/boot/firmware/,而不是Raspberry Pi OS的/boot/)。
使用编辑器打开该文件:
sudo nano /boot/firmware/config.txt在文件末尾添加或修改以下几行关键配置:
# 禁用蓝牙对PL011 UART的占用(这将使蓝牙无法工作,如果不需要蓝牙可以这样做) dtoverlay=disable-bt # 或者,更优雅的方式:明确指定UART的映射关系,并保持蓝牙可用(使用miniUART) # 启用UART,并将PL011 (ttyAMA0) 分配给GPIO引脚 enable_uart=1 # 告诉系统,将蓝牙分配给miniUART (ttyS0) dtoverlay=miniuart-bt配置解释:
enable_uart=1:这是基础,启用UART功能。dtoverlay=miniuart-bt:这个配置非常关键。它执行了两个操作:1) 将PL011 UART (ttyAMA0) 从蓝牙手中“解放”出来,分配给GPIO引脚。2) 将蓝牙功能转移到miniUART (ttyS0) 上。这样,我们就得到了一个稳定的、可用的/dev/ttyAMA0设备用于通信,同时蓝牙功能(虽然性能可能受影响)依然存在。dtoverlay=disable-bt:这是更彻底的方案,直接禁用板载蓝牙硬件。如果你完全不需要蓝牙,用这个配置最简单,也能确保PL011 UART独占。
修改完成后,保存文件 (Ctrl+O,Enter,Ctrl+X),然后必须重启系统以使更改生效。
sudo reboot3.3 步骤三:验证设备与设置权限
重启后,再次登录系统,进行验证。
检查设备文件:
ls -l /dev/ttyAMA0 /dev/ttyS0 /dev/serial*你应该能看到
/dev/ttyAMA0和/dev/ttyS0存在。并且/dev/serial0和/dev/serial1是它们的符号链接。通常,经过miniuart-bt配置后,/dev/serial0(GPIO串口) 会指向/dev/ttyAMA0。测试串口回环(硬件准备): 最直接的验证方法是进行回环测试。你需要一根杜邦线,将树莓派GPIO14 (TXD) 和 GPIO15 (RXD)短接。这样,从该串口发送出去的数据会被自己接收回来。
警告:在连接任何外部设备前,请务必确认电压电平匹配。树莓派GPIO是3.3V电平,严禁直接连接5V或更高电压的设备,否则会损坏树莓派!对于RS-232等不同电平的设备,必须使用电平转换模块(如MAX3232)。
使用命令行工具测试: 安装一个简单的串口工具,如
minicom或screen。sudo apt update sudo apt install minicom -y在进行回环短接后,打开minicom进行测试:
sudo minicom -D /dev/ttyAMA0 -b 115200进入minicom界面后(默认是空白),尝试敲击键盘。如果你能看到屏幕上同步出现你输入的字符,说明串口发送和接收通路都是正常的。按
Ctrl+A, 然后按X键退出minicom。解决权限问题: 你可能注意到,上面的测试用了
sudo。因为默认情况下,串口设备文件属于dialout用户组。为了让普通用户也能无需sudo直接访问串口,需要将当前用户加入dialout组:sudo usermod -a -G dialout $USER执行此命令后,需要注销并重新登录,或者开启一个新的终端会话,用户组变更才会生效。之后,你就可以用
minicom -D /dev/ttyAMA0 ...而不需要sudo了。
4. 软件层面实现串口通信:从命令行到Python
系统配置妥当后,我们就可以在应用层进行编程了。这里分别介绍命令行工具和Python编程两种方式。
4.1 命令行工具:快速测试与调试
除了刚才用到的minicom,还有几个非常实用的命令行工具:
screen: 一个终端复用器,但常被用来做简单的串口监视。用法非常简洁:screen /dev/ttyAMA0 115200要退出screen,按
Ctrl+A, 然后按K, 再按Y确认。cu: 一个历史悠久的呼叫另一台Unix系统的工具,也支持串口,语法直观。sudo apt install cu cu -l /dev/ttyAMA0 -s 115200输入
~.退出。echo和cat: 最基础的组合,用于快速发送指令和接收数据。# 发送字符串 “AT\r\n” 到串口 echo -e "AT\r\n" > /dev/ttyAMA0 # 从串口读取数据并显示(非阻塞方式,读取已有数据) timeout 1 cat /dev/ttyAMA0这对于测试像GSM/4G模块这样的AT指令设备非常方便。
4.2 Python编程:使用pyserial库
Python的pyserial库是处理串口通信的事实标准,功能强大且易于使用。
安装:
pip install pyserial # 或者使用系统包管理器 sudo apt install python3-serial基础通信示例: 下面是一个完整的Python脚本示例,实现了打开串口、发送数据、接收数据并解析的基本流程。我们以向一个模拟传感器发送查询指令
READ?并等待回复为例。#!/usr/bin/env python3 import serial import time import sys # 配置串口参数 SERIAL_PORT = '/dev/ttyAMA0' # 根据你的实际设备修改 BAUD_RATE = 9600 TIMEOUT = 2 # 读超时,秒 def main(): try: # 创建串口对象 # 关键参数说明: # port: 设备文件路径 # baudrate: 波特率,必须与设备一致 # bytesize: 数据位,默认 serial.EIGHTBITS (8) # parity: 校验位,默认 serial.PARITY_NONE # stopbits: 停止位,默认 serial.STOPBITS_ONE (1) # timeout: 读超时。None为阻塞等待,0为非阻塞,正数为超时秒数。 # write_timeout: 写超时,类似。 ser = serial.Serial( port=SERIAL_PORT, baudrate=BAUD_RATE, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=TIMEOUT, write_timeout=TIMEOUT ) except serial.SerialException as e: print(f"无法打开串口 {SERIAL_PORT}: {e}") sys.exit(1) # 确保串口是打开的 if ser.is_open: print(f"串口 {SERIAL_PORT} 已打开,波特率 {BAUD_RATE}") else: print("串口打开失败。") sys.exit(1) try: # 示例:清空输入输出缓冲区(非必需,但有时很有用) ser.reset_input_buffer() ser.reset_output_buffer() time.sleep(0.1) # 短暂延时 # 要发送的指令,注意换行符。有些设备需要 \r\n,有些只需要 \n。 command = b"READ?\r\n" print(f"发送指令: {command.decode('ascii').strip()}") # 发送指令 bytes_written = ser.write(command) print(f"已发送 {bytes_written} 字节.") # 确保数据被发送出去(刷新输出缓冲区) ser.flush() # 等待并读取回复 # 这里我们假设设备会在短时间内回复一行以换行符结尾的数据 response_line = ser.readline() if response_line: # decode 将字节转换为字符串,errors='ignore' 忽略无法解码的字符 print(f"收到回复: {response_line.decode('ascii', errors='ignore').strip()}") else: print("读取超时,未收到回复。") # 更复杂的读取示例:读取直到遇到特定字符或达到一定字节数 # ser.read_until(expected=b'\n', size=None) # ser.read(size=10) except KeyboardInterrupt: print("\n用户中断。") except Exception as e: print(f"通信过程中发生错误: {e}") finally: # 无论如何,最后都要关闭串口 ser.close() print("串口已关闭。") if __name__ == '__main__': main()高级技巧与注意事项:
- 编码与解码:串口传输的是原始字节(
bytes)。发送时,需要将字符串编码为字节(如"AT".encode('ascii')或b"AT")。接收时,需要将字节解码为字符串(如data.decode('utf-8', errors='ignore'))。务必确认设备使用的字符编码,常见的是ASCII或UTF-8。 - 换行符处理:这是最常见的坑之一。不同的设备对行结束符的要求不同,可能是
\r、\n或\r\n。发送时务必匹配设备要求。readline()函数默认以\n为行结束符,如果设备只发\r,它会一直等待。这时可以使用read_until(b'\r')或更通用的方法:ser.read(ser.in_waiting)读取所有可用字节,然后自己解析。 - 超时设置:
timeout参数至关重要。设置为None时,read()会一直阻塞直到收到指定数量的字节。设置为正数时,会在超时后返回已读取的数据(可能少于请求的字节数)。对于交互式协议,合理的超时可以防止程序死锁。 - 流控:如果设备支持并启用了硬件流控(RTS/CTS),需要在初始化时设置
rtscts=True。软件流控(XON/XOFF)则设置xonxoff=True。大多数简单场景不需要。 - 线程安全:如果在多线程环境中使用同一个串口对象进行读写,需要自行加锁(
threading.Lock)来保证操作原子性,因为pyserial的实例本身不是线程安全的。
- 编码与解码:串口传输的是原始字节(
5. 实战排坑:那些年我踩过的串口通信“天坑”
理论配置和基础代码都跑通了,并不意味着在实际项目中就能高枕无忧。下面分享几个我亲身经历或高频被问到的疑难杂症及其排查思路。
5.1 现象:能发送,但收不到数据,或者收到全是乱码
这是最经典的问题。排查链路应该像侦探破案一样,层层递进:
第一步:物理层检查
- 接线:确认TXD接对方的RXD,RXD接对方的TXD。是的,就是交叉连接,我见过不止一个资深工程师在加班到深夜时犯这个迷糊。
- 共地:确保树莓派的GND和目标设备的GND连接在一起。没有共同的参考地,信号无法被正确识别。
- 电平:用万用表测量一下信号线电压。树莓派GPIO是3.3V CMOS电平。如果对方是5V TTL或RS-232(±12V),必须使用电平转换器,直接连接必烧毁或通信异常。
- 电源:目标设备是否供电充足?功率不足的模块可能工作不稳定。
第二步:参数层检查
- 波特率:这是乱码的罪魁祸首之首。发送方和接收方的波特率必须精确一致。9600就是9600,115200就是115200,差一点都不行。检查设备说明书,并尝试所有可能的常用波特率(9600, 19200, 38400, 57600, 115200)。
- 数据格式:数据位(8位最常见)、停止位(1位或2位)、校验位(无、奇、偶)必须完全匹配。一个7位数据位、偶校验的设备,用8位无校验去读,得到的就是乱码。
stty命令可以查看和设置终端参数,但对已由程序打开的串口设备可能无效,最好在代码中确认。 - 流控:如果一方开启了硬件流控(RTS/CTS),而另一方没有正确连接对应的流控线(GPIO的对应引脚),数据流就会被挂起,表现为发送一段后停止。尝试在代码中明确禁用流控(
rtscts=False, xonxoff=False)。
第三步:软件与系统层检查
- 设备文件是否正确:再次用
ls -l /dev/tty*确认你操作的设备文件(如/dev/ttyAMA0)是否存在,并且其符号链接(/dev/serial0)指向是否正确。 - 权限问题:即使加入了
dialout组,在某些情况下(如通过某些IDE或系统服务启动程序)环境变量可能不同。尝试用sudo运行你的程序,如果sudo能工作而普通用户不能,就是权限问题。检查/dev/ttyAMA0的权限:ls -l /dev/ttyAMA0,应该是crw-rw---- 1 root dialout ...。 - 缓冲区与冲刷:发送数据后,调用
ser.flush()确保数据从Python的内部缓冲区写入操作系统。对于接收,有时需要先ser.reset_input_buffer()清空可能存在的陈旧数据。 - 字节序与编码:如果传输的是多字节数据(如int, float),要确认设备使用的字节序(大端/小端)。如果是文本,确认编码。中文乱码常常是UTF-8和GBK不匹配造成的。
- 设备文件是否正确:再次用
5.2 现象:通信间歇性失败,时好时坏
这种问题最折磨人,可能的原因有:
- 电气干扰:如果通信线缆较长,且靠近电源线或电机等干扰源,信号质量会下降。使用带屏蔽的双绞线,并尽量远离干扰源。
- 接地环路:如果通信双方通过不同的电源适配器供电,且地线电位有细微差异,可能形成接地环路引入噪声。尝试让双方共用一个高质量的电源。
- 电源噪声:树莓派或外设的电源质量差,电压纹波大,会影响核心时钟稳定性,进而影响miniUART的波特率精度。换用官方电源或质量可靠的5V/3A电源。
- CPU频率缩放:树莓派的CPU频率会根据负载动态调整,这可能会影响miniUART的时钟源,导致波特率漂移。这就是为什么强烈推荐使用PL011 UART,因为它有独立的时钟源。如果必须用miniUART,可以尝试在
/boot/firmware/config.txt中设置core_freq=250或core_freq_min=250来固定核心频率,提升miniUART的稳定性。 - 软件超时与阻塞:检查代码中的超时设置。如果
read操作没有设置超时(timeout=None),而设备在某些情况下没有回复,线程就会永远阻塞。始终设置一个合理的超时时间,并实现重试机制。
5.3 使用strace和dmesg进行深度调试
当常规手段无效时,需要祭出系统级调试工具。
dmesg: 内核环缓冲区消息。当你插拔USB转串口适配器时,观察dmesg的输出,可以看到系统是否识别到了新设备,分配了什么设备名(如ttyUSB0),以及是否有错误信息。# 实时监控内核消息 sudo dmesg -w # 插拔USB设备,观察输出你可能会看到类似
cp210x converter now attached to ttyUSB0的成功信息,也可能会看到pl011: probe of ... failed with error -16这样的错误,提示资源冲突。strace: 系统调用跟踪器。它可以显示你的程序到底对系统做了什么。例如,你的Python程序打不开设备,可以用strace跟踪:strace python3 your_script.py 2>&1 | grep ttyAMA0你会看到
openat系统调用的详细参数和返回值(如-1 EACCES (Permission denied)或-1 ENODEV (No such device)),这能精准定位是权限错误还是设备不存在。setserial: 查看和设置串口底层参数。虽然现代系统多用ioctl,但这个工具有时能提供信息。sudo apt install setserial sudo setserial -g /dev/ttyAMA0查看UART类型、端口地址等信息。
6. 进阶应用:连接USB转串口适配器与多串口管理
很多时候,GPIO上的一个串口不够用,或者需要连接RS-232/RS-485设备,这时就需要USB转串口适配器。
6.1 驱动与自动识别
常见的芯片如CP2102、CH340、PL2303、FT232等,在Ubuntu 22.04的内核中通常都有内置驱动。插入适配器后,系统一般会自动识别并创建/dev/ttyUSB0、/dev/ttyUSB1等设备文件。使用dmesg | tail可以查看识别日志。
驱动问题:如果遇到无法识别的情况(lsusb能看到设备但无ttyUSB*),可能需要手动安装驱动。例如某些旧版PL2303芯片需要特定驱动。但Ubuntu 22.04内核较新,对主流芯片支持良好。
6.2 固定设备名(解决ttyUSB序号漂移问题)
一个非常实际的问题是:当你同时使用多个相同的USB转串口适配器时,/dev/ttyUSB0和/dev/ttyUSB1的分配顺序可能在每次重启或插拔后发生变化,这会导致你的程序连接到错误的设备。
解决方案是通过设备的唯一序列号或物理端口来创建固定的符号链接。这可以通过udev规则实现。
找到设备的唯一属性:
# 插入一个USB转串口设备 lsusb # 找到你的设备,记下ID,例如 Bus 001 Device 004: ID 10c4:ea60 Silicon Labs CP210x UART Bridge # 使用udevadm获取详细属性,其中ID_SERIAL_SHORT是序列号,ID_PATH是物理端口路径 sudo udevadm info -a -n /dev/ttyUSB0 | grep -E \"(ATTRS{idVendor}|ATTRS{idProduct}|ATTRS{serial}|ATTRS{idVendor}|ATTRS{idProduct})\" # 或者更精确地查找序列号 sudo udevadm info -q property -n /dev/ttyUSB0 | grep ID_SERIAL_SHORT假设我们得到序列号为
CP2102_USB_to_UART_123456。创建udev规则文件:
sudo nano /etc/udev/rules.d/99-usb-serial.rules添加以下内容(将
YOUR_SERIAL替换为实际的序列号):# 为特定序列号的CP2102设备创建固定符号链接 SUBSYSTEM==\"tty\", ATTRS{idVendor}==\"10c4\", ATTRS{idProduct}==\"ea60\", ATTRS{serial}==\"CP2102_USB_to_UART_123456\", SYMLINK+=\"ttyMySensor\"这条规则的意思是:当发现一个子系统为tty,且供应商ID为10c4,产品ID为ea60,序列号为指定值的设备时,除了创建默认的
ttyUSBx,再额外创建一个固定的符号链接/dev/ttyMySensor指向它。重新加载udev规则并触发:
sudo udevadm control --reload-rules sudo udevadm trigger拔插USB设备,现在你应该能看到
/dev/ttyMySensor这个链接,并且它始终指向你的那个特定适配器。在你的代码中,就可以放心地使用/dev/ttyMySensor这个固定名称了。
6.3 在Python中枚举可用串口
对于需要动态发现串口的应用,pyserial提供了工具函数:
import serial.tools.list_ports ports = serial.tools.list_ports.comports() for port in ports: print(f"设备: {port.device}") print(f" 名称: {port.name}") print(f" 描述: {port.description}") print(f" 硬件ID: {port.hwid}") print(f" 制造商: {port.manufacturer}") print(f" 产品: {port.product}") print(f" 序列号: {port.serial_number}") print("-" * 20)你可以根据description、manufacturer、serial_number等属性来筛选出你需要的特定设备。
7. 性能优化与可靠通信架构建议
当串口用于关键数据采集或控制时,稳定性和可靠性至关重要。
错误处理与重试机制: 不要相信一次通信就能成功。在打开串口、发送、接收的每个环节都要用
try...except包裹,并实现指数退避的重试逻辑。import time def send_with_retry(ser, data, max_retries=3): for attempt in range(max_retries): try: ser.write(data) ser.flush() return True except (serial.SerialTimeoutException, serial.SerialException) as e: print(f"发送失败,第{attempt+1}次重试。错误: {e}") time.sleep(2 ** attempt) # 指数退避 # 可选:尝试重新初始化串口 # ser.close() # time.sleep(1) # ser.open() return False使用线程或异步处理: 对于需要同时处理多个串口或需要在等待串口数据时执行其他任务的程序,建议使用
threading模块为每个串口创建一个读写线程,或者使用asyncio配合pyserial-asyncio库进行异步操作。避免在主线程中进行阻塞式的read()调用。协议设计: 对于自定义设备,设计一个简单的应用层协议能极大提升可靠性。例如,在每个数据帧前后加上帧头(如
0xAA、0x55)和帧尾,并包含长度字段和校验和(CRC或累加和)。接收方通过帧头定位帧起始,通过长度字段确定帧边界,通过校验和验证数据完整性。这能有效解决粘包(多个帧粘在一起)和断帧(一个帧被拆开接收)的问题。日志记录: 将重要的发送和接收数据(尤其是原始字节的十六进制表示)记录到日志文件或数据库中,这在后期排查偶发性故障时是无价之宝。
import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # 发送时记录 logging.debug(f"Tx Hex: {command.hex()}") # 接收时记录 logging.debug(f"Rx Hex: {response_line.hex()}")资源清理: 确保在程序退出(包括异常退出)时,串口被正确关闭。使用
try...finally块或在上下文管理器(with serial.Serial(...) as ser:)中操作串口,可以保证资源释放。
从硬件引脚定义到系统服务配置,从权限管理到Python代码实现,再到深层次的排错与优化,打通树莓派4B上的Ubuntu 22.04串口通信,需要的就是这份耐心和对细节的把握。希望这篇超过五千字的详细梳理,能让你在下次连接传感器、单片机或任何串口设备时,少走弯路,一次成功。
