资讯详情

资讯详情

树莓派4实战:智能坐垫物联网项目从选型到远程监控

1. 从一块坐垫说起为什么树莓派4的发布值得折腾小wu的坐垫其实是一块带压力传感和温控的智能坐垫。最早那版用的是树莓派3B跑一个轻量级的Python守护进程读取薄膜压力传感器的模拟信号再通过GPIO控制加热片。功能不复杂但3B的痛点很明显USB和以太网共享带宽Wi-Fi只有2.4G内存1GB跑个带桌面的系统就喘。树莓派4发布之后我第一时间注意到几个关键变化——USB 3.0、千兆以太网、双频Wi-Fi、蓝牙5.0、最高8GB内存以及那个争议很大的USB-C供电口。这些变化对坐垫项目意味着什么简单说就是原来需要外挂USB扩展坞和独立Wi-Fi模块的方案现在一块板子全包了。这篇文章不是树莓派4的泛泛评测而是围绕“坐垫升级”这个具体场景把树莓派4从选型、系统安装、传感器接入、数据采集到远程监控的完整链路讲清楚。如果你手头也有类似的小型物联网项目——不管是智能家居、环境监测还是毕设级别的数据采集——这篇内容可以直接参考。我会把踩过的坑、参数计算的依据、以及那些文档里不会写的细节都摊开讲。先明确一个前提坐垫项目的核心需求是长时间稳定运行、低功耗、能跑轻量级Web服务做数据展示、支持远程SSH维护。树莓派4的4GB版本在价格和性能之间是最平衡的选择8GB对这类项目属于性能过剩2GB在跑Ubuntu Server加Docker时会比较紧张。下面所有内容基于4GB版本展开。2. 系统选型Ubuntu Server 20.04还是Raspberry Pi OS2.1 两个系统的实际差异树莓派4刚发布时官方Raspberry Pi OS当时还叫Raspbian对64位支持不完善很多库还是32位的。Ubuntu 20.04 for Raspberry Pi 4是Canonical官方适配的64位系统对Docker、Python科学计算库的支持更顺。但到了现在Raspberry Pi OS的64位版本已经成熟两者差距缩小了。我最终选Ubuntu Server 20.04理由有三个第一坐垫项目需要跑一个基于Flask的数据接口Ubuntu的Python环境更干净不会像Raspberry Pi OS那样预装一堆教育软件第二Ubuntu的软件源更新更及时安装某些Python包时不需要额外编译第三我习惯用netplan管理网络Ubuntu Server的配置方式更统一。但Ubuntu Server 20.04在树莓派4上有一个已知问题默认没有桌面环境Wi-Fi配置需要手动改network-config文件。如果你用的是无屏幕安装这一步必须做对否则第一次开机连不上网。2.2 烧录系统的具体操作烧录工具我用的是Raspberry Pi Imager虽然名字叫Raspberry Pi但它支持烧录任意IMG文件到SD卡。操作步骤下载Ubuntu Server 20.04 for Raspberry Pi 4的IMG文件约1GB。打开Raspberry Pi Imager选择“Use custom”选中下载的IMG。选择SD卡建议32GB Class 10以上坐垫项目需要记录历史数据16GB会很快满。烧录完成后不要急着拔卡。在电脑上打开SD卡的system-boot分区编辑network-config文件。network-config的Wi-Fi配置模板version: 2 ethernets: eth0: dhcp4: true optional: true wifis: wlan0: dhcp4: true optional: true access-points: 你的Wi-Fi名称: password: 你的密码注意Ubuntu Server 20.04的network-config对缩进极其敏感必须用空格不能用Tab。我第一次配置时用了Tab结果开机后Wi-Fi没起来只能接网线救急。另外access-points下面的Wi-Fi名称和密码都要加引号如果密码里有特殊字符比如$或!不加引号会被shell解析出错。2.3 首次开机后的基础配置烧录并配置好网络后插入SD卡上电。树莓派4的启动电流比3B大官方推荐5V 3A的电源。我用的是5V 3A的USB-C电源实测启动峰值电流在1.2A左右但如果你接了USB硬盘或风扇3A是底线。首次SSH登录Ubuntu Server默认用户名ubuntu密码ubuntu首次登录会强制改密码ssh ubuntu树莓派IP登录后先做三件事# 更新软件源 sudo apt update sudo apt upgrade -y # 安装常用工具 sudo apt install -y python3-pip python3-venv git htop # 修改主机名方便在路由器里识别 sudo hostnamectl set-hostname cushion-pi提示Ubuntu 20.04的默认软件源在国外更新速度可能很慢。可以换成国内镜像源但注意树莓派4是arm64架构要选支持arm64的镜像。换源后执行sudo apt update如果出现Release file is not valid yet说明系统时间不对用sudo date -s 2024-01-01 12:00:00临时设置再同步NTP。3. 坐垫硬件的重新接线从3B到4B的引脚变化3.1 树莓派4的GPIO布局与3B的异同树莓派4的40针GPIO排针与3B在物理布局上完全兼容引脚定义也一致。这意味着坐垫项目原来的传感器接线可以直接插到4B上不需要转接板。但有一个细节树莓派4的GPIO驱动能力与3B略有不同单个引脚最大输出电流仍然是16mA所有引脚合计不超过100mA。坐垫项目用的薄膜压力传感器是模拟输出需要经过ADC模数转换器才能被树莓派读取因为树莓派没有内置ADC。我用的ADC是MCP30088通道10位SPI接口。接线方式MCP3008引脚树莓派4 GPIO功能VDD3.3V (引脚1)电源VREF3.3V (引脚1)参考电压AGNDGND (引脚6)模拟地DGNDGND (引脚6)数字地CLKGPIO11 (引脚23)SPI时钟DOUTGPIO9 (引脚21)MISODINGPIO10 (引脚19)MOSICSGPIO8 (引脚24)片选压力传感器接MCP3008的CH0加热片控制接GPIO17引脚11通过一个NPN三极管驱动继电器。3.2 加热片控制的电路细节加热片是5V 2A的规格直接用GPIO驱动是不可能的。我用的是S8050三极管加一个5V继电器模块。GPIO17输出高电平时三极管导通继电器吸合加热片通电。这里有一个坑继电器线圈在断电瞬间会产生反向电动势可能击穿三极管。所以要在继电器线圈两端并联一个续流二极管1N4007阴极接5V阳极接三极管集电极。注意树莓派4的3.3V引脚最大输出电流约50mA继电器模块如果自带光耦隔离控制端电流通常在5mA左右可以直接接GPIO。但如果你的继电器模块没有光耦控制端电流可能超过10mA长期运行会加速GPIO老化。建议加一个1kΩ限流电阻。3.3 风扇针脚的特别说明树莓派4发热比3B大坐垫项目又需要长时间运行散热必须考虑。树莓派4有一个专用的4针风扇接口位于GPIO排针旁边支持PWM调速。但注意这个接口的引脚定义与普通PC风扇不同引脚功能15V2GND3未使用4未使用实际上树莓派4的风扇接口只有5V和GND两针有效PWM调速需要通过GPIO软件实现。我用的5V 0.2A小风扇直接接在引脚1和引脚2上风扇常转。如果你想要温控调速可以用GPIO14引脚8输出PWM信号通过一个MOS管控制风扇电源。4. 数据采集程序的架构与实现4.1 为什么用Python而不是C坐垫项目的数据采集频率是每秒10次每次读取MCP3008的CH0和CH1CH1接温度传感器LM35。这个频率下Python的性能完全够用。C语言虽然更快但开发效率低调试麻烦。Python的spidev库和RPi.GPIO库在树莓派4上运行稳定配合numpy做滑动平均滤波CPU占用率不到5%。程序架构分三层采集层一个独立的Python进程通过SPI读取ADC做初步滤波把数据写入SQLite数据库。服务层Flask应用提供REST API从SQLite读取数据返回JSON。展示层一个简单的HTML页面用Chart.js画实时曲线通过AJAX每5秒刷新一次。4.2 SPI通信的初始化代码import spidev import time spi spidev.SpiDev() spi.open(0, 0) # bus 0, device 0 (CE0) spi.max_speed_hz 1350000 # MCP3008最大支持1.35MHz def read_adc(channel): if channel 0 or channel 7: return -1 # MCP3008的SPI协议起始位1单端模式1通道号3位 adc spi.xfer2([1, (8 channel) 4, 0]) data ((adc[1] 3) 8) adc[2] return data def read_pressure(): # 读取CH0做10次平均 values [read_adc(0) for _ in range(10)] return sum(values) / len(values)提示spi.max_speed_hz不要设得太高MCP3008在3.3V下的最大时钟频率是1.35MHz超过这个值读数会不稳定。我试过设成2MHz结果数据跳变严重。4.3 数据存储与清理策略SQLite数据库文件放在/home/ubuntu/cushion/data.db表结构CREATE TABLE sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, pressure REAL, temperature REAL );坐垫项目需要长期运行数据量会持续增长。每秒10次一天就是864000条记录一个月就是2592万条。SQLite单表超过1000万条后查询会变慢。我的策略是每10秒写入一条聚合数据取10次采样的平均值这样一天只有8640条。每天凌晨3点删除30天前的数据。每周执行一次VACUUM回收数据库空间。import sqlite3 from datetime import datetime, timedelta def cleanup_old_data(): conn sqlite3.connect(/home/ubuntu/cushion/data.db) cursor conn.cursor() cutoff datetime.now() - timedelta(days30) cursor.execute(DELETE FROM sensor_data WHERE timestamp ?, (cutoff,)) conn.commit() cursor.execute(VACUUM) conn.close()4.4 开机自启动的配置采集程序需要开机自动运行。我用的是systemd服务而不是rc.local因为systemd可以管理进程崩溃后的自动重启。创建/etc/systemd/system/cushion.service[Unit] DescriptionCushion Data Collector Afternetwork.target [Service] Typesimple Userubuntu WorkingDirectory/home/ubuntu/cushion ExecStart/usr/bin/python3 /home/ubuntu/cushion/collector.py Restartalways RestartSec10 [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable cushion.service sudo systemctl start cushion.service注意Restartalways会在进程退出时自动重启但如果程序有bug导致频繁崩溃systemd会不断重启日志会刷爆。建议加StartLimitIntervalSec60和StartLimitBurst5限制重启频率。5. 远程监控与数据可视化5.1 Flask接口的设计Flask应用监听5000端口提供两个接口GET /api/latest返回最近一条数据。GET /api/history?hours24返回过去N小时的数据用于画曲线。from flask import Flask, jsonify, request import sqlite3 app Flask(__name__) app.route(/api/latest) def latest(): conn sqlite3.connect(/home/ubuntu/cushion/data.db) cursor conn.cursor() cursor.execute(SELECT timestamp, pressure, temperature FROM sensor_data ORDER BY id DESC LIMIT 1) row cursor.fetchone() conn.close() if row: return jsonify({timestamp: row[0], pressure: row[1], temperature: row[2]}) return jsonify({error: no data}), 404 app.route(/api/history) def history(): hours int(request.args.get(hours, 24)) conn sqlite3.connect(/home/ubuntu/cushion/data.db) cursor conn.cursor() cursor.execute(SELECT timestamp, pressure, temperature FROM sensor_data WHERE timestamp datetime(now, ?) ORDER BY timestamp, (f-{hours} hours,)) rows cursor.fetchall() conn.close() return jsonify([{timestamp: r[0], pressure: r[1], temperature: r[2]} for r in rows]) if __name__ __main__: app.run(host0.0.0.0, port5000)5.2 前端页面的实现前端用一个单HTML文件引入Chart.js的CDN。每5秒请求一次/api/latest更新当前数值每5分钟请求一次/api/history?hours1刷新曲线。!DOCTYPE html html head title坐垫监控/title script srchttps://cdn.jsdelivr.net/npm/chart.js/script /head body h2当前压力: span idpressure--/span/h2 h2当前温度: span idtemperature--/span/h2 canvas idchart width800 height400/canvas script const ctx document.getElementById(chart).getContext(2d); const chart new Chart(ctx, { type: line, data: { labels: [], datasets: [{ label: 压力, data: [], borderColor: blue, fill: false }] }, options: { responsive: false } }); async function updateLatest() { const res await fetch(/api/latest); const data await res.json(); document.getElementById(pressure).textContent data.pressure.toFixed(1); document.getElementById(temperature).textContent data.temperature.toFixed(1); } async function updateChart() { const res await fetch(/api/history?hours1); const data await res.json(); chart.data.labels data.map(d d.timestamp.slice(11, 19)); chart.data.datasets[0].data data.map(d d.pressure); chart.update(); } setInterval(updateLatest, 5000); setInterval(updateChart, 300000); updateLatest(); updateChart(); /script /body /html5.3 内网穿透的替代方案坐垫项目需要在外网查看数据但家庭宽带没有公网IP。常见的做法是用内网穿透工具但这类工具往往涉及第三方服务器存在数据泄露风险。我的方案是在树莓派上跑一个MQTT客户端把数据推送到一个自己搭建的MQTT Broker部署在云服务器上然后云服务器上的Web应用订阅MQTT主题展示数据。这样数据流向是树莓派 - 云服务器MQTT - Web展示不依赖任何第三方穿透服务。MQTT发布代码import paho.mqtt.publish as publish def publish_data(pressure, temperature): publish.single( topiccushion/data, payloadf{{pressure: {pressure}, temperature: {temperature}}}, hostnameyour-cloud-server-ip, port1883, auth{username: cushion, password: your-password} )注意MQTT Broker一定要设置用户名密码并且关闭匿名访问。我见过有人把Mosquitto的匿名访问开着结果被扫描到后疯狂发垃圾消息流量跑了几十个GB。6. 实测中的坑与优化经验6.1 USB-C供电口的兼容性问题树莓派4早期批次的USB-C口有一个硬件设计缺陷某些支持USB-C PD协议的充电器会被识别为音频适配器导致无法供电。我用的是一款65W的GaN充电器第一次插上树莓派4毫无反应换了另一款老式的5V 3A充电器才正常。后来查资料才知道这是树莓派4的CC引脚电阻设计问题后期批次已经修复。如果你手头的树莓派4是2019年生产的建议用官方电源或者不带PD协议的普通USB-C电源。6.2 温度对压力传感器的影响薄膜压力传感器的输出会随温度漂移。坐垫加热片工作时传感器附近的温度会升高10-15°C导致压力读数虚高。我的解决方案是在加热片和传感器之间加一层隔热棉同时在软件里做温度补偿。补偿公式是修正压力 原始压力 - (当前温度 - 25) * 0.8这个0.8的系数是实测得出的温度每升高1°C压力读数增加约0.8个单位。不同批次的传感器系数可能不同需要自己标定。6.3 SD卡寿命问题树莓派用SD卡作为系统盘长期写入数据会加速SD卡磨损。坐垫项目每秒写一次数据库SD卡可能几个月就坏了。我的优化措施把数据库文件放在一个USB 3.0的U盘上而不是SD卡。树莓派4的USB 3.0接口速度足够而且U盘坏了换一个就行不用重装系统。在/etc/fstab里给U盘加上noatime挂载选项减少不必要的写入。把系统日志改为内存存储在/etc/systemd/journald.conf里设置Storagevolatile。6.4 Wi-Fi断连的自动恢复树莓派4的Wi-Fi在长时间运行后偶尔会断连尤其是在路由器重启或信号波动时。我写了一个看门狗脚本每5分钟ping一次网关如果连续3次失败就重启Wi-Fi接口。#!/bin/bash FAIL0 for i in {1..3}; do if ! ping -c 1 -W 2 192.168.1.1 /dev/null 21; then FAIL$((FAIL1)) fi done if [ $FAIL -ge 3 ]; then sudo ip link set wlan0 down sleep 2 sudo ip link set wlan0 up sudo netplan apply fi把这个脚本加到crontab里每5分钟执行一次*/5 * * * * /home/ubuntu/cushion/wifi_watchdog.sh6.5 系统时间同步的坑树莓派4没有内置RTC实时时钟断电后时间会丢失。Ubuntu Server默认用systemd-timesyncd同步NTP但首次开机时如果网络还没通时间会是错的。时间错误会导致SQLite的CURRENT_TIMESTAMP写入错误的时间戳数据曲线就乱了。我的做法是在采集程序启动时先检查时间是否合理比如年份大于2020如果不对就等待NTP同步完成再开始采集。import subprocess import time from datetime import datetime def wait_for_ntp(): for _ in range(30): result subprocess.run([timedatectl, show, --propertyNTPSynchronized], capture_outputTrue, textTrue) if yes in result.stdout: return True time.sleep(2) return False if not wait_for_ntp(): print(NTP同步失败使用本地时间)7. 后续可以扩展的方向坐垫项目跑通之后树莓派4的潜力还远没有用完。我最近在尝试把数据采集和边缘计算结合起来在树莓派4上跑一个轻量级的TensorFlow Lite模型根据压力分布判断坐姿是否正确。树莓派4的4GB内存跑TFLite模型绰绰有余推理一次只要几十毫秒。另一个方向是接入Home Assistant把坐垫数据作为智能家居的触发条件——比如检测到有人坐下就自动开灯离开就关灯。树莓派4的蓝牙5.0可以直接扫描附近的BLE设备判断人是否在房间内比单纯用压力传感器更可靠。如果你也在用树莓派做类似的项目我的建议是先把数据采集和存储跑稳再考虑上层应用。很多项目失败不是因为功能不够炫而是因为底层数据不可靠。坐垫项目最核心的价值就是那一条条准确的压力和温度记录其他都是锦上添花。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →