使用GD32303C-EVAL开发板和MPL3115A2模块测量气压或高度数据,两者间使用硬件I2C进行通讯。
一开始发现I2C读写异常,EXMC模块时钟使能就会导致I2C读取失败。通过逐步排查与调试验证,最终确认问题为PB7引脚使用冲突,EXMC模块与I2C均使用了开发板的PB7引脚。通过对I2C0功能的引脚remap配置,已经将问题解决。
那么下面就进入到了MPL3115A2模块的配置和使用了。
下面是焊接前后的模块实物图。
下面是所使用的GD32303C-EVAL开发板实物图。
由于前面已经调通了I2C硬件驱动,这里开始编写MPL3115A2模块初始化程序。
首先查看MPL3115A2的芯片手册,确认使用过程。
找到MPL3115A2的内部寄存器列表,查看需要使用的内容。
这里可以看到,0x01-0x05寄存器是保存测量结果的,其中0x01-0x03是保存气压值或高度值的,0x04和0x05是保存温度值的。
MPL3115有两种工作模式,气压计模式和高度计模式,在不同的工作模式下,0x01-0x03寄存器中保存的值是不同的,分别跟对应的工作模式匹配。就是说无论工作在什么模式,读取结果都是同样的寄存器地址。
0x00寄存器是状态寄存器。这个地址有点特别,在不同的数据模式下指向不同的寄存器地址。
MPL3115除了具有单次采样结果存储的模式之外,还具有FIFO功能,在开启FIFO数据模式的情况下,芯片可以保存最多32组数据,每组数据都包括气压值/高度值和温度值(5字节数据)。
在这个数据模式下,0x00地址指向的是0x0D寄存器,即I2C读取0x00地址的值,实际上读取的是0x0D寄存器(当然,直接读取0x0D寄存器也是可以的),这个寄存器是FIFO系统状态寄存器,可以查询有没有发生FIFO数据溢出事件以及FIFO数据有没有存储到设定数量。
在这个模式下,读取数据也不是0x01-0x05寄存器读取了。为了方便循环读取,可以始终读取0x01寄存器的值,读取到的值会轮流按照“气压值/高度值高字节→中字节→低字节→温度高字节→低字节→下一组值的气压/高度值高字节”的形式一直循环读取,直到读取完毕所有FIFO数据的值。而且按照I2C通讯连续读取时寄存器地址自增模式进行匹配,0x01寄存器地址在这个模式下的自增地址还是0x01,即按照I2C总线通讯模式进行连续读取,寄存器地址选择为0x01,一直读取,会一直读到0x01地址的值,直到读取完所有数据。
而如果没有开启FIFO数据模式,0x00寄存器地址指向的是0x06寄存器,这个寄存器也是状态寄存器,这个寄存器可以查看有没有出现数据覆盖以及当前数据有没有准备好。
由于没有启用FIFO数据模式,这个模式下只能保存一组数据(气压值/高度值和温度值,共5字节),分别存放在0x01-0x05寄存器里,读取时直接连续读取5字节即可。如果一直循环读取,读完0x05寄存器之后寄存器指针会自动再次定位到0x00寄存器,再次进行一轮读取,但是由于这时候下一组采样数据还没有准备好,因此读出来的值无效(应该为0,不过没有验证)。
工作模式大概说了一下,继续查看如何配置工作模式。首先使用最简单的模式:不启用FIFO数据的连续采样模式。
可以看到,控制寄存器有5个,分别查看每个寄存器的介绍,发现只要控制第一个控制寄存器0x26即可完成初步使用。
这个寄存器可以配置芯片工作在气压计模式或高度计模式,可以配置过采样次数,可以配置芯片的待机模式和激活模式。
通过描述来看,直接将SBYB位置1即可使芯片进入激活模式,开始进行采样。
于是初始化函数如下(气压计模式,过采样设置为最大128,增加数据稳定性):
init8_t MPL_Init(void)
{
uint8_t reg_data, get_data;
reg_data = 0x39;
eeprom_byte_write(& reg_data, CTRL_REG1_ADDR);
eeprom_buffer_read(&get_data, CTRL_REG1_ADDR, 1);
if(get_data==reg_data)
{
return 0;
}
else
{
return -1;
}
}
其中CTRL_REG1_ADDR为0x26。
同时编写结果读取函数(刚开始只是把结果读取出来,至于数值转换等读取成功之后再处理):
int8_t MPL_Result_Read(void)
{
uint8_t status;
uint8_t data_buf[5];
eeprom_buffer_read(&status, DATA_STATUS_ADDR, 1);
if((status & 0x08) == 0)
{
return -1;
}
eeprom_buffer_read(data_buf, DATA_START_ADDR, 5);
return 0;
}
其中DATA_STATUS_ADDR为0x00,而DATA_START_ADDR为0x01。
编译、烧录、运行,结果发现读取DATA_STATUS_ADDR始终为0。好吧,继续找问题。
单步调试运行,发现控制寄存器写入成功了,但是读取状态寄存器确实为0,那么久确认问题不是出在I2C读写上。
继续看手册,同时网上找一下例程(例程放到附件里)。
通过对比网上找的例程,发现多了一个寄存器的操作:0x13寄存器。查看手册,发现这个是数据更新状态置位允许寄存器,就是说这个寄存器可以配置允许哪些情况产生事件更新标志。
原来问题在这里。这个寄存器配置了允许,数据采样完成后才会在状态寄存器里产生标志位。
OK!了解了。然后按照例程里的模式,对控制寄存器进行写入,并增加了读取验证过程,同时增加了气压计/高度计模式选择控制,避免来回切换工作模式都要改动初始化函数。并在运行完成之后显示初始化结果。
void MPL_Init(uint8_t mode)
{
uint8_t i, mode_data,reg_data, get_data=0;
if(mode==0)
{
mode_data = 0x38;
}
else
{
mode_data = 0xB8;
}
reg_data = mode_data;
eeprom_byte_write(& reg_data, CTRL_REG1_ADDR);
eeprom_buffer_read(&get_data, CTRL_REG1_ADDR, 1);
if(get_data!=reg_data)
{
MPL_ERR_Display();
return;
}
reg_data = 0x07;
eeprom_byte_write(& reg_data, PT_DATA_CFG_ADDR);
eeprom_buffer_read(&get_data, PT_DATA_CFG_ADDR, 1);
if(get_data!=reg_data)
{
MPL_ERR_Display();
return;
}
reg_data = mode_data + 1;
eeprom_byte_write(& reg_data, CTRL_REG1_ADDR);
eeprom_buffer_read(&get_data, CTRL_REG1_ADDR, 1);
if(get_data!=reg_data)
{
MPL_ERR_Display();
return;
}
MPL_OK_Display();
}
再次编译、烧录、运行,这次果然读取到状态和数据了。
下面就是数据格式转换了,这个相对简单,按照手册上的说明对数据进行位移和拼接处理,即可得到正确的结果。这里为了显示方便,直接将整数部分和小数部分分别存储,然后拼接显示在TFT屏幕上的。
int8_t MPL_Result_Read(void)
{
uint8_t status;
uint8_t data_buf[5];
eeprom_buffer_read(&status, DATA_STATUS_ADDR, 1);
if((status & 0x08) == 0)
{
return -1;
}
eeprom_buffer_read(data_buf, DATA_START_ADDR, 5);
height_integer = (uint32_t)data_buf[0]*0x100 + (uint32_t)data_buf[1];
height_decimal = (data_buf[2]>>4) * 625;
temp_integer = data_buf[3];
temp_decimal = (data_buf[4]>>4) * 625;
pressure_value = (uint32_t)data_buf[0]*0x400 + (uint32_t)data_buf[1]*0x04 + (data_buf[2]>>6);
return 0;
}
这里将高度计模式和气压计模式两种模式下的转换关系都保存了下来,当然,同一模式下肯定有一组转换结果是错误的,但是只使用正确的结果就可以了,防止每次切换模式都去修改代码,毕竟调试阶段经常变换模式是很正常的。同时这里将气压计模式的小数位直接丢弃了(精度不够)。
下面是高度计模式的运行结果示意,分别显示高度值和温度值。
然后开始运行,同时查看测量结果的变化是否符合预期。
初步观察,发现测量结果会有波动,但是波动值范围在±0.3m范围内,符合预期情况。
由于我是在4楼办公,于是准备逐层下到1楼,再上到4楼,来查看测量结果的变化。
实验结果如下:一开始在4楼时高度约16m,下到3楼高度约12.5m,下到2楼高度约7m,下到1楼高度约2.5m,再上到2楼高度约7m,上到3楼高度约12m,上到4楼高度约15.5m。
通过初步测试,剔除掉绝对误差后,发现基本可以测量出楼层高度。
但是在办公室一直运行时发现一个问题,测量值一直在漂移,一两个小时测量结果就能漂移10m左右,而温度的变化很小,不应该引起这么大的漂移。以为是没有配置好芯片工作模式,于是继续查资料,找例程。同时对接淘宝卖家,问下有没有技术支持或者例程代码之类的。
最终卖家发过来一个例程代码包,C++语言编写的,不过大致看一下配置过程还是OK的。(例程见附件)
多方对比之下,发现我的配置流程和读取流程没有问题,就是这么用的。
那么是什么原因导致的测量结果漂移呢?
为了确认漂移的情况,我对程序增加了结果上传功能,通过串口实时将测量结果上传到电脑上,通过串口助手接收保存数据。
然后进行长时间的采样、数据保存,并对采集到的数据按照时间进行图表绘制,确认其变化趋势。
可以看出,温度的变化(右侧的附坐标轴)很小,基本上在25.5-27.5度之间变化,而高度值在整个过程中(约13小时)从23m左右最终下降到0m附近。可以看出其瞬时值还是相对比较准的(趋势线不是特别粗),那是什么原因导致了长时间的测量值漂移这么大呢?
这时候我想到了一个问题,一天之内大气压是稳定的么?毕竟大气是气体,整体包裹在地球外部,并没有一个特定的容器去限制它,而且天气、风、云等等也可能影响到大气压。于是我去搜了一下,大气压在一天之内有没有变化。
结果搜到一个“气压日变化”的词条,里面说了大气在一天之内的变化规律。
气压日变化
气压日变化的特点是在一天中有一个最高值和一个次高值,一个最低值和一个次低值。最高值出现在9 ~10时,次高值出现在21~22时;最低值出现在15~16时,次低值出现在3~4时。
气压的日变化在低纬度地区比较明显。气压日振幅(一日中最高值与最低值之差,又称为日较差)随纬度的增高而减小。在低纬地区,平均日振幅可达3~4百帕,到纬度50度附近日振幅不足1 百帕了。不同纬度上气压日变化的情况,在我国中纬度地区气压日振幅为1~2.5百帕,在低纬地区为2.5~4百帕,而在西藏高原东部边缘的山谷中气压的日振幅有时可达6. 5百帕。
对照这个说明,可以看到大气压在一天之内变化几百帕是正常的,换算成高度的话差不多有几十米,因此测到的漂移应该是正常的。
为了验证这个结果,我继续进行长时间连续数据采集,采集够24小时的测量结果(早上上班前约7点50分测量到第二天早上上班前),得到的趋势图如下:
可以从趋势图中看出,高度值在上午9时-10时左右最低,在下午16时左右最高,这刚好对应了上面词条里说的气压最高值出现在9-10时,最低值出现在15-16时(气压越高海拔越低)。而较小的波峰波谷也基本上能对应词条里的次低值和次高值时段。
因此基本确认了长时间的测量结果漂移实际上是大气压的真实变化,而不是芯片测量不准确。
至此,问题基本解决,测量结果也符合预期,后期加上数据修正和基准补偿之后,可以相对准确的获取当前的高度值。
注:代码里配置寄存器时eeprom_byte_write(& reg_data, CTRL_REG1_ADDR);这里&和reg_data中间不应该有空格的,但是不加空格的话论坛会显示®_data。这个只能在这里做一个说明。
代码见原文。
---------------------
作者:blust5
链接:https://bbs.21ic.com/icview-3301492-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。