再有5天就是“黃金”假期~放七天假,上七天班(學)!不管怎么樣,該做什么就做什么。學習便學習,工作便工作,走路便走路,吃飯便吃飯。
今天我們一起完成一個比較完整的作品,基于DS18B20和LabVIEW的多點溫度測量系統(tǒng)。我重點介紹實現(xiàn)多點DS18B20溫度驅動模塊的思路,具體實現(xiàn)大家可以閱讀源碼。驅動源碼參考了不少資料,在此感謝那些樂于分享的程序員。分享,傳遞,沉淀,這一直都是我們堅持的信念。
關于DS18B20的特性、工作原理、時序等,請參考相關資料:
-
DS18B20官方手冊:https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf
-
DS18B20的復位(初始化)、讀時序、寫時序:https://blog.csdn.net/qq_17017545/article/details/82120467
-
DS18B20多點測溫方案(多個DS18B20掛在一根總線上):https://blog.csdn.net/redeemer_Qi/article/details/108854687
一、多點溫度測量系統(tǒng)架構
多點溫度測量系統(tǒng)框圖如圖1所示。編號為1#~8#的DS18B20連接到8051單片機的P0口,每個DS18B20占P0口的一個I/O,1#對應P0.1,2#對應P0.1, ......, 8#對應P0.7。8051單片機周期讀取多點溫度,通過串口上報到LabVIEW上位機。?

圖1? 多點溫度測量系統(tǒng)框圖
? ? ?在我們的例子中,只實現(xiàn)了3點溫度。由于我們采用了模塊化編程,要擴展到8路只需改動2個地方(猜猜是哪里)。圖2給出了仿真電路圖。我們在串口仿真電路圖上增加了3個DS18B20,分別接P0.0、P0.1和P0.2。
? ??
圖2 多點測溫仿真電路圖
二、DB18B20多點溫度驅動模塊設計思路
網上有很多單個DS18B20溫度驅動程序源碼,可惜的是這些源碼無法直接使用,因為源碼里DS18B20初始化函數(shù)、讀溫度函數(shù)、寫DS18B20函數(shù)等代碼綁定到了固定的I/O引腳(如P1.0),讀和寫都是基于單個I/O實現(xiàn)。以至于代碼無法復用。
https://blog.csdn.net/redeemer_Qi/article/details/108854687提供了多個DS18B20掛在單一總線的多點測溫方案,大家可以去研究研究。我們今天使用另外的思路。?
思路來源:Arduino里的I/O讀寫函數(shù)( digitalRead,digitalWrite)是通過指定pin序號來實現(xiàn)數(shù)字引腳的讀寫操作的。在分析這兩個函數(shù)的原型時,發(fā)現(xiàn)它們是通過PORT和BIT_MASK來對整個PORT的寄存器操作實現(xiàn)的。舉例來說,我們要寫1到P0.0,則對P0寄存器進行以下操作:
- ?
P0 = P0|0x01; //或寫成:P0|=0x01;
某位或1,就能置該位為1。
對P0.0寫0,則是:
- ?
P0?=?P0&(~0x01);?//或寫為?P0?&=?~0x01;
某位與0,則該位清零。某位與1,該位保持不變。
P0是PORT, 0x01是P0.0在P0寄存器的BIT MASK。表1給出了P0.0~P0.7的位掩碼(BIT MASK)。
| I/O |
位掩碼(二進制) |
位掩碼(十六進制) |
|
P0.0 |
0000_0001b |
0x01 |
|
P0.1 |
0000_0010b |
0x02 |
|
P0.2 |
0000_0100b |
0x04 |
|
P0.3 |
0000_1000b |
0x08 |
|
P0.4 |
0001_0000b |
0x10 |
|
P0.5 |
0010_0000b |
0x20 |
|
P0.6 |
0100_0000b |
0x40 |
|
P0.7 |
1000_0000b |
0x80 |
練習:使用位掩碼對P0.5操作,寫1和清零。
- ?
- ?
- ?
//你的答案
前面解決了寫I/O。哪如何實現(xiàn)讀取某個IO的狀態(tài)呢?使用位掩碼~正確。
- ?
- ?
- ?
- ?
- ?
uchar value = PORT & BIT_MASK; //非零表示輸入高電平,全零表示輸入低電平。if(value): //高電平do somethingelse: //低電平do something
? ? ??
例如 if(P0 & 0x04)就能讀取到P0.2的輸入狀態(tài)。請分析為什么?
我們設計了ds18b20.h,在該頭文件里定義了PORT、BIT_MASK和相關的驅動函數(shù)(DS18B20初始化、讀字節(jié)、寫字節(jié)、讀溫度)。下面簡要概述ds18b20.h。
1、PORT和BIT_MASK
使用宏定義了PORT和BIT_MASK。
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//三個DS18B20,分別接到P0.1, P0.2, P0.3//P0口最多連接8個DS18B20#define DS18B20_PORT P0#define ds18b20_1_mask 0x01 //sensor no. 1#define ds10b20_2_mask 0x02 //sensor no. 2#define ds10b20_3_mask 0x04 //sensor no. 3#define ds10b20_4_mask 0x08 //sensor no. 4#define ds10b20_5_mask 0x10 //sensor no. 5
?
ds18b20_get_mask( ) 函數(shù)實現(xiàn)了傳感器編號到BIT_MASK的映射。例如, 1的dq引腳接到Px.0, 掩碼為0x01。
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// 由序號獲得ds18b20的引腳mask// no: 1,2,3uchar ds18b20_get_mask(uchar no){uchar pin_mask;switch(no){case 1: {pin_mask = ds18b20_1_mask; break;}case 2: {pin_mask = ds10b20_2_mask; break;}case 3: {pin_mask = ds10b20_3_mask; break;}default: break;}return pin_mask;}
2、重要驅動函數(shù)
(1)DS18B20初始化函數(shù)
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//初始化ds18b20uchar ds18b20_init(uchar sensor_no){uchar pin_mask;uchar ack;pin_mask= ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1delay_10xus(1); //延時10usDS18B20_PORT &= ~pin_mask; //清零delay_10xus(90);//拉低900usDS18B20_PORT |= pin_mask; //置1delay_10xus(8); //80us后讀ds18b20的響應ack = DS18B20_PORT & pin_mask; //讀引腳delay_10xus(50);return ack;}
?
初始化函數(shù)供讀操作、寫操作前調用。也可以單獨調用來判斷DS18B20是否存在。ACK為0表示傳感器應答,ACK為1表示傳感器未應答(多次未應答可視為傳感器不存在或損壞)。
(2)讀溫度驅動函數(shù)
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// 讀溫度函數(shù),返回浮點類型溫度float ds18b20_read_temperature(uchar sensor_no){uchar low_byte = 0;uchar hight_byte = 0;int temp = 0;float temperature = 0;if(ds18b20_init(sensor_no) == 0) // 溫度傳感器應答了{is_ds10b20_exist = 1;ds18b20_start_convert(sensor_no); //開始轉換ds18b20_start_read(sensor_no); //開始讀取low_byte = ds18b20_read_byte(sensor_no); //讀溫度的低八位hight_byte = ds18b20_read_byte(sensor_no); //讀溫度的高八位temp = (hight_byte<<8)|low_byte;}else{is_ds10b20_exist = 0;}if((temp & 0xF800) == 0xF800) //負溫度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}else{temperature = temp * 0.0625; //12位的溫度分辨率為0.0625℃}return temperature;}
讀溫度驅動函數(shù)主要完成以下操作:
① 調用ds18b20_init()判斷DS18B20是否存在。
② 存在,使用ds18b20_start_convert()函數(shù)讓DS18B20進行溫度轉換;等待一定時間后,讀溫度字節(jié),并把溫度字節(jié)轉換為float數(shù)據,再返回。
③ 不存在,置 is_ds10b20_exist為0。
關于讀溫度函數(shù),有幾點要說明:
①? 溫度字節(jié)可以認為是A/D的數(shù)字量輸出,其量化單位q就是溫度分辨率。12位的是0.0625℃。
DS18B20默認是12位分辨率,可軟件配置為9、10、11、12位,分辨率分別為0.5、0.25、0.125、0.0625℃。
② 溫度MSB字節(jié)的高5位是符號位, 11111表示是負的溫度,以補碼儲存。所以先取反+1得到絕對值,再乘以分辨率,最后變成負數(shù)。代碼如下:?
- ?
- ?
- ?
- ?
- ?
if((temp & 0xF800) == 0xF800) //負溫度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}
?
③ LOW_BYTE和HIGH_BYTE對應于圖3中的LSB BYTE, MSB BYTE。注意,DS18B20先傳輸LSB字節(jié),且是最低位先傳輸(LSb First)。

圖3 溫度數(shù)據
④ 溫度讀取函數(shù)有瑕疵。溫度轉換的代碼不管傳感器存在是否,都會進行。當傳感器不存在時,始終返回0,埋了一個大坑~~試一試,改進代碼。
(提示:不存在返回250,超量程了就是設備不存在)。在主程序再做處理。
⑤? is_ds10b20_exist 原來是針對單個DS18B20測溫設計的。此處實在是雞肋。你能把它用起來嗎?提示:結合第④點。
3、(串口)數(shù)據傳輸協(xié)議
我們直接使用C51編程入門(二十三)串口編程入門--串口應用協(xié)議(二)里設計的協(xié)議。每個傳感器上報的數(shù)據包括1字節(jié)的設備號、4字節(jié)的溫度(float)。三個傳感器的數(shù)據一起“打包”上報,如下。
|
1#設備號(1B) |
1#溫度(4B) |
2#設備號(1B) |
2#溫度(4B) |
3#設備號(1B) |
3#溫度(4B) |
主函數(shù)如下。主函數(shù)所在的.c源碼除了增加#include"ds18b20.h"并另存為新文件名外,其它內容與C51編程入門(二十三)串口編程入門--串口應用協(xié)議(二)的一模一樣,未作任何修改~(難能可貴..)
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
void main(){unsigned char ds18b20_no = 1; //設備號unsigned char ds18b20_N = 3; //ds18b20總數(shù)float temperature; //溫度uart_init();while(1){temperature = ds18b20_readTemperature(ds18b20_no); //讀溫度sendTemperature(ds18b20_no, temperature); //發(fā)送溫度ds18b20_no++;if(ds18b20_no > ds18b20_N)//已經讀完所有點的溫度{ds18b20_no = 1;}delayMS(1000); //等待1s左右}}
?
三、LabVIEW上位機程序改進
1、添加溫度保存子VI(saveTemperature.vi),如圖4所示。實現(xiàn)將三個溫度和當前時間戳存儲到一個表格。

圖4?saveTemperature.vi程序框圖
程序說明如下:
① 創(chuàng)建文件路徑,使用了應用程序目錄,實現(xiàn)將程序存儲到程序目錄下。目標文件由文件名和當前日期(年月日)組成。這樣實現(xiàn)一天一個文件。
.xls擴展名指定文件為表格。
② 打開/創(chuàng)建文件,并設置文件指針到文件末尾,即從文件末尾新增數(shù)據。這樣,就不會覆蓋舊數(shù)據。
③ 調用格式化寫入文件,巧妙地通過格式化將數(shù)據寫到表格里。格式化字符為:
- ?
%.1f %.1f %.1f %s
?
三個%.1f對應三個溫度值,存為1位小數(shù)的浮點數(shù)據。 是制表符,移動到下一個表格單元。%s為字符串,這里對應著時間戳字符串。
是換行,保證下一次數(shù)據存儲到表格末尾的新的一行。
2、串口解釋單個傳感器數(shù)據的子VI(getReceiveData.vi)
程序框圖如圖5所示。說明如下:
① 先讀取1個字節(jié)數(shù)據,并調用強制類型轉換函數(shù)轉換為U8數(shù)據。此為設備號,1個字節(jié)。
② 再讀取4個字節(jié)數(shù)據,并調用強制類型轉換函數(shù)轉換為SGL數(shù)據。此為溫度數(shù)據,4個字節(jié)。注意,不能轉換為DBL數(shù)據,因為LabVIEW的DBL為64位,8個字節(jié),類型不匹配。

圖5?getReceiveData.vi程序框圖
下圖為LabVIEW主程序框圖。需要注意的是,初始化串口時,禁用串口的啟用停止符選項(F常量連接的選項)。

圖6? 主程序框圖
三、運行結果
LabVIEW上位機運行后,立馬收到了很多數(shù)據(這些都是緩沖在電腦串口緩存里)。如果想要丟棄掉,可以在進入while循環(huán)前清空串口緩沖區(qū)。
使用ds18b20.h時,應注意設置(修改):
1. DS18B20_PORT宏定義,改為實際使用的PORT(P0、P1、P2、P3)

?
2. 新增BIT_MASK, ds18b20.h只定義了5個,即ds18b20_1_mask到ds18b20_5_mask。?
關于BIT_MASK,其實也無需預先定義宏。我們可根據sensor_no算出來,核心代碼如下:
- ?
bit_mask = 0x01<<(sensor_no-1); //sensor_no = 1~8
3. 注意,DS18B20上電溫度轉換結果默認為85℃,第一次讀到的溫度始終是85。因此,我們在正式讀取之前,應該調用一次讀取溫度函數(shù)(如在while循環(huán)前)。
?
四、結束語
串口程序編寫教程到此告一個段落,希望相關文章對大家有所助益。原本計劃繼續(xù)寫串口校驗和和AT命令,后面視情況而定吧。如何在有限的時間里,完成更多的事情是一個值得研究和探討的話題。如果您有感興趣的主題,可后臺發(fā)消息給我。?
如果你覺得本篇文章有所幫助,請點贊、打賞。?分享,傳遞,沉淀。
附錄:源代碼
ds18b20驅動源碼(ds18b20.h)
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// ds18b20.h#ifndef _DS18B20_H#define _DS18B20_H#include "intrins.h"#include "reg51.h"float temperature = 0;bit is_ds10b20_exist = 0; //1: 存在, 0:不存在#define uchar unsigned char//三個DS18B20,分別接到P0.1, P0.2, P0.3//P0口最多連接8個DS18B20#define DS18B20_PORT P0#define ds18b20_1_mask 0x01 //sensor no. 1#define ds10b20_2_mask 0x02 //sensor no. 2#define ds10b20_3_mask 0x04 //sensor no. 3#define ds10b20_4_mask 0x08 //sensor no. 4#define ds10b20_5_mask 0x10 //sensor no. 5// 10us延時函數(shù)void delay_10xus(uchar n){//每個循環(huán)約10us左右, 110次循環(huán)約1mswhile(n--);}// 由序號獲得ds18b20的引腳mask// no: 1,2,3uchar ds18b20_get_mask(uchar no){uchar pin_mask;switch(no){case 1: {pin_mask = ds18b20_1_mask; break;}case 2: {pin_mask = ds10b20_2_mask; break;}case 3: {pin_mask = ds10b20_3_mask; break;}default: break;}return pin_mask;}//初始化ds18b20uchar ds18b20_init(uchar sensor_no){uchar pin_mask;uchar ack;pin_mask= ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1delay_10xus(1); //延時10usDS18B20_PORT &= ~pin_mask; //清零delay_10xus(90);//拉低900usDS18B20_PORT |= pin_mask; //置1delay_10xus(8); //80us后讀ds18b20的響應ack = DS18B20_PORT & pin_mask; //讀引腳delay_10xus(50);return ack;}//從ds18b20讀一個字節(jié)數(shù)據//先接接收低位LSB bituchar ds18b20_read_byte(uchar sensor_no){unsigned char i = 0;unsigned char byte_rx = 0;uchar pin_mask = ds18b20_get_mask(sensor_no);DS18B20_PORT |= pin_mask; //置1_nop_();_nop_(); //延時2usfor(i=8; i>0; i--){????????DS18B20_PORT?&=?~pin_mask;??//清零byte_rx >>= 1;DS18B20_PORT |= pin_mask; //置1_nop_();_nop_();if(DS18B20_PORT & pin_mask) //讀到1{byte_rx |=0x80;}delay_10xus(30);DS18B20_PORT |= pin_mask; //置1}return(byte_rx);}// 寫一個字節(jié)到DS18B20void ds18b20_write_byte(uchar c, uchar sensor_no){uchar i;uchar pin_mask = ds18b20_get_mask(sensor_no);for(i=0;i<8;i++){????DS18B20_PORT?&=?~pin_mask;?//清零、寫0_nop_();if(c & 0x01) //判斷是否是寫1{DS18B20_PORT |= pin_mask; //置1}delay_10xus(5); //延時50usDS18B20_PORT |= pin_mask; //置1,釋放總線c >>= 1; //取下一位,準備發(fā)送}}// 開始溫度采集轉換void ds18b20_start_convert(uchar sensor_no){ds18b20_init(sensor_no);ds18b20_write_byte(0xcc, sensor_no); //SKIP ROMds18b20_write_byte(0x44, sensor_no); //Convert command}// 開始讀取溫度void ds18b20_start_read(uchar sensor_no){ds18b20_init(sensor_no);ds18b20_write_byte(0xcc, sensor_no); //SKIP ROMds18b20_write_byte(0xbe, sensor_no); //READ Command}// 讀溫度,返回浮點類型溫度float ds18b20_read_temperature(uchar sensor_no){uchar low_byte = 0;uchar hight_byte = 0;int temp = 0;float temperature = 0;if(ds18b20_init(sensor_no) == 0) // 溫度傳感器應答了{is_ds10b20_exist = 1;ds18b20_start_convert(sensor_no); //開始轉換ds18b20_start_read(sensor_no); //開始讀取low_byte = ds18b20_read_byte(sensor_no); //讀溫度的低八位hight_byte = ds18b20_read_byte(sensor_no); //讀溫度的高八位temp = (hight_byte<<8)|low_byte;}else{is_ds10b20_exist = 0;}if((temp & 0xF800) == 0xF800) //負溫度{temperature = ((~temp)+1)*0.0625;temperature = -temperature;}else{temperature = temp * 0.0625;}return temperature;}#endif
?
主程序.c源碼
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//uart_ds18b20_temperatureMonitor.c#include "uart.h"//#include"reg51.h"#include"ds18b20.h"sbit beeper_en = P2^0;sbit key_s1 = P1^0;char msg[] = "Welcome back. ";unsigned char uart_rx_buffer[2];unsigned int count = 0;//函數(shù)定義void delayMS(unsigned int nms);void keyScan(); //按鍵掃描float ds18b20_readTemperature(unsigned char no); //讀取DS18B20溫度void sendTemperature(unsigned char no, float temperature); //發(fā)送溫度函數(shù)void main(){????unsigned?char?ds18b20_no?=?1;?//設備號unsigned char ds18b20_N = 3; //ds18b20總數(shù)float temperature; //溫度uart_init();while(1){temperature = ds18b20_readTemperature(ds18b20_no); //讀溫度sendTemperature(ds18b20_no, temperature); //發(fā)送溫度ds18b20_no++;if(ds18b20_no > ds18b20_N)//已經讀完所有點的溫度{ds18b20_no = 1;}delayMS(1000); //等待1s左右}}void keyScan(){float temperature;if(key_s1 == 0){delayMS(10); //消抖if(key_s1 == 0) //按鍵按下,讀取并上報1#地點的溫度{temperature = ds18b20_readTemperature(1); //讀溫度sendTemperature(1, temperature); //發(fā)送溫度}}}//延時函數(shù)void delayMS(unsigned int nms){unsigned int i,j;for(i=0;ifor(j=0;j<130;j++);}//讀取DS18B20溫度(模擬)float ds18b20_readTemperature(uchar senor_no){float temperature;temperature = ds18b20_read_temperature(senor_no);return temperature;}//發(fā)送溫度函數(shù)void sendTemperature(unsigned char no, float temperature){uart_sendUchar(no);uart_sendFloat(temperature);}//串口中斷函數(shù)void isr_uart() interrupt 4{static unsigned char rx_byte_count = 0;if(RI) //收到數(shù)據{uart_rx_buffer[rx_byte_count] = SBUF;rx_byte_count++;RI = 0;if(rx_byte_count == 2) //接收到2個字節(jié)數(shù)據{rx_byte_count = 0;//下面是命令解析及執(zhí)行~魔幻的if elseif(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00){beeper_en = 1; //beeper offprintf("執(zhí)行命令:關閉蜂鳴器!");}else if (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01){beeper_en = 0; //beeper onprintf("執(zhí)行命令:開啟蜂鳴器!");}else if (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00){count = 0;printf("執(zhí)行命令:清零count!");}else if (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F){printf("將關閉串口,再見!");TR1 = 0;}}}}
?
審核編輯:湯梓紅
電子發(fā)燒友App












評論