简介:这是一套以51单片机和LabVIEW为核心的温度监测系统资料,适合电子、自动化、计算机等专业学生以及嵌入式入门开发者,用于完成课程设计或理解单片机与上位机联调方法。压缩包共29个文件,大小约444KB,包含LabVIEW的vi程序、C语言源文件、头文件、HEX烧录文件、工程配置文件,以及用户管理、温度记录和报警记录三个数据库,完整覆盖数据采集、通信、显示、存储与报警等模块。已有369人学习。资源内可对照单片机端C代码(主程序、温度采集、LCD显示)理清传感器驱动与界面逻辑;也可结合LabVIEW端主监测界面、通讯设置和数据查询程序,学习串口通信、实时曲线显示与数据库管理。工程与烧录文件齐全,便于直接编译验证,适合作为本科毕设或创新项目的参考原型。
1. 把温度监测系统拆成两条线:51单片机的数据采集与LabVIEW的界面呈现
在搭建温度监测系统时,很多工程师容易一头扎进上位机界面的控件摆放里,忽略了底层串口报文是否规整、数据帧有没有 CRC 校验。基于单片机和 LabVIEW 的温度监测系统,本质上要解决两件事:让 51 单片机把温度传感器读到的值,按约定帧格式通过串口送出来;让 LabVIEW 作为上位机,把这串字节流解析成可读的温度数字,再画成曲线或触发表格。这套组合之所以常被用于课程设计和工业小样机,是因为 51 单片机成本低、C 语言开发资料庞杂,而 LabVIEW 把串口收发封装成了图形化节点,不写一行文本代码就能完成串口配置、字节截取和波形显示。如果你手头有一块 STC89C52 或 AT89S52 开发板、一个 I2C 接口的温度传感器(比如 TC74),加上一台装有 LabVIEW 的 PC,几个小时就能打通一条可靠的数据链路。
2. 51单片机端的数据采集与 Modbus 帧接收程序
这个标题里的“温度监测”,在单片机侧最实在的落地方式,是用 I2C 温敏芯片替代热敏电阻加 ADC 的老方案。热敏电阻需要自己标定分压电阻和查表,而 I2C 温度传感器直接给出数字量,省掉了大量校准时间。下面以 TC74A0 为例,讲清楚接线、I2C 读取、串口发送和帧协议封装。
2.1 TC74 温度传感器的接线与 I2C 初始化
TC74 是一个兼容 I2C 接口的 8 位温度传感器,测量范围 -40℃ 到 +125℃,分辨率 1℃,在 0℃ 到 +70℃ 范围内的误差只有 ±1℃。它的 I2C 从机地址由硬件引脚 A2、A1、A0 决定,默认地址是 0x48 到 0x4F 区间。以常见的 0x48 为例,8 位地址字节左移后是 0x90 写地址、0x91 读地址。接线时 SCL 接 P1.0,SDA 接 P1.1,两个引脚各接一个 4.7kΩ 上拉电阻到 VCC,这是 I2C 总线工作在标准模式(100kbps)的必需条件。
单片机端的 I2C 通信不能用传统的延时函数糊弄,因为系统还要同时跑串口中断和定时器。我习惯用 GPIO 模拟时序,但是把 SCL 翻转和 SDA 采样做成宏定义,方便在 STC89C52 和 STC15 系列之间移植。
#include <reg52.h> sbit SCL = P1^0; sbit SDA = P1^1; #define I2C_DELAY() { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start(void) { SDA = 1; SCL = 1; I2C_DELAY(); SDA = 0; I2C_DELAY(); SCL = 0; } void I2C_Stop(void) { SDA = 0; SCL = 1; I2C_DELAY(); SDA = 1; } bit I2C_WriteByte(unsigned char dat) { unsigned char i; bit ack; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; SCL = 1; I2C_DELAY(); SCL = 0; I2C_DELAY(); } SDA = 1; // 释放SDA线,等待从机应答 SCL = 1; I2C_DELAY(); ack = SDA; SCL = 0; return ack; } unsigned char I2C_ReadByte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { SCL = 1; I2C_DELAY(); dat = (dat << 1) | SDA; SCL = 0; I2C_DELAY(); } return dat; }这段代码的关键在于应答位的处理。I2C_WriteByte 函数里,写完 8 位数据后把 SDA 置 1,再拉高 SCL 读取从机的 ACK。如果 TC74 没有正确应答,返回的 ack 会被置位,主函数可以根据这个标志决定是重发还是报错。I2C_ReadByte 则是在每个 SCL 高电平期间采样 SDA,注意必须在 SCL 拉高之前把 SDA 从输出模式切换为输入模式,GPIO 模拟 I2C 时最常见的错误就是漏了这一步,导致读回来的字节恒为 0xFF。
读 TC74 温度的时序是:先发 Start 信号,再发 0x90 写地址,然后写配置寄存器地址 0x01 并写入 0x00 让芯片进入正常工作模式,最后发 Stop;接着重新发 Start,再发 0x91 读地址,连续读取两个字节,前一个是温度寄存器数据,后一个是 CRC 或无效填充。由于 TC74 是 8 位分辨率,温度值就是第一个数据字节的有符号数。
2.2 串口帧结构设计:让 LabVIEW 十分钟内能解析
单片机采集到温度后,不能裸发十六进制数,LabVIEW 那边需要契定的停顿时间和帧头识别。我用的协议是简化版 Modbus RTU:地址 1 字节、功能码 1 字节、数据长度 1 字节、温度高字节、温度低字节、CRC16 低字节、CRC16 高字节。这样既能让 LabVIEW 的程序框图按索引截取,也能配合串口调试助手人工验帧。完整的 Modbus 帧接收在单片机上也可以用状态机实现,但采样系统收发的数据量很小,可以直接用固定长度帧协议。
#define FRAME_LEN 7 unsigned char uart_buf[FRAME_LEN]; unsigned char uart_cnt = 0; bit frame_done = 0; void UART_ISR() interrupt 4 { unsigned char dat; if (RI) { RI = 0; dat = SBUF; if (uart_cnt == 0) { if (dat != 0xAA) { // 帧头匹配 return; } uart_buf[0] = dat; uart_cnt = 1; } else if (uart_cnt < FRAME_LEN) { uart_buf[uart_cnt++] = dat; if (uart_cnt >= FRAME_LEN) { frame_done = 1; uart_cnt = 0; } } } }这段串口中断程序把 UART 收到的每一字节都放进缓冲数组。第一字节必须等于 0xAA 才会继续接收,否则直接丢弃,这能过滤掉上电瞬间的噪声。当收满 7 字节后,frame_done 置 1,主循环检测到这个标志后就执行 CRC 校验和温度封装。
主循环的发送部分如下,其中 crc16 是标准的 Modbus CRC 算法,计算结果在发送时低字节在前。
unsigned int crc16_update(unsigned char *buf, unsigned char len) { unsigned int crc = 0xFFFF; unsigned char i, j; for (i = 0; i < len; i++) { crc ^= buf[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; } void send_temperature_frame(int temp) { unsigned char buf[FRAME_LEN]; unsigned int crc; buf[0] = 0xAA; // 帧头 buf[1] = 0x01; // 设备地址 buf[2] = 0x04; // 功能码,上报温度 buf[3] = (unsigned char)((temp >> 8) & 0xFF); buf[4] = (unsigned char)(temp & 0xFF); crc = crc16_update(buf, 5); buf[5] = (unsigned char)(crc & 0xFF); buf[6] = (unsigned char)((crc >> 8) & 0xFF); for (unsigned char i = 0; i < FRAME_LEN; i++) { SBUF = buf[i]; while (!TI); TI = 0; } }CRC 计算时只针对帧头、地址、功能码和数据四个字段,CRC 自身不参与计算。这是 Modbus 协议最核心的约定。发送函数里的 while 等待方式在低速 51 上完全可用,因为串口波特率只有 9600,发送 7 字节耗时大约 7.3ms,不会阻塞温度采样主循环。
2.3 串口参数配置与波特率误差控制
51 单片机的串口工作在模式 1(8 位可变波特率 UART),波特率由定时器 1 的溢出率决定。标准的配置是晶振 11.0592MHz,这是串口通信的首选晶振频率,因为 9600 波特率下它的分频系数是整数,不会产生累积误差。
| 配置项 | 寄存器值 | 说明 |
|---|---|---|
| TMOD | 0x20 | 定时器 1 工作在模式 2(8 位自动重装) |
| TH1 | 0xFD | 波特率 9600 的重装值 |
| TL1 | 0xFD | 与 TH1 相同,装入后即启动 |
| SCON | 0x50 | 串口模式 1,允许接收 |
| PCON | 0x00 | SMOD=0,波特率不倍增 |
如果使用 12MHz 晶振,9600 波特率的误差会上升到约 2.1%,在短帧通信时勉强可用,但环境温度变化大的工况下会出现偶发乱码。所以课程设计时尽量选 11.0592MHz 晶振。PCON 寄存器里 SMOD 位为 0,代表波特率是定时器溢出率的 64 分频;如果 PCON 置为 0x80,SMOD=1,溢出率变成 32 分频,相同重装值下波特率翻倍变成 19200,但此时 TH1 要改成 0xFA 才能维持 9600。初学者最常犯的错误是在串口助手配置了 9600,但单片机实际按 19200 发出,导致上位机收到全部乱码。
3. LabVIEW 上位机:串口读取、帧解析与温度显示
LabVIEW 做上位机的优势在于不需要记忆复杂的文本 API,串口 VISA 节点拖出来配置好参数就能收发。但图形化编程容易让人忽略数据流方向,尤其是字符串转数值这一步,字节顺序错了温度就会变成离谱的大数。
3.1 用 VISA 配置串口建立通信基础
打开 LabVIEW 后新建一个空白 VI,在前面板放置一个“VISA 资源名称”下拉列表控件、一个“串口配置”函数、一个“VISA 读取”函数和一个“VISA 关闭”函数。串口配置是 VISA 函数面板里最重要的节点,需要设置以下参数:
| 参数 | 推荐值 | 原因 |
|---|---|---|
| 波特率 | 9600 | 与单片机 TH1=0xFD 匹配 |
| 数据位 | 8 | 单片机 UART 模式 1 为 8 位数据 |
| 校验位 | None | 帧内已有 CRC16 校验 |
| 停止位 | 1.0 | 标准 Modbus RTU 设置 |
| 流控 | None | 51 单片机无 CTS/RTS 硬件握手 |
这里有一个经常被忽略的细节:VISA 配置串口的“启用终止符”默认是 TRUE,终止符是 0xA(换行符)。单片机发的原始帧是 7 字节无换行的二进制数据,如果开了终止符,LabVIEW 会死等换行符到来才停止 VISA 读取,程序会一直卡在读取节点。所以在程序框图中必须把“启用终止符”取消勾选,或者把终止符改成 0xAA 这样永远不会单独出现的值。串口超时时间设置 2000ms 即可,温度监测系统是周期性请求,不需要长超时。
3.2 从串口字节流中解析温度的完整逻辑
LabVIEW 程序的循环结构采用消费者模式:一个 While 循环负责定时发送请求帧,另一个循环负责 VISA 读取并解析。发送请求帧用“VISA 写入”节点,数据源是一个字符串常量,内容为十六进制字节AA 01 04 00 00加 CRC 的低高字节。单片机收到这个请求后才会回传温度帧。
收到数据后,解析流程分三步。第一步,用“字符串长度”函数检查 VISA 读取返回的字节数是否为 7,不是 7 就丢弃并置错误标志。第二步,用“字符串截取”函数分别截出第 4 字节和第 5 字节作为温度的高 8 位和低 8 位。第三步,将两个字节按高字节左移 8 位加上低字节的方式拼接,再转换为数值显示。
具体的字节拼接方式可以用 LabVIEW 的“类型转换”函数:先创建一个 U16 类型的数值常量,把高字节用“数值-强制类型转换”变成 U8,再与低字节组合。更简单的做法是使用“平化字符串转数值”函数:把温度两个字节的字符串按高字节在前、低字节在后的顺序拼接,再强制转换为 U16,结果就是真正的温度值。
需要注意温度是带符号的。TC74 在负温度时最高位为 1,比如 -5℃ 对应的字节是 0xFB。直接用 U16 转换会得到 251,需要在显示控件里配置为 I16 类型,或者在 LabVIEW 里先转成 U16 再判断是否大于 127,大于则减 256。这个符号扩展是温度解析最隐蔽的坑。
3.3 温度曲线显示与报警阈值的界面搭建
前面板放一个波形图表控件和一个布尔报警灯。波形图表接收的温度数据以数组形式进入,图表的历史长度设为 600 个点,代表最近 10 分钟每分钟一个采样点的曲线。报警逻辑用“大于”比较函数,把解析出的温度与阈值常量 50.0 比较,结果送入指示灯。
数据记录可以先用“写入带分隔符电子表格”函数,把时间戳和温度组成一维数组写入 CSV。这个函数可以设置追加模式不变写模式,这样每次循环写入一行而不是覆盖已有文件。需要注意的是,“写入带分隔符电子表格”内部会自动增加一个换行符,所以前面板的时间显示控件用“格式化日期时间字符串”节点生成%Y-%m-%d %H:%M:%S格式的文本,再与温度数字拼成两列。
4. 联调排错:乱码、CRC错误与 LabVIEW 读取超时
单片机程序和 LabVIEW 程序单独跑都看不出问题,联调时才会暴露接口层面的故障。这个环节的排查效率,决定了整个温度监测系统能不能在半天内交付。
4.1 先用串口调试助手独立验证单片机帧输出
不要着急打开 LabVIEW,先用纯串口调试助手做硬件侧检查。插好 USB 转 TTL 模块,TX 接单片机 RX,RX 接单片机 TX,GND 共地,注意 TTL 电平不能直接接 RS232 口的 DB9 接口。串口调试助手发送区填入AA 01 04 00 00加 CRC 低字节加 CRC 高字节,波特率选 9600,数据位 8,停止位 1,无校验。点击发送后,接收区应该能收到AA 01 04 [温度高字节] [温度低字节] [CRC低] [CRC高]。
如果没有任何返回,首先检查单片机程序里是否开启了总中断 EA 和串口中断 ES。STC89C52 默认复位后串口是关闭的,必须把EA=1; ES=1;放在串口初始化之后。其次检查 MAX232 还是 USB 转 TTL,如果用了 MAX232 但接入的是 USB 转 TTL,电平不匹配会导致收不到任何数据。
4.2 CRC 校验失败和偶发乱码的六大根因
联调中最难排查的问题是帧结构看起来对、温度数值也偶尔正确,但累计几十帧后就报错。下面按经验概率排序,先查通信参数,再查硬件时序。
| 症状 | 最可能原因 | 解决方案 |
|---|---|---|
| 连续乱码,无完整帧 | 波特率不匹配,单片机实际波特率是 19200 | 检查 PCON 寄存器 SMOD 位 |
| 帧头时有时无 | 总线竞争,调试助手和 LabVIEW 同时占用串口 | 关闭串口调试助手,同一时间只允许一个程序打开端口 |
| 温度字节全为 0xFF | I2C 读地址发错,或 SDA 无上拉电阻 | 确认发送 0x91 读地址,上拉电阻 4.7k |
| CRC 偶尔错误 | 晶振频偏,尤其是未知来源的开发板 | 换 11.0592MHz 晶振,或改波特率为 4800 |
| 数据间隔过大 | VISA 读取超时设置太长 | 关闭终止符识别,超时设为 1000ms |
| 负温度显示成 251 | 符号位未扩展 | 数值控件改为 I16 类型并做负值折算 |
还有一个隐藏问题:TC74 的 SCL 上拉电阻依赖 I2C 总线的规范值,部分开发板把 P1.0 和 P1.1 已经接上了 LED 和排阻,可能改变了上拉等效电阻。处理办法是在程序里把读取温度到发送帧之间的延时拉长到 50ms,给 I2C 总线足够的电平稳定时间。
4.3 LabVIEW 运行时的 VISA 资源名称与驱动问题
LabVIEW 代码如果报“VISA 资源名称无效”,多半不是代码问题,而是 NI-VISA 驱动没装或版本不匹配。装上 LabVIEW 2018 后还要单独装 NI-VISA Runtime,这个运行库负责操作系统串口与 VISA 函数之间的桥接。安装完驱动后,在“设备管理器”里能看到串口号,比如 COM3,LabVIEW 的 VISA 资源名称下拉列表里选择 COM3 即可。
51 单片机板载的 USB 转串口芯片如果是 CH340,Windows 10 以上系统会自动识别;如果是老式的 PL2303,建议安装厂商专用驱动,但注意旧版 PL2303 芯片在 Win10 下会被系统禁用,这时候需要换一根 CH340 的下载线或转接模块。LabVIEW 界面上如果发生运行时错误 56(VISA 资源已关闭),原因是 While 循环里 VISA 关闭节点被误放进循环内,每次循环都会关闭端口。正确做法是 VISA 配置串口和 VISA 关闭节点都在主循环外,循环内只保留 VISA 写入和 VISA 读取。
5. 进阶:把温度监测系统改造成多通道连续记录平台
单一温度点监测跑通后,下一步可以是多通道扩展,或者优化数据存储方式。这里给出几个容易落地且提升明显的改造点。
5.1 用 TDMS 文件替代 CSV 提升写入速度
LabVIEW 的“写入带分隔符电子表格”每次写入都打开关闭一次文件,采样频率超过 10Hz 时文件句柄反复开关会占用大量 CPU。TDMS 是 NI 专有的二进制数据格式,写入效率比文本高一个数量级。程序框图中的改动很小:从“文件 I/O”面板拖出“TDMS 写入”函数,在进入循环前用“TDMS 打开”函数创建文件并设置属性“采样率=1Hz”,循环内把温度数组送入 TDMS 写入,循环结束后用“TDMS 关闭”函数收尾。TDMS 文件可以用 Excel 或 NI Diadem 打开,对后续数据处理也友好。
5.2 用状态机实现断线自动重连
实际运行中用户可能不小心拔掉 USB 转串口线,LabVIEW 程序会一直停在 VISA 读取节点上报超时错误。用一个生产者-消费者状态机可以解决:主状态循环里设置“连接失败”状态,检测到 VISA 读取错误后进入“等待重连”分支,先调用 VISA 关闭节点释放资源,再延时 3 秒重新执行 VISA 配置串口。重连后要注意清空旧的串口缓冲区,否则开机残留的半帧数据会破坏第一帧解析。
5.3 软件时间戳与传感器数据对齐
LabVIEW 程序里用“获取日期时间”函数捕获接收到完整帧的时刻,但串口传输本身有延迟,LabVIEW 收到帧尾的瞬间比传感器采样的实际时刻晚了大约 1 个采样周期。如果监测系统要用于过程分析,可以在单片机发送帧里附带一个 16 位毫秒计数器作为时间戳,LabVIEW 解析温度的同时解出该计数器值,并用差值换算成实际采样时刻。这样做的好处是,即使不同帧在串口队列里延迟不一致,上位机记录的时间轴依然能反映真实采集顺序。
以上改进都围绕数据可靠性和采集连续性展开,在不更换核心硬件的前提下,已经能把课程设计级别的温度监测系统拉到工程样机的水平。实际运行时,把 LabVIEW 的“程序框图”界面最小化,在 Windows 任务计划里设置开机启动该 VI,就能作为无人值守的上位机监测终端运行。
本文还有配套的精品资源,点击获取