1、DS18B20驱动
一般常见的DS18B20如下所示,当然也有那种金属头防水的,本质上用的芯片还是这个,这里我们不对他进行单独介绍。
根据官方的数据手册,关于DS18B20的描述,这里翻译如下:
官方的数据手册我也放到我的gitee了,有需要的可以直接下载: DS18B20
DS18B20 数字温度计提供 9 至 12 位(可配置)温度读数,用于指示设备的温度。信息通过 1-Wire 接口发送到 DS18B20 或从 DS18B20 发送,因此只有一根线(和地线)需要从中央微处理器连接到 DS18B20。读取、写入和执行温度转换的电源可以来自数据线本身,无需外部电源。因为每个 DS18B20 都包含一个唯一的芯片序列号,所以多个 DS18B20 可以存在于同一条 1-Wire 总线上。这允许将温度传感器放置在许多不同的地方。此功能有用的应用包括 HVAC 环境控制、建筑物、设备或机械内部的温度感应以及过程监控。
总结一下就是:
- 单总线,并且每个设备出厂唯一地址,所以理论上可以不断扩展设备
- 精度9-12位
- 最少仅仅接两根线(信号和地即可完成控制)
一般我们常用的原理图如下所示:
关于DS18B20的数据读取,官方手册描述如下:
与 DS18B20 的通信是通过 1-Wire 端口进行的。对于 1-Wire 端口,在 ROM 功能协议建立之前,存储器和控制功能将不可用。主机必须首先提供五个 ROM 功能命令之一:
- (1)读取 ROM
- (2)匹配 ROM
- (3)搜索 ROM
- (4)跳过 ROM
- (5) 报警搜索
这些命令在每个器件的 64 位激光 ROM 部分上运行,如果 1-Wire 线路上存在许多器件,则可以挑选出特定器件,并向总线主机指示存在器件的数量和类型。在成功执行 ROM 功能序列后,可以访问内存和控制功能,然后主机可以提供六个内存和控制功能命令中的任何一个。
一个控制功能命令指示 DS18B20 执行温度测量。该测量结果将被放入 DS18B20 的暂存器存储器中,并且可以通过发出读取暂存器存储器内容的存储器功能命令来读取。温度报警触发器 TH 和 TL 各由 1 个字节的 EEPROM 组成。如果报警搜索命令未应用于 DS18B20,这些寄存器可用作通用用户存储器。暂存器还包含一个配置字节,用于设置温度到数字转换的所需分辨率。写入 TH、TL 和配置字节是使用存储器功能命令完成的。对这些寄存器的读取访问是通过暂存器进行的。所有数据都先读取和写入最低有效位。
2、DS18B20驱动实战
从数据手册中可以获知DS18B20数据读写时序图
- 复位信号
对应代码编写如下:
- 数据读写时序图
对应代码如下
- 之后就是整合数据了,具体如下所示
完整代码如下所示
ds18b20.c
#include "ds18b20.h"
#define DS18B20_HIGH HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_SET)
#define DS18B20_LOW HAL_GPIO_WritePin(DS18B20_GPIO_Port, DS18B20_Pin, GPIO_PIN_RESET)
#define fac_us 72 //时钟频率,单位MHZ
/*微秒级延时函数*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
void Ds18b20_Write_Out_Input(uint8_t cmd) //这里和iic那个部分差不多
{
GPIO_InitTypeDef GPIO_InitStruct;
if(cmd) //为1的时候是输出模式
{
GPIO_InitStruct.Pin = DS18B20_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DS18B20_GPIO_Port, &GPIO_InitStruct);
}
else // 为0的时候上拉输入
{
GPIO_InitStruct.Pin = DS18B20_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
HAL_GPIO_Init(DS18B20_GPIO_Port, &GPIO_InitStruct);
}
}
uint8_t Ds18b20_Reset(void) //可用此函数检测是否存在ds18b20
{
uint8_t data;
Ds18b20_Write_Out_Input(1);
DS18B20_LOW;
delay_us(480);
DS18B20_HIGH;
delay_us(60);
Ds18b20_Write_Out_Input(0);
data = HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_Pin);
delay_us(420);
return data;
}
void Ds18b20_Write_Byte(uint8_t data)
{
for(uint8_t i = 0;i<8;i++)
{
Ds18b20_Write_Out_Input(1);
DS18B20_LOW;
delay_us(2);
if(data&0x01) //写1拉高,否则拉低
DS18B20_HIGH;
else
DS18B20_LOW;
delay_us(60);
DS18B20_HIGH;
data >>= 1; //写完一位之后数据移位
}
}
uint8_t Ds18b20_Read_Byte(void)
{
uint8_t data = 0;
for(uint8_t i = 0;i<8;i++)
{
data >>= 1;
Ds18b20_Write_Out_Input(1);
DS18B20_LOW;
delay_us(2);
DS18B20_HIGH;
Ds18b20_Write_Out_Input(0);
if(HAL_GPIO_ReadPin(DS18B20_GPIO_Port, DS18B20_Pin) == 1)
{
data |= 0x80; // >>1
}
delay_us(60);
}
return data;
}
float Ds18b20_Read_Temp(void)
{
uint8_t MSB = 0,MSL = 0;
uint16_t Temp = 0;
float temp = 0;
if(Ds18b20_Reset() == RESET)
{
Ds18b20_Reset();
Ds18b20_Write_Byte(0XCC);
Ds18b20_Write_Byte(0X44);
HAL_Delay(750);
Ds18b20_Reset();
Ds18b20_Write_Byte(0XCC);
Ds18b20_Write_Byte(0XBE);
MSL = Ds18b20_Read_Byte();
MSB = Ds18b20_Read_Byte();
Temp = MSB;
Temp = Temp << 8|MSL;
if((Temp&0XF800) == 0XF800)//如果结果是负数
{
temp = (((~Temp)+0x10)*-0.0625);
}
else
{
temp = Temp*0.0625; //这个就是浮点数参数了
// Temp = temp*10+0.5;
}
return temp;
}
else
return 0;//没有检测到传感器
}
ds18b20.h
#include "main.h"
uint8_t Ds18b20_Reset(void);
float Ds18b20_Read_Temp(void);
void delay_us(uint32_t nus);
在主函数中周期打印结果如下:
读取结果如下所示,可以看到数据基本正常
3、多个DS18B20一起驱动
先看下最终的效果:
因为在最开始就提到这个传感器每个地址都不一样嘛,这样就可以进行配置了,具体命令见原理图如下所示:
然后是代码部分,相比上面的代码部分,增加了读传感器ROM的相关操作
void DS18B20_Read_ROM(void)
{
uint8_t morid[8];
Ds18b20_Reset();
Ds18b20_Write_Byte(0x33);
for(uint8_t i=0;i<8;i++){
morid[i] = Ds18b20_Read_Byte();
printf("%#X,",morid[i]);
}
printf("\r\n");
}
void DS18B20_Write_ROM(uint8_t *id)
{
Ds18b20_Reset();
Ds18b20_Write_Byte(0x55);
for(uint8_t i=0;i<8;i++){
Ds18b20_Write_Byte(*id++);
}
}
读数据的函数也做了相关变化如下所示:
实际代码如下所示
float Ds18b20_Read_Temp(uint8_t *id)
{
uint8_t MSB = 0,MSL = 0;
uint16_t Temp = 0;
float temp = 0;
if(Ds18b20_Reset() == RESET)
{
DS18B20_Write_ROM(id);
Ds18b20_Write_Byte(0X44);
HAL_Delay(750);
DS18B20_Write_ROM(id);
Ds18b20_Write_Byte(0XBE);
MSL = Ds18b20_Read_Byte();
MSB = Ds18b20_Read_Byte();
Temp = MSB;
Temp = Temp << 8|MSL;
if((Temp&0XF800) == 0XF800)//如果结果是负数
{
temp = (((~Temp)+0x10)*-0.0625);
}
else
{
temp = Temp*0.0625; //这个就是浮点数参数了
// Temp = temp*10+0.5;
}
return temp;
}
else
return 0;//没有检测到传感器
}
之后首先在主函数初始化部分读取下我们连上的设备id
之后将获取到的id存到我们事先准备好的数组中去
下面开始测试功能是否正常
3、DHT11驱动
一般我们用的如下所示
参数详情
连接图
通信时序:
官方数据手册描述如下:
当 MCU 发出启动信号时,DHT11 从低功耗模式切换到运行模式,等待 MCU 完成启动信号。完成后,DHT11 会向 MCU 发送一个包含相对湿度和温度信息的 40 位数据的响应信号。用户可以选择收集(读取)一些数据。没有来自 MCU 的启动信号,DHT11 不会给 MCU 响应信号。数据采集完毕后,DHT11 将切换到低功耗模式,直到再次收到来自 MCU 的启动信号。
- 起始信号
- 数据0
- 数据1
详情见下面的代码编辑部分!!!
4、DHT11驱动实战
完整代码如下所示:
dht11.c
#include "dht11.h"
#include "ds18b20.h"
uint16_t time = 0;
#define DHT11_HIGH HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET)
#define DHT11_LOW HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET)
void DHT11_Write_Out_Input(uint8_t cmd) //这里和iic那个部分差不多
{
GPIO_InitTypeDef GPIO_InitStruct;
if(cmd) //为1的时候是输出模式
{
GPIO_InitStruct.Pin = DHT11_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
}
else // 为0的时候上拉输入
{
GPIO_InitStruct.Pin = DHT11_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
}
}
uint8_t DHT11_Read_Byte(void)
{
uint8_t data = 0;
DHT11_Write_Out_Input(0);
for(uint8_t i = 0;i<8;i++)
{
while((HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_RESET)&&(++time<1000));
time = 0;
data <<= 1;
delay_us(40);
if(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET)
{
data |= 0x01;
while((HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET)&&(++time<1000));
}
}
return data;
}
uint8_t DHT11_Read_Humi_Temp(uint8_t *humi_h,uint8_t *humi_l,uint8_t *temp_h,uint8_t *temp_l)
{
uint8_t data[5]; //四十位数据
DHT11_Write_Out_Input(1);
DHT11_LOW;
HAL_Delay(20);
DHT11_HIGH;
delay_us(30);
DHT11_Write_Out_Input(0);
while((HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_RESET) && (++time<1000));
time = 0;
while((HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET) && (++time<1000));
time = 0;
for(uint8_t i=0;i<5;i++)
{
data[i] = DHT11_Read_Byte();
}
HAL_Delay(1);
if(data[0]+data[1]+data[2]+data[3] == data[4])
{
*humi_h = data[0];
*humi_l = data[1];
*temp_h = data[2];
*temp_l = data[3];
}
else
return 1;
return 0;
}
dht11.h
uint8_t DHT11_Read_Humi_Temp(uint8_t *humi_h,uint8_t *humi_l,uint8_t *temp_h,uint8_t *temp_l);
在主函数中编写温度读取函数
效果如下所示: