清采NOTE

理解他人 善待自己

用户工具

站点工具


courses:embedded_system:ds18b20温度测量

DS18B20温度测量

实验目的

学习DS18B20的操作。

实验内容

学习使用单总线技术,通过它来实现STM32和外部温度传感器(DS18B20)的通信,并把从温度传感器得到的温度数据通过LCD1602显示出来。


实验说明

1. DS18B20简介

DS18B20是由DALLAS半导体公司推出的一种的“一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。一线总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃,精度为±0.5℃。现场温度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现9~12位的数字值读数方式。它工作在3~5.5V的电压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及用户设定的报警温度存储在EEPROM中,掉电后依然保存。ROM中的64位序列号是出厂前被光记好的,它可以看作是该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。64位ROM的排列是:前8位是产品家族码,接着48位是DS18B20的序列号,最后8位是前面56位的循环冗余校验码(CRC=X8+X5+X4+1)。ROM作用是使每一个DS18B20都各不相同,这样就可实现一根总线上挂接多个。所有的单总线器件要求采用严格的信号时序,以保证数据的完整性。DS18B20共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里我们简单介绍这几个信号的时序:

1) 复位脉冲和应答脉冲

单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少 480us,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~ 60us,并进入接收模式(Rx)。接着DS18B20拉低总线60~240us,以产生低电平应答脉冲, 若为低电平,再延时480us。

2) 写时序

写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。写1时序:主机输出低电平,延时2us,然后释放总线,延时60us。写0时序:主机输出低电平,延时60us,然后释放总线,延时2us。

3) 读时序

单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态。典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。

在了解了单总线时序之后,我们来看看DS18B20的典型温度读取过程,DS18B20的典型温度读取过程为:复位→发SKIPROM命令(0XCC)→发开始转换命令(0X44)→延时→复位→发送SKIPROM命令(0XCC)→发读存储器命令(0XBE)→连续读出两个字节数据(即温度)→结束。

DS18B20的介绍就到这里,更详细的介绍,请大家参考DS18B20的手册。


2. 硬件电路

DS18B20原理图如上图所示,PB8接DS18B20数据端,显示部分接LCD1602,详见LCD1602显示实验部分。


3. DS18B20的初始化

1) 在进行初始化之前,要先进行IO口的初始化。

/************************************************************
* 函 数 名		: ds18b20_init
* 函数功能		: IO端口时钟初始化函数	   
* 输    入		: 无
* 输    出		: 无
************************************************************/
void ds18b20_init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 
	GPIO_InitStructure.GPIO_Pin=dq;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIO_ds18b20,&GPIO_InitStructure);
}

管脚定义是在其头文件内:

// PB8 <-- 第一行
#define dq (GPIO_Pin_8) 
#define GPIO_ds18b20 GPIOB
#define ds18b20_dq_H GPIO_SetBits(GPIO_ds18b20,dq)
#define ds18b20_dq_L GPIO_ResetBits(GPIO_ds18b20,dq)

2) DS18B20的初始化

主机首先发出一个480–960微秒的低电平脉冲,然后释放总线变为高电平,并在随后的480微秒时间内对总线进行检测,如果有低电平出现说明总线上有器件已做出应答。

若无低电平出现一直都是高电平说明总线上无器件应答。作为从器件的DS18B20在一上电后就一直在检测总线上是否有480–960微秒的低电平出现,如果有,在总线转为高电平后等待15–60微秒后将总线电平拉低60–240微秒做出响应存在脉冲,告诉主机本器件已做好准备。若没有检测到就一直在检测等待。

3) 初始化时序图

4) 例程代码

/*****************************************
* 函 数 名	: ds18b20init
* 函数功能	: DS18B20初始化时序	   
* 输    入	: 无
* 输    出	: 无
*****************************************/
void ds18b20init()
{
	DQOUTINT(); //输出
	ds18b20_dq_L;
	delay_us(480); //延时480微妙	
	ds18b20_dq_H;
	delay_us(480); //延时480微妙
}

4. DS18B20读操作

主机发出各种操作命令都是向DS18B20写0和写1组成的命令字节,接收数据时也是从DS18B20读取0或1的过程。因此首先要搞清主机是如何进行写0、写1、读0和读1的。写周期最少为60微秒,最长不超过120微秒。写周期一开始做为主机先把总线拉低1微秒表示写周期开始。随后若主机想写0,则将总线置为低电平,若主机想写1,则将总线置为高电平,持续时间最少60微秒直至写周期结束,然后释放总线为高电平至少1微秒给总线恢复。而DS18B20则在检测到总线被拉底后等待15微秒然后从15us到45us开始对总线采样,在采样期内总线为高电平则为1,若采样期内总线为低电平则为0。

1) 读操作时序图

2) 读操作程序

/***********************************************************
* 函 数 名	: DS18b20rd
* 函数功能	: DS18B20读数据时序	   
* 输    入	: 无
* 输    出	: value
***********************************************************/
u8 DS18b20rd()
{
	u8 i=0,value=0;
 
	for(i=0;i<8;i++)
	{
		value>>=1;
		DQOUTINT(); //输出
		ds18b20_dq_L; //拉低
		delay_us(4); //延时4微妙
		ds18b20_dq_H;
		delay_us(10); //延时10微妙
		DQININT(); //输入配置
 
		if(GPIO_ReadInputDataBit(GPIO_ds18b20,dq)==1)
		{
		   value|=0x80; //读数据从低位开始
		}
 
		delay_us(45); //延时45微妙
	}
 
	return value;	
}

5. DS18B20写操作

对于读数据操作时序也分为读0时序和读1时序两个过程。读周期是从主机把单总线拉低1微秒之后就得释放单总线为高电平,以让DS18B20把数据传输到单总线上。作为从机DS18B20在检测到总线被拉低1微秒后,便开始送出数据,若是要送出0就把总线拉为低电平直到读周期结束。若要送出1则释放总线为高电平。主机在一开始拉低总线1微秒后释放总线,然后在包括前面的拉低总线电平1微秒在内的15微秒时间内完成对总线进行采样检测,采样期内总线为低电平则确认为0。采样期内总线为高电平则确认为1。完成一个读时序过程,至少需要60微秒才能完成。

1) 写操作时序图

2) 写操作程序

/*****************************************************
* 函 数 名	: ds18b20wr
* 函数功能	: DS18B20写数据时序	   
* 输    入	: dat
* 输    出	: 无
*****************************************************/
void ds18b20wr(u8 dat)
{
	u8 i=0;
	DQOUTINT();//输出
 
	for(i=0;i<8;i++)
	{
		ds18b20_dq_L; // 拉低
		delay_us(15); //延时15微妙
 
		if((dat&0x01)==1)
		{
			ds18b20_dq_H;
		}
		else
		{
			ds18b20_dq_L;
		}
		delay_us(60); //延时60微妙
		ds18b20_dq_H;
 
		dat>>=1; //准备下一位数据的发送	
	}
}

6. 读取温度操作

1)读取温度操作

DS18B20经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第1个字节。所以当我们只想简单的读取温度值的时候,只用读取暂存器中的第0和第1个字节就可以了。读取一次D18B20温度的操作步骤如下:

a) 初始化DS18B20
b) 跳过ROM操作(ROM里面可以读取DS18B20的地址、型号,还有配置分辨率等,我们只使用一个DS18B20,所以不用读取地址型号,配置直接使用默认的12位分辨率就好了。)
c) 发送温度转换命令。
d) 跳过ROM操作
e) 发送读取温度命令
f) 读取温度值。

补码介绍

  • 正数的补码是正数本身
  • 负数的补码是原码取反,然后再加1

DS18B20存储的温度值是以补码的形式存储的,所以读出来的温度值是实际温度值的补码,要把的转换为原码。

  • 正温度的话,原码就是补码本身,所以在12位分辨率下,温度的计算公式为 $$\text{温度值}=\text{读取值}\times 0.0625$$
  • 负温度的话,原码是补码减1再取反,所以在12位分辨率下,计算公式为 $$\text{温度值}=-(\text{读取值减1再取反})\times 0.0625$$

读取温度程序函数

/**************************************************
* 函 数 名	: readtemp
* 函数功能	: DS18B2温度寄存器配置,温度读取	   
* 输    入	: 无
* 输    出	: value
**************************************************/
double readtemp()	   // 读取温度内需要复位的
{
	u16 temp;
	u8 a,b;
	double value;
	ds18b20init();	   //初始化
	ds18b20wr(0xcc);   //发送忽略ROM指令
	ds18b20wr(0x44);   //发送温度转换指令
	delay_ms(800);
	ds18b20init();	   //初始化
	ds18b20wr(0xcc);   //发送忽略ROM指令
	ds18b20wr(0xbe);   //发读暂存器指令
	a=DS18b20rd();	   //温度的低八位
	b=DS18b20rd();	   //温度的高八位
	temp=b;
	temp=(temp<<8)+a;
	if((temp&0xf800)==0xf800)
	{
		temp=(~temp)+1;
		value=temp*(-0.0625);
	}
	else
	{
		value=temp*0.0625;	
	}
	return value;
}

7. 例程主函数

int main()
{	u16 wd;
	double temp;
	LCD1602_GPIO_Init();		 
  	LCD1602_Init();	
	ds18b20_init();	 // DS18B20初始化
	while(1)
	{
		temp=readtemp();  // 读取温度
		wd=temp*100;	 // 显示二位小数
 
		LCD1602_MoveToPosition(0,0);
		LCD1602_DisplayString("wendu");	
 
		LCD1602_MoveToPosition(1,0);
		LCD1602_DisplayOneCharOnAddr(0,9,wd/1000+0x30); // 显示十位	
		LCD1602_DisplayOneCharOnAddr(0,10,wd%1000/100+0x30); // 显示个位 
		LCD1602_DisplayOneCharOnAddr(0,11,'.'); // 显示. 
		LCD1602_DisplayOneCharOnAddr(0,12,wd%1000%100/10+0x30); // 显示小数1位 
		LCD1602_DisplayOneCharOnAddr(0,13,wd%1000%100%10+0x30); // 显示小数2位 
		LCD1602_DisplayOneCharOnAddr(0,14,0xdf); // ℃
		LCD1602_DisplayOneCharOnAddr(0,15,'C'); // 显示C 		
	}			
}

主程序的效果是,读取到的温度值通过LCD1602上显示出来。


实验步骤

1. 使用1根1P杜邦线连接核心板PB8与“两路温度传感器”单元JP35(WD1)相连。
2. 使用1根8P杜邦线连接核心板PA0~PA7与“LCD1602液晶”单元JP33(D0~D7)。
3. 使用3根1P线连接PA8~PA11与“LCD1602液晶”单元JP31(RS,RW,EN)。
4. 在KEIL软件中调试运行“DS18B20温度测量”工程文件。
5. 实验现象:在液晶上显示“wendu xx.xx℃”。

实验任务

1. 设置温度显示范围为小数点后4位;
2. 改变测量温度的分辨率为10位,即最小刻度变化为0.25°C,此时可以观察到温度显示的最后两位始终为0;
3. 设置温度超过某一阈值即报警(蜂鸣器响),注意接线。
任务1.c
int main()
{	u32 wd;  // 这里要改为 u32,因为u16最多表示2^16=65536
	double temp;
	LCD1602_GPIO_Init();		 
  	LCD1602_Init();	
//	printf_init();	 //printf初始化
	ds18b20_init();	 //DS18B20初始化
	while(1)
	{
		temp=readtemp();  //读取温度
		wd=temp*10000;	 //显示四位小数
		LCD1602_MoveToPosition(0,0);
			LCD1602_DisplayString("Temperature:");	
		LCD1602_MoveToPosition(1,0);
			LCD1602_DisplayOneCharOnAddr(1,7,wd/100000+0x30);//显示十位	
			LCD1602_DisplayOneCharOnAddr(1,8,wd%100000/10000+0x30);//显示个位 
			LCD1602_DisplayOneCharOnAddr(1,9,'.');//显示. 用ox2e效果一样
			LCD1602_DisplayOneCharOnAddr(1,10,wd%100000%10000/1000+0x30);//显示小数1位 
			LCD1602_DisplayOneCharOnAddr(1,11,wd%100000%10000%1000/100+0x30);//显示小数2位 
			LCD1602_DisplayOneCharOnAddr(1,12,wd%100000%10000%1000%100/10+0x30);//显示小数3位 
			LCD1602_DisplayOneCharOnAddr(1,13,wd%100000%10000%1000%100%10+0x30);//显示小数4位 
			LCD1602_DisplayOneCharOnAddr(1,14,0xdf); //显示°,对照LCD1602的字符表查询
			LCD1602_DisplayOneCharOnAddr(1,15,'C');//显示C 
 
		//printf("当前温度为:%0.4lf ℃\r\n",temp);		
	}