嵌入式產品開發中,我們常常會有檢測環境溫度、壓力、濕度的需求。如果有一個集成有這3個傳感器的元件,無疑將是很方便的。博世的BME280就能實現這一要求。在這一篇中我們將討論BME280的驅動設計與實現。
1、功能概述
BME280是一款專為移動應用而開發的集成環境傳感器,其尺寸和低功耗是關鍵設計限制。該裝置結合了單個高線性度,高精度壓力,濕度和溫度傳感器,采用8引腳金屬蓋2.5 x 2.5 x0.93mm³LGA封裝,設計用於低電流消耗(3.6μA@ 1Hz),長期穩定性高EMC穩健性。
1.1、硬件描述
BME280壓力濕度溫度傳感器實現了高性能的溫度壓力濕度檢測,包括有壓力、溫度、濕度3個傳感器和一個高精度ADC及接口邏輯,其結構框圖如下圖所示:
濕度傳感器具有極快的響應時間,可滿足新興應用的性能要求,例如環境感知和寬溫度范圍內的高精度。壓力傳感器是絕對氣壓傳感器,具有極高的精度和分辨率,噪音極低。集成的溫度傳感器經過優化,噪音極低,分辨率高。它主要用於壓力和濕度傳感器的溫度補償,也可用於估算環境溫度。
BME280壓力溫度傳感器采用了小巧的8引腳LGA封裝形式。其引腳排布就功能如下圖所示:
BME280支持全套操作模式,可靈活地優化器件的功耗,分辨率和濾波器性能。
1.2、通訊接口
BME280壓力溫度傳感器支持3種通訊接口方式:四線SPI、三線SPI以及I2C。在不同的接口模式下,各引腳的定義也是有差異的,關於這三種接口模式各引腳的定義如下:
對應3種不同的接口方式,BME280壓力溫度傳感器存在三種與總線連接的方式。首先我們來看四線SPI接口方式,包括CSB片選、SCK時鍾、SDI數字輸入、SDO數字輸出。其總線連接方式如下圖:
接下來我們來看三線SPI接口方式,包括CSB片選、SCK時鍾、SDI數字輸入/SDO數字輸出。其與4線SPI的區別是數字輸入輸出使用同一引腳,第3腳就是輸入也是輸出,而第5腳浮空。其總線連接方式如下圖:
最后我們來看I2C接口方式,包括SCL時鍾、SDA數字輸入輸出。在I2C接口模式下,第2腳CSB連接到高電平,以設置BME280壓力溫度傳感器使用I2C接口。而第5腳則可以通過連接高電平或低電平來設置設備地址的最后一位,不可以浮空。所以根據第5腳電頻不同,BMP280壓力溫度傳感器的I2C設備7位地址為:0x76和0x77。其總線連接方式如下圖:
BME280壓力溫度傳感器在使用SPI接口時,支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。而在使用I2C接口時,支持標准模式、快速模式以及高速模式。接口的選擇實際上是通過CSB的電位實現的,低電平時就是SPI,高電平時就是I2C。
1.3、內部寄存器
對BME280壓力溫度傳感器的所有操作都是通過讀寫對應的寄存器來實現的。BME280壓力溫度傳感器中所有的寄存器都是8位的。這些寄存器在存儲器中的地址分配如下圖所示。
在上圖並未包括系統保留的寄存器,這些寄存器不可以進行寫操作,讀出來的值也是無意義的。接下來我們來詳細描述上圖中的這些寄存器。
先來看看兩個比較特殊的寄存器。首先是ID寄存器,這個寄存器是只讀的,而且其存儲的值也固定為0x60,用來代表設備為BME280壓力溫度傳感器。這個寄存器在系統上電后即可讀取。還有復位寄存器,這個寄存器是只寫的,固定向其寫0xB6來實現BME280壓力溫度傳感器的復位。同樣只要系統上電后即可以寫復位寄存器。
濕度控制寄存器用以配置濕度數據的測量精度。但是修改這個寄存器的值不會立即生效,必須對測量控制寄存器做了寫操作之后才會有效。濕度控制寄存器的各位定義如下圖:
狀態寄存器是只讀的,其實只使用了其中的兩位,這兩位分別表示數據測量是否完成和影響寄存器是否更新。下圖是狀態寄存器的詳細說明:
測量控制寄存器是可讀寫的,用以配置BME280壓力溫度傳感器數據獲取的方式。分別配置溫度采樣、壓力采樣和工作模式。工作模式有三種:休眠模式、強制模式、正常模式。系統上電后即為休眠模式,通過這一寄存器的配置可以使BME280壓力溫度傳感器進入強制模式或正常模式運行。濕度控制器修改后必須寫這個寄存器才會生效。測量控制寄存器的各位定義如下圖:
配置寄存器用於設置BME280壓力溫度濕度傳感器的速率、過濾器以及接口模式。在休眠模式下寫配置寄存器是允許的,但在正常模式下會被忽略,所以在系統復位后,進入正常模式前先寫配置寄存器。配置寄存器各位的定義如下圖所示:
壓力數據寄存器存儲有壓力測量數據輸出的原始值。使用了三個寄存器中的20位來下存儲壓力數據。壓力數據寄存器各位的定義如下圖所示:
溫度數據寄存器存儲有溫度測量數據輸出的原始值。使用了三個寄存器中的20位來下存儲溫度數據。溫度數據寄存器各位的定義如下圖所示:
濕度數據寄存器存儲有濕度測量數據輸出的原始值。使用了兩個寄存器中的16位來下存儲濕度數據。濕度數據寄存器各位的定義如下圖所示:
此外還有校准數據寄存器,總共是41個寄存器,存儲了計算壓力溫度最終值的廠家校准數據。這些校准寄存器的定義及地址分配如下圖所示:
我們已經說過面向BME280壓力溫度濕度傳感器的所有操作都是基於寄存器進行的,我們已經了解了BME280壓力溫度濕度傳感器的各個寄存器,現在可以來實現它的操作了。
2、驅動設計與實現
上一節我們描述了BME280壓力濕度溫度傳感器的引腳功能、數據存儲格式以及通訊接口的基本內容。在這一節中我們將考慮如何設計並實現BME280壓力濕度溫度傳感器的驅動程序。
2.1、對象定義
在使用一個對象之前我們需要獲得一個對象。同樣的我們想要BME280壓力濕度溫度傳感器就需要先定義BME280壓力濕度溫度傳感器的對象。
2.1.1、對象的抽象
我們要得到BME280壓力濕度溫度傳感器對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下BME280壓力濕度溫度傳感器的對象。
先來考慮屬性,作為屬性肯定是用於標識或記錄對象特征的東西。我們來考慮BME280壓力濕度溫度傳感器對象屬性。BME280壓力濕度溫度傳感器的ID寄存器用於標識設備是否為BME280;配置寄存器和測量控制寄存器都用關於系統配置,指示了設備的工作狀態,所以我們將這幾個寄存器定義為對象的屬性。而使用的通訊接口決定了訪問BME280壓力濕度溫度傳感器的行為,所以我們需要記住這一配置;而校准數據則在計算數據時所要使用的,我們也需要記住這些參數,所以我們將它們也都定義為屬性。在I2C接口模式時,設備地址是區分總線上設備的唯一標志,所以我們將其定義為屬性。同樣測量數據指示了設備當前的工作狀態,我們將器作為屬性。
接着我們還需要考慮BME280壓力濕度溫度傳感器對象的操作問題。我們需要與BME280壓力濕度溫度傳感器通訊就需要向其寫數據並從其讀數據,而不論是SPI接口還是I2C接口,讀寫操作都以來與具體的硬件平台,所以我們將他們作為對象的操作。此外,為控制時序,我們需要延時操作,而延時行為的實現亦依賴於具體的軟硬件平台,所以我們將延時也作為對象的操作。
根據上述我們對BME280壓力濕度溫度傳感器的分析,我們可以定義BME280壓力濕度溫度傳感器的對象類型如下:
1 /*定義Bme280操作對象*/ 2 typedef struct BME280Object{ 3 uint8_t chipID; //芯片ID 4 uint8_t bmeAddress; //I2C通訊時的設備地址 5 uint8_t config; //配置寄存器 6 uint8_t ctrlMeas; //測量控制寄存器 7 uint8_t ctrlHumi; //濕度測量控制寄存器 8 BME280PortType port; //接口選擇 9 BME280CalibParamType caliPara; //校准參數 10 float temperature; //溫度值 11 float pressure; //壓力值 12 float humidity; //濕度值 13 void (*Read)(struct BME280Object *bme,uint8_t regAddress,uint8_t *rData,uint16_t rSize); //讀數據操作指針 14 void (*Write)(struct BME280Object *bme,uint8_t regAddress,uint8_t command); //寫數據操作指針 15 void (*Delayms)(volatile uint32_t nTime); //延時操作指針 16 void (*ChipSelect)(BME280CSType cs); //使用SPI接口時,片選操作 17 }BME280ObjectType;
2.1.2、對象初始化
我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮BME280壓力濕度溫度傳感器對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計BME280壓力濕度溫度傳感器對象的初始化函數如下:
1 /*實現BME280初始化配置*/ 2 void BME280Initialization(BME280ObjectType *bme, //BMP280對象 3 uint8_t bmeAddress, //I2C接口是設備地址 4 BME280PortType port, //接口選擇 5 BME280TimeSBType t_sb, //間隔周期 6 BME280IIRFilterType filter, //過濾器 7 BME280SPI3wUseType spi3W_en, //3線SPI控制 8 BME280TempSampleType osrs_t, //溫度精度 9 BME280PresSampleType osrs_p, //壓力精度 10 BME280PowerModeType mode, //電源模式 11 BME280HumiSampleType osrs_h, //濕度精度 12 BME280Read Read, //讀數據操作指針 13 BME280Write Write, //寫數據操作指針 14 BME280Delayms Delayms, //延時操作指針 15 BME280ChipSelect ChipSelect //片選操作指針 16 ) 17 { 18 uint8_t try_count = 5; 19 uint8_t regAddress=0; 20 uint8_t command=0; 21 22 if((bme==NULL)||(Read==NULL)||(Write==NULL)||(Delayms==NULL)) 23 { 24 return; 25 } 26 bme->Read=Read; 27 bme->Write=Write; 28 bme->Delayms=Delayms; 29 30 bme->chipID=0x00; 31 bme->pressure=0.0; 32 bme->temperature=0.0; 33 bme->humidity=0.0; 34 bme->bmeAddress=0x00; 35 bme->port=port; 36 if(bme->port==BME280_I2C) 37 { 38 if((bmeAddress==0xEC)||(bmeAddress==0xEE)) 39 { 40 bme->bmeAddress=bmeAddress; 41 } 42 bme->ChipSelect=NULL; 43 } 44 else 45 { 46 if(ChipSelect!=NULL) 47 { 48 bme->ChipSelect=ChipSelect; 49 } 50 else 51 { 52 bme->ChipSelect=BME280ChipSelectDefault; 53 } 54 } 55 56 bme->caliPara.t_fine=0; 57 58 if(!ObjectIsValid(bme)) 59 { 60 return; 61 } 62 63 while(try_count--) 64 { 65 bme->chipID=ReadBME280Register(bme,REG_BME280_ID); 66 if(0x60==bme->chipID) 67 { 68 BME280SoftReset(bme); 69 70 break; 71 } 72 } 73 74 if(try_count) 75 { 76 /*配置配置寄存器:間隔周期0.5ms、IIR濾波系數16、不使用SPI3線通訊*/ 77 regAddress=REG_CONFIG; 78 command=t_sb|filter|spi3W_en; 79 WriteBME280Register(bme,regAddress,command); 80 81 /*配置測量控制寄存器:溫度20位,壓力20位,電源正常模式*/ 82 regAddress=REG_CTRL_MEAS; 83 command=osrs_t|osrs_p|mode; 84 WriteBME280Register(bme,regAddress,command); 85 86 /*配置濕度測量控制寄存器*/ 87 regAddress=REG_CTRL_HUM; 88 command=osrs_h; 89 WriteBME280Register(bme,regAddress,command); 90 91 bme->Delayms(10); 92 bme->config=ReadBME280Register(bme,REG_CONFIG); 93 bme->Delayms(10); 94 bme->ctrlMeas=ReadBME280Register(bme,REG_CTRL_MEAS); 95 bme->Delayms(10); 96 bme->ctrlHumi=ReadBME280Register(bme,REG_CTRL_HUM); 97 bme->Delayms(10); 98 99 /*讀取校准值*/ 100 GetBME280CalibrationData(bme); 101 } 102 }
2.2、對象操作
我們已經完成了BME280壓力濕度溫度傳感器對象類型的定義和對象初始化函數的設計。但我們的主要目標是獲取對象的信息,接下來我們還要實現面向BME280壓力濕度溫度傳感器的各類操作。
2.2.1、寫寄存器
我們已經說過了,對BME280的操作都是通過讀寫寄存器實現的。這里我們先來看寫寄存器。在I2C接口方式下,寫寄存器操作是在從站地址的最后一位來識別的,再加上要寫的寄存器地址和數據來實現的,這也是I2C協議的標准做法。其時序圖如下所示:
而在SPI接口方式下,由於SPI並未有設備地址,也不存在用從還在那地址最后為來標記讀寫的模式。通常一些設備需要定義操作碼來實現讀寫區分,但BME280采取了將寄存器地址的最高位置零表示為寫。之所以可以這樣定義,是因為BME280寄存器地址分配的特殊性決定的。改變寄存器地址的最高位也能區分不同的寄存器,絕不會重復。在SPI接口方式下,寫寄存器的時序圖如下所示:
根據上述描述和時序圖,我們可以實現寫BME280壓力濕度溫度傳感器寄存器的程序。
1 /* 向BME280寄存器寫一個字節 */ 2 static void WriteBME280Register(BME280ObjectType *bme,uint8_t regAddress,uint8_t command) 3 { 4 if(ObjectIsValid(bme)) 5 { 6 if(bme->port==BME280_SPI) 7 { 8 regAddress&=0x7F; 9 bme->ChipSelect(BME280CS_ENABLE); 10 bme->Delayms(1); 11 bme->Write(bme,regAddress,command); 12 bme->Delayms(1); 13 bme->ChipSelect(BME280CS_DISABLE); 14 } 15 else 16 { 17 bme->Write(bme,regAddress,command); 18 } 19 } 20 }
2.2.2、讀寄存器
讀寄存器的處理方式與寫寄存器是類似。在I2C接口方式下,將從站地址的最低位置1來表示讀。在I2C接口方式下,讀寄存器的時序圖如下所示:
而在SPI接口方式下,通過將寄存器地址的最高位置1來標識為讀操作。事實上,所有寄存器地址的最高為都是1,所以在讀操作時實際不需要做處理。在SPI接口方式下,讀寄存器的時序圖如下所示:
根據上述描述和時序圖,我們可以實現讀BME280壓力溫度濕度傳感器寄存器的程序。
1 /*從BMP280寄存器讀取一個字節*/ 2 static uint8_t ReadBME280Register(BME280ObjectType *bme,uint8_t regAddress) 3 { 4 uint8_t regValue=0xFF; 5 6 if(ObjectIsValid(bme)) 7 { 8 if(bme->port==BME280_SPI) 9 { 10 regAddress |= 0x80; 11 bme->ChipSelect(BME280CS_ENABLE); 12 bme->Delayms(1); 13 bme->Read(bme,regAddress,®Value,1); 14 bme->Delayms(1); 15 bme->ChipSelect(BME280CS_DISABLE); 16 } 17 else 18 { 19 bme->Read(bme,regAddress,®Value,1); 20 } 21 } 22 23 return regValue; 24 }
3、驅動的使用
我們描述了BME280壓力濕度溫度傳感器的基本原理並實現了驅動程序。我們還需要設計一個簡單的應用來驗證這一驅動設計是否合乎我們的要求,所以接下來我們就來設計一個簡單的驗證應用。
3.1、聲明並初始化對象
使用基於對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的BME280壓力濕度溫度傳感器對象類型聲明一個BME280壓力濕度溫度傳感器對象變量,具體操作格式如下:
BME280ObjectType bme280;
聲明了這個對象變量並不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
BME280ObjectType *bme,BMP280對象
uint8_t bmeAddress,I2C接口是設備地址
BME280PortType port,接口選擇
BME280TimeSBType t_sb,間隔周期
BME280IIRFilterType filter,過濾器
BME280SPI3wUseType spi3W_en,3線SPI控制
BME280TempSampleType osrs_t,溫度精度
BME280PresSampleType osrs_p,壓力精度
BME280PowerModeType mode,電源模式
BME280HumiSampleType osrs_h,濕度精度
BME280Read Read,讀數據操作指針
BME280Write Write,寫數據操作指針
BME280Delayms Delayms,延時操作指針
BME280ChipSelect ChipSelect,片選操作指針
對於這些參數,對象變量我們已經定義了。接口選擇、間隔周期、過濾器、3線SPI控制、溫度精度、壓力精度、濕度精度、電源模式等都是枚舉量我們根據實際情況輸入即可。而使用I2C接口時需要的設備地址,也按具體地址給入就好。主要的是我們需要定義幾個函數,並將函數指針作為參數。這幾個函數的類型如下:
1 /* 定義讀數據操作函數指針類型 */ 2 typedef void (*BME280Read)(struct BME280Object *bme,uint8_t regAddress,uint8_t *rData,uint16_t rSize); 3 4 /* 定義寫數據操作函數指針類型 */ 5 typedef void (*BME280Write)(struct BME280Object *bme,uint8_t regAddress,uint8_t command); 6 7 /* 定義延時操作函數指針類型 */ 8 typedef void (*BME280Delayms)(volatile uint32_t nTime); 9 10 /* 定義使用SPI接口時,片選操作函數指針類型 */ 11 typedef void (*BME280ChipSelect)(BME280CSType cs);
對於這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平台有關系。若采用的SPI接口則需注意片選操作,片選操作函數用於多設備需要軟件操作時,如采用硬件片選可以傳入NULL即可。同樣如果采用的是I2C接口,則片選可以傳入NULL即可。具體函數定義如下:
1 /*讀BME280寄存器值*/ 2 static void ReadDataFromBME280(BME280ObjectType *bme280,uint8_t regAddress,uint8_t *rData,uint16_t rSize) 3 { 4 HAL_I2C_Master_Transmit(&bme280hi2c, bme280->bmeAddress,®Address,1,1000); 5 6 HAL_I2C_Master_Receive(&bme280hi2c, bme280->bmeAddress+1,rData, rSize, 1000); 7 } 8 9 /*寫BME280寄存器值*/ 10 static void WriteDataToBME280(BME280ObjectType *bme280,uint8_t regAddress,uint8_t command) 11 { 12 uint8_t pData[2]; 13 14 pData[0]=regAddress; 15 pData[1]=command; 16 17 HAL_I2C_Master_Transmit(&bme280hi2c,bme280->bmeAddress, pData, 2,1000); 18 }
對於延時函數我們可以采用各種方法實現。我們采用的STM32平台和HAL庫則可以直接使用HAL_Delay()函數。於是我們可以調用初始化函數如下:
1 BME280Initialization(&bme280, //BME280對象 2 0xEC, //I2C接口是設備地址 3 BME280_I2C, //接口選擇 4 BME280_T_SB_0P5, //間隔周期 5 BME280_IIR_FILTER_COEFF_X16, //過濾器 6 BME280_SPI3W_DISABLE, //3線SPI控制 7 BME280_TEMP_SAMPLE_X16, //溫度精度 8 BME280_PRES_SAMPLE_X16, //壓力精度 9 BME280_POWER_NORMAL_MODE, //電源模式 10 BME280_HUMI_SAMPLE_X16, //濕度精度 11 ReadDataFromBME280, //讀數據操作指針 12 WriteDataToBME280, //寫數據操作指針 13 HAL_Delay, //延時操作指針 14 NULL //片選操作指針 15 );
3.2、基於對象進行操作
我們定義了對象變量並使用初始化函數給其作了初始化。接着我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據並轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。
1 /*獲取大氣壓力和溫度*/ 2 void BME280GetEnvironmentalData(void) 3 { 4 float pressure; //壓力值 5 float temperature; //溫度值 6 float humidity; //濕度值 7 8 GetBME280Measure(&bme280); 9 10 pressure=bme280.pressure; 11 temperature=bme280.temperature; 12 humidity=bme280.humidity; 13 }
4、應用總結
BME280壓力濕度溫度傳感器的驅動已經實現並做了簡單的應用。在我們測試時,得到的數據與其它方法獲得的溫度壓力數據基本是一致的,這說明我們的驅動程序總體來說是正確的。
BME280壓力濕度溫度傳感器支持SPI和I2C兩種接口,而且SPI也支持3線和4線模式,但我們在測試應用中只使用了I2C接口,SPI接口還有待測試。
在使用驅動時需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號是通過硬件電路來實現的,我們在初始化時給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數。而如果采用I2C接口,那么在初始化時也應傳遞NULL值。
BME280壓力濕度溫度傳感器在使用SPI接口時,支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。而在使用I2C接口時,支持標准模式、快速模式以及高速模式。而且在使用I2C接口時,SDO引腳必須接高電平或低電平,以確定設備地址。
源碼下載:https://github.com/foxclever/ExPeriphDriver
歡迎關注: