目錄
一、前言
二、環境與准備(開發環境、硬件准備、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的需要以下步驟:
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;
最后就成功讀取到了正確的數據
希望能幫助到和我一樣困擾的人~