使用STM32和DS18B20传感器读取温度

DS18B20

简介

DS18B20是一款具有高精度温度测量的芯片,测温范围是-55摄氏度到125摄氏度。
DS18B20 使用单总线协议,总线通讯通过一根控制信号线实现。控制线需要一个弱上拉电阻,这样所有的器件都通过三态或者开漏极端口(就是DS18B20 的 DQ 引脚)连接到总线上。在这个总线系统中,单片机(主机)通过每个器件的唯一 64 位编码识别并寻址总线上的器件。因为每个器件都有唯一的编码,实际上挂在总线上并可以被寻址的设备数量是无限的。
DS18B20 的另一个特点是其可以不需要额外供电运行。这种情况下供电是总线为高的时候,通过单总线在 DQ 引脚上的上拉电阻提供给器件的。总线高信号对一个内部电容充电,然后在总线低的时候,内部电容就会维持对器件供电。这种从单总线获取电源的方法被称为“寄生供电”。 但是一般都是通过 VDD 由外部供电。
DS18B20上电的默认分辨率是12位,对应的温度分度是0.0625(即将采集到的数据乘以0.0625得到的就是实际的温度)。还有9、10、11位的分辨率可供选择,他们对应的温度分度分别为0.5、0.25、0.125。

温度寄存器

这里补充一个知识点:二进制补码,
比如short类型数据占两个字节,范围为-32768~32767如果一个 short a=0xfc90;那么a为 -(0x10000 – 0xfc90)=-(65536-64656)=-880;该传感器的负温度值就是这样计算的。
温度数据存储为 16 位符号扩展温度寄存器中的二进制补码(见下图)。
符号位(S,高5位bint11~bit15)指示温度为正或负:对于正数 S = 0,对于负数 S = 1。
如果DS18B20 配置为 12 位分辨率,温度寄存器中的所有位都将包含有效数据。对于 11 位分辨率,位 0 未定义。对于 10 位分辨率,位 1 和 0 未定义,对于 9 位分辨率位 2,1 和 0 未定义。
下图是在12位分辨率的情况下输出数据和温度读数:

单总线时序

DS18B20 的单总线通讯协议定义了几种信令类型:复位脉冲,存在脉冲,写 0,写 1, 读 0,读 1。除了存在脉冲之外,所有信令都由总线主机发起。
初始化时序——复位和存在脉冲
DS18B20的初始化是主机发送一个复位脉冲(主机通过将单总线拉低至少480 µs),总线主机随后释放总线进入接收模式。当总线被释放后,5kΩ 上拉电阻会把总线拉高。当 DS18B20 检测到这个上升沿,它等待15µs 到 60µs 然后发出存在脉冲(DS18B20把单总线拉低 60µs 到240µs )。

//复位DS18B20
void DS18B20_Rst(void)	   
{                 
	DS18B20_IO_OUT();   //设置为输出
	DS18B20_DQ_OUT=0;  	//拉低DQ
	MY_Delay_us(750);      //拉低750us
	DS18B20_DQ_OUT=1;  	//DQ=1 
	MY_Delay_us(15);       //15US
}

//等待DS18B20的回应
//DS18B20复位后会拉低总线60—240us来表示存在
//返回1:未检测到DS18B20的存在
//返回0:存在
uint8_t DS18B20_Check(void) 	   
{   
	uint8_t retry=0;
	DS18B20_IO_IN();    //设置为输入
  while (DS18B20_DQ_IN&&retry<200) 
	{
		retry++;
		MY_Delay_us(1);
	}	 
	if(retry>=200)return 1;
	else retry=0;
  while (!DS18B20_DQ_IN&&retry<240)
	{
		retry++;
		MY_Delay_us(1);
	}
	if(retry>=240)return 1;	    
	return 0;
}

/*初始化DS18B20
等待DS18B20的回应
DS18B20复位后会拉低总线60—240us来表示存在
返回1:未检测到DS18B20的存在
返回0:存在*/
void DS18B20_Init(void)	   
{  
	DS18B20_Rst();
	DS18B20_Check();
}

读时序
DS18B20 只能在主机发布读时隙期间可以传送数据到主机。例如主机可以在发布 Convert T[44h](温度转换指令)后读取温度数据。
所有读时隙必须持续至少 60µs,并且两个写时隙之间恢复时间不少于 1µs。读时隙的产生是通过主机拉低单总线至少 1µs 然后释放总线来实现。主机发起读时隙之后,DS18B20 会开始在总线上传输 1 或 0。DS18B20 通过保持总线高发送 1 并通过拉低总线发送 0。
当传输 0 的时候,DS18B20 会在时隙结束时释放总线,之后总线会被上拉电阻拉回高空闲状态。DS18B20 的输出数据在启动时隙的下降沿后 15µs 之内有效。所以,主机必须在时隙启动之后 15µs 之内释放总线并采样总线状态。

//从DS18B20读取一个位
//返回值:1/0
uint8_t DS18B20_Read_Bit(void) 
{
	uint8_t data;
	DS18B20_IO_OUT();   //设置为输出
	DS18B20_DQ_OUT=0; 
	MY_Delay_us(2);  //两个读时隙之间不少于1us
	DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();    //设置为输入
	MY_Delay_us(12);  //以上这个过程必须在15us内完成。然后判断数据是0还是1
	if(DS18B20_DQ_IN)data=1;
	else data=0;	
	
	MY_Delay_us(50);    //所有读时隙必须持续至少60us       
	return data;
}

//从DS18B20读取一个字节
//返回值:读到的数据
uint8_t DS18B20_Read_Byte(void)   
{        
	uint8_t i,j,dat;
	dat=0;
	for (i=1;i<=8;i++) 
	{
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
	return dat;
}

写时序
有两种写时隙:“写 1”时隙和“写 0”时隙。总线主机通过写1 时隙把一个逻辑1写入DS18B20,通过写 0 时隙把一个逻辑 0 写入 DS18B20。所有写时隙必须持续最少 60µs,并且两个写时隙之间至少有 1µs 的恢复时间。两种写时隙都是通过主机把单总线拉低来发起。
写“1”,把单总线拉低之后,总线主机必须在 15µs 内释放单总线。总线被释放后,5kΩ 上拉电阻会把总线拉高,(这里由主机控制总线输出1也可)。
写 “0”,把单总线拉低之后,总线主机必须在整个时隙期间持续保持总线低(至少 60µs)。
DS18B20 在主机发起写时隙后,会在至少 15µs到 60µs 的时间窗口内采样单总线。如果在这个采样时间窗口总线为高,一个 1 就被写入 DS18B20。如果总线是低,一个 0 会被写入 DS18B20。

//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(uint8_t dat)     
 {             
    uint8_t j;
    uint8_t testb;
    DS18B20_IO_OUT();     //设置为输出
    for (j=1;j<=8;j++) 
	{
        testb=dat&0x01;
        dat=dat>>1;
        if(testb)       // 写1
        {
            DS18B20_DQ_OUT=0;
            MY_Delay_us(2);  //两个写时隙之间至少有 1μs 的恢复时间                          
            DS18B20_DQ_OUT=1;
            MY_Delay_us(60);  //写时隙必须持续最少 60μs           
        }
        else            //写0
        {
            DS18B20_DQ_OUT=0;
            MY_Delay_us(60);   //写时隙必须持续最少 60μs            
            DS18B20_DQ_OUT=1;
            MY_Delay_us(2);  //两个写时隙之间至少有 1μs 的恢复时间                            
        }
    }
}

开启温度转换

程序流程:
1、初始化(复位、存在脉冲)
2、发送Skip ROM(忽略ROM) 指令(0xcc)
3、发送Convert T(温度转换)指令(0x44)
4、初始化(复位、存在脉冲)
5、发送Skip ROM(忽略ROM) 指令(0xcc)
6、发送Read Scratchpad(读取暂存器)指令(0xbe)
7、先获取低8位数据
8、再获取高8位数据
9、判断温度是正还是负
10、温度数据转换

//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    uint8_t temp;
    uint8_t TL,TH;
    short tem;
    DS18B20_Start ();           //开始转换
		DS18B20_Init();	
    DS18B20_Write_Byte(0xcc);   // skip rom
    DS18B20_Write_Byte(0xbe);   // convert	    
    TL=DS18B20_Read_Byte();     // LSB   
    TH=DS18B20_Read_Byte();     // MSB   
    if(TH>7)  //高5位为1温度值为负数
    {
        TH=~TH;
        TL=~TL; 
        temp=0;//温度为负  
    }
		else //高5位为0温度值为正数
			temp=1;//温度为正	  	  
    tem=TH; //获得高八位
    tem<<=8;    
    tem+=TL;//获得底八位
    tem=((double)tem*0.0625)*100;   //将采集到的数据*0.0625转换成实际温度
																	//*100保留两位为小数    
		if(temp)
			return tem; //返回温度值
		else 
			return -tem;    
}

STM32的IO口设置

我在用STM32CubeMX配置工程时发现,STM32的IO口要么只能设置成输入要么只能设置成输出,但是我们使用的是单总线,就是一个IO口纪要输出也要输入。这里提供两个方法:1、通过设置GPIO的寄存器来改变对应IO口的方向;2、就是把IO口配置成开漏输出模式,还要再在IO口接一个上拉电阻,并且在进行数据读取前要先把IO置1。第一个方法我实现过了的是可行的。
STM32的IO口有8中工作模式:
1、输入浮空
2、输入上拉
3、输入下拉
4、模拟输入
5、开漏输出
6、推挽输出
7、推挽式复用功能
8、开漏复用功能

STM32 的每个 IO 端口都有 7 个寄存器来控制。他们分别是:配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH(一个控制GPIO的低8位,一个控制GPIO的高8位);2 个 32 位的数据寄存器 IDR 和 ODR(一个控制输入,一个控制输出);1 个 32 位的置位/复位寄存器BSRR;一个 16 位的复位寄存器 BRR;1 个 32 位的锁存寄存器 LCKR;
我们只了解一下CRL、CRH、IDR、ODR。

CRL


具体参考正点原子的寄存器开发手册。

//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
   

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 

 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define DS18B20_Pin GPIO_PIN_2
#define DS18B20_GPIO_Port GPIOB

//IO方向设置
#define DS18B20_IO_IN()  {DS18B20_GPIO_Port->CRL&=0XFFFFF0FF;DS18B20_GPIO_Port->CRL|=8<<8;}
#define DS18B20_IO_OUT() {DS18B20_GPIO_Port->CRL&=0XFFFFF0FF;DS18B20_GPIO_Port->CRL|=3<<8;}

//IO操作函数											   
#define	DS18B20_DQ_OUT PBout(2) //数据端口	PB2
#define	DS18B20_DQ_IN  PBin(2)  //数据端口	PB2

微秒级延时

由于DS18B20要用到微秒延时,但是STM32CubeMX生成的工程中提供的延时函数只有一个毫秒级延时函数,所以需要我们自己编写一个。我这里采用的方法是配置一个定时器用于产生微秒级延时。
因为我选择的芯片是STM32F103C8T6,主频是72MHz,所以这里选择72分频,定时器得到的频率就是1MHz,即每1us记一次数。
添加代码:

//记得添加tim.h头文件
#define US_TIM	htim1

void MY_Delay_us(uint32_t us)
{
	__HAL_TIM_SetCounter(&US_TIM,0);  //先清零
	HAL_TIM_Base_Start(&US_TIM);  //开启定时器
	while(__HAL_TIM_GetCounter(&US_TIM) < us);
	HAL_TIM_Base_Stop(&US_TIM);
}

__HAL_TIM_GetCounter(&US_TIM)函数是获取当前计数值。

附上整个代码
ds18b20.c

#include "ds18b20.h"
#include "mydelay.h"

//复位DS18B20
void DS18B20_Rst(void)	   
{                 
	DS18B20_IO_OUT();   //设置为输出
	DS18B20_DQ_OUT=0;  	//拉低DQ
	MY_Delay_us(750);      //拉低750us
	DS18B20_DQ_OUT=1;  	//DQ=1 
	MY_Delay_us(15);       //15US
}

//等待DS18B20的回应
//DS18B20复位后会拉低总线60—240us来表示存在
//返回1:未检测到DS18B20的存在
//返回0:存在
uint8_t DS18B20_Check(void) 	   
{   
	uint8_t retry=0;
	DS18B20_IO_IN();    //设置为输入
  while (DS18B20_DQ_IN&&retry<200) 
	{
		retry++;
		MY_Delay_us(1);
	}	 
	if(retry>=200)return 1;
	else retry=0;
  while (!DS18B20_DQ_IN&&retry<240)
	{
		retry++;
		MY_Delay_us(1);
	}
	if(retry>=240)return 1;	    
	return 0;
}

/*初始化DS18B20
等待DS18B20的回应
DS18B20复位后会拉低总线60—240us来表示存在
返回1:未检测到DS18B20的存在
返回0:存在*/
void DS18B20_Init(void)	   
{  
	DS18B20_Rst();
	DS18B20_Check();
}

//从DS18B20读取一个位
//返回值:1/0
uint8_t DS18B20_Read_Bit(void) 
{
	uint8_t data;
	DS18B20_IO_OUT();   //设置为输出
	DS18B20_DQ_OUT=0; 
	MY_Delay_us(2);  //两个读时隙之间不少于1us
	DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();    //设置为输入
	MY_Delay_us(12);  //以上这个过程必须在15us内完成。然后判断数据是0还是1
	if(DS18B20_DQ_IN)data=1;
	else data=0;	
	
	MY_Delay_us(50);    //所有读时隙必须持续至少60us       
	return data;
}

//从DS18B20读取一个字节
//返回值:读到的数据
uint8_t DS18B20_Read_Byte(void)   
{        
	uint8_t i,j,dat;
	dat=0;
	for (i=1;i<=8;i++) 
	{
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
	return dat;
}

//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(uint8_t dat)     
 {             
    uint8_t j;
    uint8_t testb;
    DS18B20_IO_OUT();     //设置为输出
    for (j=1;j<=8;j++) 
	{
        testb=dat&0x01;
        dat=dat>>1;
        if(testb)       // 写1
        {
            DS18B20_DQ_OUT=0;
            MY_Delay_us(2);  //两个写时隙之间至少有 1μs 的恢复时间                          
            DS18B20_DQ_OUT=1;
            MY_Delay_us(60);  //写时隙必须持续最少 60μs           
        }
        else            //写0
        {
            DS18B20_DQ_OUT=0;
            MY_Delay_us(60);   //写时隙必须持续最少 60μs            
            DS18B20_DQ_OUT=1;
            MY_Delay_us(2);  //两个写时隙之间至少有 1μs 的恢复时间                            
        }
    }
}
 
//开始温度转换
void DS18B20_Start(void)
{   
    DS18B20_Init();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0x44);// convert
}

//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    uint8_t temp;
    uint8_t TL,TH;
    short tem;  //两个字节
    DS18B20_Start ();           //开始转换
		DS18B20_Init();	
    DS18B20_Write_Byte(0xcc);   // skip rom
    DS18B20_Write_Byte(0xbe);   // convert	    
    TL=DS18B20_Read_Byte();     // LSB   
    TH=DS18B20_Read_Byte();     // MSB   
    if(TH>7)  //高5位为1温度值为负数
    {
        TH=~TH;
        TL=~TL; 
        temp=0;//温度为负  
    }
		else //高5位为0温度值为正数
			temp=1;//温度为正	  	  
    tem=TH; //获得高八位
    tem<<=8;    
    tem+=TL;//获得底八位
    tem=((double)tem*0.0625)*100;   //将采集到的数据*0.0625转换成实际温度
																	//*100保留两位为小数  
		if(temp)
			return tem; //返回温度值
		else 
			return -tem;    
}

ds18b20.h

#ifndef __DS18B20_H
#define __DS18B20_H

#include "main.h"

//IO方向设置
#define DS18B20_IO_IN()  {DS18B20_GPIO_Port->CRL&=0XFFFFF0FF;DS18B20_GPIO_Port->CRL|=8<<8;}
#define DS18B20_IO_OUT() {DS18B20_GPIO_Port->CRL&=0XFFFFF0FF;DS18B20_GPIO_Port->CRL|=3<<8;}

//IO操作函数											   
#define	DS18B20_DQ_OUT PBout(2) //数据端口	PB2
#define	DS18B20_DQ_IN  PBin(2)  //数据端口	PB2

void DS18B20_Rst(void);	 
uint8_t DS18B20_Check(void);			//检测是否存在DS18B20  
void DS18B20_Init(void);			//初始化DS18B20

short DS18B20_Get_Temp(void);	//获取温度
void DS18B20_Start(void);		//开始温度转换
void DS18B20_Write_Byte(uint8_t dat);//写入一个字节
uint8_t DS18B20_Read_Byte(void);		//读出一个字节
uint8_t DS18B20_Read_Bit(void);		//读出一个位

记得在main.h里添加寄存器配置代码

/* USER CODE BEGIN Private defines */

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
   

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 

 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 


/* USER CODE END Private defines */
物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32和DS18B20传感器读取温度

发表评论