一、DS18B20
1. DS18B20簡介
DS18B20是一種常見的數字溫度傳感器,其控制命令和數據都是以數字信號的方式輸入輸出,相比較於模擬溫度傳感器,具有功能強大、硬件簡單、易擴展、抗干擾性強等特點
- 測溫范圍:-55°C 到 +125°C
- 通信接口:1-Wire(單總線)
- 其它特征:可形成總線結構、內置溫度報警功能、可寄生供電
2. 電路原理圖

其中1和3號引腳分別連接GND
和VCC
,而二號引腳則用於使用1-Wire(單總線)接口進行通信。
即:

3. 內部結構
內部完整結構框圖

- 64-BIT ROM:作為器件地址,用於總線通信的尋址
- SCRATCHPAD(暫存器):用於總線的數據交互
- EEPROM:用於保存溫度觸發閾值和配置參數
其中配置寄存器可以配置溫度變化的精度值。
存儲器結構

當我們希望修改EEPROM中存儲的內容時,我們需要先將數據寫入到暫存器中,然后再發送一條指令使從機將暫存器中的數據寫入到EEPROM中。
二、單總線(1-Wire BUS)
由於DS18B20使用的通信接口是1-Wire,因此我們需要學習1-Wire相關的通信協議,這樣才能使單片機和它進行通信。
1. 單總線簡介
單總線(1-Wire BUS)是由Dallas
公司開發的一種通用數據總線
- 一根通信線:DQ
- 異步、半雙工
- 單總線只需要一根通信線即可實現數據的雙向傳輸,當采用寄生供電時,還可以省去設備的VDD線路,此時,供電加通信只需要DQ和GND兩根線
2. 電路規范
- 設備的DQ均要配置成開漏輸出模式
- DQ添加一個上拉電阻,阻值一般為4.7KΩ左右
- 若此總線的從機采取寄生供電,則主機還應配一個強上拉輸出電路

3. 單總線的時序結構
① 初始化:
- 主機將總線拉低至少480us
- 然后釋放總線,等待15~60us
- 存在的從機拉低總線60~240us以響應主機
- 最后從機將釋放總線
對應的信號時序圖:

② 發送一位:
- 主機將總線拉低60~120us,然后釋放總線,表示發送0;
- 主機將總線拉低1~15us,然后釋放總線,表示發送1。
- 從機將在總線拉低30us后(典型值)讀取電平,整個時間片應大於60us
對應的信號時序圖:

③ 接收一位:
- 主機將總線拉低1~15us,然后釋放總線
- 然后主機在拉低后15us內讀取總線電平(盡量貼近15us的末尾)
- 讀取為低電平則為接收0,讀取為高電平則為接收1 ,整個時間片應大於60us
對應的信號時序圖:

④ 發送一個byte和接收一個byte:
這個過程和使用I2C發送和接收一個字節的數據的過程類似,都是重復發送一位或接受一位8次即可發送或接受一個byte的數據了:


注意發送和接收到的數據都是低位在前的。
4. DS18B20操作流程
- 初始化:從機復位,主機判斷從機是否響應
- ROM操作:ROM指令+本指令需要的讀寫操作
- 功能操作:功能指令+本指令需要的讀寫操作
對應的ROM操作和功能操作的指令如下:
ROM指令 | 功能指令 |
---|---|
SEARCH ROM [0xF0] | CONVERT T [0x44] |
READ ROM [0x33] | WRITE SCRATCHPAD [0x4E] |
MATCH ROM [0x55] | READ SCRATCHPAD [0xBE] |
SKIP ROM [0xCC] | COPY SCRATCHPAD [0x48] |
ALARM SEARCH [0xEC] | RECALL E2 [0xB8] |
READ POWER SUPPLY [0xB4] |
各個功能指令的作用:
- CONVERT T:使用溫度傳感器更新溫度值
- WRITE SCRATCHPAD:將各個EEPROM中的值寫入到暫存器中
- READ SCRATCHPAD:讀取暫存器中的值
- COPY SCRATCHPAD:將暫存器中的內容復制到EEPROM中
- RECALL E2:將EEPROM中的內容復制到暫存器中
- READ POWER SUPPLY:讀取設備的供電模式【獨立供電|寄生供電】
5. DS18B20數據幀
① 溫度變換:初始化→跳過ROM →開始溫度變換

② 溫度讀取:初始化→跳過ROM →讀暫存器→連續的讀操作

6. 溫度的存儲格式
里面的溫度是使用補碼形式存儲的,具體的例子如下:


溫度表示的范圍是-55到+125度
三、編碼實現
1. 單總線部分
我們將這部分編寫到OneWire
模塊中
首先是DQ引腳,根據電路原理圖我們可以知道他對應的是芯片的P37
引腳,因此先把它定義出來:
sbit OneWire_DQ = P3 ^ 7;
① 初始化:

unsigned char OneWire_Init() {
unsigned char i;
unsigned char Ack;
EA=0;
{
OneWire_DQ = 1;
OneWire_DQ = 0; // 拉低
i = 227; while (--i); // delay 500μs
OneWire_DQ = 1; // 釋放
i = 29; while (--i); // delay 70μs,等待從機將電平拉低
// 獲取從機應答
Ack = OneWire_DQ;
i = 227; while (--i); // delay 500μs 等待時序走完
}
EA=1;
// 將從機應答返回,0表示有應答,1表示無應答
return Ack;
}
可以發現,我們的代碼中出現了EA=0;
和EA=1;
,這兩句代碼分別是操作終端系統的總開關進行關閉和開啟中斷系統的,因為在單總線發送信號的過程中,如果突然來了中斷,CPU轉而運行中斷程序,那么延時就會受到巨大的影響:
例如當前運行到delay 70μs的位置,此時來了一個中斷信號,當運行完中斷程序回來的時候,可能就已經延時了10ms了,這對單總線通信的影響是致命的,故我們在使用單總線發送或接受信號時都需要先關閉中斷。
tips:其中的延時代碼都是使用stc-isp軟件進行生成的,在生成代碼時需要注意晶振和8051指令集的選擇:
![]()
② 發送一位:

void OneWire_SendBit(unsigned char Bit) {
unsigned char i;
EA=0;
{
OneWire_DQ = 0; // 拉低
i = 3; while (--i); // delay 10μs
// 若為1則釋放總線,代表發送1;若為0一直為低電平,代表發送0
OneWire_DQ = Bit;
i = 22; while (--i); // delay 50μs
OneWire_DQ = 1; // 最后釋放總線
}
EA=1;
}
③ 接收一位:

unsigned char OneWire_ReceiveBit() {
unsigned char i;
unsigned char Bit;
EA=0;
{
OneWire_DQ = 0; // 拉低
i = 1; while (--i); // delay 5μs
OneWire_DQ = 1; // 釋放
i = 1; while (--i); // delay 5μs
Bit = OneWire_DQ; // 采樣
i = 22; while (--i); // delay 50μs
}
EA=1;
return Bit;
}
④ 發送一個byte和接收一個byte:

void OneWire_SendByte(unsigned char byte) {
unsigned char i;
for(i = 0; i < 8; i++) {
OneWire_SendBit(byte & (0x01 << i));
}
}

unsigned char OneWire_ReceiveByte() {
unsigned char i;
unsigned char byte = 0x00;
for(i = 0; i < 8; i++) {
if(OneWire_ReceiveBit()) {
byte |= (0x01 << i);
}
}
return byte;
}
2. DS18B20模塊
首先我們把需要的命令和依賴的模塊(OneWire模塊)添加進來:
#include "OneWire.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
① 溫度轉換:

void DS18B20_ConvertT() {
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
② 溫度讀取:

float DS18B20_ReadT() {
unsigned char TLSB, THSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
TLSB = OneWire_ReceiveByte(); // 接收低八位
THSB = OneWire_ReceiveByte(); // 接收高八位
Temp = (THSB << 8) | TLSB;
T = Temp / 16.0; // 轉換成float類型
return T;
}
3. main.c部分
void main() {
float T, TShow;
LCD_Init();
DS18B20_ConvertT();
Delay(1000);
while (1) {
DS18B20_ConvertT(); // 溫度轉換
T = DS18B20_ReadT(); // 讀取溫度
// 顯示符號位
if (T < 0) {
LCD_ShowString(2, 0, "-");
TShow = -T;
} else {
LCD_ShowString(2, 0, "+");
TShow = T;
}
LCD_ShowNum(2, 2, TShow, 3); // 整數部分
LCD_ShowString(2, 5, "."); // 小數點
LCD_ShowNum(2, 6, (unsigned long) (TShow * 10) % 10, 1); // 小數部分,一位小數
}
}
這樣我們就可以實時地展示溫度了。
tips:在開頭進行一次溫度轉換並
Delay(1000);
的原因是:溫度轉換是需要一定的時間的,如果我們不進行延時而直接取出溫度值就會得到初始值25.0。