1. 项目概述:为什么要在树莓派4B上折腾Ubuntu 22.04的串口?
如果你手头有一块树莓派4B,并且已经厌倦了官方的Raspberry Pi OS,或者你的项目需要更标准的Linux发行版环境,那么给树莓派4B刷上Ubuntu 22.04 Server绝对是一个值得尝试的选择。Ubuntu Server提供了更纯粹的Linux体验、更稳定的软件源和更广泛的社区支持,特别适合用于物联网网关、边缘计算节点或者需要长期稳定运行的服务端应用。
然而,当你兴冲冲地装好系统,准备用最经典的串口(UART)方式连接传感器、单片机或者调试其他嵌入式设备时,很可能会发现事情没那么简单。树莓派4B的硬件串口默认被蓝牙模块占用,而Ubuntu 22.04在树莓派上的默认配置又和Raspberry Pi OS有所不同,直接使用/dev/ttyS0或/dev/ttyAMA0可能会遇到没数据、权限错误或者根本找不到设备的问题。这个项目要解决的,就是打通树莓派4B在Ubuntu 22.04系统下的串口通信链路,让你能像在传统嵌入式开发板上一样,稳定、可靠地使用串口进行数据收发和调试。
我之所以花时间折腾这个,是因为在一个工业数据采集项目中,需要树莓派作为网关,通过串口连接多个RS485传感器。Raspberry Pi OS在某些驱动和库的版本上遇到了兼容性问题,而Ubuntu 22.04 LTS的长期支持特性更符合项目要求。踩过一系列坑之后,我把从系统安装、串口配置、权限设置到实际测试的完整流程和心得记录下来,希望能帮你省下大量搜索和试错的时间。
2. 核心需求解析:树莓派4B串口的特殊性
要搞定串口通信,首先得理解树莓派4B的串口硬件布局和Ubuntu系统下的软件映射,这和我们在x86电脑或者传统ARM开发板上的经验不太一样。
2.1 树莓派4B的UART资源盘点
树莓派4B板载了多个UART控制器,但并非所有都能直接给用户使用:
- PL011 UART:这是一个功能完整的、高性能的UART,通常对应硬件
UART0。在早期的树莓派上,它默认映射到GPIO的TX(GPIO14)和RX(GPIO15)引脚,也就是那个40针排针的8号和10号针脚。然而在树莓派4B上,这个PL011 UART默认分配给了蓝牙模块使用,以实现蓝牙的HCI通信。这是第一个关键点。 - Mini UART:这是一个由内核驱动实现的、功能相对简单的UART,通常对应
UART1。它的波特率依赖于核心时钟频率,因此稳定性不如PL011。在默认配置下,这个Mini UART被映射到了GPIO14和GPIO15,供用户使用。
所以,我们的目标通常有两种:一是直接使用默认分配给用户的Mini UART(/dev/ttyS0),二是通过配置“交换”,把功能更强的PL011 UART(/dev/ttyAMA0)从蓝牙那里“抢”过来给GPIO引脚用。选择哪种方案,取决于你的需求。
2.2 Ubuntu 22.04 for Raspberry Pi的默认状态
当你从Ubuntu官网下载并安装“Ubuntu 22.04 LTS for Raspberry Pi”镜像后,系统对串口的处理方式与Raspberry Pi OS有细微差别。默认情况下:
- 串口控制台登录被禁用:与一些基础镜像不同,Ubuntu Server默认不会通过串口提供登录终端(getty服务)。这其实是好事,意味着串口设备可以被我们独占用于应用通信,而不会被登录会话占用。
- 设备文件映射:你需要通过
ls -l /dev/tty*命令来查看具体的设备文件。通常,/dev/ttyAMA0对应PL011 UART,/dev/ttyS0对应Mini UART。但它们的最终功能取决于设备树(Device Tree)的配置。 - 权限问题:默认情况下,串口设备文件属于
dialout用户组。普通用户(如ubuntu)需要被添加到dialout组才能有读写权限,否则会报“Permission denied”错误。
理解这些是后续所有操作的基础。我的建议是,除非你有必须使用蓝牙的需求,否则将PL011 UART交换到GPIO引脚是更优选择,能获得更稳定的通信性能,特别是在高波特率(如115200以上)或需要硬件流控(RTS/CTS)的场景下。
3. 系统准备与串口配置实战
理论清楚了,我们开始动手。整个过程可以分为系统安装、基础配置、串口功能切换和权限设置四个步骤。
3.1 为树莓派4B安装Ubuntu 22.04 Server
首先,你需要准备一张至少8GB的Micro SD卡。从Ubuntu官方网站下载“Ubuntu 22.04 LTS for Raspberry Pi”的镜像文件(通常是.xz压缩格式)。我推荐使用Raspberry Pi Imager这个工具进行烧录,它非常直观,而且会自动处理镜像解压。
在Raspberry Pi Imager中,选择你的SD卡,然后点击“Choose OS” -> “Other general-purpose OS” -> “Ubuntu” -> “Ubuntu Server 22.04 LTS (RPi 4/400)” 。接着点击“Choose Storage”选择你的SD卡。这里有一个关键操作:不要急着点“WRITE”,先按下键盘上的Ctrl+Shift+X,这会打开“高级选项”菜单。
在高级选项中,我强烈建议你预先做两件事:
- 启用SSH并设置密码:勾选“Enable SSH”,并选择“Use password authentication”,设置一个强密码。这样系统首次启动后,你就可以通过网络SSH登录,无需连接显示器和键盘。
- 配置Wi-Fi(可选):如果你不方便接网线,在这里填入你的Wi-Fi SSID和密码。这样树莓派开机后就能自动联网,方便后续通过SSH访问和安装软件。
设置完成后,点击“Save”,再点击“WRITE”开始烧录。烧录完成后,将SD卡插入树莓派4B,上电启动。
3.2 首次登录与基础环境配置
等待几分钟(首次启动会进行扩容等操作),你应该可以通过之前设置的密码,使用SSH连接到树莓派。默认用户名是ubuntu。
ssh ubuntu@<你的树莓派IP地址>登录后,第一件事是更新系统,确保所有软件包都是最新的:
sudo apt update && sudo apt upgrade -y更新完成后,建议重启一次:sudo reboot。
接下来,安装一些我们后续可能会用到的工具,比如用于串口调试的minicom和用于查看硬件信息的device-tree-compiler:
sudo apt install -y minicom device-tree-compiler3.3 核心步骤:配置设备树以启用GPIO串口
这是整个流程中最关键的一步,我们需要修改引导配置,告诉内核我们想怎么使用串口硬件。
备份原始配置:操作前先备份总是一个好习惯。
sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.bak sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak编辑
config.txt文件:这个文件决定了硬件的基本初始化参数。sudo nano /boot/firmware/config.txt在文件末尾,添加以下几行配置。它们的含义如下:
# 启用UART(无论是Mini UART还是PL011) enable_uart=1 # 将PL011 UART (ttyAMA0) 交换到GPIO14/15引脚 dtoverlay=uart5 # 禁用蓝牙,以彻底释放PL011 UART。如果你需要蓝牙,则不要加这一行,并考虑使用Mini UART方案。 dtoverlay=disable-btenable_uart=1:这是必须的,它确保UART控制器被上电并初始化。dtoverlay=uart5:这个叠加层(Overlay)是树莓派4B专用的,它的作用是将ttyAMA0(PL011 UART)重新映射到GPIO14和GPIO15引脚。注意:在树莓派4B上,这个叠加层的名字是uart5,而不是早期型号的pi3-miniuart-bt或pi3-disable-bt,用错会导致配置无效。dtoverlay=disable-bt:这行会禁用蓝牙硬件。因为PL011之前被蓝牙占用,如果我们想独占PL011用于GPIO串口,最简单的办法就是禁用蓝牙。如果你的项目同时需要蓝牙和高质量串口,那么配置会复杂很多,可能需要手动配置蓝牙使用Mini UART,这超出了本文基础范围。
添加完成后,按
Ctrl+X,然后按Y,再按Enter保存退出。检查并清理
cmdline.txt文件:这个文件包含了传递给Linux内核的启动参数。我们需要确保里面没有关于串口控制台的参数,否则它会占用我们想用的串口设备。sudo nano /boot/firmware/cmdline.txt检查这一行内容。在Ubuntu for Raspberry Pi的默认安装中,通常不包含类似
console=serial0,115200或console=ttyAMA0,115200这样的参数。如果发现有,请将其删除。删除后,这一行应该只剩下关于根文件系统等的基本参数。保存并退出。重启系统:让所有配置生效。
sudo reboot
3.4 验证配置与设置权限
重启并重新SSH登录后,我们来验证配置是否成功。
查看串口设备:
ls -l /dev/ttyAMA0 /dev/ttyS0你应该能看到类似以下的输出:
crw-rw---- 1 root dialout 204, 64 May 10 10:30 /dev/ttyAMA0 crw-rw---- 1 root dialout 4, 64 May 10 10:30 /dev/ttyS0这表示两个串口设备都存在。根据我们的配置(
uart5叠加层),现在/dev/ttyAMA0(PL011)应该已经被映射到了GPIO14/15引脚。验证引脚映射:我们可以通过读取设备树状态来确认。
sudo dtoverlay -l这个命令会列出所有已加载的设备树叠加层。你应该能看到
uart5在列表中。 更详细地,可以查看:sudo cat /sys/kernel/debug/pinctrl/fe201000.serial/pins或者,一个更简单的方法是使用
raspi-gpio工具(如果已安装)来查看GPIO14/15的功能复用状态,但Ubuntu Server默认可能没有。一个实用的替代方法是,我们直接进行物理连接测试。将用户加入
dialout组:为了避免每次都用sudo,把你的用户添加到串口所属的用户组。sudo usermod -a -G dialout $USER重要:这个改动需要重新登录(退出SSH会话再重新连接)才能生效。仅仅
su - $USER可能不够。
4. 硬件连接与通信测试
配置好了软件,现在需要连接硬件进行实测。你需要一个USB转TTL串口模块(比如常见的CH340、CP2102、FT232等芯片的模块)。
4.1 物理连接注意事项
安全第一!务必在树莓派断电的情况下进行连接!
连接USB转TTL模块到树莓派4B的GPIO引脚:
- USB转TTL模块的GND->树莓派GPIO的GND(例如第6针脚)。
- USB转TTL模块的RX->树莓派GPIO的TX (GPIO14)(第8针脚)。
- USB转TTL模块的TX->树莓派GPIO的RX (GPIO15)(第10针脚)。
核心口诀:RX接TX,TX接RX,GND接GND。千万不要把模块的VCC(通常是5V或3.3V)接到树莓派的GPIO上,树莓派的GPIO是3.3V电平,接5V可能会损坏板子!我们的连接只涉及数据线和地线。
将USB转TTL模块插入你的电脑(用于测试的电脑,不是树莓派)。
4.2 使用Minicom进行双向测试
首先,在你的电脑上(比如另一台Linux机器或Windows上使用Putty、MobaXterm等),打开串口终端,连接USB转TTL模块对应的端口(如COM3或/dev/ttyUSB0),设置波特率为115200,数据位8,停止位1,无校验位(8N1),无流控。
然后,在树莓派的SSH终端里,我们使用minicom作为串口调试工具。
启动Minicom,配置并发送数据:
sudo minicom -D /dev/ttyAMA0 -b 115200如果提示
minicom: cannot open /dev/ttyAMA0: Permission denied,说明之前的用户组设置还没生效,请确认已重新登录,或者暂时用sudo运行。进入minicom界面(是一个简单的终端),直接打字。你输入的字符会通过树莓派的TX线发送出去。此时,你应该能在电脑的串口终端上看到这些字符。
接收数据测试: 在minicom界面中,按
Ctrl+A,然后按Z,可以看到minicom的帮助菜单。再按E可以打开本地回显(这样你在树莓派端也能看到自己发的字符),但这不是必须的。 现在,在电脑的串口终端里输入一些字符。这些字符会通过电脑发送到树莓派的RX线。如果一切正常,你将在树莓派的minicom界面中看到这些字符。测试完成后,在minicom中按
Ctrl+A,然后按X,选择Yes退出。
4.3 使用Python脚本进行自动化测试
使用命令行工具测试直观,但实际项目更多是用编程语言控制。这里提供一个简单的Python3测试脚本,它既能发送也能接收。
首先,在树莓派上安装Python的串口库pyserial:
sudo apt install -y python3-serial # 或者使用pip安装: pip3 install pyserial然后,创建一个测试脚本uart_test.py:
#!/usr/bin/env python3 import serial import time import threading def read_from_port(ser): while True: if ser.in_waiting > 0: data = ser.read(ser.in_waiting) print(f"Received: {data.decode('utf-8', errors='ignore')}", end='') def main(): # 配置串口参数,根据你的实际设备文件修改 port = '/dev/ttyAMA0' # 使用我们配置好的PL011 UART baudrate = 115200 try: # 打开串口 ser = serial.Serial(port, baudrate, timeout=1) print(f"Serial port {port} opened at {baudrate} baud.") except serial.SerialException as e: print(f"Error opening serial port: {e}") return # 启动一个线程来持续监听接收数据 reader_thread = threading.Thread(target=read_from_port, args=(ser,)) reader_thread.daemon = True reader_thread.start() try: while True: # 从标准输入读取一行并发送(在终端中手动输入) # 注意:在SSH中运行,这样可以直接输入 send_data = input("Enter text to send (or 'quit' to exit): ") if send_data.lower() == 'quit': break ser.write((send_data + '\n').encode('utf-8')) print(f"Sent: {send_data}") except KeyboardInterrupt: print("\nExiting...") finally: ser.close() print("Serial port closed.") if __name__ == "__main__": main()运行这个脚本:python3 uart_test.py。在一个终端运行它,然后在电脑的串口终端发送信息,你应该能在树莓派的这个脚本输出中看到接收到的数据。同时,在脚本提示“Enter text to send”时输入内容,电脑的串口终端也能收到。
这个脚本采用了多线程,一个线程负责持续监听串口接收缓冲区并打印出来,主线程负责处理用户输入和发送,是一个很实用的基础框架。
5. 高级配置与性能调优
基础通信通了,但为了应对更复杂的场景,我们还需要了解一些高级配置。
5.1 调整串口缓冲区与超时
在高频、大数据量传输时,默认的缓冲区大小可能不够,可能导致数据丢失。pyserial和系统底层都提供了调整空间。
在Python中,可以在初始化Serial对象时设置:
ser = serial.Serial( port='/dev/ttyAMA0', baudrate=115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=None, # 读超时,None为阻塞模式 xonxoff=False, # 软件流控 rtscts=False, # 硬件RTS/CTS流控(需连接对应引脚) dsrdtr=False, # 硬件DSR/DTR流控 write_timeout=None, # 写超时 inter_byte_timeout=None, # 字节间超时 # 以下两个参数影响读性能 # read()操作会尝试读取最多这个字节数,但受限于底层缓冲 # exclusive=True 可以阻止其他进程打开同一端口 )对于Linux系统本身,可以通过stty命令或ioctl系统调用来调整底层终端设备参数,但这通常需要更深入的编程。
5.2 硬件流控(RTS/CTS)的使用
如果你的设备支持硬件流控(用于防止数据丢失),树莓派4B的PL011 UART是支持的。你需要额外连接GPIO引脚:
- 树莓派GPIO16 (BCM 23)-> 设备的CTS
- 树莓派GPIO17 (BCM 24)-> 设备的RTS
然后在config.txt中确保你的uart5叠加层没有禁用流控(默认是启用的)。在Python代码中,将rtscts=True。注意,Mini UART不支持硬件流控。
5.3 固定设备名与udev规则
默认的/dev/ttyAMA0名字是固定的,但如果你有多个串口设备(比如通过USB转串口扩展),设备名可能会变(如/dev/ttyUSB0,/dev/ttyUSB1)。为了在程序中可靠地使用特定设备,可以通过udev规则为其创建固定的符号链接。
例如,为特定的USB转串口模块(通过其供应商ID和产品ID识别)创建一个别名:
- 插入设备,用
lsusb找到其ID,比如1a86:7523(CH340)。 - 创建规则文件:
sudo nano /etc/udev/rules.d/99-usb-serial.rules - 加入一行(将
1a86和7523替换为你的设备ID):SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyMyDevice" - 重新加载udev规则并重启服务:
sudo udevadm control --reload-rules sudo udevadm trigger - 重新插拔设备,现在除了
/dev/ttyUSBx,还会出现一个固定的/dev/ttyMyDevice链接。
6. 常见问题与深度排查指南
即使按照步骤操作,你也可能会遇到一些问题。这里汇总了我遇到过的典型情况及其解决方法。
6.1 问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
ls /dev/ttyAMA0无此设备 | 1.config.txt中enable_uart=1未设置或错误。2. 设备树叠加层未正确加载。 | 1. 检查/boot/firmware/config.txt内容,确保enable_uart=1存在且无误。2. 运行 sudo dtoverlay -l和`sudo vcdbg log msg |
Permission denied错误 | 当前用户不在dialout组。 | 1. 执行groups命令查看当前用户所属组。2. 使用 sudo usermod -a -G dialout $USER添加,并重新登录SSH会话。3. 临时使用 sudo运行命令测试。 |
| Minicom或Python能打开设备,但收发无数据 | 1. 线接反了(TX/RX)。 2. 波特率等参数不匹配。 3. 流控设置错误。 4. 串口被其他进程占用。 | 1.重点检查:确保是树莓派TX接模块RX,树莓派RX接模块TX。 2. 双方(树莓派和电脑终端)的波特率、数据位、停止位、校验位必须完全一致。 3. 在minicom或代码中关闭流控(xonxoff, rtscts设为False)。 4. 使用 sudo lsof /dev/ttyAMA0查看是哪个进程占用了端口。 |
| 发送数据正常,但接收不到任何数据 | 1. 电脑端串口终端设置或连接问题。 2. 树莓派端RX引脚接触不良或损坏。 3. 电平不匹配(虽不常见,但3.3V与5V设备混接可能有问题)。 | 1. 用短接测试:在树莓派断电下,用杜邦线将GPIO14 (TX) 和 GPIO15 (RX)短接。上电后,用minicom发送字符,如果能在minicom里看到自己发送的字符(本地回显),说明树莓派自身串口环路是通的,问题在外围。 2. 换一个USB转TTL模块或电脑USB口测试。 3. 确保模块是3.3V电平的,或者确认其RX脚能容忍3.3V输入。 |
| 通信数据出现乱码 | 波特率不匹配是最大可能。 | 1. 精确核对双方波特率。115200和9600是常用值,但必须一致。 2. 检查电脑端串口工具和树莓派代码中的波特率设置。 3. 尝试降低波特率(如降到9600)测试是否稳定。 |
| 系统启动后蓝牙失效 | 在config.txt中加入了dtoverlay=disable-bt。 | 如果你需要蓝牙,不要添加disable-bt叠加层。此时,你需要使用Mini UART (/dev/ttyS0) 进行GPIO串口通信,但需要额外配置蓝牙使用PL011 UART,这涉及更复杂的设备树和hciattach配置,通常不建议新手操作。 |
6.2 深度排查命令工具箱
当问题比较复杂时,以下命令能帮你深入系统内部查看状态:
查看内核启动信息中的串口相关部分:
dmesg | grep -E \"tty|uart|serial\"这会显示内核识别串口设备的过程,看看是否有错误信息。
查看详细的硬件映射信息:
# 查看串口设备对应的物理地址和中断 sudo cat /proc/tty/driver/serial # 或查看更详细的信息 sudo cat /sys/kernel/debug/pinctrl/fe201000.serial/pingroups # 对于PL011检查串口当前参数设置:
stty -F /dev/ttyAMA0 -a这个命令会输出该串口设备的所有当前设置,包括波特率、数据位等,非常有用。
测试串口发送(不依赖Python):
echo \"hello\" > /dev/ttyAMA0这行命令会直接向串口发送字符串“hello”。可以在短接TX/RX后,用
cat /dev/ttyAMA0来接收,测试最基本的发送功能。检查设备树叠加层是否生效:
# 查看所有已加载的叠加层 sudo dtoverlay -l # 查看某个叠加层的参数(如uart5) sudo dtparam -l | grep uart
6.3 关于Mini UART (ttyS0) 的稳定性问题
如果你因为需要蓝牙而选择使用Mini UART (/dev/ttyS0),需要特别注意它的一个固有缺陷:其波特率依赖于核心时钟频率(core_freq)。当CPU频率因负载或节能策略发生变化时(如启用ondemand调速器),Mini UART的波特率可能会发生漂移,导致通信错误。
解决方案: 在/boot/firmware/config.txt中固定核心时钟频率:
core_freq=250或者,更激进地,直接固定CPU频率调速器为性能模式(但这可能增加功耗):
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor为了持久化,可以将后者写入启动脚本。但最根本的解决方案,还是如前所述,使用PL011 UART并禁用蓝牙,以获得最好的稳定性。
7. 项目集成与实战心得
将串口通信集成到实际项目中,远不止是收发几个字符那么简单。下面分享几个我在实际项目中积累的经验点。
7.1 在后台服务中可靠地使用串口
如果你写的是一个需要长期运行的后台服务(比如一个数据采集守护进程),直接使用上面的Python脚本可能不够健壮。你需要考虑:
- 异常处理与重连:串口设备可能因为意外拔插、电磁干扰等原因断开。你的代码必须能捕获
serial.SerialException异常,并在等待一段时间后尝试重新初始化连接。 - 资源释放:确保在程序退出(无论是正常退出还是被信号终止)时,能关闭串口。可以使用
atexit模块注册清理函数,或者使用try...finally语句块。 - 使用
pySerial的read_until或readline:对于有明确分隔符(如换行符\n)的协议,使用ser.readline()比手动循环读取ser.read()更方便。但要注意设置合理的timeout,避免在数据不完整时无限阻塞。 - 考虑使用异步:对于高并发或需要同时处理网络和串口IO的项目,可以考虑使用
asyncio配合pyserial-asyncio库,避免线程管理的复杂性。
7.2 与常见硬件协议对接
串口通信本身只是物理层,实际应用中需要遵循特定的应用层协议。
- Modbus RTU:这是工业领域最常用的协议之一。在Python中,可以使用
pymodbus库(pip3 install pymodbus)的RTU版本来轻松实现主站(读取传感器)或从站(模拟设备)功能。关键是要正确设置串口参数(波特率、数据位、停止位、校验位——Modbus RTU通常使用8N1或8E1)。 - 自定义文本协议:很多传感器输出简单的ASCII文本,如
\"TEMP:25.6,HUM:60\"。处理这类协议时,解析的鲁棒性很重要。建议使用状态机或正则表达式来解析,并处理好数据帧不完整、粘包的情况。 - 二进制协议:处理二进制数据时,
struct模块是你的好帮手,可以方便地打包和解包数据。一定要明确字节序(大端/小端)。
7.3 性能与稳定性监控
对于7x24小时运行的项目,加入简单的监控是很有必要的。
- 日志记录:使用Python的
logging模块,将发送和接收的重要数据、发生的错误记录到文件或系统日志(syslog)中。设置日志轮转,避免日志文件无限增大。 - 简单看门狗:可以设计一个简单的线程,定期(比如每分钟)通过串口发送一个“心跳”查询指令,并期待设备的响应。如果连续多次无响应,则触发报警或重启串口连接。
- 系统服务化:使用
systemd将你的Python脚本管理为系统服务。这样可以实现开机自启、崩溃自动重启、集中日志管理。一个简单的.service文件就能搞定。
我个人在多个基于树莓派和Ubuntu Server的物联网网关项目中,都采用了“PL011 UART + 禁用蓝牙 + Python守护进程 + systemd管理”的方案。这套组合拳提供了接近工业级的稳定性和可维护性。最大的教训就是:硬件连接一定要反复确认,特别是TX/RX;在投入长期运行前,务必进行至少24小时的压力测试,模拟各种数据收发情况,才能提前发现潜在的稳定性问题。