最近使用Bosch家BME680的过程中,发现这个传感器跟个祖宗一样难伺候。虽然我自己也还是个菜鸡,但是想在这里记录一下我个人使用这个传感器的过程和经验,希望能帮助有需要的人少走一些弯路,如果有大佬看到也希望能得到一番指点。
前言
BME680是博世家的一款四合一传感器,可以测量温度,湿度,气压和空气质量IAQ(Indoor Air Quality)。
首先拿到这个传感器,我们得去找到它的datasheet,顺便去下载一下它的程序
https://www.bosch-sensortec.com/software-tools/software/bsec/
bsec也有一个自己的说明。官网也提供了一个example在压缩包里。并且在他们的github里可以找到更多的例子
https://github.com/BoschSensortec/BSEC-Arduino-library
但是鉴于这些例子和我下的lib有些出入,我基本没怎么看。
另外说在前面,如果在使用这个传感器时遇到任何困难都可以去官方论坛寻找解决方案。论坛里提问的人很多,在里面找到答案不难,但是如果你懒得找,那可以直接注册提问,回答速度还是不错的,都是博世专业人员进行回答,什么初级问题都可以解答,就算datasheet里已经说过了,他们也会贴心地告诉你在哪页。我最近老在上面提问,基本一天一两次的程度(毕竟我菜),我都快和负责我的技术人员成为好朋友了(手动狗头)。
怕有人找不到,我把网址贴在这:https://community.bosch-sensortec.com/t5/Bosch-Sensortec-Community/ct-p/bst_community
添加BSEC
我用的是STM32及配套的Cube IDE。所以先新建一个项目然后把lib文件全都粘进去。我们就发现有个叫libalgobsec.a的文件,这个是bsec的静态库,需要打开project->properties->C/C++ Build->settings->tool settings->MCU GCC Linker->libraries里添加algobsec,在下面的路径选择它的路径。因为我直接放workspace里了,发现需要给这个静态库单独建一个文件夹。
另外我们会发现bsec_iot_example里有很多function里面是空的。我在bus_read和bus_write里分别加了HAL_I2C_MEM_Read和HAL_I2C_MEM_Write。
看论坛上说有个地方的代码有些错误,我直接用了它改了的,不知道改之前能不能正常使用。
uint8_t bsec_state[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t bsec_config[BSEC_MAX_PROPERTY_BLOB_SIZE] = {0};
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE] = {0};
int bsec_state_len, bsec_config_len;
这时这个码八成就可以运行了。其他的函数后面再说。
根据情况改动BSEC程序
1.设备中有其他热源会影响BME的温度测量
在bsec_integration里的offset里设置数值,抵消其他热源导致的温度上升。这个数值可以来自设备中其他的温度传感器,也可以自己给定一个值。温度数值精确度还是比较重要的,后面湿度也会用到这个数值。
2. 需要获取eVOC和eCO2数值
我们可以发现获取测量数值的函数是process,但是函数说明说它获取什么数值要去update_subscription里设置,所以在原来的基础上要再添加两个。
记得把NUM_USED_OUTPUTS和BSEC_MAX_PHYSICAL_SENSOR也改了。
static bsec_library_return_t bme680_bsec_update_subscription(float sample_rate)
{
bsec_sensor_configuration_t requested_virtual_sensors[NUM_USED_OUTPUTS];
uint8_t n_requested_virtual_sensors = NUM_USED_OUTPUTS;
bsec_sensor_configuration_t required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
bsec_library_return_t status = BSEC_OK;
/* note: Virtual sensors as desired to be added here */
requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ;
requested_virtual_sensors[0].sample_rate = sample_rate;
requested_virtual_sensors[1].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
requested_virtual_sensors[1].sample_rate = sample_rate;
requested_virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
requested_virtual_sensors[2].sample_rate = sample_rate;
requested_virtual_sensors[3].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
requested_virtual_sensors[3].sample_rate = sample_rate;
requested_virtual_sensors[4].sensor_id = BSEC_OUTPUT_RAW_GAS;
requested_virtual_sensors[4].sample_rate = sample_rate;
requested_virtual_sensors[5].sensor_id = BSEC_OUTPUT_RAW_TEMPERATURE;
requested_virtual_sensors[5].sample_rate = sample_rate;
requested_virtual_sensors[6].sensor_id = BSEC_OUTPUT_RAW_HUMIDITY;
requested_virtual_sensors[6].sample_rate = sample_rate;
requested_virtual_sensors[7].sensor_id = BSEC_OUTPUT_STATIC_IAQ;
requested_virtual_sensors[7].sample_rate = sample_rate;
requested_virtual_sensors[8].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
requested_virtual_sensors[8].sample_rate = sample_rate;
requested_virtual_sensors[9].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
requested_virtual_sensors[9].sample_rate = sample_rate;
3. 需要缩短气体传感器的校准时间
首先要知道气体传感器的校准status是通过IAQ_accuracy显示的,有0,1,2,3,四种状态,IAQ_accuracy=3才代表校准完成可以获取比较可信的测量数据。而这一校准过程是非常漫长的,在不进行任何程序改动的情况下,在普通环境中需要半个小时至一个多小时甚至几个小时,在测量气体被污染程度比较大的情况下(比如疫情期间我把酒精洗手液放旁边),也需要20分钟左右。
这是远远超过了我的目标时间5分钟的,5分钟顶多够它从0变到1的。这时有一个半可行的方法,就是把它之前校准的数据存在NVM里,我存在STM32的DATAEEPROM里了。这里就用到了example里的state_save和state_load函数了。
我最后还是决定放个码吧,毕竟我感觉它运行得还行,但是有些问题。
/*!
* @brief Load previous library state from non-volatile memory
*
* @param[in,out] state_buffer buffer to hold the loaded state string
* @param[in] n_buffer size of the allocated state buffer byte
*
* @return number of bytes copied to state_buffer
*/
uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
{
// ...
// Load a previous library state from non-volatile memory, if available.
//
// Return zero if loading was unsuccessful or no state was available,
// otherwise return length of loaded state string.
// ...
uint32_t i;
for(i=0;i<n_buffer;i++){
*(state_buffer+i)=*(__IO uint8_t *)(EEPROM_BASE_ADDR+i);
}
return n_buffer;
}
/*!
* @brief Save library state to non-volatile memory
*
* @param[in] state_buffer buffer holding the state to be stored
* @param[in] length length of the state string to be stored
*
* @return none
*/
void state_save(const uint8_t *state_buffer, uint32_t length)
{
// ...
// Save the string some form of non-volatile memory, if possible.
// ...
// a page is composed 1 single word or 4 bytes in data EEPROM
HAL_FLASHEx_DATAEEPROM_Unlock();
for(int i=0;i<length;i++){
if(i%4==0){
HAL_FLASHEx_DATAEEPROM_Erase(EEPROM_BASE_ADDR+i);
}
HAL_FLASHEx_DATAEEPROM_Program (FLASH_TYPEPROGRAMDATA_BYTE, EEPROM_BASE_ADDR+i, *(state_buffer+i)); //third argument is not type pointer
}
HAL_FLASHEx_DATAEEPROM_Lock();
}
问题就是,state_load需要返回load的数据长度,如果没成功或者没数据就返回0。我没想明白他怎么知道自己load到哪,怎么判断读到的数据是不是它的state数据。不过按照我几次实验得出的数据来看,它的所有state数组都是以bsec的版本号开头,长度好像就是sBSEC_MAX_STATE_BLOB_SIZE(有待考证),通过这两个判断条件应该是可以编出正确的function的。
那么现在存储的函数有了,该看存储条件了。在process函数最后加一个flag,一旦IAQ_accuracy=3,flag=1,且改变process函数返回量,使它返回flag的值,用此值判断是否启用state_save函数。
至此这个小功能基本就实现了,但是值得注意的是,存下来的数据只能在相同环境下使用,如果更换了环境需要重新按照正常步骤校准。如果环境改变仍使用了存储数据校准,IAQ_accuracy会掉回2/1/0的状态,或者使IAQ_accuracy从0变到3的时间变长。
依旧待解决的问题
虽然气体传感器校准时间确实变短了,但是还是没达到5分钟。不是我说,这个东西真的就不是这么用的,令人头大。另外,如果校准时间长那我也认了,但是这个时间是真的没个准头,完全看它所处的环境情况。对于一个仅靠小电池供电的移动设备实在是不友好,看客服的说法这个问题他们也没办法解决。