树莓派4B Ubuntu 22.04串口配置与通信实战指南

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有细微差别。默认情况下:

  1. 串口控制台登录被禁用:与一些基础镜像不同,Ubuntu Server默认不会通过串口提供登录终端(getty服务)。这其实是好事,意味着串口设备可以被我们独占用于应用通信,而不会被登录会话占用。
  2. 设备文件映射:你需要通过ls -l /dev/tty*命令来查看具体的设备文件。通常,/dev/ttyAMA0对应PL011 UART,/dev/ttyS0对应Mini UART。但它们的最终功能取决于设备树(Device Tree)的配置。
  3. 权限问题:默认情况下,串口设备文件属于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,这会打开“高级选项”菜单。

在高级选项中,我强烈建议你预先做两件事:

  1. 启用SSH并设置密码:勾选“Enable SSH”,并选择“Use password authentication”,设置一个强密码。这样系统首次启动后,你就可以通过网络SSH登录,无需连接显示器和键盘。
  2. 配置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-compiler

3.3 核心步骤:配置设备树以启用GPIO串口

这是整个流程中最关键的一步,我们需要修改引导配置,告诉内核我们想怎么使用串口硬件。

  1. 备份原始配置:操作前先备份总是一个好习惯。

    sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.bak sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak
  2. 编辑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-bt
    • enable_uart=1:这是必须的,它确保UART控制器被上电并初始化。
    • dtoverlay=uart5:这个叠加层(Overlay)是树莓派4B专用的,它的作用是将ttyAMA0(PL011 UART)重新映射到GPIO14和GPIO15引脚。注意:在树莓派4B上,这个叠加层的名字是uart5,而不是早期型号的pi3-miniuart-btpi3-disable-bt,用错会导致配置无效。
    • dtoverlay=disable-bt:这行会禁用蓝牙硬件。因为PL011之前被蓝牙占用,如果我们想独占PL011用于GPIO串口,最简单的办法就是禁用蓝牙。如果你的项目同时需要蓝牙和高质量串口,那么配置会复杂很多,可能需要手动配置蓝牙使用Mini UART,这超出了本文基础范围。

    添加完成后,按Ctrl+X,然后按Y,再按Enter保存退出。

  3. 检查并清理cmdline.txt文件:这个文件包含了传递给Linux内核的启动参数。我们需要确保里面没有关于串口控制台的参数,否则它会占用我们想用的串口设备。

    sudo nano /boot/firmware/cmdline.txt

    检查这一行内容。在Ubuntu for Raspberry Pi的默认安装中,通常不包含类似console=serial0,115200console=ttyAMA0,115200这样的参数。如果发现有,请将其删除。删除后,这一行应该只剩下关于根文件系统等的基本参数。保存并退出。

  4. 重启系统:让所有配置生效。

    sudo reboot

3.4 验证配置与设置权限

重启并重新SSH登录后,我们来验证配置是否成功。

  1. 查看串口设备

    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引脚。

  2. 验证引脚映射:我们可以通过读取设备树状态来确认。

    sudo dtoverlay -l

    这个命令会列出所有已加载的设备树叠加层。你应该能看到uart5在列表中。 更详细地,可以查看:

    sudo cat /sys/kernel/debug/pinctrl/fe201000.serial/pins

    或者,一个更简单的方法是使用raspi-gpio工具(如果已安装)来查看GPIO14/15的功能复用状态,但Ubuntu Server默认可能没有。一个实用的替代方法是,我们直接进行物理连接测试。

  3. 将用户加入dialout:为了避免每次都用sudo,把你的用户添加到串口所属的用户组。

    sudo usermod -a -G dialout $USER

    重要:这个改动需要重新登录(退出SSH会话再重新连接)才能生效。仅仅su - $USER可能不够。

4. 硬件连接与通信测试

配置好了软件,现在需要连接硬件进行实测。你需要一个USB转TTL串口模块(比如常见的CH340、CP2102、FT232等芯片的模块)。

4.1 物理连接注意事项

安全第一!务必在树莓派断电的情况下进行连接!

连接USB转TTL模块到树莓派4B的GPIO引脚:

  1. USB转TTL模块的GND->树莓派GPIO的GND(例如第6针脚)。
  2. USB转TTL模块的RX->树莓派GPIO的TX (GPIO14)(第8针脚)。
  3. 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作为串口调试工具。

  1. 启动Minicom,配置并发送数据

    sudo minicom -D /dev/ttyAMA0 -b 115200

    如果提示minicom: cannot open /dev/ttyAMA0: Permission denied,说明之前的用户组设置还没生效,请确认已重新登录,或者暂时用sudo运行。

    进入minicom界面(是一个简单的终端),直接打字。你输入的字符会通过树莓派的TX线发送出去。此时,你应该能在电脑的串口终端上看到这些字符。

  2. 接收数据测试: 在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识别)创建一个别名:

  1. 插入设备,用lsusb找到其ID,比如1a86:7523(CH340)。
  2. 创建规则文件:
    sudo nano /etc/udev/rules.d/99-usb-serial.rules
  3. 加入一行(将1a867523替换为你的设备ID):
    SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyMyDevice"
  4. 重新加载udev规则并重启服务:
    sudo udevadm control --reload-rules sudo udevadm trigger
  5. 重新插拔设备,现在除了/dev/ttyUSBx,还会出现一个固定的/dev/ttyMyDevice链接。

6. 常见问题与深度排查指南

即使按照步骤操作,你也可能会遇到一些问题。这里汇总了我遇到过的典型情况及其解决方法。

6.1 问题速查表

问题现象可能原因排查步骤与解决方案
ls /dev/ttyAMA0无此设备1.config.txtenable_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语句块。
  • 使用pySerialread_untilreadline:对于有明确分隔符(如换行符\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小时的压力测试,模拟各种数据收发情况,才能提前发现潜在的稳定性问题。