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....\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;

 

最后就成功讀取到了正確的數據

希望能幫助到和我一樣困擾的人~

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM