zoukankan      html  css  js  c++  java
  • 外设驱动库开发笔记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

    欢迎关注:

  • 相关阅读:
    python 数据可视化(一)
    python unittest自动测试框架
    使用selenium抓取淘宝信息并存储mongodb
    python之re模块(正则表达式)
    [原] OpenGL ES 学习笔记 (二)
    [原] OpenGL ES 学习笔记 (一)
    [转] iOS开发同学的arm64汇编入门
    [转]ARM64 汇编
    [转]iOS高级调试&逆向技术-汇编寄存器调用
    [转] CGTime CMTimeRange CMTimeMapping 小结
  • 原文地址:https://www.cnblogs.com/foxclever/p/14198381.html
Copyright © 2011-2022 走看看