最近在搞一个小东西用到了气压传感器,最终选择了BMP388。搜索发现网上关于388的资料少之又少,官方给出的Arduino代码几千行全是英文注释。。。所以去翻看了一下数据手册做了些笔记,分享出来希望帮助有需要的同学。第一次发文章如有错误还请海涵。下面先贴上成功的图片:
BMP388的一些特性介绍:
- 它有FIFO存储器,可以不断采集数据存入其中然后一次读出,不使用。
- 有中断引脚,可产生FIFO溢出中断或者数据准备完毕中断等,不使用。
- 有SPI模式和IIC模式,我用的是IIC模式,此时CS引脚要接高。SDO用于决定IIC地址,SDO接地为0x76,SDO为0x77,这里我们的SDO接地。
- 有三种模式,Sleep是睡眠模式,Force是一次采样进入Sleep,Normal是按照一定的采样间隔连续多次采样,这里采用Normal。
相关寄存器的介绍:
- 0x00:存放芯片ID,可以尝试读取寄存器作为IIC通讯的功能测试,读取到0x50则说明主控与IC通讯成功。
- 0x02:最后3BIT是一些错误指示位,要确保为0。
- 0x03:状态寄存器,BIT4为1说明准备完毕可以开始写指令(这里的指令主要是reset啦)了,BIT5为1说明气压数据准备完毕,BIT6为1说明温度准备完毕,读取该寄存器后寄存器自动置0。
- 0x1B: BIT0置1使能气压采样,BIT1置1使能温度采样,BIT[5:4]设置模式,为00则为Sleep Mode,为01或者10是Forced Mode,为11是Normal Mode。模式选择最好放在最后面,先配置好其他寄存器再设置Mode,设置好之后会立刻跳出Sleep,然后按照之前配置的开始采样。
- 0x1C:相关的过采样设置,BIT[2:0]是气压的设置(osr_p),BIT[5:3]是温度的设置(osr_t),设置的值最大是5,对应着2的n次方的过采样倍数,数值越大精确度越高,但是用时越久。
- 0x1F:IIR滤波器设置,这个滤波器可以滤除一些突如其来的噪声比如突然关门带来的气压变化,只写BIT[3:1],共8个等级,寄存器值越大滤波效果越好,寄存器为0则不滤波。
(上面是一些场景的推荐设置,从上到下分别是低功耗手持设备,动态手持设备,天气预测,掉落检测,室内导航,无人机。osrs_p是气压过采样等级,osrs_t是温度过采样等级,IIR那个是滤波等级。)
- 0x1D:大概是采样速率的一个分频?只写BIT[4:0],大小是0x00到0x11,寄存器值是0x00的时候两次开始采样之间的间隔是5ms,即200Hz,值为0x01是2分频即100Hz。而采样本身需要时间,如果两次采样开始之间的间隔小于采样的时间,可能会出问题,所以如果要用Normal模式,应该是要根据相关公式设置这个寄存器的。后面会再讲一下这个寄存器的设置。
- 0x7E:控制指令寄存器,往里面写0xB6可以软重启。
- 0x06-0x04:气压数据的BIT[23:0]。
- 0x09-0x07:温度数据的BIT[23:0]。
- 0x17:BIT0置零关闭FIFO功能,上电默认关闭,不用设置。
- 0x19:BIT6置零关闭数据准备完毕中断,上电默认关闭,不用设置。
- 0x1A:BIT1置零关闭IIC的看门狗,上电默认关闭,不用设置。
- 0x31-0x45:MCP388需要一些修正系数来计算最终的结果,这些值的固定不变存储在ROM中的,但是每一个388的值都不一样,所以需要在开始采样之前一次全部读取出来用于后面的计算。要注意数据类型哦。
0x1D寄存器的一些补充介绍
以下内容都是我猜测的哈,不知道正不正确。
如图,在Normal模式下,每一个采样间隔Ts由采样时间和空闲时间组成,这个Ts通过设置0x1D寄存器(odr_sel)来设置。寄存器为0x00的时候是1分频,Ts=5ms即200Hz,当寄存器为0x01的时候是2分频,寄存器为0x03的时候是4分频这个样子,最大为0x11。但是我们要预防一种情况,就是采样是需要时间的,如果这个时间比Ts还大,那么还没采样结束就又开始了一次采样,可能就出问题了,所以我们要预防这种情况,因此需要计算采样时间Tconv,计算公式如下:
举个例子,在我们代码中设置osr_t和osr_p都是000,也就是设置的过采样系数都是1,当然这么设置精度相对会低一些。
按照公式,这时候Tconv = 4939us ,这时候算出来的odr_sel是0x00(对应分频系数1),因此最大输出速率可以设置为200Hz,0x1D那个寄存器写0x00就行了。不过因为转换时间Tconv跟Ts=5ms太接近了,为了避免意外,我们最后在0x0D寄存器写0x01对应输出2分频,这样两次采样间隔就是10ms,由4.949ms的Tconv和(10-4.949)ms的空闲时间组成。
读写寄存器
这里我们用的是IIC协议,大致说一下读写过程。(建议IIC协议stm32的输出引脚配置为开漏吧,虽然我也不知道为啥。)
IIC写:
- 首先发送开始信号,然后发送从机地址左移一位加上读写控制位构成的8位数据,其中读写控制位为0是写,是1则为读(看图),然后等待从机应答。
- 发送寄存器地址、等待从机应答、发送要写的数据、等待从机应答
- 重复2直到要配置的寄存器配置完(如果只写一个寄存器可以跳过这步)。
- 发送结束信号。
IIC读:
- 发送开始信号,然后先设置为写模式,等待应答,然后发送要读取的寄存器地址,等待应答,可以选择发送一个结束信号也可以不发送。
- 再发送开始信号,设置为读模式,等待从机应答,然后就会接收到要读的内容,数据高位在前,读完8位后,如果此时主机发送应答信号给从机,那么从机将自动将下一个寄存器的数据发送过来,直到主机发送不应答信号,主机再发送一个结束信号通讯结束。
- 一般来说,我们需要读的是气压和温度数据,而这48位数据是再连续的地址中,因此可以设置要读的寄存器为0x04,然后就可以连续读取6次数据,每读完8位发送一个应答信号,这样就可以一次性读完48位数据,连续读是官方推荐的方式,官方不推荐每次只读一个寄存器的数据,因为会容易造成数据混乱。同理,我们也可以一次读完那21个修正系数相关的寄存器。
总的流程
1.尝试读取0x00寄存器,看读到的是否为0x50,如果是则通信成功。
2.读取21个修正寄存器的值保存下来后面会用到。
3.往0x7E寄存器写0xB6重置全部寄存器(这一步应该是可以跳过的)。
4.配置过采样系数,配置滤波系数,使能气压采样或者温度采样或者都使能,配置分频寄存器的值,选择Normal模式即可开始采样。
5.查询0x03寄存器相关位是否1,若为1则代表采样结束了,可以读数了。
6.按照修正算法修正读到的值。
相关算法代码如下:
修正系数产生:
//这里是把读到的21个寄存器值合成相关的修正系数,存在结构体内
calib_data.par_t1 = Concat_Bytes(reg_data[1], reg_data[0]);
calib_data.par_t2 = Concat_Bytes(reg_data[3], reg_data[2]);
calib_data.par_t3 = (int8_t)reg_data[4];
calib_data.par_p1 = (int16_t)Concat_Bytes(reg_data[6], reg_data[5]);
calib_data.par_p2 = (int16_t)Concat_Bytes(reg_data[8], reg_data[7]);
calib_data.par_p3 = (int8_t)reg_data[9];
calib_data.par_p4 = (int8_t)reg_data[10];
calib_data.par_p5 = Concat_Bytes(reg_data[12], reg_data[11]);
calib_data.par_p6 = Concat_Bytes(reg_data[14], reg_data[13]);
calib_data.par_p7 = (int8_t)reg_data[15];
calib_data.par_p8 = (int8_t)reg_data[16];
calib_data.par_p9 = (int16_t)Concat_Bytes(reg_data[18], reg_data[17]);
calib_data.par_p10 = (int8_t)reg_data[19];
calib_data.par_p11 = (int8_t)reg_data[20];
先修正温度
//先修正温度,里面有个t_lin在修正气压的时候用到,因为气压跟温度有某种关系嘛
//uncomp是一个结构体保存了未修正的温度和气压
void compensate_temperature()
{
uint64_t partial_data1;
uint64_t partial_data2;
uint64_t partial_data3;
int64_t partial_data4;
int64_t partial_data5;
int64_t partial_data6;
int64_t comp_temp;
partial_data1 = uncomp_data.temperature - (256 * calib_data.par_t1);
partial_data2 = calib_data.par_t2 * partial_data1;
partial_data3 = partial_data1 * partial_data1;
partial_data4 = (int64_t)partial_data3 * calib_data.par_t3;
partial_data5 = ((int64_t)(partial_data2 * 262144) + partial_data4);
partial_data6 = partial_data5 / 4294967296;
calib_data.t_lin = partial_data6;
comp_temp = (int64_t)((partial_data6 * 25) / 16384);
comp_data.temperature = comp_temp; //修正结果保存在comp结构体内
}
再修正气压
//修正气压
void compensate_pressure()
{
int64_t partial_data1;
int64_t partial_data2;
int64_t partial_data3;
int64_t partial_data4;
int64_t partial_data5;
int64_t partial_data6;
int64_t offset;
int64_t sensitivity;
uint64_t comp_press;
partial_data1 = calib_data.t_lin * calib_data.t_lin;
partial_data2 = partial_data1 / 64;
partial_data3 = (partial_data2 * calib_data.t_lin) / 256;
partial_data4 = (calib_data.par_p8 * partial_data3) / 32;
partial_data5 = (calib_data.par_p7 * partial_data1) * 16;
partial_data6 = (calib_data.par_p6 * calib_data.t_lin) * 4194304;
offset = (calib_data.par_p5 * 140737488355328) + partial_data4 + partial_data5 + partial_data6;
partial_data2 = (calib_data.par_p4 * partial_data3) / 32;
partial_data4 = (calib_data.par_p3 * partial_data1) * 4;
partial_data5 = (calib_data.par_p2 - 16384) * calib_data.t_lin * 2097152;
sensitivity = ((calib_data.par_p1 - 16384) * 70368744177664) + partial_data2 + partial_data4 + partial_data5;
partial_data1 = (sensitivity / 16777216) * uncomp_data.pressure;
partial_data2 = calib_data.par_p10 * calib_data.t_lin;
partial_data3 = partial_data2 + (65536 * calib_data.par_p9);
partial_data4 = (partial_data3 * uncomp_data.pressure) / 8192;
partial_data5 = (partial_data4 * uncomp_data.pressure) / 512;
partial_data6 = (int64_t)((uint64_t)uncomp_data.pressure * (uint64_t)uncomp_data.pressure);
partial_data2 = (calib_data.par_p11 * partial_data6) / 65536;
partial_data3 = (partial_data2 * uncomp_data.pressure) / 128;
partial_data4 = (offset / 4) + partial_data1 + partial_data5 + partial_data3;
comp_press = (((uint64_t)partial_data4 * 25) / (uint64_t)1099511627776);
comp_data.pressure = comp_press;
}
上面就是主要的开发流程和修正算法啦,如果有什么不的可以看看数据手册哦,如果想要完整的程序(带有详细注释哦),请到我的主页找找下载链接,也算是对本人的支持啦!转载请注明出处,谢谢!