尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Arduino与DS18B20的PC温度监控系统:从传感器到上位机全链路实践

基于Arduino与DS18B20的PC温度监控系统:从传感器到上位机全链路实践 1. 项目缘起为什么我们需要一个独立的PC温度监控器作为一名机电工程专业的学生或者对硬件DIY、系统监控感兴趣的爱好者你可能经常遇到这样的场景电脑在高负荷运行时风扇狂转机箱发烫你心里没底不知道CPU、GPU到底多少度了会不会过热降频甚至损坏任务管理器只能看个大概第三方软件又常常伴随着广告、后台服务甚至数据上传的隐私担忧。尤其是在进行一些专业课程项目比如MEGR 3171这类涉及机电一体化的课程时实时、准确地获取核心硬件的温度数据往往是进行热管理分析、风扇控制算法验证甚至是故障诊断的第一步。这就是“MEGR 3171 PC Temperature Sensor”这个项目存在的核心价值。它不是一个简单的软件读数工具而是一个从底层传感器数据获取到上位机数据显示的完整硬件-软件系统。通过自己动手搭建你不仅能获得一个完全受控、无干扰的温度监控终端更能深入理解计算机硬件的传感器接口如SMBus/I2C、微控制器编程、串口通信以及桌面应用开发的全链路知识。相比于直接下载一个现成的监控软件这个过程带来的学习收益和掌控感是完全不同的。市面上常见的方案是使用AIDA64、HWMonitor等软件它们功能强大但作为“黑盒”你无法定制其数据采样率、无法直接获取原始数据用于自己的算法、更无法将其集成到你自己的项目中。而本项目则从一颗最基础的传感器芯片开始教你如何将它“嫁接”到你的电脑主板上如何编写固件读取数据并最终在电脑桌面上创建一个简洁、实时的监控窗口。这不仅是完成一个课程作业更是打造一个属于你自己的、可扩展的硬件监控平台。2. 核心硬件选型与电路设计解析要实现PC温度监控第一步是选择合适的“感知器官”——温度传感器。对于PC内部监测我们主要关注CPU、GPU、主板芯片组、硬盘以及机箱内环境温度。针对不同位置传感器选型也有讲究。2.1 传感器芯片选型DS18B20 vs. LM35 vs. 专用IC对于机箱环境温度或非核心发热元件的监测数字温度传感器DS18B20和模拟传感器LM35是常见选择。DS18B20它的最大优势是单总线1-Wire协议仅需一根数据线加上电源和地即可与微控制器通信布线简单且每个器件有唯一64位ID支持在同一总线上挂载多个传感器非常适合监测机箱内多个位置的空气温度。其精度为±0.5°C完全满足PC监控需求。LM35这是一款经典的模拟输出温度传感器输出电压与摄氏温度成线性关系10mV/°C。其优点是接口极其简单只需读取模拟电压值。但需要微控制器具备ADC模数转换器功能且长距离传输模拟信号易受干扰。专用监控IC如常见的LM75、TMP102等。它们通过I2C或SMBus接口通信精度高集成度高通常内置在主板、显卡上用于监控核心温度。但直接连接它们需要找到主板上的相应总线并小心操作有一定风险。注意对于CPU/GPU核心温度强烈不建议初学者直接外接传感器测量。现代CPU/GPU内部集成了高精度的数字热敏二极管DTS其读数通过处理器内部寄存器提供由主板SMBus读取。试图在散热器上粘贴外部传感器来测量核心温度误差会非常大且可能影响散热。本项目的更佳实践是使用DS18B20监测机箱环境温度和硬盘等部件表面温度同时通过软件接口后续章节会讲直接读取CPU/GPU内部DTS数据两者结合形成完整的温度视图。本项目推荐方案采用DS18B20作为扩展温度监测点。理由如下数字信号抗干扰能力强单总线简化布线易于扩展多个点Arduino等开发板有成熟的库支持开发速度快。2.2 微控制器与通信方案Arduino的桥梁作用传感器产生了数据我们需要一个“中间人”来收集数据并传递给PC。这里Arduino Uno/Nano这类开发板是绝佳选择。核心角色Arduino充当了协议转换器和数据采集器。它通过数字引脚读取DS18B20的数据使用OneWire和DallasTemperature库然后将整理好的温度数据通过其内置的USB转串口芯片以串行通信UART的格式发送给PC。电路连接DS18B20的VDD接Arduino 5VGND接GNDDQ数据线接一个数字引脚如D2并在DQ与5V之间连接一个4.7kΩ的上拉电阻这是单总线协议的标准要求。Arduino通过USB线连接至PC虚拟出一个COM口在Windows或TTY设备在Linux/macOS。为什么是串口串口通信简单、可靠、跨平台。几乎任何编程语言都能轻松操作串口。这使得我们的上位机软件可以用Python、C#、Java甚至Node.js来编写灵活性极高。2.3 电源与布线注意事项电源整个系统由PC的USB口供电非常方便。但需注意如果连接多个DS18B20总电流消耗很小每个约1mAUSB口足以应对。布线DS18B20的导线不宜过长建议使用带屏蔽的网线或专用传感器延长线最长不要超过20米以防信号衰减。在机箱内布线时避开风扇电源线等强干扰源并做好固定防止卷入风扇。3. 固件开发让Arduino“开口说话”固件Firmware是烧录在Arduino里的程序它决定了硬件如何工作。我们的目标是让Arduino定时读取所有传感器并将数据格式化为字符串通过串口发送出去。3.1 开发环境与库安装首先在PC上安装Arduino IDE。然后我们需要两个关键的库来驱动DS18B20OneWire用于处理单总线通信协议。DallasTemperature在OneWire基础上提供了更高层、更易用的温度传感器操作接口。可以通过Arduino IDE的“库管理器”Sketch - Include Library - Manage Libraries...搜索并安装这两个库。3.2 核心代码逻辑剖析下面是一个完整的、带有详细注释的固件示例。它实现了连接多个DS18B20并每秒轮询一次通过串口输出JSON格式的数据。#include OneWire.h #include DallasTemperature.h // 单总线数据引脚连接至Arduino的2号数字引脚 #define ONE_WIRE_BUS 2 // 初始化单总线实例 OneWire oneWire(ONE_WIRE_BUS); // 将单总线实例传递给DallasTemperature库 DallasTemperature sensors(oneWire); // 用于存储发现的传感器设备地址 DeviceAddress sensorAddress; // 统计找到的传感器数量 int numberOfDevices; void setup(void) { // 启动串口通信波特率设置为9600与上位机约定一致 Serial.begin(9600); // 初始化温度传感器库 sensors.begin(); // 扫描总线上的传感器数量 numberOfDevices sensors.getDeviceCount(); Serial.print(Found ); Serial.print(numberOfDevices); Serial.println( temperature sensor(s).); // 循环遍历每个传感器打印其地址唯一ID for (int i 0; i numberOfDevices; i) { if (sensors.getAddress(sensorAddress, i)) { Serial.print(Sensor ); Serial.print(i); Serial.print( Address: ); printAddress(sensorAddress); // 调用自定义函数打印地址 Serial.println(); } } Serial.println(--- Start Reading ---); } void loop(void) { // 发送命令让所有传感器开始温度转换这会有一点延迟 sensors.requestTemperatures(); // 创建一个JSON格式的字符串开头 String jsonOutput {\sensors\:[; // 遍历所有传感器读取温度并添加到JSON字符串 for (int i 0; i numberOfDevices; i) { float tempC sensors.getTempCByIndex(i); // 通过索引获取摄氏温度 jsonOutput {\id\:; jsonOutput i; jsonOutput ,\temp\:; jsonOutput tempC; jsonOutput }; // 如果不是最后一个传感器添加逗号分隔 if (i numberOfDevices - 1) { jsonOutput ,; } } jsonOutput ]}; // 将完整的JSON字符串通过串口发送给PC Serial.println(jsonOutput); // 等待1秒控制数据更新频率 delay(1000); } // 辅助函数以十六进制格式打印传感器地址 void printAddress(DeviceAddress deviceAddress) { for (uint8_t i 0; i 8; i) { if (deviceAddress[i] 16) Serial.print(0); Serial.print(deviceAddress[i], HEX); } }代码关键点解析sensors.requestTemperatures()此函数向所有总线上的DS18B20发送“开始转换”命令。DS18B20需要一定时间最多750ms来完成模数转换。getTempCByIndex函数内部会处理这个等待所以代码是阻塞的。对于需要极高采样率的应用可以采用异步方式先发送转换命令在loop()中稍后再读取。JSON格式输出我们选择输出{sensors:[{id:0,temp:25.3},{id:1,temp:32.1}]}这样的格式。JSON是跨语言的标准数据交换格式上位机无论用什么语言开发都能轻松解析极大地提升了系统的兼容性和可扩展性。未来如果你想增加湿度、转速等传感器只需在JSON中添加新的字段即可。波特率Serial.begin(9600)中的9600是波特率表示每秒传输9600比特。这个值需要与上位机软件设置完全一致否则会出现乱码。3.3 烧录与测试将代码编译并上传到Arduino后打开Arduino IDE自带的串口监视器工具 - 串口监视器。确保右下角的波特率设置为9600。你应该会先看到扫描到的传感器地址然后每秒刷新一行JSON数据。这是验证硬件连接和固件逻辑是否正确的最直接方法。4. 上位机软件开发在PC端打造监控仪表盘固件让数据流到了串口上位机软件的任务就是打开这个串口读取数据解析并展示出来。这里我们选择使用Python因为它库丰富、跨平台、开发快速。我们将使用pyserial库进行串口通信psutil库来获取CPU/GPU内部温度如果系统支持以及tkinter或PyQt5来创建图形界面。4.1 环境准备与库安装首先确保安装了Python3.6以上版本。然后通过pip安装必要的库pip install pyserial psutil对于图形界面如果你追求简单可以使用内置的tkinter通常随Python安装。如果你需要更美观、功能更强大的界面可以安装PyQt5pip install PyQt54.2 核心数据获取模块我们创建一个data_acquisition.py模块负责所有数据的收集。import serial import json import psutil import time from threading import Thread, Event class TemperatureMonitor: def __init__(self, com_portCOM3, baudrate9600): 初始化监控器 :param com_port: Arduino连接的串口号如COM3(Windows), /dev/ttyUSB0(Linux) :param baudrate: 波特率必须与Arduino固件设置一致 self.com_port com_port self.baudrate baudrate self.serial_conn None self.external_temps {} # 存储外部传感器温度键为传感器ID self.cpu_temp None self.gpu_temp None self._stop_event Event() self._thread None def connect_arduino(self): 连接Arduino串口 try: self.serial_conn serial.Serial(self.com_port, self.baudrate, timeout2) print(f成功连接到串口 {self.com_port}) # 清空可能存在的缓冲数据 self.serial_conn.reset_input_buffer() time.sleep(2) # 等待Arduino重启完成 return True except serial.SerialException as e: print(f无法打开串口 {self.com_port}: {e}) return False def _read_serial_thread(self): 持续读取串口数据的线程函数 while not self._stop_event.is_set(): if self.serial_conn and self.serial_conn.in_waiting: try: line self.serial_conn.readline().decode(utf-8).strip() if line.startswith({) and line.endswith(}): # 简单的JSON格式检查 data json.loads(line) for sensor in data.get(sensors, []): sensor_id sensor.get(id) temp sensor.get(temp) if temp ! 85.0 and temp ! -127.0: # DS18B20的错误值过滤 self.external_temps[sensor_id] temp except (UnicodeDecodeError, json.JSONDecodeError) as e: print(f解析串口数据时出错: {e}) except Exception as e: print(f读取串口时发生未知错误: {e}) time.sleep(0.1) # 短暂休眠避免CPU占用过高 def _read_system_temps(self): 读取CPU/GPU内部温度平台相关 # CPU温度 (Linux) if hasattr(psutil, sensors_temperatures): temps psutil.sensors_temperatures() if coretemp in temps: # Intel CPU常见 self.cpu_temp max([item.current for item in temps[coretemp]]) elif k10temp in temps: # AMD CPU常见 self.cpu_temp max([item.current for item in temps[k10temp]]) # 注意psutil在Windows上获取CPU温度需要额外依赖如pywin32且不一定可靠。 # GPU温度通常需要额外库如pynvml for NVIDIA, pyadl for AMD. # 此处为简化GPU温度读取暂不实现可作为扩展。 self.gpu_temp None # 预留 def start(self): 启动所有数据采集线程 if self.connect_arduino(): self._thread Thread(targetself._read_serial_thread, daemonTrue) self._thread.start() print(外部传感器数据采集已启动。) # 系统温度读取可以放在主循环或另起一个定时线程 print(系统温度监控已启动。) def stop(self): 停止数据采集 self._stop_event.set() if self._thread: self._thread.join(timeout1) if self.serial_conn and self.serial_conn.is_open: self.serial_conn.close() print(监控器已停止。) def get_all_temperatures(self): 获取所有当前温度数据 self._read_system_temps() # 更新系统温度 return { external: self.external_temps.copy(), # 返回副本避免线程问题 cpu: self.cpu_temp, gpu: self.gpu_temp, timestamp: time.time() } # 使用示例 if __name__ __main__: monitor TemperatureMonitor(com_portCOM3) # 请根据实际情况修改串口号 monitor.start() try: while True: data monitor.get_all_temperatures() print(f数据: {data}) time.sleep(2) # 每2秒打印一次 except KeyboardInterrupt: monitor.stop()关键点与避坑指南串口号这是最容易出错的地方。在Windows上可以在“设备管理器”的“端口COM和LPT”下查看在Linux/macOS上通常是/dev/ttyUSB0或/dev/ttyACM0。程序启动时如果报错首先检查端口号。数据解析与错误处理DS18B20在初始化或通信失败时会返回85.0或-127.0这样的特定值。我们在代码中过滤了这些值避免显示错误数据。同时使用try...except捕获并处理串口数据解析中的各种异常如乱码、不完整JSON保证程序健壮性。多线程串口读取是一个阻塞或持续等待的过程如果放在主线程会导致GUI界面“卡死”。因此我们使用threading.Thread创建一个后台线程专门负责读取串口数据主线程或GUI线程可以流畅地更新界面。系统温度获取psutil库在Linux上获取CPU温度比较方便但在Windows上需要安装pywin32等且支持有限。GPU温度则需要更专业的库如NVIDIA的pynvml。这部分可以根据你的具体硬件和操作系统进行扩展。4.3 图形界面GUI实现示例使用Tkinter下面是一个简单的Tkinter界面用于实时显示温度数据。import tkinter as tk from tkinter import ttk import threading import time from data_acquisition import TemperatureMonitor # 导入上面写的数据获取类 class TemperatureApp: def __init__(self, root): self.root root self.root.title(MEGR 3171 PC温度监控仪) self.root.geometry(400x300) self.monitor TemperatureMonitor(com_portCOM3) # 修改为你的端口 self.data_labels {} # 创建显示区域 self.setup_ui() # 启动监控 self.monitor.start() # 启动GUI更新循环 self.update_display() def setup_ui(self): # 标题 title_label ttk.Label(self.root, text实时温度监控, font(Arial, 16)) title_label.pack(pady10) # 创建一个框架来放置温度标签 frame ttk.Frame(self.root) frame.pack(padx20, pady10, filltk.BOTH, expandTrue) # 初始化标签 - 外部传感器 ttk.Label(frame, text 外部传感器 , font(Arial, 10, bold)).grid(row0, column0, stickyw, pady5) for i in range(3): # 假设最多3个外部传感器 label ttk.Label(frame, textf传感器 {i}: --.- °C) label.grid(rowi1, column0, stickyw, padx20) self.data_labels[fext_{i}] label # CPU温度标签 ttk.Label(frame, text 系统温度 , font(Arial, 10, bold)).grid(row5, column0, stickyw, pady(15,5)) cpu_label ttk.Label(frame, textCPU: --.- °C) cpu_label.grid(row6, column0, stickyw, padx20) self.data_labels[cpu] cpu_label # GPU温度标签 (预留) gpu_label ttk.Label(frame, textGPU: N/A) gpu_label.grid(row7, column0, stickyw, padx20) self.data_labels[gpu] gpu_label # 退出按钮 quit_btn ttk.Button(self.root, text退出, commandself.on_closing) quit_btn.pack(pady20) def update_display(self): 定时从监控器获取数据并更新界面 data self.monitor.get_all_temperatures() # 更新外部传感器 ext_temps data.get(external, {}) for i in range(3): label_key fext_{i} temp ext_temps.get(i) text f传感器 {i}: {temp:.1f} °C if temp is not None else f传感器 {i}: --.- °C self.data_labels[label_key].config(texttext) # 更新CPU温度 cpu_temp data.get(cpu) cpu_text fCPU: {cpu_temp:.1f} °C if cpu_temp is not None else CPU: N/A self.data_labels[cpu].config(textcpu_text) # 更新GPU温度 (示例实际需要根据获取到的数据) # gpu_temp data.get(gpu) # ... # 设置1秒后再次更新 self.root.after(1000, self.update_display) def on_closing(self): 关闭窗口时的清理工作 self.monitor.stop() self.root.destroy() if __name__ __main__: root tk.Tk() app TemperatureApp(root) root.protocol(WM_DELETE_WINDOW, app.on_closing) root.mainloop()这个GUI界面虽然简单但具备了核心功能实时更新显示。root.after(1000, self.update_display)是Tkinter的定时器方法它每隔1000毫秒1秒调用一次update_display函数从而实现了数据的动态刷新。5. 系统集成、校准与进阶优化将硬件安装到PC机箱内并让软件开机自启才算一个完整的项目。5.1 硬件安装与布线实战确定监测点常见的监测点包括机箱前部进气口附近环境温度、CPU散热器出风口CPU散热效率、显卡尾部GPU散热效率、硬盘架附近存储设备环境。避免将传感器直接贴在供电模块、芯片等发热极高的元件上DS18B20的测量范围是-55°C到125°C但长期超过125°C会损坏。传感器固定使用耐高温胶带如聚酰亚胺胶带或导热胶将DS18B20的金属探头部分固定在需要监测的表面上。如果要测空气温度最好将其悬空或使用有金属护套的探头型号避免接触物体。走线管理将传感器的导线沿着机箱边缘或背板走线用扎带固定。从机箱后部的PCIe挡板孔或水冷管孔等预留孔洞穿出连接至机箱外部的Arduino。确保导线不会被风扇叶片刮到。Arduino放置将Arduino放在机箱顶部或桌面安全位置。如果追求极致集成甚至可以尝试用主板上的USB插针需转接线为Arduino供电并将其固定在机箱内的闲置位置。5.2 软件校准与数据验证任何测量系统都需要校准。虽然DS18B20出厂精度不错但为了更可靠可以进行简单验证。冰水混合物法将传感器探头插入冰水混合物0°C中等待读数稳定查看上位机显示是否接近0°C。如果存在固定偏差可以在上位机软件的数据解析环节增加一个偏移量Offset进行软件校准。例如如果显示2.5°C则所有该传感器的读数减去2.5°C。对比法在电脑空闲和满载运行AIDA64 FPU压力测试或FurMark时对比你的监控器读数与HWMonitor等专业软件的读数。关注变化趋势是否一致而非绝对数值。CPU核心温度应以专业软件为准我们的外部传感器主要看环境温度变化。5.3 开机自启动与后台服务化我们希望这个监控工具能像其他系统服务一样开机即运行。Windows将Python脚本打包成.exe文件使用pyinstaller库pyinstaller --onefile --windowed your_script.py这样可以避免用户安装Python环境。创建一个该.exe文件的快捷方式。按下Win R输入shell:startup打开启动文件夹。将快捷方式拖入此文件夹。下次开机时程序就会自动运行。Linux创建一个systemd服务文件例如/etc/systemd/system/pc-temp-monitor.service。文件内容示例[Unit] DescriptionPC Temperature Monitor Aftermulti-user.target [Service] Typesimple Useryour_username ExecStart/usr/bin/python3 /path/to/your_script.py Restarton-failure [Install] WantedBymulti-user.target运行sudo systemctl enable pc-temp-monitor.service启用开机自启。5.4 项目进阶与扩展思路这个基础项目有巨大的扩展潜力数据记录与可视化使用sqlite3或csv模块将温度数据按时间戳保存下来。结合matplotlib库可以绘制过去一段时间如24小时的温度曲线图分析电脑在不同使用场景下的热行为。智能风扇控制这是MEGR 3171项目可能深入的方向。Arduino可以根据读取到的温度通过PWM脉宽调制信号控制连接到其上的机箱风扇需使用MOSFET或风扇驱动模块。实现简单的温控逻辑例如温度低于40°C时风扇低速40-60°C中速高于60°C全速。请注意安全确保电路设计能承受风扇的电流。网络远程监控让Arduino连接Wi-Fi模块如ESP8266或者让上位机Python程序作为一个Web服务器使用Flask框架。这样你就可以在手机或同一网络下的其他电脑上通过浏览器实时查看温度。报警功能在上位机软件中设置温度阈值如CPU85°C环境45°C当超过阈值时触发桌面通知使用plyer库、发出声音警报甚至发送邮件或短信需集成相应API。集成更多传感器单总线可以挂载多个DS18B20。I2C总线可以连接大气压强传感器BMP280、湿度传感器Si7021等打造一个完整的机箱环境监控站。通过这个从传感器到上位机的完整实践你收获的不仅仅是一个温度计而是一套解决实际问题的硬件-软件协同开发方法论。它涉及电路设计、嵌入式编程、桌面应用开发、系统集成等多个环节是对机电一体化知识的绝佳综合应用。
返回列表