1. 项目缘起:为什么树莓派4B的串口配置是个“技术活”?
如果你手头有一块树莓派4B,想用它连接个GPS模块、调试个单片机、或者搞个工业级的RS485通信,第一反应可能就是去用它的GPIO引脚上那对标注为TXD和RXD的串口。但当你兴冲冲地接上线,打开minicom或screen,却发现要么没数据,要么全是乱码,甚至系统直接卡住,这时候你大概率是踩进了树莓派串口配置的“经典深坑”。
树莓派4B的串口配置,和它的前辈们(3B+及更早型号)相比,复杂度直接上了一个台阶。这主要是因为它的硬件设计发生了变化:它内部其实有多达六个UART(通用异步收发传输器)硬件资源,但默认状态下,很多都被分配给了其他功能,或者处于“休眠”状态。我们常说的“开启六个串口”,并不是凭空变出六个,而是把芯片里已有的硬件资源正确地映射、启用并配置好。
网上很多教程还停留在老版本树莓派上,只教你开启一个/dev/ttyS0(mini UART)或者/dev/ttyAMA0(PL011),对于4B来说这远远不够,甚至可能因为配置冲突导致蓝牙失效。所以,今天我们就来彻底捋清楚树莓派4B上这六个串口(UART0到UART5)到底是怎么回事,如何根据你的需求(是用于稳定的高速通信,还是简单的控制台输出)来正确开启和配置它们,并分享一些我实测中遇到的坑和解决方案。无论你是做物联网网关、机器人控制器,还是嵌入式学习,这篇都能帮你省下大量折腾的时间。
2. 硬件底牌:树莓派4B的UART资源全景图
要正确使用,必须先了解家底。树莓派4B采用的博通BCM2711芯片,其内部集成了多个UART控制器。我们通常所说的六个串口,指的是以下这些:
UART0 (PL011):这是一个全功能的、高性能的PL011型UART。在树莓派4B上,它默认被分配给了蓝牙模块使用。也就是说,/dev/ttyAMA0这个设备节点,默认是和蓝牙绑定的。如果你强行把它用于GPIO串口,蓝牙就废了。
UART1 (PL011):这是另一个PL011 UART。在某些配置下,它可以被映射到GPIO的14(TXD)、15(RXD)引脚上。性能与UART0相当。
UART2、UART3、UART4、UART5 (均为PL011):这四个是额外的PL011 UART。它们默认是禁用的,需要手动在设备树(Device Tree)中启用。启用后,它们可以被映射到GPIO上多个备选的引脚组合,提供了极大的灵活性。
Mini UART:这是一个简化版的UART,功能有限(例如不支持奇偶校验等高级功能),并且其波特率依赖于核心时钟频率,不稳定。在树莓派4B上,它默认被映射到GPIO 14/15,作为Linux控制台使用,对应的设备文件是/dev/ttyS0。
所以,当你拿到一块全新的树莓派4B,GPIO 14/15引脚上的串口,默认是那个不太稳定的/dev/ttyS0(Mini UART),它跑着系统的串口控制台。而性能强大的/dev/ttyAMA0正在为蓝牙服务。另外四个UART则在“睡大觉”。
我们的目标,就是根据需求重新“排兵布阵”。常见的需求场景有两个:
- 需求A:需要稳定、高速、功能完整的串口用于应用通信(如连接传感器、下位机),同时保留蓝牙功能。
- 需求B:需要多个串口,例如连接多个RS485设备,或者同时与多个单片机通信。
下面,我们就针对这两种主流需求,给出详细的配置步骤。
3. 实战配置一:启用高性能串口并保留蓝牙(最常用场景)
这个场景下,我们的策略是:“蓝牙不动,把Mini UART从控制台解放出来,再把一个强大的PL011 UART映射到GPIO上”。
具体操作是:将控制台从Mini UART (ttyS0) 切换到UART1 (PL011),然后将释放出来的Mini UART (ttyS0) 分配给我们的应用使用。而UART0 (ttyAMA0) 继续服务蓝牙。
步骤1:编辑引导配置文件首先,通过SSH或直接接上显示器键盘,登录到树莓派。我们需要修改/boot/firmware/config.txt文件(对于使用最新Bullseye或Bookworm系统镜像的用户)或/boot/config.txt(对于较老系统)。
sudo nano /boot/firmware/config.txt在文件末尾,添加或修改以下几行:
# 启用UART(总开关) enable_uart=1 # 将控制台从默认的Mini UART切换到UART1 (PL011) dtoverlay=uart1 # 可选:为Mini UART指定一个稳定的时钟源,提高其波特率稳定性 # 注意:这可能会轻微影响其他外设性能,非必需可不加 core_freq=250关键参数解析:
enable_uart=1:这是启用UART功能的全局开关。没有它,后续配置可能不生效。dtoverlay=uart1:这个设备树覆盖(Device Tree Overlay)指令是关键。它做了两件事:1) 启用硬件UART1 (PL011);2) 将Linux系统的串口控制台(console)绑定到这个UART1上。执行后,UART1会被映射到GPIO 14(TXD)和15(RXD)引脚。core_freq:为Mini UART锁定核心时钟频率。因为Mini UART的波特率由核心时钟分频而来,如果CPU频率动态调整(DVFS),波特率就会飘。固定核心频率可以解决这个问题。250是一个常用值。
步骤2:修改引导命令行参数接下来,需要修改引导时的命令行参数,告诉内核将控制台输出到我们新指定的UART1上。
编辑/boot/firmware/cmdline.txt(或/boot/cmdline.txt):
sudo nano /boot/firmware/cmdline.txt你会看到一行很长的参数。找到其中类似console=serial0,115200或console=ttyAMA0,115200或console=ttyS0,115200的部分。将其修改或替换为:
console=ttyAMA1,115200为什么是ttyAMA1?因为dtoverlay=uart1启用的是UART1这个PL011硬件,它在Linux系统中的设备节点默认名就是ttyAMA1。serial0是一个别名,其指向会根据配置变化,为了明确,这里直接使用确定的设备名。
步骤3:重启并验证保存所有文件,然后重启树莓派。
sudo reboot重启后,验证配置是否成功:
- 检查设备节点:使用
ls /dev/ttyA*和ls /dev/ttyS*命令。你应该能看到/dev/ttyAMA0(蓝牙)、/dev/ttyAMA1(现在的控制台)、/dev/ttyS0(空闲的Mini UART,可供我们使用)。 - 测试蓝牙:运行
hciconfig或bluetoothctl,确认蓝牙适配器(通常显示为hci0)存在且状态正常。 - 测试新控制台(可选):如果你有USB转TTL串口线,可以将其连接到GPIO 14/15引脚(注意交叉:你的RX接树莓派TX,你的TX接树莓派RX,共地)。在电脑上用串口工具(如Putty、minicom)选择对应COM口,波特率115200,应该能看到树莓派的启动日志和登录提示。注意:此时GPIO 14/15是控制台,如果你在这里输入,会干扰系统。所以应用通信不要用这对引脚了。
- 测试应用串口:现在,
/dev/ttyS0(Mini UART)是空闲的。但它默认可能还在GPIO 14/15上,和我们的控制台冲突了。别急,我们需要把它映射到别的引脚。
步骤4:将Mini UART (ttyS0) 映射到其他GPIO引脚/dev/ttyS0默认复用引脚也是14/15,我们需要用设备树覆盖把它“挪走”。比如,我们把它映射到GPIO 32(TXD)和33(RXD)(这是GPIO编号,对应物理引脚12和13)。
再次编辑/boot/firmware/config.txt:
sudo nano /boot/firmware/config.txt在刚才添加的内容后面,再加上一行:
# 将 miniUART (ttyS0) 映射到 GPIO 32 和 33 dtoverlay=miniuart-bt等等,miniuart-bt这个覆盖层不是用来把蓝牙给Mini UART的吗?是的,它的主要作用是交换ttyAMA0和ttyS0的功能。但在我们当前的配置下(控制台已切到ttyAMA1),启用miniuart-bt的效果是:将ttyS0(Mini UART)从默认引脚移开,并使其可用。同时,它会确保蓝牙仍然绑定在ttyAMA0上。这个覆盖层会自动处理引脚复用。
更精确的控制,可以使用dtoverlay=uart2或uart3等,并配合dtoverlay=uartx的txd_pin和rxd_pin参数来指定任意引脚。但miniuart-bt是一个针对我们当前场景的快捷方式。
添加后,再次重启。重启后,/dev/ttyS0就应该出现在新的引脚上了。你可以用万用表测电压,或者接一个USB转TTL模块到GPIO 32/33,用echo “test” > /dev/ttyS0命令,在电脑端串口工具查看是否收到数据来验证。
实操心得1:引脚电压与电平转换树莓派的GPIO引脚是3.3V电平的!绝对不要直接接入5V TTL或RS232设备,会烧毁芯片。连接外部设备时,务必使用电平转换模块(如MAX3232、MAX3485等)。这是新手最容易犯的硬件错误,我烧过一个GPIO口才长记性。
至此,我们实现了:一个稳定的PL011 UART (ttyAMA1) 作为控制台(也可禁用控制台后用作高速应用串口),蓝牙功能完好,并且还有一个可用的Mini UART (ttyS0) 在指定引脚上供应用使用。如果你觉得Mini UART性能不够,可以继续启用更多的PL011 UART。
4. 实战配置二:启用多个PL011串口(UART2-UART5)
当你需要连接多个可靠的外设时,Mini UART的性能和稳定性可能不够看。这时候就需要请出那四个“沉睡”的PL011 UART了。我们以启用UART2和UART3为例,将它们映射到指定的GPIO引脚上。
步骤1:确定引脚复用情况树莓派的GPIO引脚功能是复用的。在启用额外UART前,需要查官方文档或引脚图,确认你想要的引脚是否支持UART功能,以及是否被其他功能占用(如I2C、SPI)。例如,UART2可以映射到GPIO 0/1、4/5等组合。我们选择GPIO 0 (TXD2) 和 GPIO 1 (RXD2) 这组(物理引脚27和28)。
步骤2:编辑config.txt添加设备树覆盖编辑/boot/firmware/config.txt:
sudo nano /boot/firmware/config.txt添加以下内容来启用UART2和UART3:
# 启用 UART2,并指定 TXD 引脚为 GPIO0, RXD 引脚为 GPIO1 dtoverlay=uart2 dtoverlay=uart2,txd2_pin=0,rxd2_pin=1 # 启用 UART3,并指定 TXD 引脚为 GPIO4, RXD 引脚为 GPIO5 dtoverlay=uart3 dtoverlay=uart3,txd3_pin=4,rxd3_pin=5重要说明:你可能发现我写了两次dtoverlay=uart2。实际上,dtoverlay=uart2本身就会以默认引脚启用UART2。第二行带参数的写法是为了更明确地指定引脚。通常只写带参数的那一行即可。但有些情况下,先写不带参数的启用,再写带参数的覆盖配置会更稳妥。系统会合并这些配置。
步骤3:重启并验证设备节点保存文件并重启。
sudo reboot重启后,使用ls /dev/ttyAMA*命令查看。你应该能看到除了ttyAMA0(蓝牙)和ttyAMA1(我们之前配置的控制台)之外,新增了ttyAMA2和ttyAMA3。
ls -l /dev/ttyAMA* crw-rw---- 1 root dialout 204, 64 Apr 10 10:00 /dev/ttyAMA0 crw-rw---- 1 root dialout 204, 65 Apr 10 10:00 /dev/ttyAMA1 crw-rw---- 1 root dialout 204, 66 Apr 10 10:00 /dev/ttyAMA2 crw-rw---- 1 root dialout 204, 67 Apr 10 10:00 /dev/ttyAMA3步骤4:测试新串口现在可以测试新启用的串口了。由于我们还没有连接外部硬件,可以用“回环测试”来验证串口本身是否工作正常。
- 短接引脚:用杜邦线将
GPIO0 (TXD2)和GPIO1 (RXD2)短接起来。这样,从这个串口发送的数据会被自己接收回来。 - 使用串口调试工具:安装
minicom或使用Python的pyserial库。sudo apt install minicom - 配置minicom测试:
进入minicom后,随便敲击键盘,如果屏幕上同步显示你输入的字符,说明回环成功,UART2工作正常。按sudo minicom -D /dev/ttyAMA2 -b 115200Ctrl+A, 然后按X退出minicom。 - Python脚本测试(更灵活):创建一个测试脚本
loopback_test.py:
运行脚本:#!/usr/bin/env python3 import serial import time # 配置串口参数,与config.txt中设置的保持一致(如果修改了) ser = serial.Serial( port='/dev/ttyAMA2', baudrate=115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1 ) test_string = b"Hello UART2 Loopback Test!\n" print(f"Writing: {test_string.decode().strip()}") ser.write(test_string) time.sleep(0.1) # 等待数据发送和回环 if ser.in_waiting: received = ser.read(ser.in_waiting) print(f"Received: {received.decode()}") else: print("No data received. Check loopback connection.") ser.close()
如果看到发送和接收的信息一致,则证明UART2硬件和驱动完全正常。python3 loopback_test.py
实操心得2:设备权限问题默认情况下,
/dev/ttyAMA*设备属于root用户和dialout组。普通用户(如pi)需要通过dialout组来获得访问权限。确保你的用户在该组内:sudo usermod -a -G dialout $USER,然后注销重新登录生效。否则你会遇到Permission denied: ‘/dev/ttyAMA2’的错误。
UART3、4、5的启用和测试方法完全类似,只需在config.txt中添加对应的dtoverlay=uart3、uart4、uart5,并指定你想要的txdX_pin和rxdX_pin即可。你可以参考树莓派官方的设备树覆盖文档,查看每个UART支持的引脚组合。
5. 高级应用与排坑指南
配置好了串口,在实际项目中应用时,还会遇到一些典型问题。
5.1 串口与蓝牙的终极取舍
有时,你可能需要极致的串口性能,或者根本不用蓝牙。这时可以“牺牲”蓝牙,释放出最强的ttyAMA0(UART0)给应用使用。方法很简单:
在/boot/firmware/config.txt中,禁用蓝牙并交换串口:
# 禁用板载蓝牙 dtoverlay=disable-bt # 将 miniUART 分配给蓝牙(如果它还存在的话,实际上禁用后就不需要了),将 PL011 (ttyAMA0) 分配给 GPIO 14/15 # 或者更直接地,确保控制台和应用都用 PL011 dtoverlay=uart0disable-bt覆盖层会禁用蓝牙硬件,并解除ttyAMA0与蓝牙的绑定。然后dtoverlay=uart0可以将ttyAMA0映射到GPIO 14/15。同时,记得把cmdline.txt中的控制台也改回console=ttyAMA0,115200。这样,你就得到了一个位于经典引脚位置的、全功能的PL011串口,但代价是失去了蓝牙。
5.2 配置稳定的波特率
Mini UART的波特率依赖核心时钟,前文提到的core_freq=250是解决方法之一。对于PL011 UART,它们有独立的时钟源,非常稳定。但在config.txt中,你也可以通过init_uart_baud参数来设置初始波特率,不过这个通常由系统或应用动态管理,一般不需要手动设置。
在应用程序中(如C程序使用termios,或Python的pyserial),确保设置的波特率与对方设备严格一致。常见的波特率有9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600等。高速率(>115200)通信时,建议使用PL011 UART。
5.3 硬件流控制(RTS/CTS)的使用
对于高速或大数据量通信,启用硬件流控制可以防止数据丢失。PL011 UART支持硬件流控制,对应的引脚是:
- UART0/1:CTS0 -> GPIO 16, RTS0 -> GPIO 17
- UART2/3/4/5:需要查看数据手册,引脚是复用的。
在config.txt中启用UART时,可以通过添加ctsrts参数来启用硬件流控制。例如:
dtoverlay=uart2,ctsrts启用后,除了连接TXD/RXD,还需要连接对应的CTS和RTS引脚。在应用程序中,也需要在串口配置里打开硬件流控制。
5.4 常见故障排查流程
现象:
/dev/ttyAMAx设备不存在。- 检查1:
config.txt中的dtoverlay=uartx是否正确添加并拼写无误?重启了吗? - 检查2:运行
sudo vcdbg log msg | grep -i uart查看启动日志,确认内核是否成功加载了UART设备树覆盖。 - 检查3:引脚冲突。确保你映射的引脚没有被其他功能(如I2C-1、SPI)占用。检查
config.txt中是否启用了冲突的覆盖层。
- 检查1:
现象:可以打开设备,但发送/接收不到数据。
- 检查1:回环测试。短接TXD和RXD,用
minicom或自写脚本测试,排除软件问题。 - 检查2:波特率、数据位、停止位、校验位是否与对端设备完全一致?
- 检查3:电平转换。确认是否使用了3.3V电平,如果是连接5V设备,必须用电平转换模块。
- 检查4:线序是否正确?TX接RX,RX接TX,GND接GND。
- 检查1:回环测试。短接TXD和RXD,用
现象:数据出现乱码或丢失。
- 检查1:地线(GND)是否可靠连接?这是最常见的干扰源。
- 检查2:波特率误差。特别是Mini UART,尝试固定核心频率
core_freq=250。 - 检查3:电气干扰。对于长距离通信(如RS485),使用双绞线,并考虑添加终端电阻。
- 检查4:缓冲区溢出。提高读取频率,或启用硬件流控制。
现象:启用某些UART后,系统启动变慢或外设(如音频)异常。
- 检查:某些UART(特别是UART2、3、4、5)与一些外设(如音频接口)共用引脚或DMA通道。查阅BCM2711数据手册的“Alternate Function Assignments”表格,避免冲突。可以尝试换一组引脚映射。
5.5 在应用层使用串口(Python示例)
这里给一个使用pyserial库进行可靠通信的模板,包含了错误处理和超时设置:
import serial import serial.tools.list_ports import time import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class RaspberryPiUART: def __init__(self, port='/dev/ttyAMA2', baudrate=115200, timeout=1): self.port = port self.baudrate = baudrate self.timeout = timeout self.ser = None def connect(self): try: self.ser = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, # 读超时 write_timeout=self.timeout, # 写超时 xonxoff=False, # 软件流控制 rtscts=False, # 硬件流控制,根据接线和配置开启 dsrdtr=False ) if self.ser.is_open: logger.info(f"Successfully connected to {self.port} at {self.baudrate} bps.") # 清空输入输出缓冲区 self.ser.reset_input_buffer() self.ser.reset_output_buffer() return True except serial.SerialException as e: logger.error(f"Could not open port {self.port}: {e}") except Exception as e: logger.error(f"Unexpected error: {e}") return False def send_data(self, data): if not self.ser or not self.ser.is_open: logger.error("Port not open.") return False try: # 确保数据是bytes类型 if isinstance(data, str): data = data.encode('utf-8', errors='ignore') written = self.ser.write(data) self.ser.flush() # 等待所有数据发送完成 logger.debug(f"Sent {written} bytes: {data}") return written except serial.SerialTimeoutException: logger.error("Write timeout.") except serial.SerialException as e: logger.error(f"Write failed: {e}") return 0 def read_data(self, max_bytes=1024): if not self.ser or not self.ser.is_open: logger.error("Port not open.") return None try: data = self.ser.read(max_bytes) if data: logger.debug(f"Received {len(data)} bytes: {data}") return data except serial.SerialException as e: logger.error(f"Read failed: {e}") return None def close(self): if self.ser and self.ser.is_open: self.ser.close() logger.info("Serial port closed.") # 使用示例 if __name__ == "__main__": uart = RaspberryPiUART('/dev/ttyAMA2', 115200) if uart.connect(): time.sleep(0.1) # 等待串口稳定 uart.send_data("AT\r\n") # 示例:发送AT指令 response = uart.read_data() if response: print(f"Response: {response.decode('utf-8', errors='ignore')}") uart.close()这个模板提供了连接、发送、接收和关闭的完整流程,并加入了基本的异常处理,在实际项目中可以直接扩展使用。
配置树莓派4B的串口,就像在管理一个资源有限但潜力巨大的小王国。搞清楚UART0到UART5各自的状态和归属,根据你的项目需求(要蓝牙还是要性能,要一个还是要多个)进行合理的资源分配,是成功的第一步。整个过程的核心就是与/boot/firmware/config.txt和cmdline.txt这两个文件打交道,通过设备树覆盖层(dtoverlay)这个强大的工具来指挥硬件。
我个人的经验是,对于大多数需要可靠串口通信的项目,“启用UART1作控制台,释放Mini UART或启用UART2/3供应用使用”这个方案是最均衡和安全的,既能保证蓝牙可用,又能获得不错的串口性能。只有当项目确实需要多个高速串口,且完全用不到蓝牙时,才会考虑禁用蓝牙并启用UART0。
最后再提一个容易忽略的点:所有配置修改后,务必重启才能生效。sudo systemctl restart相关的服务是没用的,因为串口底层驱动是在内核启动早期由设备树配置的。多动手测试,善用回环测试和dmesg | grep tty查看内核信息,能帮你快速定位问题所在。