树莓派4串口通信实战:从零搭建温湿度传感器数据采集系统
你有没有遇到过这样的场景?手头有一个支持UART输出的工业级温湿度传感器,想把它接到树莓派上做实时监测,结果发现串口要么打不开,要么收到一堆乱码。别急——这几乎是每个嵌入式新手都会踩的坑。
问题的关键往往不在代码,而在于树莓派默认把串口拿去当系统调试用了。更复杂的是,从Raspberry Pi 3B+开始,蓝牙模块占用了主UART资源,导致GPIO14/15引脚默认绑定的是性能不稳定的mini-UART。如果不加处理直接使用,轻则丢包重试,重则通信完全失败。
今天我们就以一个典型的Modbus RTU温湿度变送器为例,带你一步步打通树莓派4与串口传感器之间的“任督二脉”,实现稳定可靠的数据采集。
先搞清楚:你的树莓派用的是哪个UART?
在动手之前,必须弄明白一件事:树莓派4有两个UART控制器——一个是PL011(也叫uart0),另一个是mini-UART(uart1)。它们的区别可大了:
| 特性 | PL011 UART | mini-UART |
|---|---|---|
| 时钟源 | 独立晶振,频率稳定 | 依赖CPU主频,随功耗调节波动 |
| 通信稳定性 | 高,适合工业环境 | 中低,易受系统负载影响 |
| 默认映射引脚 | GPIO14/15(需配置) | GPIO14/15(出厂默认) |
听起来是不是有点反直觉?明明硬件引脚一样,为什么还会分两种?这是因为为了给板载蓝牙腾地方,官方把原本属于PL011的GPIO14/15让给了mini-UART。
所以,如果你不做任何配置就直接打开/dev/ttyS0,大概率连的是那个“靠CPU吃饭”的mini-UART,一旦系统进入节能模式,波特率一偏移,数据全乱套。
那怎么办?答案就是:强制切换回PL011主串口。
第一步:释放被“征用”的串口资源
我们要做的第一件事,就是告诉操作系统:“别再用串口打印启动信息了,我要拿来接传感器。”
1. 关闭串口控制台输出
编辑启动参数文件:
sudo nano /boot/cmdline.txt找到这一段:
console=serial0,115200 console=tty1 root=PARTUUID=... ro rootwait删掉console=serial0,115200这部分,保存退出。否则系统会一直往串口发日志,不仅干扰通信,还可能烧坏某些敏感传感器。
2. 启用UART硬件并锁定主控制器
接下来修改设备树配置:
sudo nano /boot/config.txt在文件末尾添加两行:
enable_uart=1 dtoverlay=uart0enable_uart=1:启用串行接口dtoverlay=uart0:将GPIO14/15重新映射到PL011控制器(即uart0)
⚠️ 注意:不同资料中可能会看到
pi3-disable-bt或miniuart-bt等写法,那是旧版系统的做法。在较新的Raspberry Pi OS(Bullseye及以上)中,推荐统一使用dtoverlay=uart0来明确指定主UART。
改完之后重启:
sudo reboot重启完成后,你可以通过以下命令验证当前串口状态:
ls -l /dev/tty*正常情况下你会看到:
-/dev/ttyAMA0→ 指向PL011(但现在通常被蓝牙占用)
-/dev/ttyS0→ 现在应该已经指向我们想要的PL011 UART
可以通过内核消息进一步确认:
dmesg | grep tty如果看到类似这样的输出,说明成功了:
[ 0.000000] Devicetree: Raspberry Pi 4 UART0 overlay [ 1.234567] serial_core: PL011 port mapped at 0xfe201000第二步:连接传感器,别接反了!
物理连接看似简单,但90%的问题出在这里。
假设你用的是一个支持Modbus RTU协议的温湿度传感器(比如DFRobot SEN0394、维克多WD-33等),典型接线如下:
| 传感器引脚 | 树莓派GPIO |
|---|---|
| VCC (3.3V) | Pin 1 (3.3V电源) |
| GND | Pin 6 (GND) |
| TX | Pin 10 (GPIO15/RX)← 注意交叉 |
| RX | Pin 8 (GPIO14/TX) |
关键点来了:传感器的TX要接树莓派的RX,反之亦然!
很多初学者误以为“TX对TX”,结果两边都在拼命发数据却谁也收不到,白白浪费半天时间排查软件问题。
另外提醒一句:绝对不要给树莓派接5V逻辑电平的设备!GPIO耐压只有3.3V,一旦接入5V TTL信号,轻则IO损坏,重则整块板报废。如果传感器是5V供电的,请务必加上电平转换芯片(如TXB0108或MAX3232)。
第三步:Python代码实战 —— 读取Modbus传感器数据
现在轮到写代码了。我们使用pyserial库来操作串口,先安装依赖:
pip install pyserial下面是一个完整的数据读取脚本,包含初始化、命令发送、响应解析和CRC校验:
import serial import time from functools import reduce # CRC16校验函数(Modbus标准) def crc16(data): crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return ((crc & 0xFF) << 8) | (crc >> 8) # 初始化串口 def init_serial(port='/dev/ttyS0', baudrate=9600, timeout=1): try: ser = serial.Serial( port=port, baudrate=baudrate, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=timeout ) if ser.is_open: print(f"✅ 串口 {port} 打开成功,波特率: {baudrate}") return ser except Exception as e: print(f"❌ 无法打开串口: {e}") return None # 读取温湿度传感器数据(功能码0x03) def read_modbus_sensor(ser, slave_addr=1, reg_start=0x0000, reg_count=2): # 构造请求帧: [设备地址][功能码][起始寄存器高][低][数量高][低][CRC16_L][H] command = [ slave_addr, 0x03, (reg_start >> 8) & 0xFF, reg_start & 0xFF, (reg_count >> 8) & 0xFF, reg_count & 0xFF ] crc = crc16(command) command.append(crc & 0xFF) # CRC低字节 command.append((crc >> 8) & 0xFF) # CRC高字节 # 发送请求 ser.write(bytes(command)) # 等待响应(根据传感器手册调整延迟) time.sleep(0.15) # 读取可用数据 if ser.in_waiting < 5: return None, None # 帧头都不够,直接放弃 # 先读前5字节判断是否合法响应 header = ser.read(5) if len(header) != 5 or header[0] != slave_addr or header[1] != 0x03: ser.reset_input_buffer() return None, None # 读取剩余数据(数据长度在第5字节) data_len = header[2] remaining = ser.read(data_len + 2) # 数据 + CRC full_response = header + remaining if len(full_response) != 5 + data_len + 2: return None, None # CRC校验 recv_crc = (full_response[-1] << 8) | full_response[-2] calc_crc = crc16(full_response[:-2]) if recv_crc != calc_crc: print("⚠️ CRC校验失败") return None, None # 解析温湿度(假设为连续两个16位寄存器) temp_raw = (full_response[3] << 8) | full_response[4] humi_raw = (full_response[5] << 8) | full_response[6] temperature = -45 + 175 * temp_raw / 65535.0 humidity = 100 * humi_raw / 65535.0 return round(temperature, 2), round(humidity, 2) # 主循环 if __name__ == "__main__": uart = init_serial('/dev/ttyS0', 9600) if not uart: exit(1) print("📡 开始采集温湿度数据...") try: while True: temp, humi = None, None for i in range(3): # 最多重试3次 temp, humi = read_modbus_sensor(uart) if temp is not None: break time.sleep(0.3) if temp is not None and humi is not None: print(f"[+] 温度: {temp:.2f}°C, 湿度: {humi:.2f}%") else: print("[-] 未获取有效数据,请检查接线或配置") time.sleep(2) except KeyboardInterrupt: print("\n⏹️ 用户中断,程序退出") finally: uart.close()关键设计说明:
- CRC校验独立实现:避免因第三方库兼容性问题导致校验失败
- 分步读取机制:先读头部判断合法性,再读后续数据,防止缓冲区错位
- 自动重试逻辑:工业环境中偶尔丢包很正常,三次重试能显著提升鲁棒性
- 合理延时控制:Modbus规定从机响应时间一般为20~200ms,太短会读不到数据,太长影响效率
常见问题怎么破?
❓ 串口打不开,提示权限不足?
运行:
sudo usermod -aG dialout pi然后重启生效。这个组专门用来管理串口设备访问权限。
❓ 收到的数据总是错位或乱码?
优先检查三点:
1. 波特率是否匹配(常见有9600、19200、38400、115200)
2. 是否启用了dtoverlay=uart0
3. 接线是否TX-RX交叉、GND共地
可以用示波器或逻辑分析仪抓一下波形,看看实际波特率有没有漂移。
❓ 多个串口设备怎么接?
树莓派原生只提供一组UART。如果需要挂多个Modbus设备,有两种方案:
- 使用RS-485总线:所有设备并联在同一对差分线上,靠地址区分,适合远距离部署;
- I²C转多路UART扩展芯片:如SC16IS752,通过I²C控制即可扩展出两个额外串口,适用于紧凑型设计。
工程级建议:不只是“能用”
当你准备把这个方案用于实际项目时,以下几个优化点值得考虑:
✅ 使用非阻塞或异步方式
不要让主线程卡在read()上。可以结合threading.Thread或将串口读取放入单独协程中处理。
✅ 添加日志记录
import logging logging.basicConfig(filename='sensor.log', level=logging.INFO)便于后期追溯异常情况。
✅ 实现看门狗机制
长时间运行下可能出现串口“假死”。定期检测最后通信时间,超时则自动重启串口实例。
✅ 电源管理
传感器尽量单独供电,尤其是电流较大的型号(如PMS5003激光粉尘仪),避免拉低树莓派电压导致重启。
写在最后
看到这里,你应该已经掌握了如何让Raspberry Pi 4真正发挥其作为嵌入式网关的能力——不再是简单的USB外设堆叠,而是深入到底层硬件层面,精准掌控每一个字节的传输。
这套方法不仅适用于温湿度传感器,同样可用于pH计、CO₂模块、风速仪等各种支持UART/Modbus协议的工业设备。只要理解了“关闭console → 切换主UART → 正确接线 → 协议解析”这条主线,绝大多数串口通信问题都能迎刃而解。
下一步你可以尝试:
- 把采集到的数据通过MQTT上传到Home Assistant
- 结合SQLite做本地存储,构建离线可用的小型监控站
- 加入Web界面,做成一个简易的环境监测仪表盘
技术的魅力就在于此:几个引脚、几行代码,就能把物理世界的信息带进数字空间。
如果你在实践中遇到了其他挑战,欢迎留言交流。我们一起把这块小卡片,变成真正的智能感知中枢。