基于DS18B20和LabVIEW的多点温度测量系统

2023-08-24 11:27:48 |人围观 | 评论:

再有5天就是“黄金”假期~放七天假,上七天班(学)!不管怎么样,该做什么就做什么。学习便学习,工作便工作,走路便走路,吃饭便吃饭。


今天我们一起完成一个比较完整的作品,基于DS18B20和LabVIEW的多点温度测量系统。我重点介绍实现多点DS18B20温度驱动模块的思路,具体实现大家可以阅读源码。驱动源码参考了不少资料,在此感谢那些乐于分享的程序员。分享,传递,沉淀,这一直都是我们坚持的信念。

关于DS18B20的特性、工作原理、时序等,请参考相关资料:

  • DS18B20官方手册:https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf

  • DS18B20的复位(初始化)、读时序、写时序:https://blog.csdn.net/qq_17017545/article/details/82120467

  • DS18B20多点测温方案(多个DS18B20挂在一根总线上):https://blog.csdn.net/redeemer_Qi/article/details/108854687

一、多点温度测量系统架构

多点温度测量系统框图如图1所示。编号为1#~8#的DS18B20连接到8051单片机的P0口,每个DS18B20占P0口的一个I/O,1#对应P0.1,2#对应P0.1, ......, 8#对应P0.7。8051单片机周期读取多点温度,通过串口上报到LabVIEW上位机

362ba390-3c7b-11ed-9e49-dac502259ad0.png

图1 多点温度测量系统框图

在我们的例子中,只实现了3点温度。由于我们采用了模块化编程,要扩展到8路只需改动2个地方(猜猜是哪里)。图2给出了仿真电路图。我们在串口仿真电路图上增加了3个DS18B20,分别接P0.0、P0.1和P0.2。

36516b20-3c7b-11ed-9e49-dac502259ad0.png

图2 多点测温仿真电路图

二、DB18B20多点温度驱动模块设计思路

网上有很多单个DS18B20温度驱动程序源码,可惜的是这些源码无法直接使用,因为源码里DS18B20初始化函数、读温度函数、写DS18B20函数等代码绑定到了固定的I/O引脚(如P1.0),读和写都是基于单个I/O实现。以至于代码无法复用。

https://blog.csdn.net/redeemer_Qi/article/details/108854687提供了多个DS18B20挂在单一总线的多点测温方案,大家可以去研究研究。我们今天使用另外的思路。

思路来源:Arduino里的I/O读写函数( digitalRead,digitalWrite)是通过指定pin序号来实现数字引脚的读写操作的。在分析这两个函数的原型时,发现它们是通过PORT和BIT_MASK来对整个PORT的寄存器操作实现的。举例来说,我们要写1到P0.0,则对P0寄存器进行以下操作:

P0 = P0|0x01; //或写成:P0|=0x01;

某位或1,就能置该位为1。

对P0.0写0,则是:

P0 = P0&(~0x01); //或写为 P0 &= ~0x01;

某位与0,则该位清零。某位与1,该位保持不变。

P0是PORT, 0x01是P0.0在P0寄存器的BIT MASK。表1给出了P0.0~P0.7的位掩码(BIT MASK)。

I/O

位掩码(二进制)

位掩码(十六进制)

P0.0

0000_0001b

0x01

P0.1

0000_0010b

0x02

P0.2

0000_0100b

0x04

P0.3

0000_1000b

0x08

P0.4

0001_0000b

0x10

P0.5

0010_0000b

0x20

P0.6

0100_0000b

0x40

P0.7

1000_0000b

0x80

练习:使用位掩码对P0.5操作,写1和清零。

//你的答案

前面解决了写I/O。哪如何实现读取某个IO的状态呢?使用位掩码~正确。

uchar value = PORT & BIT_MASK; //非零表示输入高电平,全零表示输入低电平。if(value): //高电平do somethingelse: //低电平do something

例如 if(P0 & 0x04)就能读取到P0.2的输入状态。请分析为什么?

我们设计了ds18b20.h,在该头文件里定义了PORT、BIT_MASK和相关的驱动函数(DS18B20初始化、读字节、写字节、读温度)。下面简要概述ds18b20.h。

1、PORT和BIT_MASK

使用宏定义了PORT和BIT_MASK。

//三个DS18B20,分别接到P0.1, P0.2, P0.3//P0口最多连接8个DS18B20#define DS18B20_PORT P0#define ds18b20_1_mask 0x01 //sensorno. 1#define ds10b20_2_mask 0x02 //sensor no. 2#define ds10b20_3_mask 0x04 //sensor no. 3#define ds10b20_4_mask 0x08 //sensor no. 4#define ds10b20_5_mask 0x10 //sensor no. 5

ds18b20_get_mask( ) 函数实现了传感器编号到BIT_MASK的映射。例如, 1的dq引脚接到Px.0, 掩码为0x01。

// 由序号获得ds18b20的引脚mask// no: 1,2,3uchar ds18b20_get_mask(uchar no){uchar pin_mask;switch(no){case 1: {pin_mask = ds18b20_1_mask; break;}case 2: {pin_mask = ds10b20_2_mask; break;}case 3: {pin_mask = ds10b20_3_mask; break;}default: break;}return pin_mask;}

2、重要驱动函数

(1)DS18B20初始化函数

//初始化ds18b20uchar ds18b20_init(uchar sensor_no){uchar pin_mask;ucharack;pin_mask= ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1delay_10xus(1); //延时10usDS18B20_PORT &= ~pin_mask; //清零delay_10xus(90);//拉低900usDS18B20_PORT |= pin_mask; //置1delay_10xus(8); //80us后读ds18b20的响应ack = DS18B20_PORT & pin_mask; //读引脚delay_10xus(50);return ack;}

初始化函数供读操作、写操作前调用。也可以单独调用来判断DS18B20是否存在。ACK为0表示传感器应答,ACK为1表示传感器未应答(多次未应答可视为传感器不存在或损坏)。

(2)读温度驱动函数

// 读温度函数,返回浮点类型温度float ds18b20_read_temperature(uchar sensor_no){uchar low_byte = 0;uchar hight_byte = 0;int temp = 0;float temperature = 0;if(ds18b20_init(sensor_no) == 0) //温度传感器应答了{is_ds10b20_exist = 1;ds18b20_start_convert(sensor_no); //开始转换ds18b20_start_read(sensor_no); //开始读取low_byte = ds18b20_read_byte(sensor_no); //读温度的低八位hight_byte = ds18b20_read_byte(sensor_no); //读温度的高八位temp = (hight_byte<<8)|low_byte;}else{is_ds10b20_exist = 0;}if((temp & 0xF800) == 0xF800) //负温度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}else{temperature = temp * 0.0625; //12位的温度分辨率为0.0625℃}return temperature;}

读温度驱动函数主要完成以下操作:

① 调用ds18b20_init()判断DS18B20是否存在。

② 存在,使用ds18b20_start_convert()函数让DS18B20进行温度转换;等待一定时间后,读温度字节,并把温度字节转换为float数据,再返回。

③ 不存在,置 is_ds10b20_exist为0。

关于读温度函数,有几点要说明:

① 温度字节可以认为是A/D的数字量输出,其量化单位q就是温度分辨率。12位的是0.0625℃。

DS18B20默认是12位分辨率,可软件配置为9、10、11、12位,分辨率分别为0.5、0.25、0.125、0.0625℃。

② 温度MSB字节的高5位是符号位, 11111表示是负的温度,以补码储存。所以先取反+1得到绝对值,再乘以分辨率,最后变成负数。代码如下:

if((temp & 0xF800) == 0xF800) //负温度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}

③ LOW_BYTE和HIGH_BYTE对应于图3中的LSB BYTE, MSB BYTE。注意,DS18B20先传输LSB字节,且是最低位先传输(LSb First)。

36668aaa-3c7b-11ed-9e49-dac502259ad0.png

图3 温度数据

④ 温度读取函数有瑕疵。温度转换的代码不管传感器存在是否,都会进行。当传感器不存在时,始终返回0,埋了一个大坑~~试一试,改进代码。

(提示:不存在返回250,超量程了就是设备不存在)。在主程序再做处理。

⑤ is_ds10b20_exist 原来是针对单个DS18B20测温设计的。此处实在是鸡肋。你能把它用起来吗?提示:结合第④点。

3、(串口)数据传输协议

我们直接使用C51编程入门(二十三)串口编程入门--串口应用协议(二)里设计的协议。每个传感器上报的数据包括1字节的设备号、4字节的温度(float)。三个传感器的数据一起“打包”上报,如下。

1#设备号(1B)

1#温度(4B)

2#设备号(1B)

2#温度(4B)

3#设备号(1B)

3#温度(4B)

主函数如下。主函数所在的.c源码除了增加#include"ds18b20.h"并另存为新文件名外,其它内容与C51编程入门(二十三)串口编程入门--串口应用协议(二)的一模一样,未作任何修改~(难能可贵..)

void main(){unsigned char ds18b20_no = 1; //设备号unsigned char ds18b20_N = 3; //ds18b20总数float temperature; //温度uart_init();while(1){temperature = ds18b20_readTemperature(ds18b20_no); //读温度sendTemperature(ds18b20_no, temperature); //发送温度ds18b20_no++;if(ds18b20_no > ds18b20_N)//已经读完所有点的温度{ds18b20_no = 1;}delayMS(1000); //等待1s左右}}

三、LabVIEW上位机程序改进

1、添加温度保存子VI(saveTemperature.vi),如图4所示。实现将三个温度和当前时间戳存储到一个表格。

369154ce-3c7b-11ed-9e49-dac502259ad0.png

图4 saveTemperature.vi程序框图

程序说明如下:

① 创建文件路径,使用了应用程序目录,实现将程序存储到程序目录下。目标文件由文件名和当前日期(年月日)组成。这样实现一天一个文件。

.xls扩展名指定文件为表格。

② 打开/创建文件,并设置文件指针到文件末尾,即从文件末尾新增数据。这样,就不会覆盖旧数据。

③ 调用格式化写入文件,巧妙地通过格式化将数据写到表格里。格式化字符为:

%.1f %.1f %.1f %s

三个%.1f对应三个温度值,存为1位小数的浮点数据。是制表符,移动到下一个表格单元。%s为字符串,这里对应着时间戳字符串。

是换行,保证下一次数据存储到表格末尾的新的一行。

2、串口解释单个传感器数据的子VI(getReceiveData.vi)

程序框图如图5所示。说明如下:

① 先读取1个字节数据,并调用强制类型转换函数转换为U8数据。此为设备号,1个字节。

② 再读取4个字节数据,并调用强制类型转换函数转换为SGL数据。此为温度数据,4个字节。注意,不能转换为DBL数据,因为LabVIEW的DBL为64位,8个字节,类型不匹配。

36baaf18-3c7b-11ed-9e49-dac502259ad0.png

图5 getReceiveData.vi程序框图

下图为LabVIEW主程序框图。需要注意的是,初始化串口时,禁用串口的启用停止符选项(F常量连接的选项)。

36e0ef20-3c7b-11ed-9e49-dac502259ad0.png

图6 主程序框图

三、运行结果

LabVIEW上位机运行后,立马收到了很多数据(这些都是缓冲在电脑串口缓存里)。如果想要丢弃掉,可以在进入while循环前清空串口缓冲区

使用ds18b20.h时,应注意设置(修改):

1. DS18B20_PORT宏定义,改为实际使用的PORT(P0、P1、P2、P3)

36efa204-3c7b-11ed-9e49-dac502259ad0.png

2. 新增BIT_MASK, ds18b20.h只定义了5个,即ds18b20_1_mask到ds18b20_5_mask。

关于BIT_MASK,其实也无需预先定义宏。我们可根据sensor_no算出来,核心代码如下:

bit_mask=0x01<<(sensor_no-1); //sensor_no=1~8

3. 注意,DS18B20上电温度转换结果默认为85℃,第一次读到的温度始终是85。因此,我们在正式读取之前,应该调用一次读取温度函数(如在while循环前)。

四、结束语

串口程序编写教程到此告一个段落,希望相关文章对大家有所助益。原本计划继续写串口校验和和AT命令,后面视情况而定吧。如何在有限的时间里,完成更多的事情是一个值得研究和探讨的话题。如果您有感兴趣的主题,可后台发消息给我。

如果你觉得本篇文章有所帮助,请点赞、打赏。分享,传递,沉淀。

附录:源代码

ds18b20驱动源码(ds18b20.h)

// ds18b20.h#ifndef _DS18B20_H#define _DS18B20_H#include "intrins.h"#include "reg51.h"float temperature = 0;bit is_ds10b20_exist = 0; //1: 存在, 0:不存在#define uchar unsigned char//三个DS18B20,分别接到P0.1, P0.2, P0.3//P0口最多连接8个DS18B20#define DS18B20_PORT P0#define ds18b20_1_mask 0x01 //sensor no. 1#define ds10b20_2_mask 0x02 //sensor no. 2#define ds10b20_3_mask 0x04 //sensor no. 3#define ds10b20_4_mask 0x08 //sensor no. 4#define ds10b20_5_mask 0x10 //sensor no. 5// 10us延时函数void delay_10xus(uchar n){//每个循环约10us左右, 110次循环约1mswhile(n--);}// 由序号获得ds18b20的引脚mask// no: 1,2,3uchar ds18b20_get_mask(uchar no){uchar pin_mask;switch(no){case 1: {pin_mask = ds18b20_1_mask; break;}case 2: {pin_mask = ds10b20_2_mask; break;}case 3: {pin_mask = ds10b20_3_mask; break;}default: break;}return pin_mask;}//初始化ds18b20uchar ds18b20_init(uchar sensor_no){uchar pin_mask;uchar ack;pin_mask= ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1delay_10xus(1); //延时10usDS18B20_PORT &= ~pin_mask; //清零delay_10xus(90);//拉低900usDS18B20_PORT |= pin_mask; //置1delay_10xus(8); //80us后读ds18b20的响应ack = DS18B20_PORT & pin_mask; //读引脚delay_10xus(50);return ack;}//从ds18b20读一个字节数据//先接接收低位LSB bituchar ds18b20_read_byte(uchar sensor_no){unsigned char i = 0;unsigned char byte_rx = 0;uchar pin_mask = ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1_nop_();_nop_(); //延时2usfor(i=8; i>0; i--){DS18B20_PORT &= ~pin_mask; //清零byte_rx >>= 1;DS18B20_PORT |= pin_mask; //置1_nop_();_nop_();if(DS18B20_PORT & pin_mask) //读到1{byte_rx |=0x80;}delay_10xus(30);DS18B20_PORT |= pin_mask; //置1}return(byte_rx);}// 写一个字节到DS18B20void ds18b20_write_byte(uchar c, uchar sensor_no){uchar i;uchar pin_mask = ds18b20_get_mask(sensor_no);for(i=0;i<8;i++){DS18B20_PORT &= ~pin_mask; //清零、写0_nop_();if(c & 0x01) //判断是否是写1{DS18B20_PORT |= pin_mask; //置1}delay_10xus(5); //延时50usDS18B20_PORT |= pin_mask; //置1,释放总线c >>= 1; //取下一位,准备发送}}// 开始温度采集转换void ds18b20_start_convert(uchar sensor_no){ds18b20_init(sensor_no);ds18b20_write_byte(0xcc, sensor_no); //SKIPROMds18b20_write_byte(0x44, sensor_no); //Convert command}// 开始读取温度void ds18b20_start_read(uchar sensor_no){ds18b20_init(sensor_no);ds18b20_write_byte(0xcc, sensor_no); //SKIP ROMds18b20_write_byte(0xbe, sensor_no); //READ Command}// 读温度,返回浮点类型温度float ds18b20_read_temperature(uchar sensor_no){uchar low_byte = 0;uchar hight_byte = 0;int temp = 0;float temperature = 0;if(ds18b20_init(sensor_no) == 0) // 温度传感器应答了{is_ds10b20_exist = 1;ds18b20_start_convert(sensor_no); //开始转换ds18b20_start_read(sensor_no); //开始读取low_byte = ds18b20_read_byte(sensor_no); //读温度的低八位hight_byte = ds18b20_read_byte(sensor_no); //读温度的高八位temp = (hight_byte<<8)|low_byte;}else{is_ds10b20_exist = 0;}if((temp & 0xF800) == 0xF800) //负温度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}else{temperature = temp * 0.0625;}return temperature;}#endif

主程序.c源码

//uart_ds18b20_temperatureMonitor.c#include "uart.h"//#include"reg51.h"#include"ds18b20.h"sbit beeper_en = P2^0;sbit key_s1 = P1^0;char msg[] = "Welcome back. ";unsigned char uart_rx_buffer[2];unsigned int count = 0;//函数定义void delayMS(unsigned int nms);void keyScan(); //按键扫描float ds18b20_readTemperature(unsigned char no); //读取DS18B20温度void sendTemperature(unsigned char no, float temperature); //发送温度函数void main(){unsigned char ds18b20_no = 1; //设备号unsigned char ds18b20_N = 3; //ds18b20总数float temperature; //温度uart_init();while(1){temperature = ds18b20_readTemperature(ds18b20_no); //读温度sendTemperature(ds18b20_no, temperature); //发送温度ds18b20_no++;if(ds18b20_no > ds18b20_N)//已经读完所有点的温度{ds18b20_no = 1;}delayMS(1000); //等待1s左右}}void keyScan(){float temperature;if(key_s1 == 0){delayMS(10); //消抖if(key_s1 == 0) //按键按下,读取并上报1#地点的温度{temperature = ds18b20_readTemperature(1); //读温度sendTemperature(1, temperature); //发送温度}}}//延时函数void delayMS(unsigned int nms){unsigned int i,j;for(i=0;i
          for(j=0;j<130;j++);}//读取DS18B20温度(模拟float ds18b20_readTemperature(uchar senor_no){float temperature;temperature = ds18b20_read_temperature(senor_no);return temperature;}//发送温度函数void sendTemperature(unsigned char no, float temperature){uart_sendUchar(no);uart_sendFloat(temperature);}//串口中断函数void isr_uart() interrupt 4{staticunsigned char rx_byte_count = 0;if(RI) //收到数据{uart_rx_buffer[rx_byte_count] = SBUF;rx_byte_count++;RI = 0;if(rx_byte_count == 2) //接收到2个字节数据{rx_byte_count = 0;//下面是命令解析及执行~魔幻的if elseif(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00){beeper_en = 1; //beeper offprintf("执行命令:关闭蜂鸣器!");}else if (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01){beeper_en = 0; //beeper onprintf("执行命令:开启蜂鸣器!");}else if (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00){count = 0;printf("执行命令:清零count!");}else if (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F){printf("将关闭串口,再见!");TR1 = 0;}}}}





标签: [db:tags]