怎样用stm32驱动ds18b20温度芯片

一、DS18B20简介

DS18B20是一一款常用的数字温度传感器,使用单个引脚完成主从机的双向通信是这款芯片的特点。

物理连接简单,那自然有特殊的时序需要满足,这也是使用这款芯片时麻烦的一点。

由于DS18B20不支持我们熟悉的IIC和SPI协议,那也就没有现成的库函数来支持我们的读写操作,也就是需要我们自行编写驱动。

但本文并不打算从解读数据手册中的时序图开始说太多废话,只给把代码给出,介绍一下驱动编写时可能遇到的一些问题,结合代码给出解决方案。


根据数据手册,这款芯片通常有三种封装:


在这里插入图片描述


三者都是通过DQ这个引脚来进行与主机的通信。所以问题简化为:


控制与DQ连接的引脚的电平变化


实际上,ds18b20支持六个操作指令:


在这里插入图片描述


只需要按照一定电平协议把这些命令发出去,就能够从DQ读取到想要的数据。而这些指令都刚好只有8位(bit),也就是一个字节。于是乎,ds18b20驱动的编写进一步简化为四个小任务:


1、初始化配置
2、通过DQ读取一个字节
3、通过DQ发送一个字节
4、读取温度

二、驱动代码介绍


1、初始化配置

初始化包含两步:


1、`DQ`对应的引脚的GPIO配置
2、通过`DQ``ds18b20`芯片的初始化

对应驱动代码的这部分:


/**
  * @brief  配置DQ并复位DS18B20
  * @param   无
  * @retval  无
  */
void DS18B20_Init(void)
{
	/* 初始化GPIO */
	DS18B20_DQGPIOConfig();
	/* 复位 */
	DS18B20_Rst();
}

DS18B20_DQGPIOConfig()使能APB2总线时钟和GPIO时钟,配置DQ对应的引脚为推挽输出模式即可:


/**
  * @brief  配置DQ对应的GPIO引脚
  * @param   无
  * @retval  无
  */
void DS18B20_DQGPIOConfig(void)
{
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd(DQ_GPIO_CLK, ENABLE);
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = PINDQ;	

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

		/*调用库函数,初始化GPIO*/
		GPIO_Init(GPIODQ, &GPIO_InitStructure);	
}

上面的代码中包含一些宏定义:


/* DQ引脚相关宏定义 */
#define GPIODQ GPIOB
#define PINDQ GPIO_Pin_8
/* GPIO端口时钟 */
#define DQ_GPIO_CLK 	    RCC_APB2Periph_GPIOB		

:为了不至于看花眼,不会一次性将所有的宏和函数实现贴出来,说到哪儿就贴到那儿。同时也不用担心不好拼凑成完整的驱动程序,因为文末会放出驱动源代码链接。


重点看DS18B20_Rst()的实现:


/**
  * @brief  复位DS18B20
  */
void DS18B20_Rst(void)
{	
	DS18B20_SetDQMode_OUT();
	/* 先将数据线置高电平“1” */
	DQ_H;
	/* 延时(该时间要求的不是很严格,但是尽可能的短一点) */
	Delay_us(1);
	/* 数据线拉到低电平“0”。 */
	DQ_L;
	/* 延时750微秒(该时间的时间范围可以从480到960微秒) */
	Delay_us(750);
	/* 数据线拉到高电平“1” */
	DQ_H;
	/* 延时等待(如果初始化成功则在15到60微秒时间之内产生一个由
	DS18B20所返回的低电平“0”。据该状态可以来确定它的存在,
	但是应注意不能无限的进行等待,不然会使程序进入死循环,
	所以要进行超时控制)。 */
	/* 超时时间不要设置得太短(500us是足够的),
		否则初始化失败,将获取不到环境温度 */
	Delay_us(500);
	uint8_t dqStatus = GPIO_ReadInputDataBit(GPIODQ,PINDQ);
	/*  将数据线再次拉高到高电平“1”后结束。 */
	DQ_H;
}

注意看每一步的注释,交代了初始化过程中DQ的电平变化。

其中最后一个延时函数:


Delay_us(500);

尤为关键,如果这里的等待时间不足,将导致初始化失败,出现温度值不变的怪病。


DS18B20_SetDQMode_OUT()是把DQ对应的引脚设置为推挽输出模式:


/**
  * @brief  设置DQ对应的引脚位推挽输出模式
  * @param   无 
  * @retval  无
  */
static void DS18B20_SetDQMode_OUT()
{
	/*定义一个GPIO_InitTypeDef类型的结构体*/
	GPIO_InitTypeDef GPIO_InitStructure;
	/*选择要控制的GPIO引脚*/
	GPIO_InitStructure.GPIO_Pin = PINDQ;	
	/*设置引脚模式为通用推挽输出*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
	/*设置引脚速率为50MHz */   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	/*调用库函数,初始化GPIO*/
	GPIO_Init(GPIODQ, &GPIO_InitStructure);	
}

DQ_H()DQ_L()分别是将DQ对应的引脚设置为高电平和低电平:


#define DQ_H GPIO_SetBits(GPIODQ,PINDQ)
#define DQ_L GPIO_ResetBits(GPIODQ,PINDQ)

Delay_us()的实现如下:


/**
  * @brief 延时函数
  * @param us_cnt 设定的延时微秒数
  * @retval 无
  */
void Delay_us(uint32_t us_cnt)
{
    TIM3->CNT = us_cnt-1;
    TIM3->CR1 |= TIM_CR1_CEN;
    while((TIM3->SR & TIM_FLAG_Update)!=SET);
    TIM3->SR = (uint16_t)~TIM_FLAG_Update;
    TIM3->CR1 &= ~TIM_CR1_CEN;
}


这个函数需要配置定时器以支持计时:


/**
  * @brief 配置延时定时器
  * @param us_cnt 设定的延时微秒数
  * @retval 无
*/
void Delay_Timer_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Down;
    TIM_TimeBaseInitStruct.TIM_Period = 100-1;
    TIM_TimeBaseInitStruct.TIM_Prescaler = (84-1);
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

    while((TIM3->SR & TIM_FLAG_Update)!=SET);
    TIM3->SR = (uint16_t)~TIM_FLAG_Update;
}

2、通过DQ读取一个字节


/**
  * @brief  通过DQ从DS18B20读取一个字节(Byte)
  * @param   无
  * @retval  读取到的数据(uint8_t类型)
  */
uint8_t DS18B20_ReadByte(void)
{
	uint8_t data = 0x00;
	for(int i=0;i<8;i++)
	{
		DS18B20_SetDQMode_OUT();
		/* 将数据线拉高“1” */
		DQ_H;
		/* 延时2微秒 */
		Delay_us(2);
		/* 将数据线拉低“0” */
		DQ_L;
		/* 延时3微秒 */
		Delay_us(3); 
		data>>=1;
		/* 将数据线拉高“1” */
		DQ_H;
		DS18B20_SetDQMode_IPU();
		/* 延时5微秒 */
		Delay_us(5);
		/* 读数据线的状态得到1个状态位,并进行数据处理 */
		if(GPIO_ReadInputDataBit(GPIODQ,PINDQ))
			data|=0x80;
		/* 延时60微秒 */
		Delay_us(60);
	}
	return data;
}

同样的,注意阅读注释中的描述。

DS18B20_SetDQMode_IPU()把DQ对应的引脚设置为输入模式:


/**
  * @brief  设置DQ对应的引脚为输入模式
  * @param   无  
  * @retval  无
  */
static void DS18B20_SetDQMode_IPU()
{
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = PINDQ;	

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   

		/*调用库函数,初始化GPIO*/
		GPIO_Init(GPIODQ, &GPIO_InitStructure);	
}

3、通过DQ发送一个字节


/**
* @brief  通过DQ向DS18B20发送一个字节(Byte)
* @param   
	@arg data 待发送的1字节数据
* @retval  无
  */
void DS18B20_WriteByte(uint8_t data)
{
	for(int i=0;i<8;i++){
		/* 数据线先置低电平“0” */
		DQ_L;
		/* 延时确定的时间为15微秒 */
		Delay_us(15);
		/* 按从低位到高位的顺序发送字节(一次只发送一位) */
		if(data&0x01){
			GPIO_WriteBit(GPIODQ,PINDQ,Bit_SET);		
		}else{
			GPIO_WriteBit(GPIODQ,PINDQ,Bit_RESET);
		}
		/* 延时时间为45微秒 */
		Delay_us(45);
		/* 将数据线拉到高电平 */
		DQ_H;
		data>>=1;
	}
}

4、读取温度


/**
  * @brief  获取温度寄存器的值并转换为温度值返回
  * @param   无
  * @retval  无
  */
float DS18B20_GetTemp(void)
{
	DS18B20_Rst();
	DS18B20_WriteByte(SKIPROMCOMMAND);
	DS18B20_WriteByte(CONVERTT);
	DS18B20_Rst();
	DS18B20_WriteByte(SKIPROMCOMMAND);
	DS18B20_WriteByte(READSCRATCHPAD);
	uint8_t tempL = DS18B20_ReadByte();
	uint8_t tempH = DS18B20_ReadByte();
	/* 反码 */
	if(tempH>0x7f)
	{
		tempL = ~tempL;
		tempH = ~tempH+1;
	}
	/* 计算温度值 */
	float temp = ((tempH<<4)|(tempL>>4))+
	(float)(tempL&0x0f)*0.0625; 

	return temp;
}


其中的SKIPROMCOMMANDCONVERTTREADSCRATCHPAD都是 ds18b20 的控制指令:


在这里插入图片描述
在这里插入图片描述


温度计算的部分需要注意一下:


/* 计算温度值 */
float temp = ((tempH<<4)|(tempL>>4))+
(float)(tempL&0x0f)*0.0625; 

数据手册的表述如下:


在这里插入图片描述


1、黑色-划掉的是多余的符号位
2、红色-对应着整数位
3、蓝色-下划线部分对应小数

首先,(高8位左移4位)或上(低八位右移4位),得到8位的整数部分:


((tempH<<4)|(tempL>>4))

然后,低8位去掉左边4位:


(tempL&0x0f)

乘2^(-4)化成小数:


(float)(tempL&0x0f)*0.0625

整数部分和小数部分加起来得到温度值:


float temp = ((tempH<<4)|(tempL>>4))+
(float)(tempL&0x0f)*0.0625; 

好啦,ds18b20的驱动就介绍完了,源代码的链接放在下面:


基于stm32F1标准库开发的DS18B20驱动

timer.x提供时序控制支持
ds18b20.x是驱动内容,供以读取温度数据


  • 31
    点赞
  • 149
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值