ARM Linux 嵌入式温度监测:从设备树采集到 Qt 可视化与自启动
2026/9/20 4:12:03 网站建设 项目流程

简介:围绕ARM Linux平台的嵌入式温度监测系统设计,这份PDF文献面向电子信息、嵌入式方向的学生与工程技术人员。内容以ARM9微处理器S3C2410A为核心,运行嵌入式Linux,串联温度采集、GPRS无线通信与远程监控中心,重点剖析单总线DS18B20经GPIO采集温度的实现。硬件部分涵盖S3C2410外围电路、RS232串口、RJ45网络接口及存储配置,并给出主控制器端口驱动能力不足时的驱动电路改进方案;软件部分讨论Linux下应用程序、数据处理与通信流程,以及单总线协议在嵌入式Linux中的落地方法。资源为1个PDF文件,压缩包约268KB,适合作为课程设计、毕业设计及中规模温度监测项目的方案参考,便于快速理解系统结构、硬件选型与软件分层。已有463人学习,对搭建ARM Linux测温原型、撰写论文或补充驱动细节具有借鉴价值。

1. 从一颗 ARM 芯片到一条温度曲线:嵌入式温度监测系统到底在做什么

机柜里的边缘计算盒子连续跑了四十分钟,进风口温度从 28 度爬到 51 度,值班的人第二天早上翻日志才发现。基于 ARM Linux 的嵌入式温度监测系统要解决的就是这类问题:一颗 ARM 处理器、一颗数字温度传感器、一套 Linux 用户态采集程序,把温度这个模拟物理量变成可查询、可告警、可回溯的数字曲线。它不追求实验室里千分之一度的精度,追求的是长时间不断电、断网能缓存、重启能自恢复。

这件事听起来简单,真做起来会同时踩到硬件接口、内核设备树、交叉编译、用户态调度、数据持久化和上位机可视化六层。适合两类人读:一类是做工业控制柜、储能机柜、边缘计算盒子的工程师,需要一份能直接抄的落地路径;另一类是刚学嵌入式 Linux,想找一个同时练到设备树、字符设备、交叉编译和 Qt 上位机的小项目练手的人。下面按硬件到内核、内核到用户态、用户态到可视化、最后到校准与长跑的顺序拆开讲。

2. ARM Linux 温度采集的硬件选型与内核对接触点

选型决定后面所有代码的形态。用 1-Wire 的 DS18B20 还是 I2C 的 LM75、TMP102、SHT30,直接决定了内核里走的是 w1-gpio 子系统还是 i2c-dev,也决定了用户态是读/sys/bus/w1/devices/还是读/sys/bus/i2c/devices/。板子这边,Cortex-A7 级别的 i.MX6ULL、STM32MP157 足够跑采集加轻量可视化,Cortex-A53/A57 的 RK3568、AXU15EG 这类开发板更适合再叠 Qt 界面。

2.1 ARM 开发板与温度传感器的选型对照

实际项目里最常纠结的是接口形式和布线成本。DS18B20 单总线能挂多颗、线可以拉几米,适合一个机柜分几个点位;I2C 传感器便宜、精度稳定,但总线电容限制在 400pF 左右,超过就得加缓冲。下面这张表是我自己选型时会对着填的。

维度DS18B20 (1-Wire)LM75/TMP102 (I2C)SHT30 (I2C)
接口占用1 个 GPIOSDA+SCLSDA+SCL
典型精度±0.5°C±2°C / ±0.5°C±0.3°C
多点能力单总线可挂多颗靠地址区分,一般 8 个以内靠地址区分
内核支持w1-gpio + w1-thermlm75 / tmp102 驱动常需自写或走 i2c-dev
适合场景分散点位、长线板载、密集测温高精度、带湿度

如果只是验证链路能不能通,我一般先上 DS18B20,因为它对时序容忍度高,w1-therm驱动是内核自带的,不用写一行驱动代码就能在 sysfs 里看到温度。等到要量产、要精度,再换 I2C 方案。

2.2 从设备树节点到 sysfs:I2C 传感器怎么接进内核

以 TMP102 接在 i2c1 上、地址 0x48 为例,设备树里要干的事就三件:把 i2c1 的 status 打开、把 pinmux 配成 I2C 功能、把传感器节点挂上去。

&i2c1 { clock-frequency = <100000>; /* 标准模式 100kHz,长线可降到 50kHz */ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; tmp102: temperature-sensor@48 { compatible = "ti,tmp102"; reg = <0x48>; /* 7 位地址,写成 8 位会 probe 失败 */ /* 可选:把 ALERT 脚接 GPIO,用于硬件过温中断 */ }; };

改完make dtbs重新烧写,开机后dmesg | grep tmp102应该能看到驱动 probe 成功的日志。逻辑说明:compatible是驱动匹配的唯一钥匙,写错一个字符内核就当没有这个设备;reg是 I2C 从机地址,很多新手把读写地址(0x90)当成reg填进去,结果 probe 一直失败。参数上,clock-frequency在总线超过 30cm 或挂了 4 个以上从机时,从 400kHz 降到 100kHz 甚至 50kHz,能明显减少 ACK 丢失。

验证通路是否真的通了,不用写程序,先看 sysfs:

# 确认设备被内核识别 ls /sys/bus/i2c/devices/1-0048/ # 读一次原始温度,单位是毫摄氏度,需要除以 1000 cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_input # 每 2 秒采一次,观察是否有跳变 watch -n 2 'cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_input'

看到 30500 这样的数字,说明从硬件到内核这一层已经打通了。如果hwmon目录不存在,先查dmesg里有没有 I2C 超时或者 NACK,再确认总线上的上拉电阻是不是没焊。

2.3 交叉编译工具链与采集程序在板端的部署验证

ARM Linux 项目几乎躲不开交叉编译。宿主机上装好工具链之后,先确认版本和 sysroot 能对上目标板的 libc。

# 以 arm-linux-gnueabihf 为例,先验证工具链可用 arm-linux-gnueabihf-gcc -v # 编译一个最简单的温度读取程序,静态链接避免 libc 版本不匹配 arm-linux-gnueabihf-gcc -static -O2 -o temp_read temp_read.c # 用 file 确认产物架构是 ARM 而不是 x86 file temp_read # scp 到板子上执行 scp temp_read root@192.168.1.50:/usr/local/bin/

关键在-static。动态链接时要保证板子上的 glibc 版本不低于交叉工具链的版本,否则会报GLIBC_2.xx not found。产品阶段为了体积和升级方便,通常会换成动态链接加一个自带 sysroot 的 rootfs,但调试期静态链接能省掉大量环境排查时间。

3. 温度采集程序:从 sysfs 读取到数据落盘的完整实现

内核层通了之后,用户态要解决三件事:怎么稳定地读、怎么把原始值变成可用温度、怎么把数据存下来不丢。顺序不能反,先有稳定的读,再谈换算和存储。

3.1 读取温度传感器的最小 C 程序

读 sysfs 本质就是openread,但温度传感器有个坑:一次 read 可能只返回部分内容,必须循环读到 EOF,否则会读到残缺的字符串。

#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #define SENSOR_PATH "/sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_input" /* 读 sysfs 温度,返回摄氏度浮点值,失败返回 -1000 */ double read_temperature(const char *path) { char buf[32] = {0}; int fd = open(path, O_RDONLY); if (fd < 0) { perror("open sensor"); return -1000.0; } ssize_t total = 0, n = 0; /* 循环读,防止单次 read 只返回一部分 */ while (total < (ssize_t)sizeof(buf) - 1 && (n = read(fd, buf + total, sizeof(buf) - 1 - total)) > 0) { total += n; } close(fd); if (total <= 0) return -1000.0; long milli = strtol(buf, NULL, 10); /* hwmon 单位是毫摄氏度 */ return milli / 1000.0; } int main(void) { double t = read_temperature(SENSOR_PATH); if (t < -100.0) { fprintf(stderr, "read failed\n"); return 1; } printf("%.3f\n", t); return 0; }

逻辑说明:strtolatof更适合 sysfs,因为 sysfs 返回的是纯整数毫摄氏度,省掉浮点解析的边界问题。返回值-1000.0是错误哨兵,正常温度不可能低于它,调用方用t < -100.0判断即可。参数上,buf开 32 字节足够,hwmon 一般返回 6 到 8 个字符。

3.2 温度换算、滑动平均与过温阈值判断

单次采样会有噪声,尤其是 I2C 走长线的时候。直接拿单点做告警容易误报,工程上一般做滑动平均。下面的实现用一个固定窗口的环形缓冲,避免动态内存。

#define WIN 8 static double win[WIN]; static int idx = 0, cnt = 0; /* 把新样本塞进窗口,返回窗口内的平均值 */ double moving_average(double sample) { win[idx] = sample; idx = (idx + 1) % WIN; if (cnt < WIN) cnt++; double sum = 0; for (int i = 0; i < cnt; i++) sum += win[i]; return sum / cnt; } /* 告警等级:0 正常,1 预警,2 严重 */ int judge(double avg) { if (avg >= 75.0) return 2; /* 严重:立即上报并触发风扇全速 */ if (avg >= 60.0) return 1; /* 预警:记录并提示 */ return 0; }

逻辑说明:窗口取 8 是经验值,采样周期 1 秒时相当于 8 秒平滑,既能压掉单点毛刺,又不会把真实的快速升温拖得太钝。阈值 60 和 75 只是示例,实际要按器件手册的结温上限反推,留 15 到 20 度余量。如果做多点采集,每个点位要各维护一套窗口状态,不能共用。

3.3 数据落盘:SQLite 还是 CSV 滚动文件

存储方案要看数据量和查询需求。每 1 秒一条、跑 30 天,大约 260 万条记录,用 SQLite 完全扛得住;如果只是最近 24 小时回看,滚动 CSV 更省事。下面这张表是两种方案在嵌入式环境下的实际差别。

维度SQLiteCSV 滚动文件
写入开销有事务,需 WAL 模式追加写,开销最小
断电安全WAL 模式下基本不丢可能丢最后一行
查询能力支持按时间、阈值筛选只能顺序扫
体积占用单文件,可定期 vacuum按天切分,需自己清理
适合规模百万级、需回溯十万级以内、只看近期

用 SQLite 时,表和索引建好,写入用 WAL 模式,避免频繁 fsync 把 SD 卡写坏。

PRAGMA journal_mode = WAL; -- 断电安全,写入更快 CREATE TABLE IF NOT EXISTS temp_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL, -- Unix 时间戳,秒 sensor TEXT NOT NULL, -- 传感器标识,多点时区分 temp_c REAL NOT NULL, level INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_ts ON temp_log(ts);

索引idx_ts是为了按时间范围查询,没有它,30 天的数据查一次要全表扫。写入时批量 100 条提交一次事务,比一条一提交快一个数量级。另外要设一个保留策略,比如只保留最近 90 天,用定时任务删旧数据并执行PRAGMA incremental_vacuum

4. 可视化与自启动:把数据送到 Qt5 界面和浏览器

数据存下来只是第一步,现场的人要看曲线,调度的人要收告警。ARM 板资源有限,可视化要按场景选方案:带屏幕的用 Qt,纯远程的用轻量 HTTP 接口。

4.1 Qt5.5.10 在 ARM 板上的交叉编译要点

Qt5.5.10 是个被大量嵌入式项目沿用的版本,稳定但对交叉编译环境挑剔。核心是先把宿主机上的 Qt 编成目标板能跑的版本,再编应用。配置阶段几个必须显式指定的参数:

# 配置 Qt5.5.10,关键在于 -platform 和 -xplatform 的分工 ./configure \ -prefix /opt/qt5.5.10-arm \ -opensource -confirm-license \ -release -shared \ -platform linux-g++ \ # 宿主机编译工具 -xplatform linux-arm-gnueabi-g++ \ # 目标板编译工具 -qt-freetype -qt-libpng -qt-zlib \ -no-opengl -no-xcb \ # 无 X11 时用 linuxfb -qt-libjpeg -no-pch \ -nomake examples -nomake tests make -j8 && make install

逻辑说明:-no-xcb-platform linuxfb是嵌入式无桌面环境的常态,直接往 framebuffer 写。-no-opengl看板子有没有 GPU,有 Mali 或 Vivante 就打开 EGLFS,帧率差别很大。参数上,-prefix指向的目录要原样拷贝到板子上,应用运行时用-platform linuxfb启动,或者设QT_QPA_PLATFORM=linuxfb

界面本身建议只做三件事:显示当前温度、画最近 1 小时曲线、在阈值越界时变色。Qt Charts 在 5.5 时代还比较重,曲线可以用QPainter自己画折线,数据点控制在 360 个以内,一帧刷新不超过 5ms。

4.2 用轻量 HTTP 接口把温度曲线推给浏览器

没有屏幕的板子,起一个几十 KB 的 HTTP 服务暴露 JSON 就够了。用 Python 起一个最小服务只需要几十行,借助http.server即可。

# temp_http.py:暴露 /latest 和 /history 两个接口 from http.server import BaseHTTPRequestHandler, HTTPServer import sqlite3, json, time DB = "/var/lib/tempmon/temp.db" class Handler(BaseHTTPRequestHandler): def do_GET(self): conn = sqlite3.connect(DB) cur = conn.cursor() if self.path == "/latest": cur.execute("SELECT ts, temp_c, level FROM temp_log ORDER BY ts DESC LIMIT 1") row = cur.fetchone() body = json.dumps({"ts": row[0], "temp": row[1], "level": row[2]}) elif self.path == "/history": since = int(time.time()) - 3600 # 最近 1 小时 cur.execute("SELECT ts, temp_c FROM temp_log WHERE ts > ? ORDER BY ts", (since,)) body = json.dumps(cur.fetchall()) else: self.send_error(404) return conn.close() data = body.encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

逻辑说明:/latest给告警脚本轮询,/history给前端画曲线。数据全部来自 SQLite,采集程序和 HTTP 服务共享同一个库文件,靠 WAL 模式解决并发读写。参数上,since用相对时间而不是绝对秒数,前端不用关心时区。注意conn要记得关,否则长时间跑会耗尽文件描述符。

4.3 systemd 自启动与看门狗守护

采集、HTTP、Qt 三类进程都要开机自起,还要在崩溃后自动拉起。systemd 的 unit 是标准做法。

# /etc/systemd/system/tempmon.service [Unit] Description=Temperature Monitor Collector After=network.target [Service] Type=simple ExecStart=/usr/local/bin/temp_collect Restart=always RestartSec=3 WatchdogSec=30 # 30 秒喂一次狗,超时重启 Environment=LD_LIBRARY_PATH=/opt/qt5.5.10-arm/lib [Install] WantedBy=multi-user.target

逻辑说明:Restart=alwaysRestartSec=3是防崩溃的底线;WatchdogSec=30要求进程每 30 秒调用一次sd_notify(0, "WATCHDOG=1"),如果因为死锁卡住,systemd 会主动杀掉重启。采集程序里加喂狗只需要在采样循环末尾调用一次,几行代码的事。参数上,WatchdogSec要大于单次采样周期的 5 倍以上,否则正常抖动也会被误判。

5. 温度精度校准与长期运行的可观测性技巧

传感器出厂有偏差,PCB 走线有自热,机箱内部还有热源干扰。想拿到可信的温度,就得做两点校准。常用做法是拿一颗经过计量的参考温度计,把传感器和参考探头放在恒温水浴或恒温箱里,取低温和高温两个点。

具体操作是:在 25 度和 70 度两个稳定点各采 200 个样本求均值,记为raw_lowraw_high,参考值为ref_lowref_high,然后按下面的公式做线性校正。

# 两点线性校准:把原始读数映射到参考温度 k = (ref_high - ref_low) / (raw_high - raw_low) b = ref_low - k * raw_low def calibrate(raw): return k * raw + b

逻辑说明:k修正增益误差,b修正零点偏移,两个系数算完写进配置文件,采集时实时套用。注意校准要在系统热平衡后进行,采样前至少通电 30 分钟,否则测到的是还没稳定的温漂。参数上,如果两点覆盖范围太窄,k会被噪声放大,低温点最好压到 10 度以下、高温点拉到 70 度以上。

长期运行最怕的是无声失败:进程还在、数据没进来、告警不响。我一般埋三条可观测性线索。第一条是每小时的计数校验,采集程序每小时往日志里写一条实际写入条数,正常应该在 3600 上下浮动 5% 以内。第二条是心跳时间戳,HTTP 接口的/latest返回的ts如果超过 30 秒没更新,外部监控就报。第三条是原始值兜底,采集失败时写入数值哨兵-999并置level=2,让曲线出现明显断层,而不是数据静默中断。

排查现场问题时,下面这几条命令会反复用到:

# 看采集进程运行多久、重启过几次 systemctl status tempmon # 看最近的驱动层报错,重点是 I2C NACK 和超时 dmesg -T | tail -50 # 检查存储空间,数据库写满会静默失败 df -h /var/lib/tempmon # 直接查库确认最新数据时间 sqlite3 /var/lib/tempmon/temp.db "SELECT datetime(max(ts),'unixepoch','localtime') FROM temp_log"

如果max(ts)和当前时间差超过两个采样周期,问题一定出在采集侧,不在显示侧。这时先看dmesg里是不是 I2C 总线被风扇或继电器干扰,再确认/sys下的传感器文件是否还在。把这三条线索和一套两点校准做完,系统就算从能跑变成能长期赖在现场了。

本文还有配套的精品资源,点击获取

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

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

立即咨询