STM32F103 驱动DS18B20

转自本人博客园博客:https://www.cnblogs.com/JYU-hsy/p/9857804.html

一、前言

   最近在做一个基于机智云平台的智能花盆,选购的传感器里包含了这款DS18B20。正是这一个类似三极管的东西花了我几天的时间,最后看了一天示波器才找到驱动的错误...血泪史啊!

                                                             STM32F103 驱动DS18B20

 

二、环境与准备

 开发环境:STM32CubeMx、keil5

硬件准备:STM32F103C8T6最小系统、4.7K的电阻、DS18B20

 

在此之前我们先来看看DS18B20的内部结构

STM32F103 驱动DS18B20

 

A、64位光刻ROM

即每个DS18B20的身份证号码,如果你只用到了一个DS181B20,你可以不关注它。

                                        STM32F103 驱动DS18B20

 

B、高速寄存器

STM32F103 驱动DS18B20

 

 

三、硬件连接

根据手册,DS18B20的硬件接法很简单,分为以下两种:

需要注意的是不管哪一种接法DQ上一定要接个上拉电阻

1.寄生接法

                                  STM32F103 驱动DS18B20

 

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.正常供电

STM32F103 驱动DS18B20

 

DS18B20_GND——————>STM32F103_GND

DS18B20_VCC——————>STM32F103_VCC

DS18B20_DQ——————>STM32F103_PB15

 

 

四、DS18B20的“1Wire”协议

 先放个传送门...

                       STM32F103 驱动DS18B20

 

哎呀,放错了...是下面的...

安利,里面的讲解真的很详细!

单总线通信

DS18B20的数据格式及转换

DS18B20的读写时序

 

经过单线访问DS18B20的需要以下步骤:

A、初始化

单总线上的所有操作均从初始化开始

所谓初始化就是发送一段特定的时序,即复位脉冲

从属器接收到这段脉冲后会拉低总线,这个拉低的动作就是应答

当你发送复位脉冲后检测到DS18B20拉低了信号,就是成功的第一步了呀。

 

STM32F103 驱动DS18B20

 

B、发送ROM命令

一旦我们检测到DS18B20的存在,我们就可以发生ROM指令啦

当有多个DS18B20连接在同一个IO口上时,我可以通过ROM指令指定DS18B20

而只有一个DS18B20时,我们通常直接发送“跳过ROM”

 

STM32F103 驱动DS18B20

 

 

C、发送功能命令

 

 STM32F103 驱动DS18B20

 

 那么我们要怎么发送这些指令呢?

又要怎么读DS18B20传送回来的温度呢?

这就涉及到DS18B20的读写时序啦

下面我们来看看读写时序

 

STM32F103 驱动DS18B20

STM32F103 驱动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函数内中

STM32F103 驱动DS18B20

 

由于是用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;

 

最后就成功读取到了正确的数据

希望能帮助到和我一样困扰的人~

上一篇:Eclipse的安装与使用


下一篇:eclipse工具的全解(右键new菜单自定义、常用快捷键)