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; //气体控制寄存器010 uint8_t ctrlgas1; //气体控制寄存器111 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 #else24 float temperature; //温度值25 float pressure; //压力值26 float humidity; //湿度值27 float gasresistence; //大气质量电阻值28 float iaq; //空气质量水平29 #endif30 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=(bmeaddresschipselect=chipselect;55 }56 else57 {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,®value,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 else18 {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 else20 {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,®address,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

中软国际邀您相约2023中国移动全球合作伙伴大会,共筑千行百业数智未来
RISC-V开源、免版税、可扩展ISA获得产业发展动力
东软睿驰获颁ISO/SAE 21434:2021认证证书
美图T9送女友 支持AI瘦身功能
揭秘一体成型电感外壳破了会影响使用吗
BME680环境传感器的驱动设计与实现
RK3588 VDD_CPU_LIT电源PCB设计
东风、吉利等车企因汽车管理被工信部点名
脑机接口领域向前迈出了一大步,脑机接口说出无言心声
32位MCU产品逐渐脱颖而出:走红2008
究竟在线监测平台方便在哪?在线监测管理系统能有哪些优势
采用钰泰ETA9742+MCU+赛芯XB4908的小风扇和移动电源方案
Google无人机实现首飞,空中互联网时代即将到来
5G和AI的加持智慧物流
德索fakra连接器的知识概述和属性特点
使用NI平台连接仸意工业网络与PLC
赛灵思XtremeDSP解决方案可快速高效发挥FPGA所有潜力
嵌入式工业级ISM5.8G无线图传模块应用场景介绍
开关电源工作原理及应用方案大全
中国企业数字化转型需掌握的5种战略是什么?