特此鸣谢:b站的up主 江协科技 有一些图是网上的,侵权删
该篇文章是看了b站博主总结的,该视频把原理还有代码细节讲的非常非常详细。如果有时间,推荐看视频。
[13-1] DS18B20温度传感器_哔哩哔哩_bilibili
【免费】中英文手册和源码下载(好处之一:该中文手册连流程图的英文都翻译了)
https://download.youkuaiyun.com/download/qq_62975093/90165423
一、DS18B20温度传感器介绍
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点
图片第一张是防水的可以测水温的,第二种不防水。但原理都一样
特点:
测温范围为-55℃到+125℃,在-10℃到+85℃范围内误差为±0.4°
通信接口:1-Wire(单总线),即使用单线进行数据的发送和接收
返回16位二进制温度数值,温度计分辨率可设置为9~12位(默认12位)
可形成总线结构(将任意多的DS18b20挂载到一根总线上,通过ROM搜索读取相应DS18B20的温度值)
独立供电或可寄生供电(不需要再外部供电下即可工作,只接DQ和GND。当总线高电平时能量由单线上拉电阻经过DQ引脚获得。高电平同时充电一个内部电容,当总线低电平时由此电容供应能量。这种供电方法被称为“寄生电源”。)
二、原理图与接线
DQ引脚单总线上必须有一个上拉电阻(R1)以实现单总线闲置时,其处于高电平状态,也可配置为开漏输出模式
原因:
【常用传感器】DS18B20温度传感器原理详解及例程代码-优快云博客
本项目使用的是外部电源供电
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路(像读EEPROM等耗电操作仅仅靠弱上拉是不行的)
给它一个强上拉就相当于给它一个非常高的电源正极,以达到内部供电的稳定。
三、内部框图
Temperature Sensor温度测量传感器。它相当于内部的一个模拟温度传感器,它自动把这些温度转换。当我们发出指令让它开始温度转换的时候,这个温度传感器就工作,然后把它的数据放在我们的暂存器RAM里面,然后再进行数据交互,就可以把温度值拿出来了。
1、如果是寄生供电
那个电源就是从DQ进来的,向内部的控制器供电。
当总线高电平时能量由单线上拉电阻经过DQ引脚获得。高电平同时充电一个内部电容,当总线低电平时由此电容cpp供应能量给内部。
这次我们的程序将不会用到寄生供电,因为寄生供电还需要一个强上拉,而我们的电路板没有强上拉的电路。我们直接使用这个外部VDD供电,就不用考虑寄生供电的部分了
2、暂存器
高速暂存器由9个字节组成
字节0~1 是温度存储器,用来存储转换好的温度。第0个字节存储温度低8位,第一个字节存储温度高8位。有一个默认值,如果上电直接读的话,默认就是85摄氏度
字节2~3 是用户用来设置最高报警和最低报警值(TH和TL)。
字节4 是配置寄存器,用来配置转换精度,可以设置为9~12 位。默认12位。
字节5~7 保留位。芯片内部使用
字节8 8位CRC生成器(CRC,循环冗余码,一种校验码)
暂存器的字节2~字节4和EEPROM上的字节一样,为什么呢?往暂存器里写东西的时候,我们要发送一条指令把暂存器里的东西复制到EEPROM里面。当然我们也可以发送指令将EEPROM里面的东西回调到暂存器里面。即不能直接读写EEPROM,需要先将数据写入暂存器,然后通过通信,将数据在写入EEPROM,读出数据同理。
掉电后,EEPROM数据还在,暂存器数据不保存。
(1)温度数据寄存器LSB/MSB格式
当温度转换命令发布后,经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第1个字节。
高字节的五个S为符号位,温度为正值时S=1,温度为负值时S=0
①当五个符号位S=0时,温度为正值,直接将后面的11位二进制转换为十进制,再乘以分辨率对应的最小刻度,就可以得到温度值;
②当五个符号位S=1时,温度为负值,先全部取反后加1,再计算十进制值。再乘以分辨率对应的最小刻度,在乘负数,就可以得到温度值;
分辨率 | 最小刻度 |
9位 | 0.5℃ |
10位 | 0.25℃ |
11位 | 0.125℃ |
12位 | 0.0625℃ |
默认12位分辨率(2^-4=0.0625,4位小数位) |
3、指令
只需关注三个指令
①CC:忽略ROM指令。因为只连接了一个DS18B20传感器,不需要ROM搜索
②44:温度转换
③BE:读取温度
此图非原创
四、时序
单总线是一种半双工通信方式,DS18B20共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前
1、初始化
/**************************************************************************************
* 描 述 : 主机给从机发送复位脉冲
* 入 参 : 无
* 返回值 : 无
**************************************************************************************/
static void DS18B20_Rst(void)
{
DS18B20_Output_Input(DS18B20_OUTPUT); //主机设置为推挽输出
DS18B20_DATA_OUT(LOW); //主机至少产生480us的低电平复位信号
delay_us(480);
DS18B20_DATA_OUT(HIGH); //主机在产生复位信号后,需将总线拉高
delay_us(15); //从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲
}
/**************************************************************************************
* 描 述 : 检测从机给主机返回的存在脉冲
* 入 参 : 无
* 返回值 : 0:成功 1:失败
**************************************************************************************/
static u8 DS18B20_Presence(void)
{
u8 pulse_time = 0;
DS18B20_Output_Input(DS18B20_INPUT); //主机设置为上拉输入
/* 等待存在脉冲的到来,存在脉冲为一个60~240us的低电平信号
* 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,若存在会在15~60us后给主机发一个存在脉冲
*/
while( DS18B20_DATA_IN() && pulse_time<100 )
{
pulse_time++;
delay_us(1);
}
if( pulse_time >=100 ) //经过100us后,存在脉冲都还没有到来
return 1; //读取失败
else //经过100us后,存在脉冲到来
pulse_time = 0; //清零计时变量
while( !DS18B20_DATA_IN() && pulse_time<240 ) // 存在脉冲到来,且存在的时间不能超过240us
{
pulse_time++;
delay_us(1);
}
if( pulse_time >=240 ) // 存在脉冲到来,且存在的时间超过了240us
return 1; //读取失败
else
return 0;
}
[点击并拖拽以移动]
2、写
总线控制器通过控制单总线高低电平持续时间从而把逻辑1或0写DS18B20中。每次只传输1位数据
①单片机想要给DS18B20写入一个0时,需要将单片机引脚拉低,保持低电平时间要在60~120us之间,然后释放总线
②单片机想要给DS18B20写入一个1时,需要将单片机引脚拉低,拉低时间需要大于1us,然后在15us内拉高总线.
注意:
先写命令的低八位字节,在写高八位字节
每个写时段最小必须有 60us 的持续时间且 独立的写时段间至少有 1us 的恢复时间。
/**************************************************************************************
* 描 述 : 写一个字节到DS18B20,低位先行
* 入 参 : u8
* 返回值 : 无
**************************************************************************************/
void DS18B20_Write_Byte(u8 dat)
{
u8 i, testb;
DS18B20_Output_Input(DS18B20_OUTPUT);
for( i=0; i<8; i++ )
{
testb = dat&0x01;
dat = dat>>1;
/* 写0和写1的时间至少要大于60us */
if (testb)
{
DS18B20_DATA_OUT(LOW);
delay_us(8); //1us < 这个延时 < 15us
DS18B20_DATA_OUT(HIGH);
delay_us(58); //58us+8us>60us
}
else
{
DS18B20_DATA_OUT(LOW);
/* 60us < Tx 0 < 120us */
delay_us(70);
DS18B20_DATA_OUT(HIGH);
/* 1us < Trec(恢复时间) < 无穷大*/
delay_us(2);
}
}
}
3、读
①主机将总线拉低1~15us,然后释放总线
②在总线拉低后 15us内,主机读取总线电平(尽量贴近15us的末尾,因为传感器电平稳定和传输需要时间),读取为低电平则为接收0,读取为高电平则为接收1
注意:
主机只有在发送读暂存器命令(0xBE)或读电源类型命令(0xB4)后,立即生成读时隙指令,DS18B20才能向主机传送数据。 也就是先发读取指令,再发送读时隙
读时序时是先读低字节,在读高字节
所有读时隙必须至少需要60us,且在两次独立的时隙之间至少需要1ps的恢复时间
/**************************************************************************************
* 描 述 : 从DS18B20读取一个bit
* 入 参 : 无
* 返回值 : u8
**************************************************************************************/
static u8 DS18B20_Read_Bit(void)
{
u8 dat;
/* 读0和读1的时间至少要大于60us */
DS18B20_Output_Input(DS18B20_OUTPUT);
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
DS18B20_DATA_OUT(LOW);
delay_us(10);
/* 设置成输入,释放总线,由外部上拉电阻将总线拉高 */
DS18B20_Output_Input(DS18B20_INPUT);
if( DS18B20_DATA_IN() == SET )
dat = 1;
else
dat = 0;
/* 这个延时参数请参考时序图 */
delay_us(45);/*DS18B20中输出的数据在初始化读时序后仅有15us的有效时间
每个读时段最小必须有60us的持续时间 */
return dat;
}
/**************************************************************************************
* 描 述 : 从DS18B20读一个字节,低位先行
* 入 参 : 无
* 返回值 : u8
**************************************************************************************/
u8 DS18B20_Read_Byte(void)
{
u8 i, j, dat = 0;
for(i=0; i<8; i++)
{
j = DS18B20_Read_Bit(); //从DS18B20读取一个bit
dat = (dat) | (j<<i);
}
return dat;
}
五、代码
此图非原创
#include "ds18b20.h"
#include "stm32f10x.h"
/**************************************************************************************
* 描 述 : 配置DS18B20用到的I/O口 PB12
* 入 参 : 无
* 返回值 : 无
**************************************************************************************/
static void DS18B20_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DS18B20_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DS18B20_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DS18B20_PORT, &GPIO_InitStructure);
GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); //DS18B20数据引脚初始化配置为高电平输出
}
/**************************************************************************************
* 描 述 : 配置DS18B20-DATA引脚模式
* 入 参 : cmd控制DS18B20-DATA引脚,1为输出模式,0为输入模式
* 返回值 : 无
**************************************************************************************/
static void DS18B20_Output_Input(DS18B20_Mode cmd){
GPIO_InitTypeDef GPIO_InitStructure;
if(cmd==DS18B20_OUTPUT){ //cmd为1——输出模式
GPIO_InitStructure.GPIO_Pin = DS18B20_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
}else{//cmd为0——输入模式
GPIO_InitStructure.GPIO_Pin = DS18B20_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
}
GPIO_Init(DS18B20_PORT,&GPIO_InitStructure);
}
/**************************************************************************************
* 描 述 : 主机给从机发送复位脉冲
* 入 参 : 无
* 返回值 : 无
**************************************************************************************/
static void DS18B20_Rst(void)
{
DS18B20_Output_Input(DS18B20_OUTPUT); //主机设置为推挽输出
DS18B20_DATA_OUT(LOW); //主机至少产生480us的低电平复位信号
delay_us(480);
DS18B20_DATA_OUT(HIGH); //主机在产生复位信号后,需将总线拉高
delay_us(15); //从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲
}
/**************************************************************************************
* 描 述 : 检测从机给主机返回的存在脉冲
* 入 参 : 无
* 返回值 : 0:成功 1:失败
**************************************************************************************/
static u8 DS18B20_Presence(void)
{
u8 pulse_time = 0;
DS18B20_Output_Input(DS18B20_INPUT); //主机设置为上拉输入
/* 等待存在脉冲的到来,存在脉冲为一个60~240us的低电平信号
* 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,若存在会在15~60us后给主机发一个存在脉冲
*/
while( DS18B20_DATA_IN() && pulse_time<100 )
{
pulse_time++;
delay_us(1);
}
if( pulse_time >=100 ) //经过100us后,存在脉冲都还没有到来
return 1; //读取失败
else //经过100us后,存在脉冲到来
pulse_time = 0; //清零计时变量
while( !DS18B20_DATA_IN() && pulse_time<240 ) // 存在脉冲到来,且存在的时间不能超过240us
{
pulse_time++;
delay_us(1);
}
if( pulse_time >=240 ) // 存在脉冲到来,且存在的时间超过了240us
return 1; //读取失败
else
return 0;
}
/**************************************************************************************
* 描 述 : 从DS18B20读取一个bit
* 入 参 : 无
* 返回值 : u8
**************************************************************************************/
static u8 DS18B20_Read_Bit(void)
{
u8 dat;
/* 读0和读1的时间至少要大于60us */
DS18B20_Output_Input(DS18B20_OUTPUT);
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
DS18B20_DATA_OUT(LOW);
delay_us(10);
/* 设置成输入,释放总线,由外部上拉电阻将总线拉高 */
DS18B20_Output_Input(DS18B20_INPUT);
if( DS18B20_DATA_IN() == SET )
dat = 1;
else
dat = 0;
/* 这个延时参数请参考时序图 */
delay_us(45);/*DS18B20中输出的数据在初始化读时序后仅有15us的有效时间
每个读时段最小必须有60us的持续时间 */
return dat;
}
/**************************************************************************************
* 描 述 : 从DS18B20读一个字节,低位先行
* 入 参 : 无
* 返回值 : u8
**************************************************************************************/
u8 DS18B20_Read_Byte(void)
{
u8 i, j, dat = 0;
for(i=0; i<8; i++)
{
j = DS18B20_Read_Bit(); //从DS18B20读取一个bit
dat = (dat) | (j<<i);
}
return dat;
}
/**************************************************************************************
* 描 述 : 写一个字节到DS18B20,低位先行
* 入 参 : u8
* 返回值 : 无
**************************************************************************************/
void DS18B20_Write_Byte(u8 dat)
{
u8 i, testb;
DS18B20_Output_Input(DS18B20_OUTPUT);
for( i=0; i<8; i++ )
{
testb = dat&0x01;
dat = dat>>1;
/* 写0和写1的时间至少要大于60us */
if (testb)
{
DS18B20_DATA_OUT(LOW);
delay_us(8); //1us < 这个延时 < 15us
DS18B20_DATA_OUT(HIGH);
delay_us(58); //58us+8us>60us
}
else
{
DS18B20_DATA_OUT(LOW);
/* 60us < Tx 0 < 120us */
delay_us(70);
DS18B20_DATA_OUT(HIGH);
/* 1us < Trec(恢复时间) < 无穷大*/
delay_us(2);
}
}
}
/**************************************************************************************
* 描 述 : DS18B20初始化函数
* 入 参 : 无
* 返回值 : u8
**************************************************************************************/
u8 DS18B20_Init(void)
{
DS18B20_GPIO_Config();
DS18B20_Rst();
return DS18B20_Presence();
}
/**************************************************************************************
* 描 述 : 从DS18B20读取温度值
* 入 参 : 无
* 返回值 : float
**************************************************************************************/
float DS18B20_Get_Temp(void)
{
u8 tpm_MSB, tpm_LSB;
short temp;
float temperature;
DS18B20_Rst(); //主机给从机发送复位脉冲
DS18B20_Presence(); //检测从机给主机返回的存在脉冲
DS18B20_Write_Byte(0XCC); /* 跳过 ROM */
DS18B20_Write_Byte(0X44); /* 开始转换 */
DS18B20_Rst();
DS18B20_Presence();
DS18B20_Write_Byte(0XCC); /* 跳过 ROM */
DS18B20_Write_Byte(0XBE); /* 读温度值 */
tpm_LSB = DS18B20_Read_Byte();
tpm_MSB = DS18B20_Read_Byte();
temp = tpm_MSB<<8;
temp = temp | tpm_LSB;
if( (temp & 0xF800) == 0 ) /* 判断前五位的符号位是否都为0,为0温度为正 */
temperature = (temp * 0.625)+0.5; //扩大十倍,目的将小数点后第一位也转换为可显示数字
//加减0.5目的是四舍五入操作。
else
temperature = -((~temp+1) * 0.625)-0.5; //取反加一变原码,乘以负号,是因为温度为负数
return temperature;
}
/*************************************END OF FILE******************************/
DS18B20.h
#ifndef __DS18B20_H
#define __DS18B20_H
#include "stm32f10x.h"
#include "delay.h"
#define HIGH 1
#define LOW 0
#define DS18B20_CLK RCC_APB2Periph_GPIOB
#define DS18B20_PIN GPIO_Pin_12
#define DS18B20_PORT GPIOB
//带参宏,可以像内联函数一样使用,输出高电平或低电平
#define DS18B20_DATA_OUT(a) if (a) \
GPIO_SetBits(GPIOB,GPIO_Pin_12);\
else \
GPIO_ResetBits(GPIOB,GPIO_Pin_12)
//读取引脚的电平
#define DS18B20_DATA_IN() GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)
typedef struct
{
u8 humi_int; //湿度的整数部分
u8 humi_deci; //湿度的小数部分
u8 temp_int; //温度的整数部分
u8 temp_deci; //温度的小数部分
u8 check_sum; //校验和
}DS18B20_Data_TypeDef;
typedef enum {
DS18B20_INPUT = 0, // 定义枚举值为0,表示输入模式
DS18B20_OUTPUT = 1 // 定义枚举值为1,表示输出模式
} DS18B20_Mode;
u8 DS18B20_Init(void);
float DS18B20_Get_Temp(void);
#endif /* __DS18B20_H */
main.c
/************************************************************************************
* 文件名 :main.c
* 描述 :
* 硬件连接:
* DS18B20传感器模块: VCC -> 5V; GND -> GND; DAT -> PB12;
*
* 功能描述:测量温度(ADC1、PA2、DMA方式读取);
串口1接收测量所得的浊度值(波特率115200);
**********************************************************************************/
#include "stm32f10x.h"
#include "bsp_usart1.h"
#include <string.h>
#include "delay.h"
#include "ds18b20.h"
float temp_data=0.0;
void TEMP_Value_Conversion()
{
temp_data=DS18B20_Get_Temp();
}
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
DS18B20_Init();
/* 配置USART1 */
USART1_Config();
while(1)
{
TEMP_Value_Conversion();
printf("%f\n",temp_data);
delay_ms(1000);
}
}
/*********************************************END OF FILE**********************/
六、代码注意事项
1、刚上电,就打印或者显示默认值85℃
原因:因为稳定转换需要时间,如果是12位分辨率,则转换时间为750ms
解决:转换后,给一个充分时间的延时
视频0:41:46处
[13-2] DS18B20温度读取&温度报警器_哔哩哔哩_bilibili
2、单总线BUG:读取温度时,会被其他中断打断,扰乱读取
原因:单总线使用的是绝对时间延时,无时钟线
解决:写时序时,把中断屏蔽。写完时序再打开中断。不过最好是用多线程,即RTOS系统
视频1:04:25处
[13-2] DS18B20温度读取&温度报警器_哔哩哔哩_bilibili
3、主机拉低电平,传感器怎么知道是读还是写。默认是写,只有发读指令后,传感器才会发数据。而且读写有主机控制。