1
测量仪表
再有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 something
else: //低电平
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,3
uchar 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初始化函数
//初始化ds18b20
uchar 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,3
uchar 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;
}
//初始化ds18b20
uchar 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 bit
uchar 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);
}
// 写一个字节到DS18B20
void 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;
}
}
}
}
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !