????????由于DS18B20數字溫度傳感器是單總線接口,所以需要使用51單片機的一個IO口模擬單總線時序與DS18B20通信,將檢測的環境溫度讀取出來
1、DS18B20模塊電路
????????傳感器接口的單總線管腳接至單片機P3.7IO口上?
2、DS18B20介紹
2.1 DS18B20外觀實物圖?
????????管腳1為GND,管腳2為數據DQ,管腳3為VDD?
2.2 DS18B20內部結構圖
2.2.1?64位光刻ROM
? ? ? ? 64位光刻ROM中的64位序列號是出廠前被光刻好的,它可以看作是該DS18B20的地址序列號。
????????64位光刻 ROM的排列是:開始8位(28H)是產品類型標號,接著的48位是該DS18B20自身的序列號,最后 8位是前面56位的循環冗余校驗碼。
????????光刻ROM的作用是使每一個DS18B20都各不相同,這樣就可以實現一根總線上掛接多個DS18B20的目的
2.2.2 高速緩存存儲器?
????????DS18B20溫度傳感器的內部存儲器包括一個高速的暫存器RAM和一個非易失性的可電擦除的 EEPROM,后者存放高溫度和低溫度觸發器TH、TL和配置寄存器?
2.2.2.1?配置寄存器
????????配置寄存器是配置不同的位數來確定溫度和數字的轉化,配置寄存器結構如下圖所示:
?
(1)低五位一直都是“1”
(2)TM是測試模式位,用于設置DS18B20在工作模式還是在測試模式。在 DS18B20出廠時該位被設置為0,用戶不需要去改動
(3)R1和R0用來設置DS18B20的精度(分辨率),精度可設置為9,10,11或12位,對應的分辨率溫度是 0.5℃,0.25℃,0.125℃和0.0625℃。R0和R1配置如下圖所示:
在初始狀態下默認的精度是12位,即R0=1、R1=1
2.2.2.2?高速暫存存儲器
? ? ? ? (1)高速暫存存儲器由9個字節組成,其分配如下圖所示:
????????當溫度轉換命令(44H)發布后,經轉換所得的溫度值以二字節補碼形式存放在高速暫存存儲器的第0和第1個字節
? ? ? ? (2)存儲的兩個字節,高字節的前5位是符號位S,單片機可通過單線接口讀到該數據,讀取時低位在前,高位在后,數據格式如下:
?
如果測得的溫度大于0,這5位為“0”,只要將測到的數值乘以0.0625(默認精度是12位)即可得到實際溫度
如果溫度小于0,這5位為“1”, 測到的數值需要取反加1再乘以0.0625即可得到實際溫度。
溫度與數據對應關系如下圖所示:
?
2.3?DS18B20時序
DS18B20時序包括如下幾種:初始化時序、寫(0和1)時序、 讀(0和1)時序
DS18B20發送所有的命令和數據都是字節的低位在前
2.3.1?初始化時序
????????主機輸出低電平,保持低電平至少480us(該時間范圍:480-960us),以產生復位脈沖。接著主機釋放總線,外部的上拉電阻將單總線拉高,延時15-60us,并進入接收模式
????????接著DS18B20拉低總線60-240us,以產生低電平應答脈沖,若為低電平,還要做延時,其延時的時間從外部上拉電阻將單總線拉高算起最少要480us
//初始化時序圖13的左半段:復位脈沖時序
void ds18b20_reset(){
?? ?DS18B20_PORT=0; ?//拉低DQ線(單總線)? ? ?主機輸出低電平
?? ?delay_10us(75); ?//750us(480us-960us)? ? ? ? 保持低電平至少480us
?? ?DS18B20_PORT=1; ?//拉高DQ線? ? ? ? ? ? ? ? ? ? ? ?主機釋放總線,外部的上拉電阻將單總線拉高
?? ?delay_10us(2); ?//20us(15us-60us)? ? ? ? ? ? ? ? ? ? ?延時15-60us
}
//初始化時序圖13的右半段:檢測DS18B20是否存在,返回值0存在;返回值1不存在
u8 ds18b20_check(){
?? ?u8 time_temp=0;
?? ?while(DS18B20_PORT&&time_temp<20){ ?//DS18B20_PORT:1
?? ??? ?time_temp++;
?? ??? ?delay_10us(1); ?//10us,要循環20次,相當于200us(60-240us)
?? ?}
?? ?if(time_temp>=20){
?? ??? ?return 1; ?//超時了,仍沒有等到低電平
?? ?}else{
?? ??? ?time_temp=0;
?? ?}
?? ?//如果等到了低電平,DS18B20_PORT變為0
?? ?while((!DS18B20_PORT)&&time_temp<20){ ?//DS18B20_PORT:0
?? ??? ?time_temp++;
?? ??? ?delay_10us(1);
?? ?}
?? ?if(time_temp>=20){
?? ??? ?return 1; ?//超時了,仍沒有等到高電平
?? ?}
?? ?return 0;
}
2.3.2?寫時序
????????寫時序包括寫0時序和寫1時序。所有寫時序至少需要60us,且在2次獨立的寫時序之間至少需要1us的恢復時間,兩種寫時序均起始于主機拉低總線
????????寫1時序:主機輸出低電平,延時2us,然后釋放總線,延時60us
????????寫0時序:主機輸出低電平,延時60us,然后釋放總線,延時2us
//寫時序:寫一個字節到ds18b20中,提前準備好數據
void ds18b20_write_byte(u8 dat){ ?
?? ?u8 i=0;
?? ?u8 temp=0;
?? ?//從低位向高位寫 eg:1001 0011
?? ?for(i=0;i<8;i++){
?? ??? ?temp=dat&0x01; ?//拿到dat中低位的數據
?? ??? ?dat>>=1;
?? ??? ?if(temp){
?? ??? ??? ?//寫1時序
?? ??? ??? ?DS18B20_PORT=0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?主機輸出低電平
?? ??? ??? ?_nop_(); ?//1us? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 延時2us
?? ??? ??? ?_nop_();
?? ??? ??? ?DS18B20_PORT=1;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?釋放總線
?? ??? ??? ?delay_10us(10); ?//100us(60-120us)? ? ? ? ? ??延時60us
?? ??? ?}else{
?? ??? ??? ?//寫0時序
?? ??? ??? ?DS18B20_PORT=0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?主機輸出低電平
?? ??? ??? ?delay_10us(6);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 延時60us
?? ??? ??? ?DS18B20_PORT=1;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?釋放總線
?? ??? ??? ?_nop_(); ?//1us? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 延時2us
?? ??? ??? ?_nop_();
?? ??? ?}
?? ?}
}??
2.3.3 讀時序?
?
????????所有讀時序至少需要60us,且在2次獨立的讀時序之間至少需要1us的恢復時間。每個讀時序都由主機發起,至少拉低總線1us。主機在讀時序期間必須釋放總線,并且在時序起始后的15us之內采樣總線狀態
????????典型的讀時序過程為:主機輸出低電平延時2us,然后主機轉入輸入模式延時12us,然后讀取單總線當前的電平,然后延時50us
?//讀一位
u8 ds18b20_read_bit(){
?? ?u8 dat=0; ?//存的是某一位(0或1)
?? ?//主機輸出低電平
?? ?DS18B20_PORT=0;
?? ?//延時2us
?? ?_nop_(); ?//1us
?? ?_nop_();
?? ?DS18B20_PORT=1;? ? ?主機轉入輸入模式
?? ?//延時2us,時間不能過長,必須在15us內讀到數據??延時12us
?? ?_nop_(); ?//1us
?? ?_nop_();
?? ?if(DS18B20_PORT){? ? ??讀取單總線當前的電平
?? ??? ?dat=1;
?? ?}else{
?? ??? ?dat=0;
?? ?}
?? ?delay_10us(5);? ? ?延時50us
?? ?return dat;
}
//讀一個字節
u8 ds18b20_read_byte(){
?? ?u8 i=0;
?? ?u8 dat=0;
?? ?u8 temp=0;
?? ?for(i=0;i<8;i++){
?? ??? ?temp=ds18b20_read_bit(); ?//i=0時,temp=1;i=1時,temp=1
?? ??? ?dat>>=1; ?//i=0時,dat=000 0000;i=1時,dat=0100 0000
?? ??? ?dat|=temp<<7; ?//i=0時,dat=1000 0000;i=1時,dat=1100 0000
?? ?}
?? ?return dat;
}
2.3.4?DS18B20的典型溫度讀取過程
復位→發SKIPROM命令(0XCC)→發開始轉換命令(0X44)→延時→復位→發送SKIPROM命令(0XCC)→發讀存儲器命令(0XBE)→連續讀出兩個字節數據(即溫度)→結束
?/*
DS18B20 的典型溫度讀取過程為:
復位→發SKIPROM命令(0XCC)→發開始轉換命令(0X44)
→延時→
復位→發送SKIPROM命令(0XCC)→發讀存儲器命令(0XBE)→連續讀出兩個字節數據(即溫度)→結束。
*/
//開始溫度轉換
void ds18b20_start(){
?? ?ds18b20_init(); ?//初始化:復位和檢查
?? ?ds18b20_write_byte(0xcc); ?//發SKIPROM命令(0XCC)
?? ?ds18b20_write_byte(0x44); ?//發開始轉換命令(0X44)
}
//從ds18b20得到溫度值
double ds18b20_read_temperture(){
?? ?double temp;
?? ?u8 dath=0;
?? ?u8 datl=0;
?? ?u16 value=0;
?? ?ds18b20_start();
?? ?ds18b20_init(); ?//初始化:復位和檢查
?? ?ds18b20_write_byte(0xcc); ?//發送SKIPROM命令(0XCC)
?? ?ds18b20_write_byte(0xbe); ?//發讀存儲器命令(0XBE)
?? ?//連續讀出兩個字節數據(即溫度)
?? ?datl=ds18b20_read_byte(); ?//低8位
?? ?dath=ds18b20_read_byte(); ?//高8位
?? ?//高8位和低8位連接起來,拼成16位
?? ?value=(dath<<8)+datl; ?
?? ?//判斷是正溫度還是負溫度
?? ?if((value&0xf800)==0xf800){ ?//負溫度
?? ??? ?value=(~value)+1;
?? ??? ?temp=value*(-0.0625);
?? ?}else{ ?//正溫度
?? ??? ?temp=value*0.0625;
?? ?}
?? ?return temp;
}?
3、實驗
要實現的功能是:插上DS18B20溫度傳感器,數碼管顯示檢測的溫度值?
如果不知道怎么進行多文件編程,51單片機——I2C-EEPROM-CSDN博客中有
3.1 Public文件
3.1.1 public.h
//頭文件中放置函數的聲明、全局變量的定義
#ifndef _public_H
#define _public_H
#include "reg52.h"
//全局變量
typedef unsigned int u16;
typedef unsigned char u8;
//兩個延遲函數聲明
void delay_10us(u16 us);
void delay_ms(u16 ms);
#endif
3.1.2?public.c
#include "public.h"
void delay_10us(u16 us){
?? ?while(us--);
}
void delay_ms(u16 ms){
?? ?u16 i=0,j=0;
?? ?for(i=0;i<ms;i++){
?? ??? ?for(j=0;j<110;j++);
?? ?}
}
3.2 動態數碼管?
3.2.1 smg.h
#ifndef _smg_H
#define _smg_H
#include "public.h"
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
#define SMG_A_DP_PORT P0
extern u8 gsmg_code[];
void smg_display(u8 save_buff[],u8 pos);
#endif
3.2.2 smg.c
?#include "smg.h"
u8 gsmg_code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
//save_buff是一個u8類型的數組,方便外部傳入要顯示的數據
//pos是數碼管從左開始第幾個位置開始顯示,取值范圍是1-8
void smg_display(u8 save_buff[],u8 pos){
?? ?u16 i=0;
?? ?u16 pos_temp=pos-1;
?? ?for(i=pos_temp;i<8;i++){
?? ??? ?//位選
?? ??? ?switch(i){
?? ??? ??? ?case 0:
?? ??? ??? ??? ?LSC=1,LSB=1,LSA=1; ?//7
?? ??? ??? ??? ?break;
?? ??? ??? ?case 1:
?? ??? ??? ??? ?LSC=1,LSB=1,LSA=0; ?//6
?? ??? ??? ??? ?break;
?? ??? ??? ?case 2:
?? ??? ??? ??? ?LSC=1,LSB=0,LSA=1; ?//5
?? ??? ??? ??? ?break;
?? ??? ??? ?case 3:
?? ??? ??? ??? ?LSC=1,LSB=0,LSA=0; ?//4
?? ??? ??? ??? ?break;
?? ??? ??? ?case 4:
?? ??? ??? ??? ?LSC=0,LSB=1,LSA=1; ?//3
?? ??? ??? ??? ?break;
?? ??? ??? ?case 5:
?? ??? ??? ??? ?LSC=0,LSB=1,LSA=0; ?//2
?? ??? ??? ??? ?break;
?? ??? ??? ?case 6:
?? ??? ??? ??? ?LSC=0,LSB=0,LSA=1; ?//1
?? ??? ??? ??? ?break;
?? ??? ??? ?case 7:
?? ??? ??? ??? ?LSC=0,LSB=0,LSA=0; ?//0
?? ??? ??? ??? ?break;
?? ??? ?}
?? ??? ?SMG_A_DP_PORT=save_buff[i-pos_temp];?
?? ??? ?delay_10us(100);
?? ??? ?SMG_A_DP_PORT=0x00; ?//消隱
?? ?}
}
3.3 DS18B20
3.3.1 ds18b20.h
#ifndef _ds18b20_H
#define _ds18b20_H
#include "public.h"
#include "intrins.h" ?//_nop_():1us
sbit DS18B20_PORT=P3^7; ?//DQ
//初始化時序圖13的左半段:復位脈沖時序
void ds18b20_reset();
//初始化時序圖13的右半段:檢測DS18B20是否存在,返回值0存在;返回值1不存在
u8 ds18b20_check();
//初始化時序
u8 ds18b20_init();
//寫時序:寫一個字節到ds18b20中,提前準備好數據
void ds18b20_write_byte(u8 dat);
//讀一位
u8 ds18b20_read_bit();
//讀一個字節
u8 ds18b20_read_byte();
//開始溫度轉換
void ds18b20_start();
//從ds18b20得到溫度值
double ds18b20_read_temperture();
#endif
3.3.2 ds18b20.c
#include "ds18b20.h"
//協議層
//初始化時序圖13的左半段:復位脈沖時序
void ds18b20_reset(){
?? ?DS18B20_PORT=0; ?//拉低DQ線(單總線)
?? ?delay_10us(75); ?//750us(480us-960us)
?? ?DS18B20_PORT=1; ?//拉高DQ線
?? ?delay_10us(2); ?//20us(15us-60us)
}//初始化時序圖13的右半段:檢測DS18B20是否存在,返回值0存在;返回值1不存在
u8 ds18b20_check(){
?? ?u8 time_temp=0;
?? ?while(DS18B20_PORT&&time_temp<20){ ?//DS18B20_PORT:1
?? ??? ?time_temp++;
?? ??? ?delay_10us(1); ?//10us,要循環20次,相當于200us(60-240us)
?? ?}
?? ?if(time_temp>=20){
?? ??? ?return 1; ?//超時了,仍沒有等到低電平
?? ?}else{
?? ??? ?time_temp=0;
?? ?}
?? ?//如果等到了低電平,DS18B20_PORT變為0
?? ?while((!DS18B20_PORT)&&time_temp<20){ ?//DS18B20_PORT:0
?? ??? ?time_temp++;
?? ??? ?delay_10us(1);
?? ?}
?? ?if(time_temp>=20){
?? ??? ?return 1; ?//超時了,仍沒有等到高電平
?? ?}
?? ?return 0;
}//初始化時序(將初始化時序圖13的左半段和右半段連接起來)
u8 ds18b20_init(){
?? ?ds18b20_reset();
?? ?return ds18b20_check();
}
//ds18b20中讀寫操作
//寫時序:寫一個字節到ds18b20中,提前準備好數據
void ds18b20_write_byte(u8 dat){ ?
?? ?u8 i=0;
?? ?u8 temp=0;
?? ?//從低位向高位寫 1001 0011
?? ?for(i=0;i<8;i++){
?? ??? ?temp=dat&0x01; ?//拿到dat中低位的數據
?? ??? ?dat>>=1;
?? ??? ?if(temp){
?? ??? ??? ?//寫1時序
?? ??? ??? ?DS18B20_PORT=0;
?? ??? ??? ?_nop_(); ?//1us
?? ??? ??? ?_nop_();
?? ??? ??? ?DS18B20_PORT=1;
?? ??? ??? ?delay_10us(10); ?//100us(60-120us)
?? ??? ?}else{
?? ??? ??? ?//寫0時序
?? ??? ??? ?DS18B20_PORT=0;
?? ??? ??? ?delay_10us(6);
?? ??? ??? ?DS18B20_PORT=1;
?? ??? ??? ?_nop_(); ?//1us
?? ??? ??? ?_nop_();
?? ??? ?}
?? ?}
}//讀時序:讀位、讀字節 1001 0011
//讀一位
u8 ds18b20_read_bit(){
?? ?u8 dat=0; ?//存的是某一位(0或1)
?? ?//主機輸出低電平
?? ?DS18B20_PORT=0;
?? ?//延時2us
?? ?_nop_(); ?//1us
?? ?_nop_();
?? ?DS18B20_PORT=1;
?? ?//延時2us,時間不能過長,必須在15us內讀到數據
?? ?_nop_(); ?//1us
?? ?_nop_();
?? ?if(DS18B20_PORT){
?? ??? ?dat=1;
?? ?}else{
?? ??? ?dat=0;
?? ?}
?? ?delay_10us(5);
?? ?return dat;
}
//讀一個字節
u8 ds18b20_read_byte(){
?? ?u8 i=0;
?? ?u8 dat=0;
?? ?u8 temp=0;
?? ?for(i=0;i<8;i++){
?? ??? ?temp=ds18b20_read_bit(); ?//i=0時,temp=1;i=1時,temp=1
?? ??? ?dat>>=1; ?//i=0時,dat=000 0000;i=1時,dat=0100 0000
?? ??? ?dat|=temp<<7; ?//i=0時,dat=1000 0000;i=1時,dat=1100 0000
?? ?}
?? ?return dat;
}
/*
DS18B20 的典型溫度讀取過程為:
復位→發SKIPROM命令(0XCC)→發開始轉換命令(0X44)
→延時→
復位→發送SKIPROM命令(0XCC)→發讀存儲器命令(0XBE)→連續讀出兩個字節數據(即溫度)→結束。
*/
//開始溫度轉換
void ds18b20_start(){
?? ?ds18b20_init(); ?//初始化:復位和檢查
?? ?ds18b20_write_byte(0xcc); ?//發SKIPROM命令(0XCC)
?? ?ds18b20_write_byte(0x44); ?//發開始轉換命令(0X44)
}
//從ds18b20得到溫度值
double ds18b20_read_temperture(){
?? ?double temp;
?? ?u8 dath=0;
?? ?u8 datl=0;
?? ?u16 value=0;
?? ?ds18b20_start();
?? ?ds18b20_init(); ?//初始化:復位和檢查
?? ?ds18b20_write_byte(0xcc); ?//發送SKIPROM命令(0XCC)
?? ?ds18b20_write_byte(0xbe); ?//發讀存儲器命令(0XBE)
?? ?//連續讀出兩個字節數據(即溫度)
?? ?datl=ds18b20_read_byte(); ?//低8位
?? ?dath=ds18b20_read_byte(); ?//高8位
?? ?//高8位和低8位連接起來,拼成16位
?? ?value=(dath<<8)+datl; ?
?? ?//判斷是正溫度還是負溫度
?? ?if((value&0xf800)==0xf800){ ?//負溫度
?? ??? ?value=(~value)+1;
?? ??? ?temp=value*(-0.0625);
?? ?}else{ ?//正溫度
?? ??? ?temp=value*0.0625;
?? ?}
?? ?return temp;
}
3.4 main.c
#include "public.h"
#include "smg.h"
#include "ds18b20.h"
/*
下載程序后,插上DS18B20溫度傳感器,數碼管顯示檢測的溫度值
*/
void main(){
?? ?u8 i=0;
?? ?int temp_value; ?//有負溫度
?? ?u8 temp_buf[5];
?? ?ds18b20_init(); ?//起始信號,檢查ds18b20在不在,要不要都可以
?? ?while(1){
?? ??? ?i++;
?? ??? ?if(i%50==0){ ?//減少讀的頻率,750us轉換一次
?? ??? ??? ?temp_value=ds18b20_read_temperture()*10;
?? ??? ?}
?? ??? ?if(temp_value<0){
?? ??? ??? ?//顯示符號位
?? ??? ??? ?temp_value=-temp_value;
?? ??? ??? ?temp_buf[0]=0x40; ?//顯示負號
?? ??? ?}else{
?? ??? ??? ?temp_buf[0]=0x00; ?//不顯示
?? ??? ?}
?? ??? ?//讀的溫度要在數碼管顯示
?? ??? ?temp_buf[1]=gsmg_code[temp_value/1000];
?? ??? ?temp_buf[2]=gsmg_code[temp_value%1000/100];
?? ??? ?temp_buf[3]=gsmg_code[temp_value%1000%100/10]|0x80;
?? ??? ?temp_buf[4]=gsmg_code[temp_value%1000%100%10];
?? ??? ?smg_display(temp_buf,4);
?? ?}
}