外設驅動庫開發筆記21:BME680環境傳感器驅動


  環境傳感器是一類我們很常用的傳感器。它可以方便我們獲取壓力、溫度、濕度以及空氣質量等數據。在這一篇中,我們將分析BME680環境傳感器的功能,並設計和實現BME680環境傳感器的驅動。

1、功能概述

  BME680是一款專為移動應用和可穿戴設備開發的集成環境傳感器,其尺寸和低功耗是關鍵要求。

1.1、硬件接口

  BME680由一個8針金屬蓋3.0 x 3.0 x0.93mm³LGA封裝組成,旨在根據特定的工作模式,長期穩定性和高EMC穩健性進行優化消耗。可以選擇采用I2C接口或者SPI接口。其管腳排布如下圖:

 

  BME680環境傳感器可以選擇使用I2C接口或者SPI接口,在不同的接口模式及下各個引腳的定義及功能有一些差別。其具體分配及定義如下所示:

 

  從上表中我們可以知道當CSB引腳接高電平VDDIO時,采用的是I2C接口。此時I2C的設備地址的最后一位由SDO引腳的電平決定。所以設備地址計7位為0x76或0x77,計8位則是0xEC或0xEE。

  當CSB引腳用作片選信號時,則使用SPI接口。SPI接口支持模式0(CPOL=0,CPHA=0)和模式3(CPOL=1,CPHA=1)。同時支持3線SPI和4線SPI。控制字節的最高位為0時表示寫,為1時表示讀。

1.2、內置傳感器

  BME680擴展了博世現有的環境傳感器系列,首次集成了高線性度和高精度的氣體,壓力,濕度和溫度傳感器。

1.2.1、氣體傳感器

  BME680內的氣體傳感器可以檢測各種氣體,以測量個人健康的空氣質量。BME680可檢測到的氣體包括油漆(如甲醛),油漆,脫漆劑,清潔用品,家具等的揮發性有機化合物(VOC)。大氣質量傳感器的特性參數如下:

 

  BME680采用了博世軟件環境群組解決方案。該解決方案使用智能算術方法將空氣質量索引(IAQ)作為輸出。該指標將IAQ划分為0到500的索引數值用以指示IAQ,具體划分如下所示:

 

1.2.2、濕度傳感器

  BME680集成了濕度傳感器用於外部環境中濕度數據的采集。濕度傳感器的性能參數如下:

 

1.2.3、壓力傳感器

  BME680集成有大氣壓力傳感器用於檢測外部環境的絕對壓力。壓力傳感器的性能參數如下:

 

1.2.4、溫度傳感器

  BME680也集成了溫度傳感器用以檢測溫度數據,溫度數據除了指示環境溫度外,同時用於壓力和濕度的補償計算。溫度傳感器的性能參數如下:

 

1.3、數據存儲結構

  BME680采用特定的存儲器區域來存儲控制及數據信息。存儲的數據包括測量數據、控制信息以及校准數據。

  對於溫度傳感器,包括3個校准參數和一個ADC測量數據,其測量數據和校准數據的存儲結構及地址如下:

 

  對於壓力傳感器,包括10個計算校准數據和一個ADC轉換數據,其測量數據的校准數據存儲結構及地址如下:

 

  對於濕度傳感器,包括7個計算校准數據和一個ADC轉換數據,其測量數據的校准數據存儲結構及地址如下:

 

  大氣質量傳感器,包括3個計算校准數據、一個加熱器范圍存儲數據、一個加熱器電阻校准因子存儲數據、氣體ADC測量數據、氣體范圍數據以及范圍轉換錯誤,其測量數據的校准數據存儲結構及地址如下:

 

  BME680環境傳感器寄存器都是8位的,所有的操作均通過對寄存器的讀寫來實現。全部控制寄存器及數據寄存器的結構和地址如下:

 

  這里我們需要說明一下,BME680的存儲器地址范圍是0x00~0xFF,在I2C接口通訊時,通訊采用的是8位寄存器地址正好符合對應的尋址范圍。但是采用SPI接口通訊時,寄存器地址的最高為被用於區分讀寫操作,所以地址只有7位,存儲空間被分為2頁。具體如下:

 

  所以在使用SPI接口時需要分辨是哪一頁。當前操作的是哪一頁由Status寄存器來決定。

2、驅動設計與實現

  我們對BME680環境傳感器的基本情況已經有了整體了解,接下來我們將為BME680環境傳感器設計並實現驅動程序。

2.1、對象定義

  我們依然是采用基於對象的操作。所以我們需要定義對象,所以我們需要抽象出對象類型,並對我們想要操作的對象進行初始化。

2.1.1、對象抽象

  對於一個對象來說,一般包括有屬性和操作兩方面的內容。接下來我們就從這兩個方面分析BME680環境傳感器的對象。

  我們需要從BME680對象抽象出其屬性,這些屬性能夠定義一個對象的特點並將其與其它對象區別開來。BME680支持SPI通訊和I2C通訊,所以我們將通訊端口作為屬性以規定對象的通訊方式。在使用I2C時,設備有地址以區別不同的設備,所以我們將I2C設備地址也定義為屬性。每台BME680都有一個ID用以區別於其它設備,所以我們將它定義為對象的屬性。還有配置寄存器、測量控制寄存器、濕度控制寄存器、氣體控制寄存器都記錄了設備的配置狀態,所以我們也將它們作為屬性。每台設備都有特定的校准數據,這些校准數據每次數據檢測都是需要的,所以我們用屬性將它們記錄下來。還有測量數據,它們標識了設備當前的工作狀態,所以我們將它們也作為屬性。

  接下來我們分析BME680的操作。首先來講,我們肯定要與BME680交互,但我們對BME680的讀寫依賴於具體的硬件平台,所以我們將它們作為對象的操作。在進行相關操作時,我們需要控制時序,則需要使用延時操作,但延時處理總是依賴於具體的軟硬件平台,所以我們將延時處理作為對象的操作。而使用SPI時,沒有設備地址但有片選信號,如何操作片選信號依賴於硬件平台,我們將對片選的操作定義為對象的操作函數。

  根據上述的分析,我們可以得到BME680環境傳感器的對象類型如下:

 1 /*定義BME680操作對象*/
 2 typedef struct BME680Object{
 3        uint8_t chipID;       //芯片ID
 4        uint8_t bmeAddress;         //I2C通訊時的設備地址
 5        uint8_t memeryPage;       //用於在SPI接口時記錄當前所處的內存頁
 6        uint8_t config;                  //配置寄存器
 7        uint8_t ctrlMeas;               //測量控制寄存器
 8        uint8_t ctrlHumi;              //濕度測量控制寄存器
 9        uint8_t ctrlGas0;               //氣體控制寄存器0
10        uint8_t ctrlGas1;               //氣體控制寄存器1
11        uint8_t resHeat;
12        uint8_t gasWait;
13       
14        BME680PortType port;                   //接口選擇
15        BME680CalibParamType caliPara;   //校准參數
16  
17 #if BME680_COMPENSATION_SELECTED > (0)
18        int32_t temperature;         //溫度值
19        int32_t pressure;                      //壓力值
20        int32_t humidity;                     //濕度值
21        int32_t gasResistence;      //大氣質量電阻值
22        int32_t iaq;                                      //空氣質量水平
23 #else
24        float temperature;             //溫度值
25        float pressure;                          //壓力值
26        float humidity;                         //濕度值
27        float gasResistence;   //大氣質量電阻值
28        float iaq;                                          //空氣質量水平
29 #endif
30  
31        void (*Read)(struct BME680Object *bme,uint8_t regAddress,uint8_t *rData,uint16_t rSize);       //讀數據操作指針
32        void (*Write)(struct BME680Object *bme,uint8_t regAddress,uint8_t command);    //謝數據操作指針
33        void (*Delayms)(volatile uint32_t nTime);       //延時操作指針
34        void (*ChipSelect)(BME680CSType cs);    //使用SPI接口時,片選操作
35 }BME680ObjectType;

  片選操作有一點需要注意,如果片選信號在硬件電路上固定有效時,可以將NULL給它,同樣在SPI接口時也需要將NULL給它。

2.1.2、對象初始化函數

  一個對象必須對其進行初始化才可使用。初始化對象主要有四個方面的內容:檢查對象賦值的合法性;屬性賦初值;為對象操作指定函數指針;對象所指向設備的初始配置。據此我們可以編寫BME680環境傳感器的初始化函數如下:

 1 /*實現BME680初始化配置*/
 2 void BME680Initialization(BME680ObjectType *bme,       //BMP280對象
 3                                    uint8_t bmeAddress,         //I2C接口是設備地址
 4                                    BME680PortType port,    //接口選擇
 5                                    BME680IIRFilterType filter,                //過濾器
 6                                    BME680SPI3wUseType spi3W_en,   //3線SPI控制
 7                                    BME680TempSampleType osrs_t,       //溫度精度
 8                                    BME680PresSampleType osrs_p,         //壓力精度
 9                                    BME680SPI3wIntType spi3wint_en,//3線SPI中斷控制
10                                    BME680HumiSampleType osrs_h,       //濕度精度
11                                    BME680GasRunType run_gas,      //氣體運行設置
12                                    BME680HeaterSPType nb_conv,  //加熱器設定點選擇
13                                    BME680HeaterOffType heat_off, //加熱器關閉
14                                    uint16_t duration,      //TPHG測量循環周期,ms單位
15                                    uint8_t tempTarget,   //加熱器的目標溫度
16                                    BME680Read Read,  //讀數據操作指針
17                                    BME680Write Write,       //寫數據操作指針
18                                    BME680Delayms Delayms,           //延時操作指針
19                                    BME680ChipSelect ChipSelect     //片選操作指針
20                             )
21 {
22        uint8_t try_count = 5;
23        uint8_t regValue=0;
24       
25        if((bme==NULL)||(Read==NULL)||(Write==NULL)||(Delayms==NULL))
26        {
27               return;
28        }
29        bme->Read=Read;
30        bme->Write=Write;
31        bme->Delayms=Delayms;
32       
33        bme->port=port;
34        if(bme->port==BME680_I2C)
35        {
36               if((bmeAddress==0xEC)||(bmeAddress==0xEE))
37               {
38                      bme->bmeAddress=bmeAddress;
39               }
40               else if((bmeAddress==0x76)||(bmeAddress==0x77))
41               {
42                      bme->bmeAddress=(bmeAddress<<1);
43               }
44               else
45               {
46                      return;
47               }
48               bme->ChipSelect=NULL;
49        }
50        else
51        {
52               if(ChipSelect!=NULL)
53               {
54                      bme->ChipSelect=ChipSelect;
55               }
56               else
57               {
58                      bme->ChipSelect=BME680ChipSelectDefault;
59               }
60        }
61       
62        bme->chipID=0x00;
63        bme->pressure=0.0;
64        bme->temperature=25.0;
65        bme->humidity=0.0;
66        bme->bmeAddress=0x00;
67        bme->caliPara.t_fine=0;
68              
69        if(!ObjectIsValid(bme))
70        {
71              return;
72        }
73     
74        while(try_count--)
75        {
76               ReadBME680Register(bme,REG_BME680_ID,&regValue,1);
77               bme->chipID=regValue;
78               if(0x61==bme->chipID)
79              {
80                   BME680SoftReset(bme);
81      
82                   break;
83              }
84        }
85  
86        if(try_count)
87        {
88              uint8_t waitTime;
89              waitTime=CalcProfileDuration(bme,duration,osrs_t,osrs_p,osrs_h);
90              
91              //控制寄存器配置
92              ConfigControlRegister(bme,filter,spi3W_en,osrs_t,osrs_p,spi3wint_en,osrs_h,run_gas,nb_conv,heat_off,waitTime,tempTarget);
93              
94              //讀取校准值
95              GetBME680CalibrationData(bme);
96       }
97 }

2.2、對象操作

  每一個對象都有操作,我們使用對象的目的當然是通過操作對象來獲取我們需要的數據。所以開發驅動時,對象的操作才是我們主要的工作內容。在這里對BME680的操作就是對其寄存器的操作。

2.2.1、寫寄存器操作

  我們已經說過了,對BME680的操作都是通過讀寫寄存器實現的。這里我們先來看寫寄存器。在I2C接口方式下,寫寄存器操作是在從站地址的最后一位來識別的,再加上要寫的寄存器地址和數據來實現的,這也是I2C協議的標准做法。其時序圖如下所示:

 

  而在SPI接口方式下,由於SPI並未有設備地址,也不存在用從還在那地址最后為來標記讀寫的模式。通常一些設備需要定義操作碼來實現讀寫區分,但BME680采取了將寄存器地址的最高位置零表示為寫。之所以可以這樣定義,是因為BME680寄存器地址分配的特殊性決定的。改變寄存器地址的最高位也能區分不同的寄存器,絕不會重復。在SPI接口方式下,寫寄存器的時序圖如下所示:

 

  根據上述描述和時序圖,我們可以實現寫BME680環境傳感器寄存器的程序。

 1 /* 向BME680寄存器寫一個字節 */
 2 static void WriteBME680Register(BME680ObjectType *bme,uint8_t regAddress,uint8_t command)
 3 {
 4        if(ObjectIsValid(bme))
 5        {
 6               if(bme->port==BME680_SPI)
 7               {
 8                      bme->ChipSelect(BME680CS_Enable);
 9                      bme->Delayms(1);
10                      SetMemeryPageNumber(bme,regAddress);
11                      regAddress&=0x7F;
12                      bme->Delayms(1);
13                      bme->Write(bme,regAddress,command);
14                      bme->Delayms(1);
15                      bme->ChipSelect(BME680CS_Disable);
16               }
17               else
18               {
19                      bme->Write(bme,regAddress,command);
20               }
21        }
22 }

2.2.2、讀寄存器操作

  讀寄存器的處理方式與寫寄存器是類似。在I2C接口方式下,將從站地址的最低位置1來表示讀。在I2C接口方式下,讀寄存器的時序圖如下所示:

 

  而在SPI接口方式下,通過將寄存器地址的最改為置1來標識為讀操作。事實上,所有寄存器地址的最高為都是1,所以在讀操作時實際不需要做處理。在SPI接口方式下,讀寄存器的時序圖如下所示:

 

  根據上述描述和時序圖,我們可以實現讀BME680環境傳感器寄存器的程序。

 1 /*從BME680寄存器讀取數據*/
 2 static uint8_t ReadBME680Register(BME680ObjectType *bme,uint8_t regAddress,uint8_t *rDatas,uint16_t rSize)
 3 {
 4       uint8_t bmeError=0xFF;
 5  
 6       if(ObjectIsValid(bme))
 7       {
 8               if(bme->port==BME680_SPI)
 9               {
10                      bme->ChipSelect(BME680CS_Enable);
11                      bme->Delayms(1);
12                      SetMemeryPageNumber(bme,regAddress);
13                      regAddress |= 0x80;
14                      bme->Delayms(1);
15                      bme->Read(bme,regAddress,rDatas,rSize);
16                      bme->Delayms(1);
17                      bme->ChipSelect(BME680CS_Disable);
18               }
19               else
20               {
21                      bme->Read(bme,regAddress,rDatas,rSize);
22               }
23              
24               bmeError=0x00;
25       }
26  
27       return bmeError;
28 }

3、驅動的使用

  上一節我們設計並實現了BME680環境傳感器的驅動程序,但這個驅動設計的是否合理還不確定,所以我們來設計一個簡單的應用驗證BME680環境傳感器的驅動。

3.1、聲明並初始化對象

  使用基於對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的BME680環境傳感器對象類型聲明一個BME680環境傳感器對象變量,具體操作格式如下:

  BME680ObjectType bme680;

  聲明了這個對象變量並不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:

  BME680ObjectType *bme,BMP680對象

  uint8_t bmeAddress,I2C接口是設備地址

  BME680PortType port,接口選擇

  BME680IIRFilterType filter,過濾器

  BME680SPI3wUseType spi3W_en,3線SPI控制

  BME680TempSampleType osrs_t,溫度精度

  BME680PresSampleType osrs_p,壓力精度

  BME680SPI3wIntType spi3wint_en,3線SPI中斷控制

  BME680HumiSampleType osrs_h,濕度精度

  BME680GasRunType run_gas,氣體運行設置

  BME680HeaterSPType nb_conv,加熱器設定點選擇

  BME680HeaterOffType heat_off,加熱器關閉

  uint16_t duration,TPHG測量循環周期,ms單位

  uint8_t tempTarget,加熱器的目標溫度

  BME680Read Read,讀數據操作指針

  BME680Write Write,寫數據操作指針

  BME680Delayms Delayms,延時操作指針

  BME680ChipSelect ChipSelect,片選操作指針

  對於這些參數,對象變量我們已經定義了。其他的參數基本都是配置參數,我們根據實際使用需求選擇輸入就好了。主要的是我們需要定義幾個函數,並將函數指針作為參數。這幾個函數的類型如下:

1 /* 定義讀數據操作函數指針類型 */
2 typedef void (*BME680Read)(struct BME680Object *bme,uint8_t regAddress,uint8_t *rData,uint16_t rSize);
3 /* 定義寫數據操作函數指針類型 */
4 typedef void (*BME680Write)(struct BME680Object *bme,uint8_t regAddress,uint8_t command);
5 /* 定義延時操作函數指針類型 */
6 typedef  void (*BME680Delayms)(volatile uint32_t nTime);
7 /* 定義使用SPI接口時,片選操作函數指針類型 */
8 typedef  void (*BME680ChipSelect)(BME680CSType cs);

  對於這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平台有關系。若采用的SPI接口則需注意片選操作,片選操作函數用於多設備需要軟件操作時,如采用硬件片選可以傳入NULL即可。同樣如果采用的是I2C接口,則片選可以傳入NULL即可。具體函數定義如下:

 1 /*讀BME680寄存器值*/
 2 static void ReadDataFromBME680(BME680ObjectType *bme680,uint8_t regAddress,uint8_t *rData,uint16_t rSize)
 3 {
 4   HAL_I2C_Master_Transmit(&bme680hi2c, bme680->bmeAddress,&regAddress,1,1000);
 5  
 6   HAL_I2C_Master_Receive(&bme680hi2c, bme680->bmeAddress+1,rData, rSize, 1000);
 7 }
 8  
 9 /*寫BME680寄存器值*/
10 static void WriteDataToBME680(BME680ObjectType *bme680,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(&bme680hi2c,bme680->bmeAddress, pData, 2,1000);
18 }

  對於延時函數我們可以采用各種方法實現。我們采用的STM32平台和HAL庫則可以直接使用HAL_Delay()函數。於是我們可以調用初始化函數如下:

 1 BME680Initialization(&bme680,  //BME280對象
 2                             0xEC,           //I2C接口是設備地址
 3                             BME680_I2C,    //接口選擇
 4                             BME680_IIR_FILTER_COEFF_X127,      //過濾器
 5                             BME680_SPI3W_DISABLE,        //3線SPI控制
 6                             BME680_TEMP_SAMPLE_X16, //溫度精度
 7                             BME680_PRES_SAMPLE_X16,          //壓力精度
 8                             BME680_SPI3W_INT_DISABLE,       ///3線SPI中斷使能
 9                             BME680_HUMI_SAMPLE_X16,         //濕度精度
10                             BME680_GAS_RUN_ENABLE,//氣體運行設置
11                             BME680_HEATER_SP0,//加熱器設定點選擇
12                             BME680_HEATER_DISABLE,//加熱器關閉
13                             20,//TPHG測量循環周期,ms單位
14                             200,//加熱器的目標溫度
15                             ReadDataFromBME680,  //讀數據操作指針
16                             WriteDataToBME680,     //寫數據操作指針
17                             HAL_Delay,                     //延時操作指針
18                             NULL                                //片選操作指針
19                      );

3.2、基於對象進行操作

  我們定義了對象變量並使用初始化函數給其作了初始化。接着我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據並轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。

 1 /*獲取環境數據*/
 2 void BME680GetEnvironmentalData(void)
 3 {
 4        float pressure;                          //壓力值
 5        float temperature;             //溫度值
 6        float humidity;                         //濕度值
 7        float gasResistance;   //氣體電阻
 8       
 9        GetBME680Measure(&bme680);
10  
11        pressure=bme680.pressure;
12        temperature=bme680.temperature;
13        humidity=bme680.humidity;
14        gasResistance=bme680.gasResistence;
15 }

4、應用總結

  我們設計並實現了BME680環境傳感器的驅動程序,並基於這一驅動程序設計了簡單的應用。我們獲得了BME680檢測的全部環境數據,結果也是令我們滿意的,這說明我們的驅動設計是正確的。

  在使用驅動時需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號是通過硬件電路來實現的,我們在初始化時給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數。而如果采用I2C接口,那么在初始化時也應傳遞NULL值。

  BME680環境傳感器支持SPI和I2C兩種接口,而且SPI也支持3線和4線模式,但我們在測試應用中只使用了I2C接口,SPI接口還有待測試。BME680環境傳感器在使用SPI接口時,支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。而在使用I2C接口時,支持標准模式、快速模式以及高速模式。而且在使用I2C接口時,SDO引腳必須接高電平或低電平,以確定設備地址。

  BME680環境傳感器有2種工作模式:休眠模式和強制模式。在設備上電后就進入休眠模式,在這種模式下設備不執行測量工作。一旦啟動強制模式則執行一遍TPHG循環檢測。模式設定的具體定義如下:

 

  對於BME680環境傳感器有一個測量范圍寄存器,這個寄存器的值對應兩組計算常數,這下常數用於測量值的計算,具體如下:

 

  總的來說對BME680環境傳感器的讀寫操作本身並不復雜,但其計算與修正關系卻相對復雜,特別是氣體質量數據更應注意。

  源碼下載:https://github.com/foxclever/ExPeriphDriver

歡迎關注:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM