再有5天就是“黃金”假期~放七天假,上七天班(學)!不管怎么樣,該做什么就做什么。學習便學習,工作便工作,走路便走路,吃飯便吃飯。
今天我們一起完成一個比較完整的作品,基于DS18B20和LabVIEW的多點溫度測量系統。我重點介紹實現多點DS18B20溫度驅動模塊的思路,具體實現大家可以閱讀源碼。驅動源碼參考了不少資料,在此感謝那些樂于分享的程序員。分享,傳遞,沉淀,這一直都是我們堅持的信念。
關于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
一、多點溫度測量系統架構
多點溫度測量系統框圖如圖1所示。編號為1#~8#的DS18B20連接到8051單片機的P0口,每個DS18B20占P0口的一個I/O,1#對應P0.1,2#對應P0.1, ......, 8#對應P0.7。8051單片機周期讀取多點溫度,通過串口上報到LabVIEW上位機。?
圖1? 多點溫度測量系統框圖
? ? ?在我們的例子中,只實現了3點溫度。由于我們采用了模塊化編程,要擴展到8路只需改動2個地方(猜猜是哪里)。圖2給出了仿真電路圖。我們在串口仿真電路圖上增加了3個DS18B20,分別接P0.0、P0.1和P0.2。
? ??
圖2 多點測溫仿真電路圖
二、DB18B20多點溫度驅動模塊設計思路
網上有很多單個DS18B20溫度驅動程序源碼,可惜的是這些源碼無法直接使用,因為源碼里DS18B20初始化函數、讀溫度函數、寫DS18B20函數等代碼綁定到了固定的I/O引腳(如P1.0),讀和寫都是基于單個I/O實現。以至于代碼無法復用。
https://blog.csdn.net/redeemer_Qi/article/details/108854687提供了多個DS18B20掛在單一總線的多點測溫方案,大家可以去研究研究。我們今天使用另外的思路。?
思路來源:Arduino里的I/O讀寫函數( digitalRead,digitalWrite)是通過指定pin序號來實現數字引腳的讀寫操作的。在分析這兩個函數的原型時,發現它們是通過PORT和BIT_MASK來對整個PORT的寄存器操作實現的。舉例來說,我們要寫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。哪如何實現讀取某個IO的狀態呢?使用位掩碼~正確。
- ?
- ?
- ?
- ?
- ?
uchar value = PORT & BIT_MASK; //非零表示輸入高電平,全零表示輸入低電平。
if(value): //高電平
do something
else: //低電平
do something
? ? ??
例如 if(P0 & 0x04)就能讀取到P0.2的輸入狀態。請分析為什么?
我們設計了ds18b20.h,在該頭文件里定義了PORT、BIT_MASK和相關的驅動函數(DS18B20初始化、讀字節、寫字節、讀溫度)。下面簡要概述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( ) 函數實現了傳感器編號到BIT_MASK的映射。例如, 1的dq引腳接到Px.0, 掩碼為0x01。
?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// 由序號獲得ds18b20的引腳mask
// no: 1,2,3
uchar 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、重要驅動函數
(1)DS18B20初始化函數
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
//初始化ds18b20
uchar ds18b20_init(uchar sensor_no)
{
uchar pin_mask;
uchar ack;
pin_mask= ds18b20_get_mask(sensor_no);
DS18B20_PORT |= pin_mask; //置1
delay_10xus(1); //延時10us
DS18B20_PORT &= ~pin_mask; //清零
delay_10xus(90);//拉低900us
DS18B20_PORT |= pin_mask; //置1
delay_10xus(8); //80us后讀ds18b20的響應
ack = DS18B20_PORT & pin_mask; //讀引腳
delay_10xus(50);
return ack;
}
?
初始化函數供讀操作、寫操作前調用。也可以單獨調用來判斷DS18B20是否存在。ACK為0表示傳感器應答,ACK為1表示傳感器未應答(多次未應答可視為傳感器不存在或損壞)。
(2)讀溫度驅動函數
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// 讀溫度函數,返回浮點類型溫度
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;
}
讀溫度驅動函數主要完成以下操作:
① 調用ds18b20_init()判斷DS18B20是否存在。
② 存在,使用ds18b20_start_convert()函數讓DS18B20進行溫度轉換;等待一定時間后,讀溫度字節,并把溫度字節轉換為float數據,再返回。
③ 不存在,置 is_ds10b20_exist為0。
關于讀溫度函數,有幾點要說明:
①? 溫度字節可以認為是A/D的數字量輸出,其量化單位q就是溫度分辨率。12位的是0.0625℃。
DS18B20默認是12位分辨率,可軟件配置為9、10、11、12位,分辨率分別為0.5、0.25、0.125、0.0625℃。
② 溫度MSB字節的高5位是符號位, 11111表示是負的溫度,以補碼儲存。所以先取反+1得到絕對值,再乘以分辨率,最后變成負數。代碼如下:?
- ?
- ?
- ?
- ?
- ?
if((temp & 0xF800) == 0xF800) //負溫度
{
temperature = ((~temp)+1)*0.0625;
temperature = -temperature;
}
?
③ LOW_BYTE和HIGH_BYTE對應于圖3中的LSB BYTE, MSB BYTE。注意,DS18B20先傳輸LSB字節,且是最低位先傳輸(LSb First)。
圖3 溫度數據
④ 溫度讀取函數有瑕疵。溫度轉換的代碼不管傳感器存在是否,都會進行。當傳感器不存在時,始終返回0,埋了一個大坑~~試一試,改進代碼。
(提示:不存在返回250,超量程了就是設備不存在)。在主程序再做處理。
⑤? is_ds10b20_exist 原來是針對單個DS18B20測溫設計的。此處實在是雞肋。你能把它用起來嗎?提示:結合第④點。
3、(串口)數據傳輸協議
我們直接使用C51編程入門(二十三)串口編程入門--串口應用協議(二)里設計的協議。每個傳感器上報的數據包括1字節的設備號、4字節的溫度(float)。三個傳感器的數據一起“打包”上報,如下。
1#設備號(1B) |
1#溫度(4B) |
2#設備號(1B) |
2#溫度(4B) |
3#設備號(1B) |
3#溫度(4B) |
主函數如下。主函數所在的.c源碼除了增加#include"ds18b20.h"并另存為新文件名外,其它內容與C51編程入門(二十三)串口編程入門--串口應用協議(二)的一模一樣,未作任何修改~(難能可貴..)
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
void main()
{
unsigned char ds18b20_no = 1; //設備號
unsigned char ds18b20_N = 3; //ds18b20總數
float temperature; //溫度
uart_init();
while(1)
{
temperature = ds18b20_readTemperature(ds18b20_no); //讀溫度
sendTemperature(ds18b20_no, temperature); //發送溫度
ds18b20_no++;
if(ds18b20_no > ds18b20_N)//已經讀完所有點的溫度
{
ds18b20_no = 1;
}
delayMS(1000); //等待1s左右
}
}
?
三、LabVIEW上位機程序改進
1、添加溫度保存子VI(saveTemperature.vi),如圖4所示。實現將三個溫度和當前時間戳存儲到一個表格。
圖4?saveTemperature.vi程序框圖
程序說明如下:
① 創建文件路徑,使用了應用程序目錄,實現將程序存儲到程序目錄下。目標文件由文件名和當前日期(年月日)組成。這樣實現一天一個文件。
.xls擴展名指定文件為表格。
② 打開/創建文件,并設置文件指針到文件末尾,即從文件末尾新增數據。這樣,就不會覆蓋舊數據。
③ 調用格式化寫入文件,巧妙地通過格式化將數據寫到表格里。格式化字符為:
- ?
%.1f %.1f %.1f %s
?
三個%.1f對應三個溫度值,存為1位小數的浮點數據。 是制表符,移動到下一個表格單元。%s為字符串,這里對應著時間戳字符串。
是換行,保證下一次數據存儲到表格末尾的新的一行。
2、串口解釋單個傳感器數據的子VI(getReceiveData.vi)
程序框圖如圖5所示。說明如下:
① 先讀取1個字節數據,并調用強制類型轉換函數轉換為U8數據。此為設備號,1個字節。
② 再讀取4個字節數據,并調用強制類型轉換函數轉換為SGL數據。此為溫度數據,4個字節。注意,不能轉換為DBL數據,因為LabVIEW的DBL為64位,8個字節,類型不匹配。
圖5?getReceiveData.vi程序框圖
下圖為LabVIEW主程序框圖。需要注意的是,初始化串口時,禁用串口的啟用停止符選項(F常量連接的選項)。
圖6? 主程序框圖
三、運行結果
LabVIEW上位機運行后,立馬收到了很多數據(這些都是緩沖在電腦串口緩存里)。如果想要丟棄掉,可以在進入while循環前清空串口緩沖區。
使用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。因此,我們在正式讀取之前,應該調用一次讀取溫度函數(如在while循環前)。
?
四、結束語
串口程序編寫教程到此告一個段落,希望相關文章對大家有所助益。原本計劃繼續寫串口校驗和和AT命令,后面視情況而定吧。如何在有限的時間里,完成更多的事情是一個值得研究和探討的話題。如果您有感興趣的主題,可后臺發消息給我。?
如果你覺得本篇文章有所幫助,請點贊、打賞。?分享,傳遞,沉淀。
附錄:源代碼
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延時函數
void delay_10xus(uchar n)
{
//每個循環約10us左右, 110次循環約1ms
while(n--);
}
// 由序號獲得ds18b20的引腳mask
// no: 1,2,3
uchar 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;
}
//初始化ds18b20
uchar ds18b20_init(uchar sensor_no)
{
uchar pin_mask;
uchar ack;
pin_mask= ds18b20_get_mask(sensor_no);
DS18B20_PORT |= pin_mask; //置1
delay_10xus(1); //延時10us
DS18B20_PORT &= ~pin_mask; //清零
delay_10xus(90);//拉低900us
DS18B20_PORT |= pin_mask; //置1
delay_10xus(8); //80us后讀ds18b20的響應
ack = DS18B20_PORT & pin_mask; //讀引腳
delay_10xus(50);
return ack;
}
//從ds18b20讀一個字節數據
//先接接收低位LSB bit
uchar 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_(); //延時2us
for(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);
}
// 寫一個字節到DS18B20
void 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); //延時50us
DS18B20_PORT |= pin_mask; //置1,釋放總線
c >>= 1; //取下一位,準備發送
}
}
// 開始溫度采集轉換
void ds18b20_start_convert(uchar sensor_no)
{
ds18b20_init(sensor_no);
ds18b20_write_byte(0xcc, sensor_no); //SKIP ROM
ds18b20_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 ROM
ds18b20_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;
//函數定義
void delayMS(unsigned int nms);
void keyScan(); //按鍵掃描
float ds18b20_readTemperature(unsigned char no); //讀取DS18B20溫度
void sendTemperature(unsigned char no, float temperature); //發送溫度函數
void main()
{
????unsigned?char?ds18b20_no?=?1;?//設備號
unsigned char ds18b20_N = 3; //ds18b20總數
float temperature; //溫度
uart_init();
while(1)
{
temperature = ds18b20_readTemperature(ds18b20_no); //讀溫度
sendTemperature(ds18b20_no, temperature); //發送溫度
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); //發送溫度
}
}
}
//延時函數
void delayMS(unsigned int nms)
{
unsigned int i,j;
for(i=0;i
for(j=0;j<130;j++);
}
//讀取DS18B20溫度(模擬)
float ds18b20_readTemperature(uchar senor_no)
{
float temperature;
temperature = ds18b20_read_temperature(senor_no);
return temperature;
}
//發送溫度函數
void sendTemperature(unsigned char no, float temperature)
{
uart_sendUchar(no);
uart_sendFloat(temperature);
}
//串口中斷函數
void isr_uart() interrupt 4
{
static unsigned char rx_byte_count = 0;
if(RI) //收到數據
{
uart_rx_buffer[rx_byte_count] = SBUF;
rx_byte_count++;
RI = 0;
if(rx_byte_count == 2) //接收到2個字節數據
{
rx_byte_count = 0;
//下面是命令解析及執行~魔幻的if else
if(uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x00)
{
beeper_en = 1; //beeper off
printf("執行命令:關閉蜂鳴器!");
}
else if (uart_rx_buffer[0] == 0x01 && uart_rx_buffer[1] == 0x01)
{
beeper_en = 0; //beeper on
printf("執行命令:開啟蜂鳴器!");
}
else if (uart_rx_buffer[0] == 0x02 && uart_rx_buffer[1] == 0x00)
{
count = 0;
printf("執行命令:清零count!");
}
else if (uart_rx_buffer[0] == 0xF0 && uart_rx_buffer[1] == 0x0F)
{
printf("將關閉串口,再見!");
TR1 = 0;
}
}
}
}
?
審核編輯:湯梓紅
評論