一般見到的都是3pin的To-92封裝, 和普通三極體一樣, 使平面朝向自己, Pin腳朝下, 從左往右依次為: GND, DQ, VDD
DS18B20內部有9位元組的暫存器和3個位元組的EEPROM儲存, EEPROM可以擦寫5萬次以上. 結構如下
DS18B20的核心功能就是數位化的溫度讀數, 可以設定為9, 10, 11, 12位元解析度, 預設解析度是12位元. 各解析度對應的讀數, 溫度解析度分別是0.5, 0.25, 0.125, 0.0625攝氏度.
在執行溫度轉換命令Convert T0x44
後, 溫度會被轉換並儲存在一個2位元組的記憶體單元, 然後通過讀取命令Read Scratchpad0xBE
讀出.
在溫度轉換命令Convert T0x44
發起到採集完成需要的時間可能會長達750 ms. 實際使用中, 不同批次 DS18B20 的轉換時間差異也很大, 有的在200-300 ms, 有的接近 800 ms. 貌似越是最近製造的時間越短(可能是工藝改進了?).
如果沒有從VDD供電, DS18B20 的 DQ 必須在轉換過程中保持高電平以提供能量, 因此在這種場景下, 採集的過程中不允許進行其他活動.
這兩個位元組各個bit分別代表的數位含義如下, 高位元組的高5位僅用於表示溫度的正負, 正溫度是0, 負溫度是1, 後面11個bit表示的數位, 負值使用的是二補數, 讀數用 (0xFF - 讀數)
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
S | S | S | S | S | \(2^6\) | \(2^5\) | \(2^4\) | \(2^3\) | \(2^2\) | \(2^1\) | \(2^0\) | \(2^-1\) | \(2^-2\) | \(2^-3\) | \(2^-4\) | |
MSB | LSB | MSB | LSB |
上電後的預設值為0x0550, 對應85°C, 如果一直讀出都是這個值, 需要檢查接線
TEMPERATURE | DIGITAL OUTPUT (Binary) | DIGITAL OUTPUT (Hex) |
---|---|---|
+125°C | 0000 0111 1101 0000 | 07D0h |
+85°C | 0000 0101 0101 0000 | 0550h* |
+25.0625°C | 0000 0001 1001 0001 | 0191h |
+10.125°C | 0000 0000 1010 0010 | 00A2h |
+0.5°C | 0000 0000 0000 1000 | 0008h |
0°C | 0000 0000 0000 0000 | 0000h |
-0.5°C | 1111 1111 1111 1000 | FFF8h |
-10.125°C | 1111 1111 0101 1110 | FF5Eh |
-25.0625°C | 1111 1110 0110 1111 | FF6Fh |
-55°C | 1111 1100 1001 0000 | FC90h |
每個 DS18B20 包含一個唯一的唯讀的64bit編碼, 其結構為
這個 64-bit ROM 和 ROM 方法允許在單線(1-Wire)匯流排上執行多個 DS18B20, 使用單線匯流排需要使用下面的方法之一發起:
After a ROM function sequence has been successfully executed, the functions specific to the DS18B20 are accessible and the
bus master may then provide one of the six memory and control function commands.
DS18B20 在讀取8位元組ROM和9位元組暫存器時, 最後一個位元組都是前面所有位元組的CRC校驗值. CRC值的比較與是否繼續操作完全由匯流排控制端決定, DS18B20 內部僅計算CRC, 並不會對CRC不匹配的情況進行處理, 需要匯流排控制端主動判斷.
計算CRC的等效多項式函數為(這是datasheet中的式子, 並非冪運算, 要結合後面的流程圖理解)
\(CRC = X^8 + X^5 + X^4 + 1\)
1-Wire匯流排的CRC計算由移位暫存器和互斥或門組成的多項式發生器來執行: 移位暫存器位初始化為0, 然後從第一個位元組的最低位開始, 一次移入一位, 根據計算結果決定是否與第4, 第5位作互斥或, 然後CRC也往右移, 最後移位暫存器的值就是CRC.
使用C語言表示的8位元CRC計算函數為
uint8_t DS18B20_Crc(uint8_t *addr, uint8_t len)
{
uint8_t crc = 0, inbyte, i, mix;
while (len--)
{
// inbyte 儲存當前參與計算的新位元組
inbyte = *addr++;
for (i = 8; i; i--)
{
// 將新位元組與CRC從低位到高位, 依次做互斥或運算, 每次運算完CRC右移一位
// 如果運算結果值為1, 則將CRC與 1000 1100 作互斥或
// 第3,4位元代表流程圖中的互斥或運算, 第7位其實就是運算結果移入的1
mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
{
crc ^= 0x8C;
}
inbyte >>= 1;
}
}
return crc;
}
當單線匯流排上掛接了多個DS18B20時, 匯流排控制端需要通過 ROM Search 命令來判斷匯流排上存在的裝置以及獲取他們的8位元組唯一ROM.
ROM搜尋演演算法是重複進行一個簡單的三步操作: 讀取一位, 讀取這位的二補數, 寫入這一位的目標值.
匯流排控制端在8位元組ROM的每一位上執行這個三步操作後, 就能知道一個 DS18B20 的 8位元組 ROM 值, 如果匯流排上有多個 DS18B20, 則需要重複多次.
下面的例子假設匯流排上有4個裝置, 對應的ROM值分別為
搜尋過程如下
總控通過單線匯流排讀取所有裝置, 每個裝置需要的時間為960 µs + (8 + 3 x 64) 61 µs = 13.16 ms
, 識別速度為每秒鐘75個裝置.
整體的邏輯是按一個固定的方向(先0後1)深度優先遍歷一個二元樹.
資料結構
每一輪的遍歷邏輯
結束條件: 和深度遍歷一樣, 每一輪遍歷後分叉點可能會上下變化, 當分叉點的位置為0時, 說明遍歷結束
uint8_t DS18B20_Buff[9];
uint8_t Search_Stack[8], Fork_Point;
uint8_t DS18B20_Detect(void)
{
uint8_t len = 64, pos = 0;
/* Reset line */
DS18B20_Reset();
/* Start searching */
DS18B20_WriteByte(ONEWIRE_CMD_SEARCHROM);
while (len--)
{
// Two reads
__BIT pb = DS18B20_ReadBit();
__BIT cb = DS18B20_ReadBit();
if (pb && cb)
{
// no device
return 0;
}
else if (pb)
{
// bit = 1
DS18B20_Buff[pos / 8] |= 0x01 << (pos % 8);
DS18B20_WriteBit(SET);
// confirm: set this bit to 1
Search_Stack[pos / 8] |= 0x01 << (pos % 8);
}
else if (cb)
{
// bit = 0
DS18B20_Buff[pos / 8] &= ~(0x01 << (pos % 8));
DS18B20_WriteBit(RESET);
// confirm: set this bit to 1
Search_Stack[pos / 8] |= 0x01 << (pos % 8);
}
else
{
// bit can be 0 or 1
if (Fork_Point == 0xFF || pos > Fork_Point)
{
// new fork point, try 0
DS18B20_Buff[pos / 8] &= ~(0x01 << (pos % 8));
DS18B20_WriteBit(RESET);
// unconfirm: set this bit to 0
Search_Stack[pos / 8] &= ~(0x01 << (pos % 8));
// record new fork point
Fork_Point = pos;
}
else if (pos == Fork_Point)
{
// reach fork point, try 1
DS18B20_Buff[pos / 8] |= 0x01 << (pos % 8);
DS18B20_WriteBit(SET);
// confirm: set this bit to 1
Search_Stack[pos / 8] |= 0x01 << (pos % 8);
}
else // middle point, remain old value
{
DS18B20_WriteBit(DS18B20_Buff[pos / 8] >> (pos % 8) & 0x01);
}
}
pos++;
}
// Reset fork point
while (Fork_Point > 0 && Search_Stack[Fork_Point / 8] >> (Fork_Point % 8) & 0x01 == 0x01) Fork_Point--;
return pos;
}
呼叫方法
Fork_Point = 0xFF;
while (Fork_Point && DS18B20_Detect())
{
UART1_TxHex(Fork_Point);
UART1_TxChar(' ');
PrintBuff(0, 8);
UART1_TxString("\r\n");
}
GND -> GND
P35 -> DQ
3.3V -> VDD
程式碼可以從GitHub或者Gitee下載
只需要一個Pin, 在STC8H中, 注意要將其設定為上拉, 否則讀出來的全是0
#define DS18B20_DQ P35
#define DS18B20_DQ_PULLUP() GPIO_SetPullUp(GPIO_Port_3, GPIO_Pin_5, HAL_State_ON)
#define DS18B20_DQ_INPUT() GPIO_P3_SetMode(GPIO_Pin_5, GPIO_Mode_Input_HIP)
#define DS18B20_DQ_OUTPUT() GPIO_P3_SetMode(GPIO_Pin_5, GPIO_Mode_InOut_OD)
#define DS18B20_DQ_LOW() DS18B20_DQ=RESET
#define DS18B20_DQ_HIGH() DS18B20_DQ=SET
讀一個bit和一個byte
__BIT DS18B20_ReadBit(void)
{
__BIT b = RESET;
/* Line low */
DS18B20_DQ = RESET;
DS18B20_DQ_OUTPUT();
SYS_DelayUs(2);
/* Release line */
DS18B20_DQ_INPUT();
SYS_DelayUs(10);
/* Read line value */
if (DS18B20_DQ) {
/* Bit is HIGH */
b = SET;
}
/* Wait 50us to complete 60us period */
SYS_DelayUs(50);
/* Return bit value */
return b;
}
uint8_t DS18B20_ReadByte(void)
{
uint8_t i = 8, byte = 0;
while (i--)
{
byte >>= 1;
byte |= (DS18B20_ReadBit() << 7);
}
return byte;
}
寫一個bit和一個byte
void DS18B20_WriteBit(__BIT b)
{
if (b)
{
/* Set line low */
DS18B20_DQ = RESET;
DS18B20_DQ_OUTPUT();
SYS_DelayUs(10);
/* Bit high */
DS18B20_DQ_INPUT();
/* Wait for 55 us and release the line */
SYS_DelayUs(55);
DS18B20_DQ_INPUT();
}
else
{
/* Set line low */
DS18B20_DQ = RESET;
DS18B20_DQ_OUTPUT();
SYS_DelayUs(65);
/* Bit high */
DS18B20_DQ_INPUT();
/* Wait for 5 us and release the line */
SYS_DelayUs(5);
DS18B20_DQ_INPUT();
}
}
void DS18B20_WriteByte(uint8_t byte)
{
uint8_t i = 8;
/* Write 8 bits */
while (i--)
{
/* LSB bit is first */
DS18B20_WriteBit(byte & 0x01);
byte >>= 1;
}
}
初始化, 注意設定上拉, 以及輸入和輸出模式的切換
void DS18B20_Init(void)
{
DS18B20_DQ_PULLUP();
DS18B20_DQ_OUTPUT();
DS18B20_DQ = SET;
SYS_DelayUs(1000);
DS18B20_DQ = RESET;
SYS_DelayUs(1000);
DS18B20_DQ = SET;
SYS_DelayUs(2000);
}
讀取溫度, 這樣讀出的值並非溫度值, 需要根據上面的溫度轉換, 乘以對應的溫度單元值(預設為0.0625攝氏度)
// 發起轉換
DS18B20_StartAll();
// 讀取匯流排, 當轉換完成時會變為高電平
while (!DS18B20_AllDone())
{
UART1_TxChar('.');
SYS_Delay(1);
}
// 重置匯流排
DS18B20_Reset();
// 跳過ROM選擇
DS18B20_WriteByte(ONEWIRE_CMD_SKIPROM);
// 寫入讀取暫存器指令
DS18B20_WriteByte(ONEWIRE_CMD_RSCRATCHPAD);
// 讀出9個位元組的資料
for (i = 0; i < 9; i++)
{
/* Read byte by byte */
data[i] = DS18B20_ReadByte();
}
// 溫度值位於第1和第2個位元組
temperature = data[1];
temperature = temperature << 8 | data[0];
讀取ROM
// 重置匯流排
DS18B20_Reset();
// 寫入讀取ROM指令, 注意這個命令不能用於連線多個裝置的匯流排, 否則結果讀數是無意義的
DS18B20_WriteByte(ONEWIRE_CMD_READROM);
// 讀出資料
for (i = 0; i < 8; i++)
{
*buf++ = DS18B20_ReadByte();
}
指定裝置地址, 讀取溫度
// 重置匯流排
DS18B20_Reset();
// 根據地址選擇裝置
DS18B20_Select(addr);
// 對選中的裝置, 發起轉換
DS18B20_WriteByte(DS18B20_CMD_CONVERTTEMP);
// 等待轉換結束
// 重置匯流排
DS18B20_Reset();
// 根據地址選擇裝置
DS18B20_Select(addr);
// 寫入讀取暫存器指令
DS18B20_WriteByte(ONEWIRE_CMD_RSCRATCHPAD);
// 讀取資料
for (i = 0; i < 9; i++)
{
*buf++ = DS18B20_ReadByte();
}