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

测量仪表

1473人已加入

描述

再有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上位机。 

测量系统

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

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

    测量系统

图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     //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

 

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;   uchar ack;   pin_mask= ds18b20_get_mask(sensor_no);   DS18B20_PORT |= pin_mask; //置1   delay_10xus(1); //延时10us   DS18B20_PORT &= ~pin_mask;  //清零   delay_10xus(90);//拉低900us    DS18B20_PORT |= pin_mask; //置1   delay_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)。

测量系统

图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所示。实现将三个温度和当前时间戳存储到一个表格。

测量系统

图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个字节,类型不匹配。

测量系统

图5 getReceiveData.vi程序框图

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

测量系统

图6  主程序框图

三、运行结果

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

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

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

测量系统

 

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次循环约1ms   while(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; //置1   delay_10xus(1); //延时10us   DS18B20_PORT &= ~pin_mask;  //清零   delay_10xus(90);//拉低900us    DS18B20_PORT |= pin_mask; //置1   delay_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_(); //延时2us    for(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); //延时50us    DS18B20_PORT |= pin_mask; //置1,释放总线    c >>= 1; //取下一位,准备发送  }}// 开始温度采集转换void ds18b20_start_convert(uchar sensor_no){     ds18b20_init(sensor_no);     ds18b20_write_byte(0xcc, sensor_no); //SKIP ROM     ds18b20_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 ROM     ds18b20_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{  static unsigned 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 else        if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)        {            beeper_en = 1; //beeper off                printf("执行命令:关闭蜂鸣器!");        }        else if    (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)        {          beeper_en = 0; //beeper on              printf("执行命令:开启蜂鸣器!");            }        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;        }     }  }}

 


  审核编辑:汤梓红


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分