☰
51单片机+DS18B20+LabVIEW温度采集与上位机显示全攻略
2026/9/30 6:00:37 网站建设 项目流程

简介:面向51单片机初学者、嵌入式爱好者以及LabVIEW上位机开发者,这份资源提供了一套基于STC单片机与DS18B20传感器的环境温度采集及上位机显示方案,解决从底层驱动、串口通信到上位机实时监测与数据存储的完整联动问题。压缩包内共2个文件,分别为LabVIEW程序(.vi)与C语言单片机代码(.c),压缩后仅27KB;前者实现上位机串口指令下发、数据接收解析、实时显示以及按日期时刻保存温度记录,后者负责DS18B20温度读取、数据组帧与串口回传逻辑。项目在作者原有串口显示功能基础上进一步修改完善,并经过实验验证,可直接用于环境温度监测、实验室数据记录等场景,也可作为51单片机与LabVIEW联调的可运行教学案例。已有370人浏览学习,适合希望通过完整工程代码快速掌握DS18B20驱动、串口协议解析及上位机数据可视化保存的读者。

1. 这个温度采集项目为什么值得动手做一遍

把51单片机、DS18B20和LabVIEW串在一条链路上,是很多工程师入门单片机与上位机协作的第一课,也是课程设计里出现频率最高的组合之一。原因很简单:DS18B20用一根数据线就能把温度读出来,省掉了运放和ADC的模拟链路;51单片机用串口把温度值发出去,不涉及USB协议栈;LabVIEW那边用VISA控件收串口数据,几十分钟就能搭出一个带波形和仪表盘的界面。整条链路每一环都是可观测、可调试的,出了问题能明确地定位到时序、波特率还是帧格式。

这个项目适合两类人:一类是刚写完LED流水灯、想接触真实传感器时序的初学者,另一类是后续要做BMS上位机、设备监控面板的工程师——串口协议怎么定、数据怎么解析、界面怎么和硬件联动,这套经验是通用的。下文按我平时调试这类项目的顺序走:先把DS18B20的温度读出来,再定串口帧格式,最后在上位机里完成显示。三步各有各的坑,但都是能一次踩完的。

2. DS18B20单总线时序:先把温度读出来再说

2.1 单总线协议的关键约束

DS18B20是Dallas(现在归Maxim)的单总线数字温度传感器,测温范围-55°C到+125°C,默认12位分辨率时精度为正负0.5°C。它只有三根引脚:VDD、GND、DQ。数据线既当电源又当信号线,所以必须在DQ上加一个4.7kΩ上拉电阻到VCC。如果省掉这个电阻,你会看到返回值固定在某个值不动,或者直接读出0xFFFF。

单总线的通信由主机发起,DS18B20永远做从机。读温度的标准步骤是:主机发复位脉冲,收到DS18B20的存在脉冲,然后发跳过ROM命令0xCC,再发温度转换命令0x44,等待转换完成后再次复位,跳过ROM后发读暂存器命令0xBE,最后连续读两个字节得到温度值。整个过程里,每一位的读写都依赖精确的延时控制,这也是51单片机在这个项目里比STM32更能练手的原因——没有硬件单总线外设,所有时隙全靠软件模拟。

2.2 用STC89C52写一版可用的初始化与读写时序

一般课程设计用STC89C52或者AT89C52,晶振选11.0592MHz。这个晶振频率直接关系到后面串口波特率能否取整,也能让延时函数时长足够精确。先看初始化代码:

sbit DQ = P1^0; // 延时函数:约15us void delay_15us(void) { unsigned char i; _nop_(); _nop_(); i = 9; while (--i); } // DS18B20初始化,存在则返回1 unsigned char ds18b20_reset(void) { unsigned char presence; DQ = 0; delay_480us(); // 拉低至少480us DQ = 1; // 释放总线 delay_60us(); // 等60-240us内的存在脉冲 presence = DQ; // 0表示设备存在,1表示无设备 delay_400us(); // 完整时序收尾 return (presence == 0) ? 1 : 0; }

这段代码的关键在于:拉低时间和释放后读取采样点的窗口。数据手册要求复位脉冲480-960us,释放总线后主机在60-240us之间采样电平。STC89C52在11.0592MHz下,一个机器周期约1.085us,所以延时函数必须精确匹配。实际调整时,如果存在脉冲读不到,优先检查的是延时常数,而不是硬件连接。

读和写一位的时序是单总线最容易出错的地方。写时隙里,主机拉低总线,延时15us后释放,DS18B20会在这段时间内采样总线电平;如果拉低后保持60us以上,会被识别为写0,如果15us内释放,会被识别为写1。读时隙则由主机拉低至少1us后释放,然后在15us内读取总线电平。

// 写一个字节,低位在前 void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = dat & 0x01; // 先写低位 delay_15us(); DQ = 1; dat >>= 1; } delay_15us(); } // 读一个字节 unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DQ = 0; _nop_(); // 至少1us DQ = 1; _nop_(); if (DQ) dat |= 0x80; // 采样总线电平 delay_60us(); } return dat; }

这两个函数的核心差异在释放总线后的行为:写时隙里主机主动控制电平,读时隙里主机释放总线让DS18B20驱动电平。很多人在读时序里也写了DQ=1之后立刻去读,但实际要加上短暂延时,让从机有足够时间把电平拉起来。这个延时的粒度在微秒级,调试时最直接的手段是逻辑分析仪抓波形,没有逻辑分析仪就串口打印原始字节观察是否跳变。

2.3 温度转换与暂存器解析

温度转换命令0x44发出后,如果是12位分辨率,转换时间最长750ms。转换完成后温度值存在暂存器的第0和第1字节:第0字节是低8位,第1字节是高8位,其中高字节的高5位是符号扩展位。正温度时高字节的高5位为0,负温度时全为1。温度值和ADC码对应关系是:12位分辨率下,1个LSB等于0.0625°C。

// 读取温度,返回值单位0.01°C int read_temperature(void) { unsigned char low, high; int temp_raw; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0x44); // 启动温度转换 delay_750ms(); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 low = ds18b20_read_byte(); high = ds18b20_read_byte(); temp_raw = (high << 8) | low; if (temp_raw & 0x8000) { temp_raw = ~temp_raw + 1; // 负温度转正数再计算 return -((temp_raw * 625) / 10); } else { return (temp_raw * 625) / 10; // 乘以0.0625的整数形式 } }

注意这里负数求补之后再做乘法,避免直接对负整数做除法造成四舍五入误差。返回值用0.01°C为单位,上位机解析时只要除以100即可得到带两位小数的温度。如果项目只需要一位小数,可以把625改成62再除以10,但建议保留两位,方便查看温度波动。

3. 串口帧协议:让LabVIEW拿到不丢字节的温度数据

3.1 为什么不能直接发温度值

单片机上获取到温度之后,下一件事就是通过串口发给上位机。最常见的错误是把浮点数直接拆成四个字节发出去,或者把整数转成字符串后不加分隔符。前者的问题是LabVIEW端解析时不知道哪里是起点,后者的问题是粘包和断包会令解析逻辑混乱。

正确做法是自定义一个帧协议。以我常用的帧格式为例:帧头0xAA,然后是温度值的高字节和低字节,接着是校验字节,最后是帧尾0x55。使用整数传输,温度值已经由单片机端乘以100,所以一个int类型正好放入两个字节。这样设计的好处是:校验简单、帧长固定、解析端无需缓存大量数据。

3.2 C51串口初始化与发送实现

void uart_init(void) { SCON = 0x50; // 模式1,8位UART,允许接收 TMOD |= 0x20; // 定时器1工作于模式2,8位自动重装 TH1 = 0xFD; // 11.0592MHz下波特率9600 TL1 = 0xFD; TR1 = 1; ES = 1; // 使能串口中断 EA = 1; }

9600波特率下,11.0592MHz的晶振让TH1取0xFD刚好没有误差。如果你用12MHz晶振做9600波特率,误差接近8%,长时间通信会偶发乱码——这正是很多人在Proteus仿真里正常、拿到实物板子上就乱码的原因。主频对应不上就直接换晶振,不要靠调上位机兜底。

发送一帧数据的逻辑:

void send_temperature_frame(int temp_x100) { unsigned char buf[5]; unsigned char checksum; buf[0] = 0xAA; // 帧头 buf[1] = (unsigned char)(temp_x100 >> 8); buf[2] = (unsigned char)(temp_x100 & 0xFF); checksum = buf[1] ^ buf[2]; // XOR校验 buf[3] = checksum; buf[4] = 0x55; // 帧尾 for (unsigned char i = 0; i < 5; i++) { SBUF = buf[i]; while (!TI); // 等待发送完成 TI = 0; } }

这里用异或做校验而不做累加和,是因为异或计算简单且在8位机上执行速度快。校验的目的不是防篡改,而是防串口干扰或时序错误导致的偶发跳变。LabVIEW端做同样的异或就能判断这一帧是否有效。注意循环里每发一个字节都要等TI标志位置位,毫秒级延时不要加在发送循环内部,否则会影响下一次温度采集的时间点。

3.3 发送周期与主循环的配合

温度转换需要750ms,建议把发送周期定在1秒。主循环结构大概是:读温度,组帧,发送,然后延时到剩余时间。如果你用delay函数做1秒延时,单片机会在转换还没完成时就去读暂存器,读到的永远是上次的值。一个简单的处理方式:

void main(void) { int temp; uart_init(); ds18b20_reset(); while (1) { temp = read_temperature(); // 该函数内部已含750ms延时 send_temperature_frame(temp); delay_100ms(); // 凑齐约1s周期 } }

注意read_temperature函数里的delay_750ms是转换时间,不能省。而延时时间偏长会导致上位机波形图横向分辨率显得迟钝。如果希望刷新率更高,可以修改DS18B20配置寄存器把分辨率改为10位,转换时间降到约187ms,代价是LSB变成0.25°C。对LabVIEW显示来说,10位分辨率配合每秒5帧的刷新其实够用,实时性更好。

4. LabVIEW上位机:VISA串口做温度波形与仪表盘

4.1 串口参数与帧解析的思路

LabVIEW里做串口通信,靠的是VISA函数选板。核心函数是VISA配置串口、VISA写入和VISA读取。在动手拖界面之前,先明确参数要和单片机完全一致:波特率9600、8位数据、1位停止位、无校验、无流控。VISA配置串口对话框里的“启用终止符”选项不要勾选,因为我们不用换行符做结束标志,而是按固定帧长读取。

读取与解析的流程如下:

  1. 用VISA读取函数请求读取5个字节
  2. 用“字符串至字节数组转换”把串口读取的字符串转成对应字节
  3. 判断第0个字节是否为0xAA,同时判断第4个字节是否为0x55
  4. 做一次异或校验:buf[1] ^ buf[2] == buf[3]
  5. 通过第1和第2字节还原int16温度原始值
  6. 除以100,送入波形图或仪表控件。

这个帧格式最简单的情况是每次读取都恰好返回完整一帧。但是串口读取返回值可能短于一帧,也可能跨帧粘连。稳妥做法是在前面板的循环里维护一个输入缓冲区,每次收到字节就追加入缓冲区,然后检查缓冲区长度是否达到5字节,是则按上述步骤解析并在解析后清空缓冲区前5字节。

4.2 一个可复用的生产者消费者结构

LabVIEW项目里,一般用While循环和事件结构做主界面,串口读取放在独立循环里,通过队列传到显示循环。下面给出不用队列的简化版实现流程,适合课程设计级别的需求,但结构上仍然可扩展:

  • 步骤1:放置VISA配置串口节点,将VISA资源名称控件指到COM口,波特率设置为9600。
  • 步骤2:进入While循环,循环内放VISA读取节点,输入字节数设置为5,超时设置为1000ms。
  • 步骤3:读取结果经过“字符串至字节数组”转换后,用索引数组取出各字节。
  • 步骤4:帧头帧尾判断用两个“等于”比较后做“与”操作,校验用“异或”节点求buf[1] xor buf[2],再和buf[3]比较。
  • 步骤5:通过条件结构处理校验成功分支:对字节数组的高位字节乘以256后加低位字节,用“除100”得到浮点温度值;如果原始int值大于32767则先做减65536处理,用于负温度。
  • 步骤6:温度值送波形图图表和仪表控件,显示单位设置为“°C”。

串口波特率设置的“VISA配置串口”对话框里,数据位和停止位默认值分别为8和1。要注意的是,“停止位”如果选成“1.5”在Windows的USB转串口驱动上会报错,保持默认即可。

4.3 负温度处理与VISA资源释放

温控场景里真正的查询要能显示负温度,不要在LabVIEW端用I8来表示温度字节。温度范围-55到+125°C,乘100后是-5500到+12500,对Int16来说范围足够,但解析时要做无符号到有符号的转换。例如读到的低位字节0x00、高位字节0xFE,则原始值是0xFE00即65024,减去65536得到-512,除以100即为-5.12°C。LabVIEW里可以用“带符号数转换”节点把U16转换为I16,然后再除以100。这步很多人在正温度时测不出来问题,一旦把探头放进冰水里就会显示一个大正数。

程序退出时,必须在循环外关闭VISA串口会话,否则下次打开VI会报“VISA资源被占用”。这也是LabVIEW安装后第一次跑串口程序时最常遇到的错误之一。“VISA关闭”节点通常放在While循环外,并且通过错误簇连线保证前面所有读取操作都结束后再执行关闭。

5. 温度不准和上位机读不到数的定位思路

5.1 优先检查的三个特征现象

先看一个现象表,便于快速定位问题方向:

现象可能原因排查方法
温度值固定为85°C上一次上电未完成转换,读到了暂存器的上电复位值复位后先发转换命令,延时750ms再读
温度值总是-0.5或正负0.5跳动分辨率设置不被识别,默认使用9位模式读配置寄存器0x88,确认高5位配置
串口助手收到乱码但温度值有变化波特率误差,晶振频率不是11.0592MHz换晶振,或者把波特率降到4800验证
LabVIEW读数偶发缺字节读取函数一次返回不足5字节使用移位寄存器缓存数据,不要丢弃不足帧
手摸探头温度不变化DS18B20与单片机共地不良或上拉电阻缺失用万用表量DQ引脚空闲电平和VCC-GND电压

“85°C”这个值是DS18B20上电后的暂存器默认值,也是最容易误判的情况。很多初学者以为传感器测出来环境温度就是85°C,其实只是没有执行0x44转换命令。代码流程上,第一次调用read_temperature之后就正常了,所以最稳妥的方法是在上电初始化时连续执行两次read_temperature,丢弃第一次结果。

5.2 单总线延时的硬件调整技巧

在代码已经确认无误的情况下,时序仍然可能因为STC单片机的内部时钟误差而偏移。STC89C52默认使用内部RC振荡器,实际频率可能偏了1%-2%,这在串口通信中勉强可用,但在单总线微秒级时序里就可能导致存在脉冲刚好落在采样窗口外。解决方法是把STC89C52配置为使用外部晶振,烧录时在STC-ISP软件里选择“使用外部晶振”选项。如果板子上的晶振和负载电容焊接正常,DS18B20的时序余量会明显改善。

另一个容易踩的坑是:在Keil里开了优化等级为9,延时函数的循环体被编译器改写。Keil C51的优化器可能把连续赋值DQ=0;DQ=1的代码合并掉,导致写时序变得过短。解决方案是把DS18B20的读写函数加#pragma optimize=none修饰,或者把关键变量声明为volatile。特别是DQ引脚对应的sbit变量虽然已经是特殊功能寄存器位,理论上不会被优化掉,但延时函数里的循环变量通常会被调整循环方式,微秒级变化还是会有的。

5.3 LabVIEW串口读取乱码的特殊排查路径

LabVIEW的VISA读取乱码还有一个常见来源:VISA读取函数的“字节数”端口如果设置为VISA属性节点动态获取“队列中的字节数”,那么当数据从设备到达和VISA查询之间有延迟时,读取到的字节数会偏少。使用固定5字节读取,设置超时100ms以上,配合按帧解析逻辑是最稳的做法。

如果使用的是LabVIEW 2018及以后版本,首次打开串口VI时可能遇到缺少VISA驱动的错误。安装NI-VISA时注意选择与你LabVIEW版本匹配的驱动版本,并在安装路径里确认是否把驱动装到了LabVIEW的vi.lib外部目录。检查方式是在LabVIEW里打开“工具-测量-仪器-NI-VISA”,如果菜单缺失则说明驱动没有安装成功。

5.4 不用示波器也能验证时序的土办法

没有逻辑分析仪时,可以用一个辅助GPIO引脚来反映DS18B20的复位时序,输出到另一块板子的串口或者直接接LED。在ds18b20_reset函数里,将P1^1引脚在拉低DQ前置高电平,在读存在脉冲之后置为低电平,LED亮灭就能反映出复位操作是否被执行。这个方法的精度不高,但能区分“代码没跑到这里”和“时序参数不对”。

另一个验证整条链路的方法是用串口直接发送0xAA 0x00 0x1C 0x1C 0x55这一帧固定数据,用串口助手观察下位机是否在循环发送。如果串口助手里能看到稳定帧,再用LabVIEW连接,就能定位问题在上位机还是单片机发送环节。把链路切成两段分别验证,是这个项目排错的核心思路。

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

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

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

立即咨询