
1. 项目概述为什么要在树莓派4B上折腾Ubuntu 22.04的串口如果你手头有一块树莓派4B并且已经厌倦了官方的Raspberry Pi OS想试试更“正经”的服务器或桌面环境比如Ubuntu 22.04 LTS那么你很可能已经踏上了这条“折腾”之路。树莓派4B的40针GPIO排针里隐藏着强大的硬件串口UART这玩意儿在嵌入式开发、机器人控制、工业数据采集、甚至是与老式设备通信的场景里是无可替代的基石。然而当你兴冲冲地在树莓派4B上装好Ubuntu 22.04后打开终端输入ls /dev/tty*准备大干一场时可能会发现事情没那么简单——串口设备可能找不到或者找到了却无法读写权限问题、服务占用、引脚映射错误一堆坑在前面等着。这正是我们今天要啃下的硬骨头在树莓派4B搭载的Ubuntu 22.04系统上完整、稳定地配置和使用硬件串口通信。这不仅仅是运行一两条命令而是要从系统层面理解树莓派的UART布局、Ubuntu的服务管理机制再到具体的权限配置和测试验证。我花了不止一个周末的时间踩遍了能踩的坑才把这套流程跑通。接下来我会把这些经验包括背后的原理、每一步的操作意图、以及那些官方文档里不会写的“血泪教训”毫无保留地分享出来。无论你是想用串口调试Arduino、连接传感器模组还是构建自己的分布式硬件网络这篇内容都能给你一份可以直接“抄作业”的指南。2. 核心思路与硬件原理拆解树莓派4B的UART到底有几副面孔在动手之前我们必须先搞清楚树莓派4B上UART的“家底”。盲目操作只会导致时间浪费在解决错误的问题上。2.1 树莓派4B的UART资源全景图树莓派4B的SoC博通BCM2711内部实际上包含了多个UART控制器但对我们开发者开放的主要有两个UART0 (PL011)这是一个功能完整的、高性能的UART控制器支持FIFO、硬件流控RTS/CTS等高级功能。在早期的树莓派型号上它默认被映射到GPIO的14TX、15RX引脚并用作主串口Primary UART也就是系统控制台Serial Console。UART2 (Mini UART)这是一个简化版的UART其波特率依赖于核心时钟VPU因此当CPU频率变化时如省电模式其波特率可能不稳定。在树莓派4B上它通常被用作辅助串口。关键点来了在树莓派4B的默认40Pin引脚定义中GPIO 14和15物理引脚8和10默认关联的是哪个UART答案取决于一个叫“设备树覆盖层Device Tree Overlay”的配置。在官方的Raspberry Pi OS中默认配置是将功能更强大的PL011 (UART0) 分配给蓝牙模块使用而将Mini UART (UART2) 分配给了GPIO 14/15。这就是为什么很多教程在Raspberry Pi OS上需要先禁用蓝牙才能使用稳定串口的原因。然而当我们安装Ubuntu 22.04 for Raspberry Pi时情况可能有所不同。Ubuntu的镜像为了追求更好的兼容性和作为通用Linux发行版的简洁性其设备树配置可能与Raspberry Pi OS有差异。我们的首要任务就是确认当前系统下GPIO 14/15到底绑定给了谁以及我们想用的那个UART是否可用。2.2 Ubuntu 22.04 on Pi 4 的串口默认状态分析Ubuntu Server/Desktop镜像为了简化初次使用体验默认不会启用串口控制台Serial Console。这意味着用于控制台的UART可能被禁用或未被初始化。同时蓝牙服务bluetooth.service或hciuart.service可能会尝试占用PL011 UART。因此我们的配置思路需要非常清晰诊断首先探查系统现有的串口设备确定硬件连接对应的设备文件通常是/dev/ttyAMA0或/dev/ttyS0。决策根据项目需求决定使用哪个UART。对于需要稳定波特率、硬件流控的严肃应用我们应优先争取使用PL011。配置通过修改固件配置、设备树参数和系统服务将目标UART从系统占用中“释放”出来并映射到正确的GPIO引脚。验证配置完成后进行读写测试确保通信稳定可靠。注意不同版本的Ubuntu镜像如官方的Ubuntu Server for Raspberry Pi 或由第三方维护的镜像初始状态可能不同。我强烈建议从Ubuntu官网下载专为树莓派制作的镜像开始以减少底层差异带来的不确定性。3. 实战第一步系统安装、基础探查与引脚连接3.1 系统安装与初始准备首先从Ubuntu官方网站下载“Ubuntu Server for Raspberry Pi”的22.04 LTS镜像。使用Raspberry Pi Imager或其他烧录工具写入SD卡。在烧录时Imager工具通常提供了“高级选项”允许你预先配置主机名、用户密码并启用SSH。务必启用SSH这是我们后续操作的主要通道因为你很可能在一段时间内都无法使用串口控制台登录。将烧录好的SD卡插入树莓派4B连接网线或配置好Wi-Fi上电启动。通过路由器管理界面或arp -a命令找到树莓派的IP地址使用SSH登录例如ssh ubuntu192.168.1.xxx默认用户是ubuntu。登录后第一件事更新系统sudo apt update sudo apt upgrade -y更新完成后建议重启一次sudo reboot。这能确保所有内核更新生效。3.2 硬件连接与设备探查在软件配置前先完成物理连接。你需要一个USB转TTL串口模块如CH340、CP2102、FT232等。连接方式至关重要接错可能损坏设备USB转TTL模块的GND-树莓派GPIO的GND物理引脚6 或任意其他GND引脚USB转TTL模块的RX-树莓派GPIO的TX物理引脚8 GPIO 14USB转TTL模块的TX-树莓派GPIO的RX物理引脚10 GPIO 15重要警告绝对不要连接USB转TTL模块的VCC通常是5V或3.3V到树莓派的任何引脚树莓派的GPIO是3.3V电平且不能耐受5V输入。仅连接GND、RX、TX三根线即可。连接好后将USB转TTL模块插入你的电脑用于调试的主机。在树莓派SSH终端里开始探查# 查看所有tty设备 ls -l /dev/tty* # 重点关注以下设备 ls -l /dev/ttyAMA* /dev/ttyS* /dev/serial*你可能会看到类似/dev/ttyAMA0或/dev/ttyS0的设备。记录下它们。接下来查看这些设备当前被谁占用以及它们的硬件关联信息# 查看串口设备硬件信息 sudo dmesg | grep -i uart sudo dmesg | grep -i tty # 一个更强大的工具查看设备树中UART的映射状态 sudo cat /proc/device-tree/aliases/serial0 2/dev/null sudo cat /proc/device-tree/aliases/serial1 2/dev/nullserial0和serial1分别指向系统认定的主、辅串口设备文件。3.3 关键诊断确认当前UART分配方案执行以下命令查看最关键的固件配置# 查看当前内核命令行参数其中包含了UART的配置信息 cat /proc/cmdline在输出中寻找如consolettyAMA0,115200或consoleserial0,115200或consoletty1这样的参数。如果console参数指向了ttyAMA0或serial0说明该UART正在被用作内核控制台这会影响我们将其用于普通数据通信。同时检查蓝牙服务是否在运行并占用了UARTsystemctl status hciuart如果这个服务是active (running)状态那么PL011 UART很可能正在被蓝牙使用。4. 核心配置释放硬件串口并映射到GPIO根据诊断结果我们通常需要执行以下配置。我们的目标是将PL011 UART (ttyAMA0)从蓝牙和控制台占用中释放出来并将其固定映射到GPIO 14/15引脚。4.1 修改固件配置文件Boot Config这是最关键的一步告诉树莓派固件如何初始化硬件。# 使用nano或vim编辑boot分区下的配置文件 sudo nano /boot/firmware/config.txt注意在Ubuntu for Pi上配置文件路径通常是/boot/firmware/config.txt 而非Raspberry Pi OS的/boot/config.txt在文件末尾添加或修改以下几行# 启用UART总开关 enable_uart1 # 将PL011 UART (ttyAMA0) 指定为主要的UART设备并映射到GPIO 14/15 dtoverlayuart5 # 注意树莓派4B上uart5这个覆盖层是将PL011映射到GPIO 14/15的常用方法。 # 另一种写法是 dtoverlayuart5, pinctrl-namesdefault; pinctrl-0uart5_pins # 确保Mini UART (ttyS0) 不被用作控制台并修复其时钟源以稳定波特率如果需要使用它 # dtoverlayminiuart-bt # 上面这行会交换PL011和Mini UART的映射并将PL011给蓝牙Mini UART给GPIO 14/15。我们不采用此方案。 # 禁用蓝牙以彻底释放PL011 UART (ttyAMA0)。如果不需要蓝牙这是最干净的做法。 dtoverlaydisable-bt参数解读enable_uart1 这是基础启用UART硬件。dtoverlayuart5 这个设备树覆盖层Device Tree Overlay是专为树莓派4B设计的它正确地将PL011控制器配置到正确的引脚组GPIO 14/15。这是区别于旧型号Pi的关键。dtoverlaydisable-bt 禁用板载蓝牙。这会同时停止hciuart服务并将PL011 UART完全让给我们使用。如果你的项目完全不需要蓝牙强烈建议禁用。4.2 禁用相关系统服务即使我们在固件层禁用了蓝牙Ubuntu系统里对应的服务可能还会尝试去访问它导致冲突。我们需要一并禁用。# 停止并禁用蓝牙相关服务 sudo systemctl stop bluetooth.service sudo systemctl disable bluetooth.service sudo systemctl stop hciuart.service sudo systemctl disable hciuart.service # 禁用通过串口启动的getty服务它会在串口上提供登录终端干扰我们的数据通信 sudo systemctl stop serial-gettyttyAMA0.service 2/dev/null sudo systemctl disable serial-gettyttyAMA0.service 2/dev/null # 也检查一下ttyS0 sudo systemctl stop serial-gettyttyS0.service 2/dev/null sudo systemctl disable serial-gettyttyS0.service 2/dev/null4.3 移除内核控制台输出可选但推荐为了让串口纯粹用于应用数据最好移除内核消息和控制台对该串口的占用。编辑/boot/firmware/cmdline.txtsudo nano /boot/firmware/cmdline.txt找到包含consolettyAMA0,115200或consoleserial0,115200的部分将其删除。注意整个cmdline.txt内容是一行编辑时要小心确保单词间空格正确。删除后这一行可能只剩下类似net.ifnames0 dwc_otg.lpm_enable0 consoletty1 root...的内容。consoletty1表示控制台输出到HDMI/屏幕这是我们希望的状态。实操心得修改cmdline.txt风险较高一旦格式错误可能导致系统无法启动。一个安全的做法是在修改前先备份原文件 (sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.backup)并且确保你始终可以通过SSH或屏幕访问系统。如果改坏了可以通过其他系统挂载SD卡的boot分区进行恢复。4.4 应用配置并重启所有配置修改完成后保存文件执行重启sudo reboot重启后重新SSH登录系统。5. 配置验证与基础通信测试重启后让我们验证配置是否成功。5.1 验证设备与权限再次列出设备ls -l /dev/ttyAMA*你应该能看到/dev/ttyAMA0并且它的权限可能是crw-rw---- 1 root dialout ...。这意味着root用户和dialout用户组的成员有读写权限。将当前用户ubuntu添加到dialout组避免每次都用sudosudo usermod -a -G dialout $USER重要这个组变更需要重新登录退出SSH再重新连接才能生效。5.2 使用minicom进行双向测试minicom是一个经典的串口终端工具。安装并配置它sudo apt install minicom -y在测试前请确保你的USB转TTL模块已连接到电脑并在电脑上确定了其端口如Windows的COM3 Linux的/dev/ttyUSB0。在电脑端使用串口助手工具如Putty、SecureCRT 或Arduino IDE的串口监视器打开该端口波特率设置为115200数据位8停止位1无校验位8N1。首先在树莓派端我们用minicom监听/dev/ttyAMA0minicom -D /dev/ttyAMA0 -b 115200如果配置正确你应该会进入一个空白的minicom界面。此时在电脑端的串口助手中发送一些字符如“Hello Pi”。这些字符应该会显示在树莓派的minicom窗口中。接着测试树莓派发送。在minicom界面中直接打字然后回车。这些字符应该会显示在电脑端的串口助手的接收区。退出minicom的快捷键是Ctrl-A 然后按X选择“Yes”退出。5.3 使用Python进行自动化脚本测试对于嵌入式应用我们更常用编程方式操作串口。Python的pyserial库是绝佳选择。# 安装pyserial sudo apt install python3-pip -y pip3 install pyserial创建一个简单的测试脚本uart_test.py#!/usr/bin/env python3 import serial import time # 配置串口参数 ser serial.Serial( port/dev/ttyAMA0, # 你的串口设备 baudrate115200, # 波特率 parityserial.PARITY_NONE, # 校验位 stopbitsserial.STOPBITS_ONE, # 停止位 bytesizeserial.EIGHTBITS, # 数据位 timeout1 # 读超时秒 ) try: if ser.is_open: print(f串口 {ser.name} 已打开。) # 发送数据 test_message Hello from Raspberry Pi!\n ser.write(test_message.encode(utf-8)) print(f已发送: {test_message.strip()}) # 等待并尝试接收回显如果你将TX和RX短接或者对端设备会回传数据 time.sleep(0.1) while ser.in_waiting: received_data ser.read(ser.in_waiting).decode(utf-8, errorsignore) print(f接收到: {received_data}) except Exception as e: print(f发生错误: {e}) finally: if ser and ser.is_open: ser.close() print(串口已关闭。)运行脚本python3 uart_test.py。观察输出并在电脑端串口助手查看是否收到“Hello from Raspberry Pi!”消息。你也可以在电脑端发送消息然后修改脚本加入持续监听的功能来测试接收。6. 高级配置与性能调优基础通信跑通后为了满足更严苛的应用场景可能还需要进行一些调优。6.1 调整串口缓冲区大小默认的串口内核缓冲区可能较小在高波特率或突发数据流下容易丢失数据。可以尝试在打开串口后特别是在C/C程序中使用ioctl调整缓冲区大小。对于Pythonpyserial它依赖于系统默认值。一种系统级的方法是使用setserial工具可能需要安装sudo apt install setserial但更通用的做法是在自己的应用程序中实现软件缓冲。6.2 硬件流控RTS/CTS的使用如果你的设备支持硬件流控并且需要用到它例如连接老式Modem或某些无线模块你需要连接额外引脚将树莓派GPIO的GPIO 17 (物理引脚11)作为RTSGPIO 18 (物理引脚12)作为CTS连接到对端设备。启用设备树覆盖层在/boot/firmware/config.txt中dtoverlayuart5实际上已经默认启用了这两个引脚作为流控引脚。你可以显式指定dtoverlayuart5,ctsrts。在代码中启用在Python中创建Serial对象时传入参数rtsctsTrue。6.3 修改默认波特率上述所有配置默认使用115200波特率。如果你想修改默认波特率需要注意config.txt中的enable_uart1不直接指定波特率。内核命令行参数consolettyAMA0,115200中的波特率只影响内核控制台。应用程序的波特率是在打开串口时指定的如Python中的baudrate9600。只要双方设备设置为相同的波特率即可。对于minicom可以通过-b参数指定或在配置菜单中设置。7. 疑难杂症与排查实录即使按照步骤操作你也可能遇到问题。以下是我踩过的坑和解决方案。7.1 常见问题速查表问题现象可能原因排查步骤与解决方案/dev/ttyAMA0不存在1. UART未在config.txt中启用。2. 错误的dtoverlay。1. 检查/boot/firmware/config.txt是否有enable_uart1。2. 树莓派4B尝试dtoverlayuart5。3. 运行sudo raspi-config如果已安装检查接口选项。权限拒绝 (Permission denied)当前用户不在dialout组。执行sudo usermod -a -G dialout $USER并重新登录。临时可用sudo运行程序。能发送数据但接收不到任何数据1. TX/RX线接反。2. 对端设备未工作或配置错误。3. 地线(GND)未连接。1.重点检查树莓派的TX应接USB模块的RX树莓派的RX接USB模块的TX。2. 确认对端设备已上电、波特率等参数一致。3. 确保GND可靠连接。收到乱码1. 波特率、数据位、停止位、校验位不匹配。2. 电平不匹配如3.3V与5V混用。3. 硬件问题或干扰。1. 仔细核对通信双方的串口参数必须完全一致。2. 确认使用的是3.3V电平的USB转TTL模块。3. 尝试降低波特率如9600测试。运行minicom或Python脚本时系统卡住或无响应1. 串口被其他进程占用如残留的getty。2. 硬件流控启用且引脚状态不对。1. 使用sudo lsof /dev/ttyAMA0查看占用进程并结束它。2. 检查config.txt中是否启用了ctsrts但未连接硬件尝试在代码中禁用流控(rtsctsFalse)。蓝牙和串口无法同时使用默认配置下PL011 UART被蓝牙独占。方案A推荐稳定串口禁用蓝牙dtoverlaydisable-bt如上文所述。方案B需蓝牙使用Mini UART (ttyS0) 做串口。在config.txt中添加dtoverlayminiuart-bt这将交换两者映射但Mini UART波特率可能不稳定。修改cmdline.txt后无法启动cmdline.txt格式错误如多余空格、换行。通过其他系统挂载SD卡的boot分区恢复备份的cmdline.txt.backup文件或参考同版本系统重新构造一行正确的参数。7.2 深度排查命令工具箱当问题复杂时这些命令能提供更深层信息# 1. 查看详细的硬件映射信息设备树 sudo dtc -I fs /sys/firmware/devicetree/base | grep -A 10 -B 10 uart # 或查找更具体的信息 find /sys/firmware/devicetree/base -name \*uart*\ -type f | xargs cat 2/dev/null # 2. 查看内核启动关于UART的详细日志 dmesg | grep -E \(uart|tty|pl011|bcm2835)\ # 3. 检查串口驱动模块状态 lsmod | grep serial # 对于树莓派相关驱动通常是内置的built-in不会显示在这里。 # 4. 使用strace跟踪程序访问串口的行为高级调试 strace -e traceopenat,ioctl,read,write python3 uart_test.py 21 | grep ttyAMA7.3 一个隐蔽的坑serial-getty服务的残留有时即使你已经禁用了serial-gettyttyAMA0.service系统在启动后仍可能因为某些依赖或缓存机制再次激活它。一个更彻底的方法是移除其符号链接sudo rm -f /etc/systemd/system/getty.target.wants/serial-gettyttyAMA0.service sudo rm -f /lib/systemd/system/serial-getty.service.wants/serial-gettyttyAMA0.service # 如果存在 sudo systemctl daemon-reload然后再次重启。8. 从测试到应用构建一个简单的串口数据转发服务最后我们来点实际的将配置好的串口用在一个小项目里创建一个简单的串口到TCP Socket的转发服务。这样你可以通过网络远程访问连接在树莓派串口上的设备。使用Python编写一个脚本uart_to_tcp_bridge.py#!/usr/bin/env python3 import serial import socket import threading import time import signal import sys # 配置 UART_PORT /dev/ttyAMA0 UART_BAUDRATE 115200 TCP_HOST 0.0.0.0 # 监听所有网络接口 TCP_PORT 8888 class UARTtoTCPBridge: def __init__(self): self.ser None self.server_socket None self.client_socket None self.running False def uart_reader(self): 从串口读取数据并发送给TCP客户端 while self.running and self.ser and self.ser.is_open: try: if self.ser.in_waiting: data self.ser.read(self.ser.in_waiting) if self.client_socket: self.client_socket.sendall(data) print(f\[UART-TCP] Sent {len(data)} bytes\) except (serial.SerialException, OSError, ConnectionResetError) as e: print(f\UART read error: {e}\) break time.sleep(0.001) # 短暂休眠避免CPU空转 def tcp_reader(self): 从TCP客户端读取数据并发送给串口 while self.running and self.client_socket: try: data self.client_socket.recv(1024) if not data: print(\TCP client disconnected.\) break if self.ser and self.ser.is_open: self.ser.write(data) print(f\[TCP-UART] Sent {len(data)} bytes\) except (socket.error, ConnectionResetError) as e: print(f\TCP read error: {e}\) break def start(self): 启动桥接服务 # 初始化串口 try: self.ser serial.Serial(UART_PORT, UART_BAUDRATE, timeout0) print(f\UART opened on {UART_PORT} at {UART_BAUDRATE} baud\) except serial.SerialException as e: print(f\Failed to open UART: {e}\) return # 初始化TCP服务器 try: self.server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((TCP_HOST, TCP_PORT)) self.server_socket.listen(1) print(f\TCP server listening on {TCP_HOST}:{TCP_PORT}\) except socket.error as e: print(f\Failed to start TCP server: {e}\) self.ser.close() return self.running True # 主循环接受TCP客户端连接 while self.running: try: print(\Waiting for TCP client connection...\) self.client_socket, client_addr self.server_socket.accept() print(f\TCP client connected from {client_addr}\) # 为每个客户端连接启动读写线程 uart_thread threading.Thread(targetself.uart_reader, daemonTrue) tcp_thread threading.Thread(targetself.tcp_reader, daemonTrue) uart_thread.start() tcp_thread.start() # 等待TCP连接断开 tcp_thread.join() print(\Client handling finished. Closing connection.\) self.client_socket.close() self.client_socket None except KeyboardInterrupt: print(\\\nShutdown signal received.\) break except Exception as e: print(f\Server error: {e}\) break self.stop() def stop(self): 停止服务清理资源 print(\Stopping bridge...\) self.running False if self.client_socket: self.client_socket.close() if self.server_socket: self.server_socket.close() if self.ser and self.ser.is_open: self.ser.close() print(\Bridge stopped.\) def signal_handler(sig, frame): print(\\nExiting...) sys.exit(0) if __name__ __main__: signal.signal(signal.SIGINT, signal_handler) bridge UARTtoTCPBridge() bridge.start()使用方式将脚本保存到树莓派并安装依赖已安装pyserial。运行脚本python3 uart_to_tcp_bridge.py。它会打开串口并在8888端口监听TCP连接。在你的电脑上使用网络调试助手如Netcat:nc 树莓派IP 8888或 MobaXterm的Socket功能连接到树莓派的IP和8888端口。现在你在网络调试助手中发送的数据会通过树莓派转发到串口设备串口设备返回的数据也会通过网络传回你的调试助手。这个简单的桥接服务展示了串口通信如何融入更大的系统架构。你可以在此基础上增加协议解析、数据日志、多客户端管理等功能使其成为一个实用的工业网关原型。整个配置和测试过程的核心在于对树莓派硬件资源分配和Linux系统服务管理的理解。每一步修改都对应着解决一个具体的冲突或设定一个明确的预期。当你成功在Ubuntu 22.04上驱动起树莓派4B的串口时这片小小的开发板就真正具备了与广阔物理世界对话的能力。