☰
树莓派4多串口配置实战:PL011与mini UART详解
2026/10/15 18:00:20 网站建设 项目流程

1. 为什么树莓派4的UART配置值得单独拿出来讲

树莓派4的UART串口配置,看起来是个小话题,但实际踩过坑的人都知道,这东西不折腾则已,一折腾能卡你大半天。我最早接触树莓派串口是在做一个多传感器数据采集的项目,当时需要同时接一个GPS模块、一个LoRa通信模块,外加一个调试串口用来打印日志。心想树莓派4有那么多GPIO引脚,UART应该不止一个吧?结果一查文档才发现,事情没那么简单。

树莓派4的BCM2711芯片内部其实有6个UART控制器,但默认情况下,只有两个能用:一个是PL011(也就是硬件全功能串口),另一个是mini UART(功能受限的简易串口)。更让人头疼的是,这两个串口的引脚分配、时钟源、波特率稳定性都不一样,而且默认的引脚映射还跟蓝牙模块冲突。你要是不做任何配置,插上串口模块大概率是收不到数据的。

这篇文章就是把我自己在树莓派4上折腾多串口通信的完整经验整理出来。从UART的基本原理、设备树覆盖配置、引脚复用规则,到多串口同时工作的实操步骤、波特率计算、常见问题排查,全部讲清楚。不管你是刚拿到树莓派的新手,还是已经用过树莓派3、现在升级到4的老玩家,只要涉及到串口通信,这篇内容都能帮你少走弯路。

核心关键词:树莓派4、UART、多串口配置、PL011、mini UART、设备树、GPIO引脚复用、串口通信。

2. 树莓派4的UART硬件架构与核心概念

2.1 六个UART控制器,为什么默认只能用两个

树莓派4用的BCM2711芯片,内部集成了6个UART外设,分别叫UART0到UART5。但你在系统里能直接用的,默认只有两个:

  • UART0(PL011):这是ARM PrimeCell PL011,功能完整,有独立的时钟源,波特率稳定,支持硬件流控。默认映射到GPIO14(TXD)和GPIO15(RXD),也就是物理引脚的第8和第10脚。
  • UART1(mini UART):这是BCM2711内部的一个简化版串口,没有独立时钟,它的波特率依赖于GPU核心时钟频率。默认映射到GPIO14和GPIO15,跟PL011共用引脚,但同一时间只能有一个生效。

剩下的UART2到UART5,默认是关闭的,需要通过设备树覆盖(Device Tree Overlay)来启用,并且要把它们映射到其他GPIO引脚上。这就是为什么很多人以为树莓派只有一个串口——因为默认确实只暴露了一个。

这里有个关键点:PL011和mini UART的引脚是复用的。在默认配置下,GPIO14和GPIO15要么给PL011用,要么给mini UART用,不能同时用。而蓝牙模块默认占用了PL011,所以你在GPIO14/15上看到的其实是mini UART。这就是为什么很多人用默认配置连串口,发现波特率怎么调都不对——因为mini UART的时钟不稳定。

2.2 PL011与mini UART的本质区别

这两个串口的差异,直接决定了你在什么场景下该用哪个。

特性PL011(UART0)mini UART(UART1)
时钟源独立时钟(48MHz)GPU核心时钟
波特率稳定性高,不受GPU频率影响低,GPU降频时波特率漂移
硬件流控支持RTS/CTS不支持
FIFO缓冲区32字节8字节
中断支持完整有限
默认用途蓝牙GPIO串口控制台

从表格能看出来,PL011明显更适合做可靠的串口通信。mini UART的问题在于它的时钟跟GPU绑定,而GPU频率会根据负载动态调整。你设了115200的波特率,GPU一降频,实际波特率可能变成112000,误差超过2%,通信就开始丢包了。

所以我的建议很明确:如果你要做稳定的串口通信,一定要把PL011从蓝牙那里抢过来,分配给GPIO引脚。蓝牙可以用mini UART,或者干脆不用蓝牙。具体怎么操作,后面会详细讲。

2.3 设备树覆盖的工作原理

树莓派4的硬件配置不是写死在系统里的,而是通过设备树(Device Tree)来描述。设备树是一棵描述硬件拓扑的树形结构,内核启动时读取它,知道哪个引脚对应哪个外设。

设备树覆盖(Overlay)是一种在运行时修改设备树的机制。你可以把它理解成给设备树打补丁:系统启动时先加载基础设备树,然后根据/boot/config.txt里的dtoverlay指令,把对应的覆盖文件加载进去,修改引脚映射、启用或禁用外设。

树莓派系统自带了很多现成的覆盖文件,放在/boot/overlays/目录下。跟UART相关的有:

  • uart0.dtbo:启用PL011到GPIO14/15
  • uart1.dtbo:启用mini UART到GPIO14/15
  • uart2.dtbo到uart5.dtbo:启用对应的UART到指定引脚
  • disable-bt.dtbo:禁用蓝牙,释放PL011

这些覆盖文件可以组合使用,但要注意引脚不能冲突。比如你同时启用uart0和uart1,它们都映射到GPIO14/15,就会冲突。正确的做法是把不同的UART映射到不同的引脚组。

3. 多串口配置的完整实操流程

3.1 准备工作:系统版本确认与备份

在动手改配置之前,先确认你的系统版本。打开终端,执行:

cat /etc/os-release uname -a

你需要确认两件事:系统是不是Raspberry Pi OS(或者基于Debian的衍生版),内核版本是不是4.19以上。树莓派4需要较新的内核才能完整支持多UART。

然后备份关键配置文件:

sudo cp /boot/config.txt /boot/config.txt.bak sudo cp /boot/cmdline.txt /boot/cmdline.txt.bak

这两个文件是串口配置的核心。config.txt控制硬件初始化和设备树覆盖,cmdline.txt控制内核启动参数,特别是串口控制台的输出。

注意:改这两个文件之前一定要备份。我见过太多人改错了配置,导致树莓派无法启动,只能拔卡重新烧录。备份只需要几秒钟,但能省你几个小时。

3.2 第一步:释放PL011,禁用蓝牙对串口的占用

默认情况下,PL011被蓝牙模块占用了。你要做的第一件事就是禁用蓝牙,把PL011释放出来。

编辑/boot/config.txt:

sudo nano /boot/config.txt

在文件末尾添加:

dtoverlay=disable-bt

保存退出后,还需要禁用蓝牙相关的系统服务:

sudo systemctl disable hciuart sudo systemctl disable bluetooth

这两条命令的作用是防止系统启动时自动加载蓝牙串口服务,避免它跟你的串口配置抢资源。

然后编辑/boot/cmdline.txt,把里面的串口控制台参数删掉。默认内容可能是这样的:

console=serial0,115200 console=tty1 root=PARTUUID=xxxx rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

把console=serial0,115200这一段删掉,变成:

console=tty1 root=PARTUUID=xxxx rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

这一步非常关键。如果不删掉,内核会把串口当成控制台输出日志,你的串口通信就会混入系统日志,根本没法用。

3.3 第二步:启用多个UART并映射到不同引脚

现在PL011已经释放了,接下来要启用额外的UART。树莓派4支持UART2到UART5,它们可以映射到不同的GPIO引脚组合。

编辑/boot/config.txt,添加以下内容:

# 启用PL011到GPIO14/15 dtoverlay=uart0 # 启用UART2到GPIO0/1 dtoverlay=uart2 # 启用UART3到GPIO4/5 dtoverlay=uart3 # 启用UART4到GPIO8/9 dtoverlay=uart4 # 启用UART5到GPIO12/13 dtoverlay=uart5

这里需要解释一下引脚映射的规则。每个UART覆盖文件都有默认的引脚分配,但也可以通过参数指定其他引脚。以uart2为例,默认映射到GPIO0(TXD)和GPIO1(RXD),但如果你写dtoverlay=uart2,pins=4,5,它就会映射到GPIO4和GPIO5。

不过要注意,不是所有引脚都能随便映射。BCM2711的引脚复用矩阵有固定规则,每个UART只能映射到特定的几组引脚上。常用的映射关系如下:

UART默认TXD引脚默认RXD引脚可选引脚组
UART0GPIO14GPIO15GPIO14/15, GPIO32/33
UART2GPIO0GPIO1GPIO0/1, GPIO4/5
UART3GPIO4GPIO5GPIO4/5, GPIO8/9
UART4GPIO8GPIO9GPIO8/9, GPIO12/13
UART5GPIO12GPIO13GPIO12/13, GPIO16/17

我个人的习惯是:UART0留给调试串口,接一个USB转TTL模块,用来打印日志;UART2接GPS模块;UART3接LoRa模块;UART4和UART5备用。这样五个串口同时工作,互不干扰。

3.4 第三步:验证串口设备节点

配置改完后,重启树莓派:

sudo reboot

重启后,检查设备节点是否正常生成:

ls -l /dev/ttyAMA* /dev/ttyS*

你应该能看到类似这样的输出:

/dev/ttyAMA0 -> PL011 (UART0) /dev/ttyAMA1 -> UART2 /dev/ttyAMA2 -> UART3 /dev/ttyAMA3 -> UART4 /dev/ttyAMA4 -> UART5

注意,设备节点的命名规则是:PL011固定叫ttyAMA0,其他UART按启用顺序依次叫ttyAMA1、ttyAMA2等。mini UART叫ttyS0,但如果你禁用了蓝牙并启用了PL011,ttyS0可能不存在或者被其他设备占用。

如果看不到预期的设备节点,先检查config.txt里的覆盖是否写对了,然后执行:

dtoverlay -l

这条命令会列出当前加载的所有覆盖。如果某个覆盖没加载,说明配置有语法错误或者跟其他覆盖冲突了。

3.5 第四步:配置串口参数与权限

设备节点有了,接下来要配置串口参数。用stty命令可以设置波特率、数据位、停止位、校验位:

sudo stty -F /dev/ttyAMA1 9600 cs8 -cstopb -parenb

这条命令的意思是:波特率9600,8位数据位,1位停止位,无校验。这是大多数GPS和LoRa模块的默认参数。

如果你需要更高的波特率,比如115200:

sudo stty -F /dev/ttyAMA1 115200 cs8 -cstopb -parenb

然后给串口设备加上读写权限,避免每次都要用sudo:

sudo chmod 666 /dev/ttyAMA1

或者把当前用户加到dialout组:

sudo usermod -a -G dialout $USER

改完组权限后需要重新登录才能生效。

提示:stty设置的参数在系统重启后会丢失。如果你需要持久化配置,可以写一个systemd服务,或者用/etc/rc.local在启动时执行。我一般是在Python脚本里用pyserial库直接设置参数,这样更灵活。

4. 多串口通信的编程实现与数据收发

4.1 用Python pyserial库同时操作多个串口

硬件配置好了,接下来就是写代码。Python的pyserial库是操作串口最方便的工具,安装很简单:

pip install pyserial

下面是一个同时读取两个串口数据的示例:

import serial import threading import time def read_serial(port, baudrate, name): ser = serial.Serial( port=port, baudrate=baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1 ) print(f"{name} 已打开: {port} @ {baudrate}") while True: if ser.in_waiting > 0: data = ser.readline() print(f"[{name}] {data.decode('utf-8', errors='ignore').strip()}") time.sleep(0.01) # 创建两个线程分别读取两个串口 t1 = threading.Thread(target=read_serial, args=('/dev/ttyAMA1', 9600, 'GPS')) t2 = threading.Thread(target=read_serial, args=('/dev/ttyAMA2', 115200, 'LoRa')) t1.start() t2.start() t1.join() t2.join()

这段代码的核心思路是:每个串口用一个独立的线程去读,避免阻塞。ser.in_waiting返回接收缓冲区里的字节数,大于0说明有数据。readline()会一直读到换行符,适合处理按行发送的文本协议。

如果你要处理二进制协议,比如LoRa模块的AT指令响应,可以用read()指定读取字节数:

data = ser.read(ser.in_waiting)

4.2 波特率误差计算与时钟源选择

前面提到mini UART的波特率不稳定,这里具体算一下误差。

PL011的时钟源是48MHz,波特率分频系数计算公式是:

分频系数 = 时钟频率 / (16 × 波特率)

以115200为例:

分频系数 = 48000000 / (16 × 115200) = 26.0417

取整数部分26,小数部分0.0417,实际波特率:

实际波特率 = 48000000 / (16 × 26.0417) ≈ 115200

误差几乎为零。

再看mini UART,它的时钟源是GPU核心时钟,默认可能是250MHz,但会动态调整。假设当前是250MHz:

分频系数 = 250000000 / (8 × 115200) = 271.267

取整271,实际波特率:

实际波特率 = 250000000 / (8 × 271) ≈ 115313

误差约0.1%,看起来还行。但如果GPU降频到200MHz:

实际波特率 = 200000000 / (8 × 271) ≈ 92250

误差超过20%,通信直接崩了。

所以结论很明确:对波特率稳定性有要求的场景,必须用PL011或者UART2到UART5,不要用mini UART。

4.3 硬件流控的配置与使用

如果你传输的数据量大,或者通信距离较长,建议启用硬件流控。PL011支持RTS/CTS,可以防止缓冲区溢出导致的数据丢失。

在config.txt里启用流控:

dtoverlay=uart0,ctsrts

然后在代码里启用:

ser = serial.Serial( port='/dev/ttyAMA0', baudrate=115200, rtscts=True )

硬件流控需要额外的两根线:RTS(请求发送)和CTS(清除发送)。树莓派4的GPIO16和GPIO17可以复用为UART0的CTS和RTS。接线时注意交叉连接:树莓派的RTS接对方的CTS,树莓派的CTS接对方的RTS。

注意:不是所有串口模块都支持硬件流控。如果你的模块只有TX、RX、GND三根线,那就用不了流控,只能靠软件层面的协议来保证可靠性。

5. 常见问题排查与避坑经验

5.1 串口收不到数据怎么办

这是最常见的问题,排查思路按以下顺序来:

第一步,检查接线。串口的TX要接对方的RX,RX接对方的TX,GND必须共地。我见过很多人TX接TX,RX接RX,然后纳闷为什么收不到数据。

第二步,检查设备节点是否存在。执行ls /dev/ttyAMA*,如果没有对应的设备节点,说明设备树覆盖没生效。检查config.txt里的dtoverlay拼写是否正确,然后dtoverlay -l看加载状态。

第三步,检查引脚是否冲突。如果你同时启用了多个UART,但它们的引脚有重叠,后面的覆盖会覆盖前面的。比如uart2默认用GPIO0/1,uart3默认用GPIO4/5,这俩不冲突。但如果你把uart2改成pins=4,5,就跟uart3冲突了。

第四步,检查波特率。用示波器或者逻辑分析仪测一下实际波特率。如果没有仪器,可以先用最低的9600波特率测试,确认通信正常后再提高。

第五步,检查串口控制台是否真的禁用了。执行cat /proc/cmdline,看看里面还有没有console=serial0。如果有,说明cmdline.txt没改对。

5.2 多串口同时工作时数据丢包

数据丢包通常有三个原因:

  • 缓冲区溢出:mini UART的FIFO只有8字节,高速通信时很容易溢出。解决办法是换用PL011或者其他UART,它们的FIFO是32字节。
  • CPU负载过高:如果Python脚本里有大量计算,读取串口的线程可能被阻塞。解决办法是把串口读取放在独立线程里,或者用select/epoll做非阻塞读取。
  • 波特率误差累积:如果两个串口的波特率误差都接近临界值,长时间通信后会出现帧错误。解决办法是尽量用PL011,或者降低波特率。

我自己的经验是:在树莓派4上同时跑5个串口,波特率115200,用Python多线程读取,CPU占用大约15%,数据基本不丢。但如果用mini UART,同样的配置丢包率能到5%以上。

5.3 系统启动后串口设备名变化

有时候你会发现,重启后ttyAMA1变成了ttyAMA2,或者顺序变了。这是因为设备节点的编号取决于UART的启用顺序和内核探测顺序,不是固定的。

解决办法是用udev规则给串口设备创建固定的别名。创建一个文件/etc/udev/rules.d/99-uart.rules:

KERNEL=="ttyAMA1", SYMLINK+="ttyGPS" KERNEL=="ttyAMA2", SYMLINK+="ttyLoRa"

这样无论设备节点怎么变,你都可以用/dev/ttyGPS和/dev/ttyLoRa来访问。

不过要注意,udev规则里的KERNEL匹配的是内核设备名,如果设备名本身变了,这个规则也不管用。更可靠的方法是根据物理路径来匹配,但树莓派的UART没有固定的物理路径,所以这个方法也有局限性。我的做法是在代码里动态扫描/dev/ttyAMA*,然后根据每个串口发送的握手协议来识别设备。

5.4 常见问题速查表

问题现象可能原因解决方法
收不到任何数据TX/RX接反交换TX和RX接线
收到乱码波特率不匹配确认双方波特率一致
数据偶尔丢失mini UART时钟漂移改用PL011或UART2-5
设备节点不存在设备树覆盖未加载检查config.txt拼写
串口被控制台占用cmdline.txt未修改删除console=serial0
权限不足用户不在dialout组usermod -a -G dialout
多串口冲突引脚映射重叠重新分配引脚
蓝牙占用PL011未禁用蓝牙dtoverlay=disable-bt

6. 进阶技巧:用C语言直接操作UART寄存器

如果你对性能有极致要求,或者需要精确控制时序,可以绕过操作系统,直接用C语言操作UART寄存器。树莓派4的BCM2711外设基地址是0xFE000000,UART0的寄存器偏移是0x201000。

#include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #define PERI_BASE 0xFE000000 #define UART0_BASE (PERI_BASE + 0x201000) #define UART_DR 0x00 #define UART_FR 0x18 #define UART_IBRD 0x24 #define UART_FBRD 0x28 #define UART_LCRH 0x2C #define UART_CR 0x30 volatile unsigned int *uart0; void uart_init() { int fd = open("/dev/mem", O_RDWR | O_SYNC); uart0 = (unsigned int *)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, UART0_BASE); // 禁用UART uart0[UART_CR / 4] = 0; // 设置波特率115200,时钟48MHz // IBRD = 48MHz / (16 * 115200) = 26 // FBRD = round(0.0417 * 64) = 3 uart0[UART_IBRD / 4] = 26; uart0[UART_FBRD / 4] = 3; // 8位数据,无校验,1位停止位,启用FIFO uart0[UART_LCRH / 4] = (3 << 5) | (1 << 4); // 启用UART,TX和RX uart0[UART_CR / 4] = (1 << 0) | (1 << 8) | (1 << 9); } void uart_send(char c) { while (uart0[UART_FR / 4] & (1 << 5)); // 等待TX FIFO有空位 uart0[UART_DR / 4] = c; } char uart_recv() { while (uart0[UART_FR / 4] & (1 << 4)); // 等待RX FIFO有数据 return uart0[UART_DR / 4] & 0xFF; }

这段代码直接映射了UART0的寄存器,初始化波特率、数据格式,然后实现了发送和接收函数。优点是延迟极低,适合做高速数据采集。缺点是没有操作系统层面的缓冲和错误处理,需要自己管理。

注意:直接操作/dev/mem需要root权限,而且如果地址写错了,可能导致系统崩溃。建议只在必要时使用,并且先在测试环境中验证。

7. 实际项目中的串口资源分配策略

做了几个项目之后,我总结出一套串口分配的原则,分享出来供参考。

原则一:PL011永远留给最关键的通信。比如GPS模块,它需要稳定的波特率和持续的数据流,用PL011最合适。

原则二:调试串口单独占用一个UART。我一般用UART4或者UART5接一个USB转TTL模块,专门用来打印系统日志和调试信息。这样即使其他串口出问题,调试通道还在。

原则三:低速设备用mini UART。如果某个设备只是偶尔发几个字节,比如温湿度传感器,用mini UART完全够用,还能省下PL011给更重要的设备。

原则四:预留一个备用串口。项目开发过程中经常需要临时接个设备调试,预留一个串口能省很多事。

原则五:注意引脚的电平匹配。树莓派的GPIO是3.3V电平,如果你的串口模块是5V的,必须加电平转换电路,否则可能烧毁GPIO。我见过有人直接把5V的Arduino串口接到树莓派上,结果树莓派的GPIO烧了。

最后再分享一个小技巧:如果你不确定某个串口是否正常工作,可以短接它的TX和RX,然后发送数据,看能不能收到。这叫回环测试,是最简单的自检方法。如果回环测试通过,说明串口硬件和驱动都没问题,问题出在外部接线或对方设备上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询