天天看點

STC8H開發(十一): GPIO單線驅動多個DS18B20數字溫度計目錄DS18B20測溫ROM讀數ROM Search 搜尋算法使用STC8H驅動DS18B20

STC8H開發(十一): GPIO單線驅動多個DS18B20數字溫度計

目錄

  • STC8H開發(一): 在Keil5中配置和使用FwLib_STC8封裝庫(圖文詳解)
  • STC8H開發(二): 在Linux VSCode中配置和使用FwLib_STC8封裝庫(圖文詳解)
  • STC8H開發(三): 基于FwLib_STC8的模數轉換ADC介紹和示範用例說明
  • STC8H開發(四): FwLib_STC8 封裝庫的介紹和使用注意事項
  • STC8H開發(五): SPI驅動nRF24L01無線子產品
  • STC8H開發(六): SPI驅動ADXL345三軸加速度檢測子產品
  • STC8H開發(七): I2C驅動MPU6050三軸加速度+三軸角速度檢測子產品
  • STC8H開發(八): NRF24L01無線傳輸音頻(對講機原型)
  • STC8H開發(九): STC8H8K64U模拟USB HID外設
  • STC8H開發(十): SPI驅動Nokia5110 LCD(PCD8544)
  • STC8H開發(十一): GPIO單線驅動多個DS18B20數字溫度計

DS18B20

參數

  • 單線總線結構, 允許一根總線上挂接多個 DS18B20 并分别通信
  • 在普通溫度下, 可以直接從資料口取電, 這時候隻需要兩根連線.
  • 供電電壓 [3.0V, 5.5V]
  • 溫度檢測範圍 [-55°C, +125°C]攝氏度, [-67°F, +257°F]華氏度
  • 精确率: 在 [-10°C, +85°C] 為 ±0.5°C

Pin腳

一般見到的都是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 T

0x44

後, 溫度會被轉換并存儲在一個2位元組的記憶體單元, 然後通過讀取指令Read Scratchpad

0xBE

讀出.

轉換時間

在溫度轉換指令Convert T

0x44

發起到采集完成需要的時間可能會長達750 ms. 實際使用中, 不同批次 DS18B20 的轉換時間差異也很大, 有的在200-300 ms, 有的接近 800 ms. 貌似越是最近制造的時間越短(可能是工藝改進了?).

如果沒有從VDD供電, DS18B20 的 DQ 必須在轉換過程中保持高電平以提供能量, 是以在這種場景下, 采集的過程中不允許進行其他活動.

讀數結構

這兩個位元組各個bit分别代表的數字含義如下, 高位元組的高5位僅用于表示溫度的正負, 正溫度是0, 負溫度是1, 後面11個bit表示的數字, 負值使用的是補碼, 讀數用 (0xFF - 讀數)

  • 正溫度時, 将16位整數乘以對應的溫度分辨率
  • 負溫度時, 将16位整數取反加1後, 乘以對應的溫度分辨率
7 6 5 4 3 2 1 7 6 5 4 3 2 1
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

ROM讀數

每個 DS18B20 包含一個唯一的隻讀的64bit編碼, 其結構為

  1. 最初 8 bits 為固定的 0x28, 1-Wire family code
  2. 接下來的 48 bits 是唯一序列号
  3. 最後的 8 bits 是前面 56 bits 的 CRC 校驗碼.

這個 64-bit ROM 和 ROM 方法允許在單線(1-Wire)總線上運作多個 DS18B20, 使用單線總線需要使用下面的方法之一發起:

  1. Read ROM,
  2. Match ROM,
  3. Search ROM,
  4. Skip ROM, or
  5. Alarm Search.

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.

CRC 計算

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;
}
           

ROM Search 搜尋算法

當單線總線上挂接了多個DS18B20時, 總線控制端需要通過 ROM Search 指令來判斷總線上存在的裝置以及擷取他們的8位元組唯一ROM.

ROM搜尋算法是重複進行一個簡單的三步操作: 讀取一位, 讀取這位的補碼, 寫入這一位的目标值.

總線控制端在8位元組ROM的每一位上執行這個三步操作後, 就能知道一個 DS18B20 的 8位元組 ROM 值, 如果總線上有多個 DS18B20, 則需要重複多次. 下面的例子假設總線上有4個裝置, 對應的ROM值分别為

  • ROM1 00110101...
  • ROM2 10101010...
  • ROM3 11110101...
  • ROM4 00010001...

搜尋過程如下

  1. 單線總線控制端(以下簡稱總控)執行 RESET, 所有的 DS18B20裝置(以下簡稱裝置)響應這個RESET
  2. 總控執行 Search ROM 指令
  3. 總控讀取1個bit. 這時每個裝置都會将自己的ROM的第一個bit放到總線上, ROM1 和 ROM4 會對總線寫0(拉低總線), 而 ROM2 和 ROM3 則會對總線寫1, 允許總線保持高電平. 這時候總控讀取的是0(低電平). 然後總控繼續讀下一個bit, 每個裝置會将第一個bit的補碼放到總線上, 這時候 ROM1 和 ROM4 寫1, 而 ROM2 和 ROM3 寫0, 是以總控依然讀到一個0, 這時候總控會知道存在多個裝置, 并且它們的ROM在這一位上的值不同. 從每次的兩步讀取中觀察到的值分别有以下的含義
  • 00 一定有多個裝置, 且在這一位上值不同
  • 01 所有裝置(不一定有多個), ROM在這一位上的值是0
  • 10 所有裝置(不一定有多個), ROM在這一位上的值是1
  • 11 總線上沒有裝置
  1. 總控寫入一個bit, 比如寫入0, 表示在後面的搜尋中屏蔽 ROM2 和 ROM3, 僅留下 ROM1 和 ROM4
  2. 總控再執行兩次讀操作, 讀到的值為0,1, 這表示總線上所有裝置在這一位上的值都是0
  3. 總控寫入一個bit, 因為值是确定的, 這次寫入的是0
  4. 總控再執行兩次讀操作, 讀到的值為0,0, 這表示總線上還有多個裝置, 在這一位上的值不同
  5. 總控寫入一個bit, 這次寫入0, 這将屏蔽 ROM1, 僅留下 ROM4
  6. 總控重複進行三步操作, 讀出 ROM4 剩餘的位, 完成第一次搜尋
  7. 總控再次重複之前的搜尋直到第7位
  8. 總控寫入一個bit, 這次寫入1, 将屏蔽 ROM4, 僅保留 ROM1
  9. 總控通過重複三步操作, 讀出 ROM1 剩餘的位
  10. 總控再次重複之前的搜尋直到第3位
  11. 總控寫入一個bit, 這次寫入1, 将屏蔽 ROM1 和 ROM4 僅保留 ROM2 和 ROM3
  12. 重複之前的邏輯, 當所有00讀數都被處理, 說明裝置的ROM已經全部被讀取.

注意:

總控通過單線總線讀取所有裝置, 每個裝置需要的時間為

960 µs + (8 + 3 x 64) 61 µs = 13.16 ms
           

是以這樣的結構的識别速度為每秒鐘75個裝置.

使用STC8H驅動DS18B20

接線

GND  -> GND
P35  -> DQ
3.3V -> VDD
           

代碼

代碼可以從GitHub或者Gitee下載下傳

  • GitHub: FwLib_STC8/tree/master/demo/gpio/ds18b20
  • Gitee: FwLib_STC8/tree/master/demo/gpio/ds18b20

定義 IO

隻需要一個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
           

IO 讀寫

讀一個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;
    }
}
           

單個 DS18B20 的場景

初始化, 注意設定上拉, 以及輸入和輸出模式的切換

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 的場景

指定裝置位址, 讀取溫度

// 重置總線
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();
}
           
rpc