第二十九章 DS18B20實驗
1)實驗平台:正點原子DNESP32S3開發板
2)章節摘自【正點原子】ESP32-S3使用指南—IDF版 V1.6
3)購買鏈接:https://detail.tmall.com/item.htm?&id=768499342659
4)全套實驗源碼+手冊+視頻下載地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html
5)正點原子官方B站:https://space.bilibili.com/394620890
6)正點原子DNESP32S3開發板技術交流羣:132780729
本章,我們將介紹ESP32-S3如何讀取外部温度傳感器的温度,來得到較為準確的環境温度。我們將學習單總線技術,通過它來實現ESP32-S3和外部温度傳感器DS18B20的通信,並把從温度傳感器得到的温度顯示在LCD上。
本章分為如下幾個小節:
29.1 DS18B20簡介
29.2 硬件設計
29.3 程序設計
29.4 下載驗證
29.1 DS18B20介紹
29.1.1 DS18B20簡介
DS18B20是由DALLAS半導體公司推出的一種“單總線”接口的温度傳感器,實物圖如下圖所示。
圖29.1.1.1 DS18B20實物圖
與傳統的熱敏電阻等測温元件相比,它是一種新型的體積小、適用電壓寬、與微處理器接口簡單的數字化温度傳感器。單總線結構具有簡潔且經濟的特點,可使用户輕鬆地組建傳感器網絡,從而為測量系統的構建引入全新的概念,測試温度範圍為-55~+125℃,精度為±0.5℃。現場温度直接以單總線的數字方式傳輸,大大提高了系統的抗干擾性。它能直接讀出被測温度,並且可根據實際要求通過簡單的編程實現9~12位的數字值讀數方式。它工作在3~5.5V的電壓範圍,採用多種封裝形式,從而使系統設置靈活、方便,設定分辨率以及用户設定的報警温度存儲在EEPROM中,掉電後依然保存。其內部結構如下圖所示。
圖29.1.1.1 DS18B20內部結構圖
ROM中的64位序列號是出廠前被標記好的,它可以看作使該DS18B20的地址序列碼,每個DS18B20的64位序列號均不相同。64位ROM的排列是:前8位是產品家族碼,接着48位是DS18B20的序列號,最後8位是前面56位的循環冗餘校驗碼(CRC=X8+X5+X4+1)。ROM作用是使每一個DS18B20都各不相同,這樣設計可以允許一根總線上掛載多個DS18B20模塊同時工作且不會引起衝突。
29.1.2 DS18B20時序介紹
所有單總線器件要求採用嚴格的信號時序,以保證數據的完整性。DS18B20共有6種信號類型:復位脈衝、應答脈衝、寫0、寫1、讀0和讀1。所有這些信號,除了應答脈衝以外,都是由主機發出同步信號。並且發送所有的命令和數據都是字節的低位在前。這裏我們簡單介紹這幾個信號的時序。
1,復位脈衝和應答脈衝
圖29.1.2.1 復位脈衝和應答脈衝時序圖
單總線上的所有通信都是以初始化序列開始。主機輸出低電平,保持低電平時間至少要在480us,以產生復位脈衝。接着主機釋放總線,4.7K的上拉電阻將單總線拉高,延時時間要在15~60us,並進入接收模式(Rx)。接着DS18B20拉低總線60~240us,以產生低電平應答脈衝。
2,寫時序
圖29.1.2.2 寫時序圖
寫時序包括寫0時序和寫1時序。所有寫時序至少需要60us,且在兩次獨立的寫時序之間至少需要1us的恢復時間,兩種寫時序均起始於主機拉低總線。寫1時序:主機輸出低電平,延時2us,然後釋放總線,延時60us。寫0時序:主機輸出低電平,延時60us,然後釋放總線延時2us。
3,讀時序
圖29.1.2.3 讀時序圖
單總線器件僅在主機發出讀時序時,才向主機傳輸數據,所以,在主機發出讀數據命令後,必須馬上產生讀時序,以便從機能夠傳輸數據。所有讀時序至少需要60us,且在2次獨立的讀時序之間至少需要1us的恢復時間。每個讀時序都由主機發起,至少拉低總線1us。主機在讀時序期間必須釋放總線,並且在時序起始後的15us之內採樣總線狀態。典型的讀時序過程為:主機輸出低電平延時2us,然後主機轉入輸入模式延時12us,然後讀取單總線當前的電平,然後延時50us。
在瞭解單總線時序之後,我們來看一下DS18B20的典型温度讀取過程,DS18B20的典型温度讀取過程為:復位→發SKIP ROM(0xCC)→發開始轉換命令(0x44)→延時→復位→發送SKIP ROM命令(0xCC)→發送存儲器命令(0xBE)→連續讀取兩個字節數據(即温度)→結束。
DS18B20的簡介,我們就介紹到這裏,關於該傳感器的詳細説明,請大家參考其數據手冊。
29.2 硬件設計
29.2.1 例程功能
DS18B20每隔100ms左右讀取一次數據,並把温度顯示在LCD上。LED閃爍用於提示程序正在運行。
29.2.2 硬件資源
1.LED燈
LED-IO1
2.USART0
U0TXD-IO43
U0RXD-IO44
3.XL9555
IIC_SDA-IO41
IIC_SCL-IO42
4.SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳線帽將IO_SET和LCD_DC相連)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
5.DS18B20
1WIRE_DQ-IO0(在P5端口,使用跳線帽將IWIRE_DQ和IO0相連)
29.2.3 原理圖
DS18B20原理圖,如下圖所示。
圖29.2.3.1 DS18B20原理圖
從上圖可以看出,1WIRE_DQ並沒有直接跟ESP32-S3的IO0相連,而是需要通過跳線帽進行連接。1WIRE_DQ和IO0連接圖如下圖所示。
圖29.2.3.2 WIRE_DQ和IO0連接圖
圖29.2.3.3 DS18B20與開發板連接的位置
29.3 程序設計
29.3.1 程序流程圖
程序流程圖能幫助我們更好的理解一個工程的功能和實現的過程,對學習和設計工程有很好的主導作用。下面看看本實驗的程序流程圖:
圖29.3.1.1 DS18B20實驗程序流程圖
29.3.2 DS18B20函數解析
這一章節除了涉及到GPIO的API函數,便沒有再涉及到其他API函數。因此,有關GPIO的API函數介紹,請讀者回顧此前的第十章的內容。接下來,筆者將直接介紹DS18B20的驅動代碼。
29.3.3 DS18B20驅動解析
在IDF版19_ds18b20例程中,作者在19_ds18b20\components\BSP路徑下新增了一個DS18B20文件夾,分別用於存放ds18b20.c、ds18b20.h這兩個文件。其中,ds18b20.h文件負責聲明DS18B20相關的函數和變量,而ds18b20.c文件則實現了DS18B20的驅動代碼。下面,我們將詳細解析這兩個文件的實現內容。
1,ds18b20.h文件
由於數據線會存在輸入輸出模式的切換以及高低電平的設置,所以為DS18B20_DQ_PIN做了相關宏函數供單總線時序函數調用。
#define DS18B20_DQ_GPIO_PIN GPIO_NUM_0
/* DS18B20引腳高低電平枚舉 */
typedef enum
{
DS18B20_PIN_RESET = 0u,
DS18B20_PIN_SET
}DS18B20_GPIO_PinState;
/* IO操作 */
#define DS18B20_DQ_IN gpio_get_level(DS18B20_DQ_GPIO_PIN) /* 數據端口輸入 */
/* DS18B20端口定義 */
#define DS18B20_DQ_OUT(x) do{ x ? \
gpio_set_level(DS18B20_DQ_GPIO_PIN,DS18B20_PIN_SET) : \
gpio_set_level(DS18B20_DQ_GPIO_PIN, DS18B20_PIN_RESET); \
}while(0)
gpio_get_level()與gpio_set_level()的介紹在GPIO章節已經介紹過了,在此不做贅述,請參照10.3.2小節的內容。
DS18B20_DQ_OUT(x)宏函數的作用是設置GPIO_NUM_0的電平狀態,x為0表示低電平,x為1表示高電平。
DS18B20_DQ_IN宏函數的作用是獲取GPIO_NUM_0的電平狀態,返回值為0表示低電平,返回值為1表示高電平。
2,ds18b20.c文件
/**
* @brief 初始化DS18B20
* @param 無
* @retval 0, 正常
* 1, 不存在/不正常
*/
uint8_t ds18b20_init(void)
{
gpio_config_tgpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引腳中斷 */
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT_OD; /* 開漏模式的輸入和輸出 */
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */
/* 設置的引腳的位掩碼 */
gpio_init_struct.pin_bit_mask = 1ull << DS18B20_DQ_GPIO_PIN;
gpio_config(&gpio_init_struct); /* 配置DS18B20引腳 */
ds18b20_reset();
return ds18b20_check();
}
在DS18B20初始化函數中,通過調用復位函數和自檢函數,可以知道DS18B20是否正常。
下面介紹一下在前面提及的幾個信號類型。
/**
* @brief 復位DS18B20
* @param 無
* @retval 無
*/
static void ds18b20_reset(void)
{
DS18B20_DQ_OUT(0); /* 拉低DQ,復位 */
esp_rom_delay_us(750); /* 拉低750us */
DS18B20_DQ_OUT(1); /* DQ=1, 釋放復位 */
esp_rom_delay_us(15); /* 延遲15US */
}
/**
* @brief 等待DS18B20的迴應
* @param 無
* @retval 0, DS18B20正常
* 1,DS18B20異常/不存在
*/
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DS18B20_DQ_IN && retry < 200) /* 等待DQ變低, 等待200us */
{
retry++;
esp_rom_delay_us(1);
}
if (retry >= 240)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ變高, 等待240us */
{
retry++;
esp_rom_delay_us(1);
}
if (retry >= 240)
{
rval = 1;
}
}
return rval;
}
以上兩個函數分別代表着前面所説的復位脈衝與應答信號,大家可以對比前面的時序圖進行理解。由於復位脈衝比較簡單,所以這裏不做展開。現在看一下應答信號函數,函數主要是對於DS18B20傳感器的迴應信號進行檢測,對此判斷其是否存在。函數的實現也是依據時序圖進行邏輯判斷,例如當主機發送了復位信號之後,按照時序,DS18B20會拉低數據線60~240us,同時主機接收最小時間為480us,我們就依據這兩個硬性條件進行判斷,首先需要設置一個時限等待DS18B20響應,後面也設置一個時限等待DS18B20釋放數據線拉高,滿足這兩個條件即DS18B20成功響應。
下面介紹的是寫函數,其定義如下:
/**
* @brief 寫一個字節到DS18B20
* @param data: 要寫入的字節
* @retval 無
*/
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0);
esp_rom_delay_us(2);
DS18B20_DQ_OUT(1);
esp_rom_delay_us(60);
}
else
{
DS18B20_DQ_OUT(0);
esp_rom_delay_us(60);
DS18B20_DQ_OUT(1);
esp_rom_delay_us(2);
}
data >>= 1;
}
}
通過形參決定是寫1還是寫0,按照前面對寫時序的分析,我們可以很清晰知道寫函數的邏輯處理。
有寫函數肯定就有讀函數,下面看一下讀函數:
/**
* @brief 從DS18B20讀取一個位
* @param 無
* @retval 讀取到的位值: 0 / 1
*/
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_DQ_OUT(0);
esp_rom_delay_us(2);
DS18B20_DQ_OUT(1);
esp_rom_delay_us(12);
if (DS18B20_DQ_IN)
{
data = 1;
}
esp_rom_delay_us(50);
return data;
}
/**
* @brief 從DS18B20讀取一個字節
* @param 無
* @retval 讀到的數據
*/
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先輸出低位數據 ,高位數據後輸出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}
在這裏,ds18b20_read_bit函數從DS18B20處讀取1位數據,在前面已經對讀時序也進行了詳細的分析,所以這裏也不展開解釋了。
下面介紹讀取温度函數,其定義如下:
/**
* @brief 從ds18b20得到温度值(精度:0.1C)
* @param 無
* @retval 温度值 (-550~1250)
* @note 返回的温度值放大了10倍.
* 實際使用的時候,要除以10才是實際温度.
*/
shortds18b20_get_temperature(void)
{
uint8_t flag = 1; /* 默認温度為正數 */
uint8_t TL, TH;
short temp;
ds18b20_start(); /* ds1820 startconvert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
TL = ds18b20_read_byte(); /* LSB */
TH = ds18b20_read_byte(); /* MSB */
if (TH > 7)
{ /* 温度為負,查看DS18B20的温度表示法與計算機存儲正負數據的原理一致:
正數補碼為寄存器存儲的數據自身,負數補碼為寄存器存儲值按位取反後+1
所以我們直接取它實際的負數部分,但負數的補碼為取反後加一,但考慮到
低位可能+1後有進位和代碼冗餘,我們這裏先暫時沒有作+1的處理,這裏需要留意 */
TH = ~TH;
TL = ~TL;
flag = 0; /* 温度為負 */
}
temp = TH; /* 獲得高八位 */
temp <<= 8;
temp += TL; /* 獲得底八位 */
/* 轉換成實際温度 */
if (flag == 0)
{ /* 將温度轉換成負温度,這裏的+1參考前面的説明 */
temp = (double)(temp + 1) * 0.625;
temp = -temp;
}
else
{
temp = (double)temp * 0.625;
}
return temp;
}
在這裏簡單介紹一下上面用到的RAM指令:
跳過ROM(0xCC),該指令只適合總線只有一個節點,它通過允許總線上的主機不提供64位ROM序列號而直接訪問RAM,節省了操作時間。
温度轉換(0x44),啓動DS18B20進行温度轉換,結果存入內部RAM。
讀暫存器(0xBE),讀暫存器9個字節內容,該指令從RAM的第一個字節(字節0)開始讀取,直到九個字節(字節8,CRC值)被讀出為止。如果不需要讀出所有字節的內容,那麼主機可以在任何時候發出復位信號以中止讀操作。
29.3.4 CMakeLists.txt文件
打開本實驗BSP下的CMakeLists.txt文件,其內容如下所示:
set(src_dirs
DS18B20
IIC
LCD
LED
SPI
XL9555)
set(include_dirs
DS18B20
IIC
LCD
LED
SPI
XL9555)
set(requires
driver)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的紅色IIC、XLP555驅動需要由開發者自行添加,以確保IIC_EXIO驅動能夠順利集成到構建系統中。這一步驟是必不可少的,它確保了IIC_EXIO驅動的正確性和可用性,為後續的開發工作提供了堅實的基礎。
29.3.5 實驗應用代碼
打開main/main.c文件,該文件定義了工程入口函數,名為app_main。該函數代碼如下。
i2c_obj_t i2c0_master;
/**
* @brief 程序入口
* @param 無
* @retval 無
*/
void app_main(void)
{
uint8_t err;
uint8_t t = 0;
short temperature;
esp_err_t ret;
/* 初始化NVS */
ret = nvs_flash_init();
if (ret ==ESP_ERR_NVS_NO_FREE_PAGES ||ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
/* 初始化LED */
led_init();
/* 初始化IIC0 */
i2c0_master = iic_init(I2C_NUM_0);
/* 初始化SPI */
spi2_init();
/* 初始化XL9555 */
xl9555_init(i2c0_master);
/* 初始化LCD */
lcd_init();
lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DS18B20 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
/* 初始化DS18B20數字温度傳感器 */
err = ds18b20_init();
if (err != 0)
{
while (1)
{
lcd_show_string(30, 110, 200, 16, 16, "DS18B20Error", RED);
vTaskDelay(200);
lcd_fill(30, 110, 239, 130 + 16, WHITE);
vTaskDelay(200);
}
}
lcd_show_string(30, 110, 200, 16, 16, "DS18B20 OK", RED);
lcd_show_string(30, 130, 200, 16, 16, "Temp: .C", BLUE);
while (1)
{
/* 每100ms讀取一次 */
if (t % 10 == 0)
{
temperature = ds18b20_get_temperature();
if (temperature < 0)
{
/* 顯示負號 */
lcd_show_char(30 + 40, 130, '-', 16, 0, BLUE);
/* 轉為正數 */
temperature = -temperature;
}
else
{
/* 去掉負號 */
lcd_show_char(30 + 40, 130, ' ', 16, 0, BLUE);
}
/* 顯示正數部分 */
lcd_show_num(30 + 40 + 8, 130, temperature / 10, 2, 16, BLUE);
/* 顯示小數部分 */
lcd_show_num(30 + 40 + 32, 130, temperature % 10, 1, 16, BLUE);
}
vTaskDelay(10);
t++;
if (t == 20)
{
t = 0;
/* LED閃爍 */
LED_TOGGLE();
}
}
}
在main函數中,完成一系列外設初始化之後,調用ds18b20_init函數完成DS18B20初始化,LCD顯示實驗信息。
在while循環中,間隔100毫秒調用ds18b20_get_temperature函數獲取DS18B20傳感器的温度數據,然後在LCD進行顯示。LED燈每隔200毫秒狀態翻轉,實現閃爍效果。
29.4 下載驗證
假定DS18B20傳感器已經接上去正確的位置,將程序下載到開發板後,可以看到LED0不停的閃爍,提示程序已經在運行了。LCD顯示當前的温度值的內容如下圖所示:
圖29.4.1 DS18B20實驗測試圖
該程序還可以讀取並顯示負温度值,具備零下温度條件可以測試一下。