蓝桥杯电子类单片机学习二:DS18B20温度传感器(OneWire驱动)教程

目录

前言

单片机资源数据包_2023(点击下载)

一、通过onewire(单总线)驱动DS18B20

1.关于onewire单总线通信

2.关于onewire驱动

二、读取DS18B20

1.ROM检查

2.DS18B20命令设置

3.DS18B20的温度数据格式 

三、代码实现



前言

本系列文章意在帮助各位正在准备蓝桥杯单片机组的同学,提供一个参考与指南,但是所有指南的前提是,默认你已经有单片机基础,本系列文章会提供本人对蓝桥杯单片机组编程方面的一些源码实现。当然,或许与你写代码的style完全不想同,那咱们也可以彼此相互交流各自的优缺点,或许你可能只是一个小白,就是想来抄一些可供借鉴的代码,这些都不重要,重要的是能给正在准备单片机组的同学提供到一些帮助。

此外代码可能有写的不完善的地方,但是每一个代码都是经过测试可行之后才发出来的,不敢保证十全十美,但是跑起来应该没问题。

此外,比赛时会提供一个单片机资源数据包,里面的内容比较多,这里只放一个下载链接,是2023年省赛是提供的单片机资源数据包(今年才2024,已经是最新的资源数据包了),正文中会直接使用资源数据包内的资料:

单片机资源数据包_2023(点击下载)

关于此资源数据包还得补充两句,他的3-底层驱动代码参考与往些年不同(但其实差异也很小,这里不再赘述),而且还是快比赛时才公布的这个资源数据包。也就是说,在写底层驱动代码时,我都是以2023年的资源数据包为基础写的,如果你使用的不是2023年的资源数据包的话,可能会跟我使用的底层驱动不完全相同,不过影响也不大。

下图为单片机资源数据包_2023内的所有资源,其中最关键的就是3-底层代码驱动以及SCH_硬件原理图V30了,当然共阳数码管段码表也十分常用,不过一般也就新写一个项目时,会把里面的东西CV到main函数里,然后这个文件就用不着了。

一、通过onewire(单总线)驱动DS18B20

1.关于onewire单总线通信

这里只简单科普一下单总线通信,不深入介绍,嫌啰嗦的可以直接PASS。

OneWire是一种数字通信协议,用于在主机设备和多个从机设备之间进行数据传输。它允许通过单个数据线同时进行数据传输和供电。也就是说,如果只用单个数据线只传输数据的话,驱动一个设备并完成数据传输最少需要三根线就可以完成:VCC,GND和单总线DQ,如果使用单个数据线在传输数据的同时也完成供电操作的话(寄生电源),则只需要两根线就可以驱动一个设备并完成数据传输:GND和单总线DQ。

OneWire协议的主要功能包括以下几点:

  1. 单线传输:OneWire只需要一根数据线来传输数据,同时使用电地作为信号的返回路径。这样可以减少连线的复杂性,提高数据传输的可靠性。

  2. 寄生电源:OneWire协议支持从主机设备提供电源给从机设备。对于一些简单的从机设备,它们可以通过从主机设备接收的电能进行运行,而无需单独外部电源。

  3. 独特的设备地址:每个OneWire设备都有一个唯一的64位地址,主机设备可以通过该地址来识别和定位每个从机设备。这样可以实现多个从机设备的并行连接和通信。

  4. 数据传输和控制:OneWire协议支持在主机设备和从机设备之间进行双向数据传输。主机设备可以向从机设备发送命令和参数,从机设备可以通过数据线将其响应发送回主机设备。

总之,OneWire是一种数字通信协议,具有单线传输、寄生电源、设备地址识别、数据传输和控制等功能,适用于各种应用领域。它使得设备之间的通信更加简单、快速和可靠,并且可以减少系统的复杂性和成本。

在蓝桥杯比赛当中,我们的单总线上只挂载了一个设备,也就是DS18B20,所以我们在使用时可以直接跳过ROM检测。蓝桥杯板子上的DS18B20接的有VCC,所以我们也可以不用使用DS18B20的寄生电源功能(单总线在传数据的同时也负责供电)。

如果以后有幸从事相关领域的工作的话,我们在使用单总线通信时,ROM检测就未必能够直接skip了,甚至为了节省资源,我们还会使用寄生电源功能。

2.关于onewire驱动

单片机资源数据包_2023里面提供了一些底层驱动,但仅包括基本的读写数据或者初始化和停止总线等。下面是单片机资源数据包_2023里面提供的onewire.c里面的代码,前边也已经提到,单片机资源数据包_2023里面提供的底层代码跟往些年的底层代码不太一样,这里不再比较2023年给的底层代码和往些年底层代码的差异,而是直接使用2023年的底层代码完成相关的任务。

/*	# 	单总线代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/

//
void Delay_OneWire(unsigned int t)  
{
	unsigned char i;
	while(t--){
		for(i=0;i<12;i++);
	}
}

//
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
	return dat;
}

//
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(12);
  	DQ = 0;
  	Delay_OneWire(80);
  	DQ = 1;
  	Delay_OneWire(10); 
    initflag = DQ;     
  	Delay_OneWire(5);
  
  	return initflag;
}

这串代码是不能直接使用的,再加入这个.c文件之后,我们只要点编译就会报错。在正常使用之前,我们需要定义DQ引脚,以及引用头文件。此外,为了使得main函数可以正常使用onewire.c文件, 

通过原理图可知,DS18B20的DQ引脚是连接在P14上的,因此我们要顶一DQ为P14引脚。

综上,我们想使用onewire.c文件,需要在代码前几行加上如下代码:

#include "onewire.h"//2023年比赛的时候没有给.h文件需要自己新建
#include <stc15.h>

sbit DQ=P1^4;

onewire.h文件里只有一个空壳,代码如下:

#ifndef _ONWIRE_H_
#define _ONWIRE_H_

#endif

做到这里,我们只完成了第一步——让我们点击编译时,不会报错。当然,此时点击编译会有许多警告,这些警告是说已经定义的函数未被调用,目前可以忽视(因为我们代码才只写了一半了)

二、读取DS18B20

1.ROM检查

DS18B20是一个温度传感器,挂载在onewire(单总线)上。

既然叫总线,那就说明一根总线就可以挂载多个支持onewire的设备,比如一根总线上可以挂载两个以上的DS18B20温度传感器,那我们启动某个特定的温度传感器,并与之通信之前,我们首先要“验证身份”,确定这个DS18B20就是我们要读取的那个温度传感器,然后才能发送控制指令。

这里的“验证身份”就是所谓的ROM检测。每个DS18B20都有一个64位的ROM,因此我们在启动总线之后,第一件事就是进行ROM检查。下面是常用的ROM检查命令:

Read ROM [33h]
这个命令允许总线控制器读到 DS1820 的 8 位系列编码、 唯一的序列号和 8 位 CRC 码。 只有在总线上存在单只 DS1820 的时候才能使用这个命令。 如果总上有不止一个从机, 当所有从机试图同时传送信号时就会发生数据冲突(漏极开路连在一起开成相与的效果)。

Match ROM [55h]
匹配 ROM 命令,后跟 64 位 ROM 序列,让总线控制器在多点总线上定位一只特定的 DS1820。只有和 64 位 ROM 序列完全匹配的 DS1820 才能响应随后的存储器操作命令。 所有和 64 位 ROM序列不匹配的从机都将等待复位脉冲。这条命令在总线上有单个或多个器件时都可以使用。

Skip ROM [CCh]
这条命令允许总线控制器不用提供 64 位 ROM 编码就使用存储器操作命令, 在单点总线情况下
右以节省时间。 如果总线上不止一个从机, 在 Skip ROM 命令之后跟着发一条读命令, 由于多
个从机同时传送信号,总线上就会发生数据冲突(漏极开路下拉效果相当于相与)。
 

当然,我们的蓝桥杯板子上的单总线只挂载了一个设备(DS18B20),因此我们可以直接跳过ROM检测,也就是说初始化总线之后直接发送0xCC跳过ROM检查,我们也能正常操作我们的DS18B20。

那么,我们读取DS18B20数据的函数,头两行代码肯定是:

    init_ds18b20();//初始化ds18b20,底层驱动里面已经给出,可以直接调用
    Write_DS18B20(0xCC);//跳过ROM检查

2.DS18B20命令设置

在进行ROM检测之后,我们就可以对DS18B20进行控制了,下表格列举了DS18B20的一些命令设置:

命令 说明
单线总线发出协议后 备 注
温度转换命令
Convert T 开始温度转换 44h <读温度忙状态> 1
存储器命令
Read Scratchpad 读取暂存器和 CRC 字节 BEh <读数据直到 9 字节>
Write
Schratchpad
把字节写入暂存器的地址 2 和 3(TH 和 TL 温度报警触发) 4Eh <写两个的字节到地址 2和 3>
Copy Scratchpad 把暂存器内容拷贝到非易失性存储器中(仅指地址2 和 3) 48h <读拷贝状态> 2
Recall E2 把非易失性存储器中的值召回暂存器(温度报警触
发)
B8h <读温度忙状态>
Read Power
Supply
标识 DS1820 的供电模式 B4h <读供电状态>

这么多的命令,我们以后如果从事相关方面的工作的话,以后可能都会用到。当然对于我们蓝桥杯比赛的话,我们只需要用到其中两个,一个是Convert T开始温度转化命令(0x44),另一个是Read Scratchpad读取暂存器命令(0xBE)。

发送开始温度转化命令之后,DS18B20会将温度转化之后的数据存储到暂存器内。此外不得不提的一点是,DS18B20温度转化需要一定的时间,可能是

3.DS18B20的温度数据格式 

刚才已经说,发送开始温度转化命令之后,DS18B20会将温度转化之后的数据存储到暂存器内。这个暂存器是两个八位的数据总共十六位,其中高八位我们给它取名为TH,低八位我们取名为TL比如暂存器数据是:0x07D0,那么也就是说TH=0x07,TL=0xD0。TH和TL都是八位的数据。这里先放一个数据的表格,再详细解释温度数据

图片里的MS其实就是上文提到的TH,LS就是TL。

TEMPERATURE (°C) DIGITAL OUTPUT
(BINARY)
DIGITAL OUTPUT
(HEX)
+125 0000 0111 1101 0000 07D0h 
+85* 0000 0101 0101 0000 0550h
+25.0625 0000 0001 1001 0001 0191h
+10.125 0000 0000 1010 0010 00A2h
+0.5 0000 0000 0000 1000 0008h
0 0000 0000 0000 0000 0000h
-0.5 1111 1111 1111 1000 FFF8h
-10.125 1111 1111 0101 1110 FF5Eh
-25.0625 1111 1110 0110 1111 FE6Fh
-55 1111 1100 1001 0000 FC90h

表格中+85°带了一个星号,再默认状态下(刚上电,没有有效数据时)DS18B20存储的温度数据时85°,这里了解即可。如果你写的代码有问题的话,有可能读到的数据就是固定的85。

TH的高四位只能是全0或者全1,是符号位,全0时表示正温度,全1时表示负温度,这样说不太恰当,这涉及到二进制表示负数的问题了,当然这里通过看TH的高四位来判断温度的正负值是没问题的,只是说负数再使用时需要转化为正数。

TH的第四位和TL的高四位表示温度的整数位,如第一行数据,TH的第四位和TL的高四位是0x7D,化为10进制的话就是125,也就是125°

TL的低四位是小数位,因为精确到了小数点后四位,所以可以理解为每一位是2的负4次方,也就是0.0625,如第五行数据的TL的低四位是0x8,化成10进制的话还是8,8*0.0625=0.5,也就是说这个数据的小数值是0.5。

对于负数转化为正数,我们常用的办法是:“取反后再+1”;

下面是一个具体的例子,假设我们要将二进制数 11111110(-2 的二进制表示)变为正数:

  1. 将绝对值转换为正数的二进制:00000010

  2. 对二进制数按位取反:11111101

  3. 将取反后的二进制数加1:11111110(此时得到的是正数 2)

再蓝桥杯比赛中,我们很少会用到负数,因为蓝桥杯比赛是在4月份,甚至连小数都很少用到,所以我在处理时会直接忽略符号位和小数位,只取中间四位,也就是TH的低四位和TL的高四位作为函数的返回值。

三、代码实现

现在完成以下功能:

1.数码管前两位显示当前读取到的温度值(可以用收捏一下DS18B20,让他的温度提高,这样显示的数据也就提高了,应该捏一下就能提高到20+度)

onewire.c

/*	# 	单总线代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/

#include "onewire.h"
#include <stc15.h>

sbit DQ=P1^4;

//
void Delay_OneWire(unsigned int t)  
{
	unsigned char i;
	while(t--){
		for(i=0;i<12;i++);
	}
}

//
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
	return dat;
}

//
bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(12);
  	DQ = 0;
  	Delay_OneWire(80);
  	DQ = 1;
  	Delay_OneWire(10); 
    initflag = DQ;     
  	Delay_OneWire(5);
  
  	return initflag;
}

//上边的代码都是在资源数据包里,比赛时会给
//下边这个函数是自己写的
unsigned int read_18b20()
{
	
	unsigned int T=0;//定义温度
	unsigned char low=0;//用于接受温度的低八位
	unsigned char high=0;//用于接受温度的高八位
	
	init_ds18b20();//初始化DS18B20
	Write_DS18B20(0xCC);//跳过ROM检测
	Write_DS18B20(0x44);//发送开始温度转换的命令
	Delay_OneWire(200);//温度转化需要时间,这里直接延时一下。。注意应避免连续读取DS18B20
	
	
	init_ds18b20();//重新初始化DS18B20
	Write_DS18B20(0xCC);//跳过ROM检查
	Write_DS18B20(0xBE);//发送读取温度数据的指令
	
	low=Read_DS18B20();//接收低八位
	high=Read_DS18B20();//接收高八位
	
	T=high;
	T&=0x0F;//第八位的高四位置0,也就是不考虑符号位
	T<<=8;
	T|=low;
	T>>=4;//舍去低八位的低四位,也就是不考虑小数位
	
	return T;
}

onewire.h

#ifndef _ONWIRE_H_
#define _ONWIRE_H_

unsigned int read_18b20();

#endif

main.c

#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xFF
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可


//关于数码管的两个函数,因为需要从数组内读取待显示的数据,还要进行位选,
//所以这两个函数只起到开关锁存器的功能,没有更新数据(会在其他地方实现数据更新和获取)
#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;

void Timer0_Init(void);		//1毫秒@11.0592MHz
void Delay100ms(void);	//@11.0592MHz
unsigned char location=0;
unsigned char Nixie_num[]={10,10,10,10,10,10,10,10};
unsigned int Temp=0;
void main()
{
	read_18b20();//上电先初始化一次DS18B20,方便后续读取(因为温度转化需要时间,相当于是把第一次温度转化的时机提前了)
	Timer0_Init();//定时器0初始化
	EA=1;//开总中断
	
	while(1)
	{
		Temp=read_18b20();//读ds18b20。。切勿连续读取,比如连续两行代码都是read_18b20()
		Nixie_num[6]=Temp/10%10;//显示十位
		Nixie_num[7]=Temp/1%10;//显示个位
		Delay100ms();
	}
	
}
void Timer0_Isr(void) interrupt 1
{

	P0=0x01<<location;NIXIE_CHECK();//选择要显示的某一位
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();//从数组中读取数据并显示
	
	if(++location==8)//位数+1,从而实现扫描
		location=0;
}

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xCD;				//设置定时初始值
	TH0 = 0xD4;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
}

void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

后续会带大家逐步深入的分享蓝桥杯比赛的代码

物联沃分享整理
物联沃-IOTWORD物联网 » 蓝桥杯电子类单片机学习二:DS18B20温度传感器(OneWire驱动)教程

发表评论