1. 为什么值得花时间把树莓派的串口摸清楚
很多人拿到树莓派的第一件事是点亮 LED、跑个 Python 脚本,再进阶一点就是接个摄像头做图像识别。但真正让树莓派从"玩具"变成"工具"的,往往是那些看起来不起眼的低速总线——UART、SPI、I2C。这三个东西加上 GPIO,构成了树莓派和外部世界对话的底层通道。你手里的传感器、屏幕、存储芯片、单片机、舵机驱动板,几乎都是通过这几根线跟树莓派打交道的。
我见过太多人卡在同一个地方:买了一个模块,照着教程接线,代码跑起来没反应,然后开始怀疑人生。问题十有八九出在串口配置上——要么是树莓派的 UART 被系统占用没释放,要么是 SPI 没在配置里打开,要么是 I2C 地址没扫到。这些坑其实都不难,但如果你对树莓派各个串口的"身份"和"脾气"没有系统性的认识,就会反复踩。
这篇内容就是把我这些年用树莓派做项目时,关于串口部分的经验整理出来。从 UART、SPI、I2C 到 GPIO 的基础概念,到树莓派 4B 和 5 上的具体配置方法,再到实际接线和调试时容易忽略的细节,都会覆盖到。不管你是刚拿到树莓派的新手,还是已经用过一段时间但总觉得串口这块"知其然不知其所以然"的开发者,应该都能从中找到有用的东西。特别是那些正在做树莓派项目、需要接各种外设模块的朋友,这篇内容可以当作一个串口速查手册来用。
2. 树莓派上到底有哪些串口,各自是什么角色
2.1 UART、SPI、I2C 三兄弟的分工逻辑
树莓派的 40 针 GPIO 排针上,并不是所有引脚都只能做数字输入输出。其中有相当一部分引脚有"第二职业",它们可以被配置成 UART、SPI 或 I2C 的通信线。理解这三者的区别,是选型的第一步。
UART 是最"古老"也最直观的串口。它只需要两根线:TX(发送)和 RX(接收),再加上地线就能工作。数据一位一位地传,没有时钟线,靠双方约定的波特率来同步。树莓派上默认的 UART 是/dev/serial0,它背后其实对应着硬件 UART 或者迷你 UART,具体是哪个取决于你的树莓派型号和配置。UART 的优点是简单、可靠、距离可以拉得比较远(几米没问题),缺点是速度相对慢,而且一般只能点对点通信,没法像 I2C 那样一条总线上挂一堆设备。
SPI 是"速度担当"。它用四根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。因为是全双工、有时钟同步,SPI 的速率可以轻松跑到几十 MHz。树莓派 4B 的 SPI 时钟最高可以配置到 125 MHz(实际稳定值要看外设),这在低速总线里算是相当快了。SPI 的代价是引脚多,而且每增加一个从设备就要多一根片选线。它适合那些需要高速数据传输的场景,比如驱动 TFT 屏幕、读取高速 ADC、跟 Flash 芯片通信。
I2C 是"省引脚担当"。它只用两根线:SDA(数据)和 SCL(时钟),就能在一条总线上挂最多 127 个设备(7 位地址)。每个设备有自己唯一的地址,主设备通过地址来寻址。I2C 的速度比 SPI 慢不少,标准模式 100 kHz,快速模式 400 kHz,高速模式 3.4 MHz,但胜在引脚少、扩展方便。它适合接那些数据量不大但数量多的传感器,比如温湿度、气压、加速度计、EEPROM 等。
注意:树莓派的 I2C 引脚是开漏输出,必须外接上拉电阻才能正常工作。树莓派板载已经有 1.8kΩ 的上拉电阻,所以一般直接接模块就行。但如果你挂了很多设备或者走线很长,可能需要额外并联更小的上拉电阻。上拉电阻太小会导致功耗增加和信号边沿变差,太大则上升沿太慢导致通信失败,这个后面会详细说。
2.2 树莓派 4B 与 5 的串口资源差异
树莓派 4B 和树莓派 5 在串口资源上有一些值得注意的变化。树莓派 4B 的 40 针 GPIO 上,默认提供了一路 UART(GPIO14/15)、两路 SPI(SPI0 在 GPIO9/10/11 和 GPIO7/8,SPI1 在 GPIO16/17/18/19/20/21)、以及一路 I2C(I2C1 在 GPIO2/3)。此外还有 I2C0 但默认被系统占用,一般不建议动。
树莓派 5 的 GPIO 排针布局跟 4B 保持兼容,但内部的总线控制器换了芯片,RP1 芯片负责 GPIO 和外设。树莓派 5 上 SPI 和 I2C 的可用实例更多,性能也更好。不过对于大多数项目来说,默认的那几路已经够用了。需要注意的是,树莓派 5 的 UART 配置方式跟 4B 略有不同,/boot/firmware/config.txt的路径和参数有些变化,后面会具体讲。
还有一个容易被忽略的点:树莓派上其实有两路 UART。一路是"硬件 UART"(PL011),性能好、波特率稳定;另一路是"迷你 UART",它其实是 GPU 的一部分,性能稍差,波特率容易受系统负载影响。默认情况下,蓝牙会占用硬件 UART,而 GPIO 排针上引出的/dev/serial0会指向迷你 UART。如果你要做对波特率精度要求高的串口通信,比如跟某些对时序敏感的模块通信,就需要把硬件 UART 分配给 GPIO,把蓝牙切到迷你 UART 上。这个交换在树莓派 4B 上通过dtoverlay=disable-bt和dtoverlay=miniuart-bt来实现。
2.3 GPIO 的复用功能与引脚映射关系
树莓派的 GPIO 引脚不是"一根筋"的,每个引脚都有多种功能,通过引脚复用(pin muxing)来切换。比如 GPIO14 默认是 UART 的 TX,但你也可以把它配置成普通的数字输出,或者 SPI 的某个信号。这种灵活性带来了便利,也带来了困惑——为什么我明明接的是 SPI 的线,代码里却读不到数据?很可能是因为这个引脚被别的功能占用了。
树莓派的引脚映射可以通过pinout命令或者gpio readall来查看。pinout是树莓派官方系统自带的工具,输出一张彩色的引脚图,非常直观。gpio readall是 WiringPi 工具集里的命令,会列出每个引脚的 BCM 编号、物理编号、当前模式和电平状态。我建议在接线之前先跑一下这两个命令,确认你要用的引脚当前是什么状态。
另外,树莓派的 GPIO 编号有三种:物理编号(就是排针上的第几针)、BCM 编号(芯片内部的编号,也是代码里最常用的)、WiringPi 编号(WiringPi 库自己的编号)。这三种编号经常让人搞混。我的习惯是统一用 BCM 编号,因为 Python 的 RPi.GPIO 和 gpiozero 库默认都用 BCM。物理编号只在接线的时候用,对照引脚图看就行。
3. 动手之前:树莓派串口配置的完整流程
3.1 系统层面的串口开关与配置修改
拿到一张新的系统卡,烧录完系统之后,第一件事不是急着写代码,而是把串口该开的开、该关的关。树莓派官方的 Raspberry Pi OS 里,raspi-config工具可以完成大部分配置。运行sudo raspi-config,进入 "Interface Options",里面可以分别启用 SPI、I2C、Serial Port。
这里有一个非常关键的选项:当你在 Serial Port 里选择启用时,系统会问你 "Would you like a login shell to be accessible over serial?",这个一定要选No。如果你选了 Yes,系统会把串口分配给控制台登录,你的代码就没法用这个串口了。然后它会问 "Would you like the serial port hardware to be enabled?",这个选Yes。这样配置之后,/dev/serial0就可以给你的程序用了。
如果你用的是 Ubuntu 22.04 或者树莓派 5 上的新系统,raspi-config可能不完全一样,或者需要手动改配置文件。在树莓派 4B 的 Raspberry Pi OS 上,配置文件在/boot/config.txt;在树莓派 5 或者 Ubuntu 上,路径可能是/boot/firmware/config.txt。你需要确保里面有这几行:
enable_uart=1 dtparam=spi=on dtparam=i2c_arm=on改完之后重启生效。重启后可以用ls /dev/serial*和ls /dev/spi*、ls /dev/i2c*来确认设备节点是否存在。
3.2 验证串口是否真正可用
配置改完不代表就能用了,还得验证。对于 UART,最直接的验证方法是把 TX 和 RX 短接(也就是回环测试),然后发数据看能不能收到。你可以用 Python 的pyserial库写个几行的小脚本:
import serial ser = serial.Serial('/dev/serial0', 9600, timeout=1) ser.write(b'hello') print(ser.read(5)) ser.close()如果 TX 和 RX 短接后能打印出b'hello',说明 UART 硬件和驱动都正常。如果读出来是空的,那就要检查是不是串口被占用了,或者波特率不对。
对于 I2C,验证方法是sudo i2cdetect -y 1。这个命令会扫描 I2C-1 总线上的所有地址,把有设备响应的地址显示出来。如果你接了模块但扫描不到,先检查接线,再检查模块是否上电,最后检查上拉电阻。对于 SPI,验证稍微麻烦一点,可以用lsmod | grep spi看驱动是否加载,然后用spidev库写个简单的读写测试。
提示:在树莓派 5 上,I2C 和 SPI 的设备节点编号可能跟 4B 不同。比如 I2C 可能是
/dev/i2c-1也可能是/dev/i2c-13,具体取决于你启用了哪一路。用ls /dev/i2c*确认一下再用。
3.3 串口权限与用户组设置
默认情况下,/dev/serial0、/dev/spidev0.0、/dev/i2c-1这些设备节点的权限是 root 或者 dialout、spi、i2c 用户组。如果你用普通用户跑程序,会遇到 "Permission denied"。解决办法是把当前用户加到对应的组里:
sudo usermod -a -G dialout,gpio,spi,i2c $USER然后注销重新登录,或者重启。这样就不用每次都用sudo跑 Python 脚本了。用sudo跑脚本虽然能解决权限问题,但会带来另一个坑:用sudo跑的时候,Python 环境变量和用户目录下的库路径可能不一样,导致import失败。所以还是老老实实配用户组比较稳妥。
4. 三大串口在实际项目中的接线与调试要点
4.1 UART 接线:交叉连接与电平匹配
UART 接线最简单的原则就是TX 接 RX,RX 接 TX,GND 接 GND。很多人第一次接的时候会直连 TX 到 TX,然后纳闷为什么收不到数据。记住,发送端要对着接收端,所以是交叉的。
电平方面,树莓派的 GPIO 是 3.3V 电平,绝对不能直接接 5V 的串口设备。如果你要跟 Arduino Uno 或者某些 5V 的单片机通信,必须加电平转换模块,或者至少加个分压电阻。我见过有人直接把 5V 的 TX 接到树莓派的 RX 上,结果把 GPIO 烧了。虽然树莓派有一定的保护,但长期这么干迟早出问题。
如果你是用 USB 转串口模块(比如 CH340、CP2102、FT232)跟树莓派通信,那模块本身输出的是 USB 电平,经过芯片转换成 3.3V 或 5V 的 TTL 电平。买的时候要确认模块支持 3.3V,很多便宜的 CH340 模块只有 5V 输出,需要改跳线或者自己加电平转换。
调试的时候,我习惯在电脑上开一个串口调试助手(Windows 下用 XCOM 或者 SSCOM,Linux 下用 minicom 或者 picocom),树莓派这边用 Python 脚本收发。这样能直观地看到数据流,排查问题很方便。如果电脑上没有物理串口,就用 USB 转串口模块,装好驱动之后会在设备管理器里多出一个 COM 口。
4.2 SPI 接线:片选、时钟极性与相位
SPI 的接线比 UART 多两根线,但逻辑也不复杂。标准四线 SPI 是 SCLK、MOSI、MISO、CS。树莓派的 SPI0 默认引脚是:
| 信号 | 物理引脚 | BCM 编号 |
|---|---|---|
| SCLK | 23 | 11 |
| MOSI | 19 | 10 |
| MISO | 21 | 9 |
| CS0 | 24 | 8 |
| CS1 | 26 | 7 |
接线的时候,SCLK 接从设备的时钟,MOSI 接从设备的输入(从设备的数据输入),MISO 接从设备的输出,CS 接从设备的片选。注意,有些模块上标的是 SDI、SDO、SCK、SS,对应关系是 SDI=MOSI、SDO=MISO、SCK=SCLK、SS=CS。
SPI 有两个容易踩坑的参数:时钟极性(CPOL)和时钟相位(CPHA)。这两个参数组合成四种模式(Mode 0~3)。如果模式不对,数据会错位或者完全读不到。大多数模块用 Mode 0(CPOL=0,CPHA=0),但有些 ADC 或者 Flash 芯片用 Mode 3。具体用哪个模式,一定要查模块的数据手册,不能凭感觉试。
还有一个坑是片选方式。树莓派的硬件 SPI 控制器自带片选信号,但有些模块需要软件控制片选(比如在传输前后手动拉低拉高某个 GPIO)。树莓派的spidev库支持硬件片选,但如果你要用软件片选,就需要把 CS 引脚配置成普通 GPIO,在代码里手动控制。这两种方式在时序上有区别,软件片选更灵活但速度慢一些。
4.3 I2C 接线:上拉电阻与地址冲突
I2C 的接线最简单,SDA 接 SDA,SCL 接 SCL,VCC 和 GND 接好就行。但 I2C 的坑往往不在接线上,而在上拉电阻和地址上。
前面说过,I2C 是开漏输出,必须靠上拉电阻把总线拉高。树莓派板载的 1.8kΩ 上拉电阻对于大多数场景是够的。但如果你挂了很多设备,或者走线很长(比如超过 30cm),总线电容会增加,上升沿变慢,可能导致通信失败。这时候可以尝试减小上拉电阻,比如并联一个 1kΩ 的电阻,让总上拉降到 1kΩ 左右。但也不能太小,太小会导致低电平时电流过大,有些设备可能拉不低。
地址冲突是另一个常见问题。很多传感器模块的 I2C 地址是固定的,比如 BMP280 是 0x76 或 0x77,MPU6050 是 0x68。如果你要挂两个同型号的传感器,就得想办法改地址。有些模块提供了地址选择引脚(比如 AD0),拉高或拉低可以切换地址。如果没有,那就只能用 I2C 多路复用器(比如 TCA9548A)来扩展总线。
用i2cdetect -y 1扫描的时候,如果看到某个地址显示为UU,说明这个地址被内核驱动占用了,你的用户程序没法直接访问。这种情况通常是因为系统自动加载了某个驱动,你需要用dtoverlay或者黑名单的方式禁用那个驱动。
5. 常见问题排查与实战避坑经验
5.1 串口设备找不到或权限报错
这是最常见的问题,没有之一。表现是ls /dev/serial0不存在,或者 Python 脚本报Permission denied。排查思路是这样的:
第一步,确认配置有没有生效。检查/boot/config.txt或/boot/firmware/config.txt里enable_uart=1有没有写对,有没有被注释掉。改完之后有没有重启。
第二步,确认串口有没有被控制台占用。运行cat /proc/cmdline,如果看到console=serial0或者console=ttyAMA0,说明串口被内核当控制台用了。这时候需要改cmdline.txt,把console=serial0,115200删掉。
第三步,确认权限。用ls -l /dev/serial0看属主和权限。如果是root dialout,而你的用户在dialout组里,那就没问题。如果不在,用groups命令确认一下,然后usermod加组。
第四步,如果以上都没问题,但 Python 还是报错,检查一下是不是用了sudo跑脚本导致环境不对。或者检查pyserial有没有装对版本。
5.2 I2C 扫描不到设备或通信不稳定
I2C 扫描不到设备,原因可能有很多。我一般按这个顺序排查:
先看接线。SDA 和 SCL 有没有接反?VCC 有没有接对?有些模块标的是 VIN 而不是 VCC,电压范围可能不一样。GND 有没有共地?I2C 必须共地,不共地什么都收不到。
再看上拉电阻。如果模块自带 10kΩ 上拉,树莓派板载 1.8kΩ,并联之后大约是 1.5kΩ,一般没问题。但如果模块的上拉是 100kΩ,那并联之后还是接近 1.8kΩ,也没问题。问题出在模块没有上拉,而树莓派的上拉又因为走线太长而失效。这时候可以在 SDA 和 SCL 上各加一个 4.7kΩ 的上拉电阻到 3.3V。
然后看地址。用i2cdetect -y 1扫描,如果扫不到,试试降低 I2C 速率。在/boot/config.txt里加dtparam=i2c_arm_baudrate=50000,把速率降到 50kHz,有时候能解决长走线或者劣质模块的问题。
最后看电源。有些 I2C 模块功耗比较大,树莓派的 3.3V 引脚供电能力有限(总共大概 50mA 到 100mA,取决于型号)。如果模块需要更大电流,得单独供电,但记得共地。
5.3 SPI 通信失败或数据错位
SPI 通信失败,最常见的原因是模式不对。我遇到过好几次,模块手册上写的是 Mode 0,但实际用 Mode 0 读出来全是 0xFF,换成 Mode 3 就正常了。所以如果数据不对,先把四种模式都试一遍。
另一个原因是片选没控制好。如果你用的是硬件片选,确认spidev打开的设备节点是/dev/spidev0.0还是/dev/spidev0.1,对应 CS0 和 CS1。如果用软件片选,确认 GPIO 有没有正确拉低和拉高,时序对不对。
还有一个隐蔽的坑是 SPI 时钟太快。树莓派 4B 的 SPI 默认时钟是 125MHz 分频出来的,实际速率可能比你设置的高。如果模块支持的最高时钟是 10MHz,你设了 20MHz,就可能出现数据错位。这时候把时钟降下来试试,比如设成 1MHz 或者 500kHz。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
/dev/serial0不存在 | UART 未启用或被控制台占用 | 检查 config.txt 和 cmdline.txt | 启用 UART,删除 console 参数 |
| Python 报 Permission denied | 用户不在 dialout 组 | groups查看当前组 | usermod -a -G dialout $USER |
| I2C 扫描不到设备 | 接线错误或上拉不足 | 检查 SDA/SCL/VCC/GND | 加 4.7kΩ 上拉,降低速率 |
| I2C 地址显示 UU | 内核驱动占用 | lsmod查看驱动 | 禁用对应驱动或换地址 |
| SPI 读出来全是 0xFF | 模式不对或片选错误 | 尝试四种模式 | 改用正确模式,检查 CS |
| SPI 数据错位 | 时钟太快 | 降低 SPI 时钟 | 设成 1MHz 或更低 |
| UART 收到乱码 | 波特率不匹配 | 确认双方波特率 | 统一波特率,检查时钟源 |
| 串口回环测试失败 | TX/RX 未短接或串口被占 | 检查短接和占用 | 短接 TX/RX,释放串口 |
提示:这张表里的问题我几乎都遇到过,特别是 I2C 上拉和 SPI 模式这两个,新手很容易卡住。建议把这张表存下来,下次遇到问题直接对照排查。
6. 从认识到熟练:一些个人体会和进阶方向
串口这个东西,看文档觉得简单,实际动手才知道细节多。我刚开始用树莓派的时候,觉得 UART 不就是两根线吗,结果被控制台占用坑了一整天。后来做项目多了,慢慢总结出一个习惯:每次接新模块之前,先跑一遍pinout和i2cdetect,确认引脚状态和总线设备,然后再动手接线。这个习惯帮我省了很多返工的时间。
另外,树莓派 5 出来之后,GPIO 的性能和灵活性都有提升,但配置方式跟 4B 有些差异。如果你是从 4B 迁移到 5,建议重新过一遍串口配置,不要直接照搬 4B 的 config.txt。特别是 SPI 和 I2C 的设备节点编号,树莓派 5 上可能会变。
如果你已经掌握了基本的 UART、SPI、I2C 用法,下一步可以试试这几个方向:一是用 SPI 驱动 TFT 屏幕,这个对时序要求高,能帮你深入理解 SPI 的极性和相位;二是用 I2C 挂多个传感器,练习地址管理和多路复用;三是把树莓派作为 SPI 从设备,跟 STM32 之类的单片机通信,这个场景在项目里很常见,但配置起来比主设备模式麻烦一些。还有一个有意思的方向是用树莓派的 UART 跟串口屏通信,串口屏的协议通常比较简单,适合练手。
最后分享一个小技巧:如果你在调试 I2C 的时候不确定上拉电阻够不够,可以用示波器看 SDA 和 SCL 的上升沿。如果上升沿明显变缓(超过 1us),说明上拉不够或者总线电容太大。没有示波器的话,用逻辑分析仪也行,几十块钱的就能用。这个工具在调试 SPI 和 I2C 的时候特别有用,能看到实际的时序波形,比猜来猜去强多了。