imuncle.github.io
imuncle.github.io copied to clipboard
DS18B20温度传感器数据读取
trafficstars
DS18B20温度传感器提供9-Bits到12-Bits的高精度温度数据,我手上的封装类型如下:

一开始盲目自信,觉得这就是一个三极管,两端电压随温度改变,直接ADC读取电压值就行了,结果半天没反应,网上一搜数据手册才知道,这温度传感器中大有文章。
DS18B20简介
单线接口
DS18B20有其独特的单线接口方式,常见的通信协议都至少是两根线,而DS18B20利用它自己的一套通信协议实现了单线半双工通信。
通过这唯一的信号线可以实现数据读取,配置寄存器等操作,温度数据直接以数字信号形式返回来,比ADC高级多了。
唯一ROM
每一个DS18B20在出厂的时候都自带了唯一的光刻64位ROM,前8位是传感器的ID,这样每一个DS18B20的ID都不一样,可以实现总线的功能,理论上总线上的设备可以无限多,而这些都是在一根信号线上完成的。
读取DS18B20数据
DS18B20对逻辑0和逻辑1有自己的定义,且收发不同,具体见下图:

DS18B20的可操作寄存器如下表所示:

DS18B20的每一次读/写操作都必须按照下面的步骤进行:
TRANSACTION SEQUENCE
The transaction sequence for accessing the DS18B20 is as follows:
- Step 1. Initialization
- Step 2. ROM Command (followed by any required data exchange)
- Step 3. DS18B20 Function Command (followed by any required data exchange) It is very important to follow this sequence every time the DS18B20 is accessed, as the DS18B20 will not respond if any steps in the sequence are missing or out of order. Exceptions to this rule are the Search ROM [F0h] and Alarm Search [ECh] commands. After issuing either of these ROM commands, the master must return to Step 1 in the sequence.
实现代码
这里涉及到了微秒延时,实现方法可参考HAL库实现us级延时。
/**
* 函数功能: 使DS18B20-DATA引脚变为上拉输入模式
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Mode_IPU(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 串口外设功能GPIO配置 */
GPIO_InitStruct.Pin = GPIO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
/**
* 函数功能: 使DS18B20-DATA引脚变为推挽输出模式
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Mode_Out_PP(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 串口外设功能GPIO配置 */
GPIO_InitStruct.Pin = GPIO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
/**
* 函数功能: 主机给从机发送复位脉冲
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Rst(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* 主机设置为推挽输出 */
DS18B20_Mode_Out_PP(GPIOx, GPIO_Pin);
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
/* 主机至少产生480us的低电平复位信号 */
DS18B20_Delay(750);
/* 主机在产生复位信号后,需将总线拉高 */
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
/*从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲*/
DS18B20_Delay(15);
}
/**
* 函数功能: 检测从机给主机返回的存在脉冲
* 输入参数: 无
* 返 回 值: 0:成功,1:失败
* 说 明:无
*/
static uint8_t DS18B20_Presence(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t pulse_time = 0;
/* 主机设置为上拉输入 */
DS18B20_Mode_IPU(GPIOx, GPIO_Pin);
/* 等待存在脉冲的到来,存在脉冲为一个60~240us的低电平信号
* 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲
*/
while( HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) && pulse_time<100 )
{
pulse_time++;
DS18B20_Delay(1);
}
/* 经过100us后,存在脉冲都还没有到来*/
if( pulse_time >=100 )
return 1;
else
pulse_time = 0;
/* 存在脉冲到来,且存在的时间不能超过240us */
while( !HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) && pulse_time<240 )
{
pulse_time++;
DS18B20_Delay(1);
}
if( pulse_time >=240 )
return 1;
else
return 0;
}
/**
* 函数功能: DS18B20 初始化函数
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
uint8_t DS18B20_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
DS18B20_Mode_Out_PP(GPIOx, GPIO_Pin);
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
DS18B20_Rst(GPIOx, GPIO_Pin);
return DS18B20_Presence (GPIOx, GPIO_Pin);
}
/**
* 函数功能: 从DS18B20读取一个bit
* 输入参数: 无
* 返 回 值: 读取到的数据
* 说 明:无
*/
static uint8_t DS18B20_ReadBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t dat;
/* 读0和读1的时间至少要大于60us */
DS18B20_Mode_Out_PP(GPIOx, GPIO_Pin);
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
DS18B20_Delay(10);
/* 设置成输入,释放总线,由外部上拉电阻将总线拉高 */
DS18B20_Mode_IPU(GPIOx, GPIO_Pin);
if( HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == SET )
dat = 1;
else
dat = 0;
/* 这个延时参数请参考时序图 */
DS18B20_Delay(45);
return dat;
}
/**
* 函数功能: 从DS18B20读一个字节,低位先行
* 输入参数: 无
* 返 回 值: 读到的数据
* 说 明:无
*/
static uint8_t DS18B20_ReadByte(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t i, j, dat = 0;
for(i=0; i<8; i++)
{
j = DS18B20_ReadBit(GPIOx, GPIO_Pin);
dat = (dat) | (j<<i);
}
return dat;
}
/**
* 函数功能: 写一个字节到DS18B20,低位先行
* 输入参数: dat:待写入数据
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_WriteByte(uint8_t dat, GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t i, testb;
DS18B20_Mode_Out_PP(GPIOx, GPIO_Pin);
for( i=0; i<8; i++ )
{
testb = dat&0x01;
dat = dat>>1;
/* 写0和写1的时间至少要大于60us */
if (testb)
{
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
/* 1us < 这个延时 < 15us */
DS18B20_Delay(8);
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
DS18B20_Delay(58);
}
else
{
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
/* 60us < Tx 0 < 120us */
DS18B20_Delay(70);
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
/* 1us < Trec(恢复时间) < 无穷大*/
DS18B20_Delay(2);
}
}
}
/**
* 函数功能: 跳过匹配 DS18B20 ROM
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_SkipRom (GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
DS18B20_Rst(GPIOx, GPIO_Pin);
DS18B20_Presence(GPIOx, GPIO_Pin);
DS18B20_WriteByte(0XCC, GPIOx, GPIO_Pin); /* 跳过 ROM */
}
/*
* 存储的温度是16 位的带符号扩展的二进制补码形式
* 当工作在12位分辨率时,其中5个符号位,7个整数位,4个小数位
*
* |---------整数----------|-----小数 分辨率 1/(2^4)=0.0625----|
* 低字节 | 2^3 | 2^2 | 2^1 | 2^0 | 2^(-1) | 2^(-2) | 2^(-3) | 2^(-4) |
*
*
* |-----符号位:0->正 1->负-------|-----------整数-----------|
* 高字节 | s | s | s | s | s | 2^6 | 2^5 | 2^4 |
*
*
* 温度 = 符号位 + 整数 + 小数*0.0625
*/
/**
* 函数功能: 在跳过匹配 ROM 情况下获取 DS18B20 温度值
* 输入参数: 无
* 返 回 值: 温度值
* 说 明:无
*/
float DS18B20_GetTemp_SkipRom (GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t tpmsb, tplsb;
short s_tem;
float f_tem;
DS18B20_SkipRom (GPIOx, GPIO_Pin);
DS18B20_WriteByte(0X44, GPIOx, GPIO_Pin); /* 开始转换 */
DS18B20_SkipRom (GPIOx, GPIO_Pin);
DS18B20_WriteByte(0XBE, GPIOx, GPIO_Pin); /* 读温度值 */
tplsb = DS18B20_ReadByte(GPIOx, GPIO_Pin);
tpmsb = DS18B20_ReadByte(GPIOx, GPIO_Pin);
s_tem = tpmsb<<8;
s_tem = s_tem | tplsb;
if( s_tem < 0 ) /* 负温度 */
f_tem = (~s_tem+1) * 0.0625;
else
f_tem = s_tem * 0.0625;
return f_tem;
}
在main.c中的调用:
int main()
{
//...
while (1)
{
DS18B20_Init(GPIOA, GPIO_PIN_6);
tempareture1 = DS18B20_GetTemp_SkipRom(GPIOA, GPIO_PIN_6);
}
}
因为我只挂载了一个DS18B20(接在PA6上),所以不需要用到ROM和ID,这里直接跳过ROM读取温度的值。