转自本人博客园博客:https://www.cnblogs.com/JYU-hsy/p/9857804.html
一、前言
最近在做一个基于机智云平台的智能花盆,选购的传感器里包含了这款DS18B20。正是这一个类似三极管的东西花了我几天的时间,最后看了一天示波器才找到驱动的错误...血泪史啊!
二、环境与准备
开发环境:STM32CubeMx、keil5
硬件准备:STM32F103C8T6最小系统、4.7K的电阻、DS18B20
在此之前我们先来看看DS18B20的内部结构
A、64位光刻ROM
即每个DS18B20的身份证号码,如果你只用到了一个DS181B20,你可以不关注它。
B、高速寄存器
三、硬件连接
根据手册,DS18B20的硬件接法很简单,分为以下两种:
需要注意的是不管哪一种接法DQ上一定要接个上拉电阻
1.寄生接法
DS18B20_GND——————>STM32F103_GND
DS18B20_VCC——————>STM32F103_GND
DS18B20_DQ——————>STM32F103_PB15
DQ引脚可接任意IO口
关于寄生方式,需要注意以下几点:
A、DS18B20的寄生方式是在DQ引脚为高电平时“窃取”电源,同时将部分能量存储在内部的电容里。
所以,上拉电阻!!一定要接上!!
B、为了使DS18B20准确完成温度转换,当温度转换发生时,IO口必须提供足够大的功率。
DS18B20的工作电流高达1mA,5K的上拉电阻使得IO口没有足够的驱动能力。
如果多个DS18B20在同一个IO上而且同时进行温度的变换时,这个问题将特别尖锐。
2.正常供电
DS18B20_GND——————>STM32F103_GND
DS18B20_VCC——————>STM32F103_VCC
DS18B20_DQ——————>STM32F103_PB15
四、DS18B20的“1Wire”协议
先放个传送门...
哎呀,放错了...是下面的...
安利,里面的讲解真的很详细!
经过单线访问DS18B20的需要以下步骤:
A、初始化
单总线上的所有操作均从初始化开始
所谓初始化就是发送一段特定的时序,即复位脉冲
从属器接收到这段脉冲后会拉低总线,这个拉低的动作就是应答
当你发送复位脉冲后检测到DS18B20拉低了信号,就是成功的第一步了呀。
B、发送ROM命令
一旦我们检测到DS18B20的存在,我们就可以发生ROM指令啦
当有多个DS18B20连接在同一个IO口上时,我可以通过ROM指令指定DS18B20
而只有一个DS18B20时,我们通常直接发送“跳过ROM”
C、发送功能命令
那么我们要怎么发送这些指令呢?
又要怎么读DS18B20传送回来的温度呢?
这就涉及到DS18B20的读写时序啦
下面我们来看看读写时序
如果你觉得时序图晦涩难懂,可以戳传送门DS18B20的读写时序
里面的讲解非常详细
五、驱动编写
关于读写时序,建议对照代码进行理解
DS18B20.h
#include "stm32f1xx_hal.h"
//IO操作
#define DS18B20_DQ_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET)
#define DS18B20_DQ_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET)
#define DS18B20_DQ_ReadPin HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)
extern void DS18B20_DQ_DDR(uint8_t ddr);
extern void delay_us(uint32_t nus);
extern int DS18B20_reset(void);
extern void DS18B20_Wbyte(uint8_t xbyte);
extern uint8_t DS18B20_Rbit(void);
extern uint8_t DS18B20_Rbyte(void);
extern int ReadTemperature(void);
DS18B20.c
#include "DS18B20.h"
/*******************************************************************************
函数名:DS18B20_DQ_DDR
功能:配置IO输入/输出状态
输入:0/1 输入0配置为输入,输入1配置为输出
输出:
返回值:
备注:我用的是PB15,其他GPIO口需自己看手册修改相应的寄存器
*******************************************************************************/
void DS18B20_DQ_DDR(uint8_t ddr)
{
if(ddr == 1)
{
GPIOB->CRH&=0X1FFFFFFF;
GPIOB->CRH|=0X10000000;
}
else
{
GPIOB->CRH&=0X8FFFFFFF;
GPIOB->CRH|=0X80000000;
}
}
//void DS18B20_DQ_DDR(uint8_t ddr)
//{
// GPIO_InitTypeDef GPIO_InitStruct;
// //使能GPIO时钟
// __HAL_RCC_GPIOB_CLK_ENABLE();
// //配置为输出
// if(ddr == 1)
// {
// GPIO_InitStruct.Pin = GPIO_PIN_15;
// GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
// GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
// HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// }
// //配置为输入
// else
// {
// GPIO_InitStruct.Pin = GPIO_PIN_15;
// GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
// HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// }
//}
/*******************************************************************************
函数名:delay_us
功能:延时us
输入:
输出:
返回值:
备注:
*******************************************************************************/
void delay_us(uint32_t nus)
{
while (nus--)
__nop();
}
/*******************************************************************************
函数名:DS18B20_reset
功能:初始化DS18B20
输入:
输出:
返回值:初始化成功为0,不成功为1
备注:
*******************************************************************************/
int DS18B20_reset(void)
{
int x = 0;
//改变DQ引脚为输出
DS18B20_DQ_DDR(1);
//先置高
DS18B20_DQ_H;
//延时700us,使总线稳定
delay_us(1400);
//复位脉冲,低电位
DS18B20_DQ_L;
//保持至少480us,这里500us
delay_us(1000);
//改变DQ引脚为输入
DS18B20_DQ_DDR(0);
//拉高数据线,释放总线
DS18B20_DQ_H;
//等待15-60us,这里33us
delay_us(60);
//等待35us,这里33us
delay_us(60);
//聆听,判断有没有初始化成功(DS18B20有没有发送应答脉冲)
x = DS18B20_DQ_ReadPin;
//printf("DS18B20 waiting....\n");
//等待应答脉冲出现
//while(x);
//printf("DS18B20 OK\n");
//至少480us后进入接收状态,这里500us
delay_us(1000);
return x;
}
/*******************************************************************************
函数名:DS18B20_Wbyte
功能:写一个字节
输入:uint8_t xbyte
输出:
返回值:
备注:
*******************************************************************************/
void DS18B20_Wbyte(uint8_t xbyte)
{
//i:循环控制变量,x:取位运算变量
int8_t i ,x = 0;
//改变DQ引脚为输出
DS18B20_DQ_DDR(1);
//8次循环实现逐位写入
for(i = 1; i <= 8; i++)
{
//先取低位
x = xbyte & 0x01;
//写1
if(x)
{
DS18B20_DQ_H;
//拉低总线
DS18B20_DQ_L;
//延时15us
delay_us(25);
//总线写1
DS18B20_DQ_H;
//延时15us
delay_us(25);
//保持高电平
DS18B20_DQ_H;
delay_us(4);
}
//写0
else
{
DS18B20_DQ_H;
//总线拉低
DS18B20_DQ_L;
//延时15us
delay_us(25);
//总线写0
DS18B20_DQ_L;
//延时15us
delay_us(25);
//保持高电平
DS18B20_DQ_H;
delay_us(4);
}
//xbyte右移一位
xbyte = xbyte >> 1;
}
}
/*******************************************************************************
函数名:DS18B20_Rbit
功能:从DS18B20读一个位
输入:
输出:
返回值:读取到的位
备注:
*******************************************************************************/
uint8_t DS18B20_Rbit(void)
{
//rbit是最终位数据,x是取状态变量
uint8_t rbit = 0x00,x = 0;
//改变DQ为输出模式
DS18B20_DQ_DDR(1);
DS18B20_DQ_H;
//总线写0
DS18B20_DQ_L;
//延时15us以内
delay_us(1);
//释放总线
DS18B20_DQ_H;
//改变DQ为输入模式
DS18B20_DQ_DDR(0);
//延时大约3us
//delay_us(7);
//获取总线电平状态
x = DS18B20_DQ_ReadPin;
//如果是1,则返回0x80,否则返回0x00
if(x)
rbit = 0x80;
//延时大约60us
delay_us(130);
return rbit;
}
/*******************************************************************************
函数名:DS18B20_Rbyte
功能:从DS18B20读一个字节
输入:
输出:
返回值:读取到的字节
备注:
*******************************************************************************/
uint8_t DS18B20_Rbyte(void)
{
//rbyte:最终得到的字节
//tempbit:中间运算变量
uint8_t rbyte = 0,i = 0, tempbit =0;
for (i = 1; i <= 8; i++)
{
//读取位
tempbit = DS18B20_Rbit();
//右移实现高低位排序
rbyte = rbyte >> 1;
//或运算移入数据
rbyte = rbyte|tempbit;
}
return rbyte;
}
int ReadTemperature(void)
{
//fg:符号位
//data:温度的整数部分
int fg;
int data;
//DS18B20初始化
DS18B20_reset();
//跳过读序列号
DS18B20_Wbyte(0xcc);
//启动温度转换
DS18B20_Wbyte(0x44);
//等待温度转换
HAL_Delay(1);
DS18B20_reset();
DS18B20_Wbyte(0xcc);
//读温度寄存器
DS18B20_Wbyte(0xbe);
uint8_t TempL = DS18B20_Rbyte();
uint8_t TempH = DS18B20_Rbyte();
//符号位为负
if(TempH > 0x70)
{
TempL = ~TempL;
TempH = ~TempH;
fg = 0;
}
else
fg = 1;
//整数部分
data = TempH;
data <<= 8;
data += TempL;
data = (float)data*0.625;
data = data / 10.0; //转换
if(fg)
return data;
else
return -data;
}
ps:如果你需要移植该代码,只需要更改以下内容:
1.头文件里的IO操作
2.重写改变IO模式的DS18B20_DQ_DDR()函数
3.延时函数可以不用改,但是由于每个板子的时钟是不同的
你需要测出实际的延时时间,按备注更改延时时间
六、问题总结
其实写驱动用不了多长的时间
问题是我在测驱动的时候遇到了很多问题,其中一个困扰最久的问题就是,读取到的数据全为1。
网上也看到有人问这样的问题,最大的可能是时序不对
所以开始一直改delay_us函数却没注意到其他函数也是占用时间的...
我们知道读取的时间过长,IO口是会被上拉电阻拉高的
而在我的DS18B20_Rbit函数内中
由于是用STM32Cube+Keil开发,开始时DS18B20_DQ_DDR()的构建
是参照GPIO初始化时的操作去改变IO口的模式
我在网上也看到很多人是这样子写的
GPIO_InitTypeDef GPIO_InitStruct;
//使能GPIO时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
最后我发现这样的操作居然花了我43us!!
于是我改用配置寄存器的方法去配置GPIO的输入\ 输出模式
GPIOB->CRH&=0X1FFFFFFF;
GPIOB->CRH|=0X10000000;
最后就成功读取到了正确的数据
希望能帮助到和我一样困扰的人~