zoukankan      html  css  js  c++  java
  • STM32F103 驱动DS18B20

    目录

    一、前言

    二、环境与准备(开发环境、硬件准备、DS18B20内部结构)

    三、硬件连接(寄生接法、正常供电)

    四、DS18B20的“1Wire”协议(初始化、发送ROM命令、发送功能命令)

    五、驱动源代码

    六、问题总结

    一、前言

       最近在做一个基于机智云平台的智能花盆,选购的传感器里包含了这款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的数据格式及转换

    DS18B20的读写时序

     

    经过单线访问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....
    ");        
        //等待应答脉冲出现
        //while(x);
        //printf("DS18B20 OK
    ");
        //至少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;

     

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

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

     

     

  • 相关阅读:
    Ubuntu下彻底卸载mysql
    Navicat连接Ubuntu中的MySQL,报错1130-host
    win10优化开机进程
    导入别人工程后项目报错,有个小红叉
    eclipse中的tomcat配置
    解决eclipse启动慢
    linux screen命令
    python pip install出现问题
    scala中执行shell命令
    spark 累加器
  • 原文地址:https://www.cnblogs.com/JYU-hsy/p/9857804.html
Copyright © 2011-2022 走看看