STM32F407单片机移植MS5611气压计(基于IIC)---同时解决温度低于20度时计算得到的大气压错误的问题

2023-10-26

        最近一个工程项目需要使用MS5611气压计,就花时间研究了一下,发现网上很多都是基于STM32F103单片机的MS5611气压计源程序,当移植到STM32F407时发现采集的大气压力和温度值不对,同时发现网上部分程序在温度高于20度时,计算得到的大气压力是正确的,但是当温度低于20度时,计算得到的大气压力是错误的。网上程序代码问题主要集中在以下几个方面:

        (1)、硬件复位之后,需要延时多少时间才可以读芯片的出厂校准值(警告:如果硬件复位后延时不够,读取的出厂校准值是错误的)。

        (2)、出厂校准值到底是读6组参数、7组参数还是8组参数。(注:网上的程序大部分人给出的源码是读7组参数,实际上应该读8组参数)

        (3)、如何验证读出的出厂校准值是对还是错误呢?(注:MS5611芯片出厂后,读出每个芯片的出厂校准值都不相同,因此你不必怀疑自己读出的出厂校准值为什么跟其他人的不一样)

        (4)、当温度AD转换的采样频率为4096时,最大转换时间为9.04ms。(因此启动温度AD转换后,需要延时至少9.04ms以后才能去读取转换结果,否则读出的结果是错误的)

        (5)、当压力AD转换的采样频率为4096时,最大转换时间为9.04ms。(因此启动压力AD转换后,需要延时至少9.04ms以后才能去读取转换结果,否则读出的结果是错误的)

        (6)、当计算得到的温度大于或等于20度时,网上程序计算得到的大气压力是正确的,但是当计算得到的温度小于20度时,网上绝大部分程序计算得到的大气压力是错误的。

       我移植到STM32F407的MS5611驱动程序避免了上述问题。

一、MS5611芯片简介

气压计芯片参见下图。

在这里插入图片描述

  1. MS5611-01BA是由压阻传感器和传感器接口组成的的集成电路,主要功能是把测得未得补偿模拟气压值经ADC转换成24位的数字值输出,同时也可以输出一个24位的数字温度值。
  2. 高度测量最大分辨率10cm
  3. MS5611支持SPI和I2C通信,可以通过上拉PS引脚( Protocol Select)选择I2C协议,下拉则选择SPI协议
  4. MS5611-01BA的I2C地址为111011Cx,其中C为CSB引脚的补码值(取反)。因为传感器内并没有微控制器,所有I2C的命令和SPI是相同的。
  5. 气压到海拔的换算公式如下图:

                                        气压到海拔计算公司

                                                   气压与海拔关系

 

 


二、通信接口IIC

 

        当PS脚接高电平时,7和8引脚复用为IIC模式,否则为SPI模式。(PS引脚决定了MS5611从哪个接口输出数据,PS拉高代表使用IIC接口,拉低使用SPI接口。)    
在这里插入图片描述

        MS5611的I2C地址为0b111011Cx,其中C比特位由CSB引脚决定,为CSB引脚的补码值(取反)。如果MS5611的CSB引脚接地,所以CSB引脚值为0,8位I2C地址为0b1110111x(0xEE),7位I2C地址为 0b1110111(0x77)。       

        由上图,CSB接地,则CSB非就是1了。所以MS5611的地址就是0xEE;

 

三、MS5611的5种命令

        MS5611有5种基础命令,分别为:1、RESET : 重启芯片(0x1E)。2、READ PROM,即:读出厂校准值命令。3、D1 CONVERSION,即:启动温度转换AD。

4、D2 CONVERSION,即:启动压力转换AD。5、READ ADC RESULT,即:读取气压和温度模数转换后的数据。

1、RESET : 重启芯片

        在读取PROM数据之前必须RESET芯片,也就是在初始化的时候reset一下,reset命令固定是0x1E。

        警告:RESET芯片后,必须要延时一段时间才能读取出厂校准值,否则如果延时时间不足,读取的出厂校准值肯定是错误的!!!

#define  MS561101BA_SlaveAddress 	(0xEE)  //定义器件在IIC总线中的从地址
#define  MS561101BA_D1 				(0x40) 
#define  MS561101BA_D2 				(0x50)
#define  MS561101BA_RST 			(0x1E) 

BOOL MS561101BA_RESET(void)
{	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);	//CSB接地,主机地址:0xEE,否则 0x77
	if (!MS5611_i2c_WaitAck())
	{
		MS5611_i2c_Stop();
		return (FALSE);
	}
	MS5611_i2c_Stop();
	MS5611_delay_us(50);
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_RST);			//发送复位命令
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	return (TRUE);
}

2、READ PROM

        读取PROM内存的数据,PROM存放8个16位数据(工厂校准数据)。

        (1)、C0为工厂保留(第一组数据),读出来的数值为0,如果读出的数值不为0,说明你的IIC时序有问题或者你硬件复位后延时时间不足时就着急读出了工厂校准数据。

        (2)、C1---C6(第二组数据---第七组数据),参见下图,用于补偿气压和温度。

        (3)、C7为CRC校验数据,用于检查读出的工厂校准数据是否正确。

           第二组数据---第七组数据的具体含义参见下图:

               在这里插入图片描述

       下面是我的单片机读取的其中一块MS5611气压计的8组数据。

        

        下面是我的单片机读取的另外一块MS5611气压计的8组数据。

      

        可以观察到2块MS5611气压计的出厂校准值不相同,但是C0相同,固定为0.

        读取出厂校准值的程序代码参见下图:

#define  PROM_NB                 	(8)
#define  MS561101BA_PROM_RD 		(0xA0) 	//出厂校准值起始地址

BOOL MS561101BA_READ_PROM(void)
{
	uint16_t d1,d2;
	uint8_t i;
	
	
	for (i = 0 ; i < PROM_NB ; i++)		//读取PROM中的8组数据
	{
		MS5611_i2c_Start();
		MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
		MS5611_i2c_WaitAck();

		MS5611_i2c_SendByte((MS561101BA_PROM_RD + i*2));
		MS5611_i2c_WaitAck();
		MS5611_i2c_Stop();
		MS5611_delay_us(200);
		
		MS5611_i2c_Start();
		MS5611_i2c_SendByte(MS561101BA_SlaveAddress + 0x01);	//进入接收模式
		MS5611_i2c_WaitAck();
		d1 = MS5611_i2c_ReadByte();
		MS5611_i2c_Ack();		
		d2 = MS5611_i2c_ReadByte();
		MS5611_i2c_NoAck();
		MS5611_i2c_Stop();		
		Cal_C[i] = (d1 << 0x08) | d2;
	}
	return (!Cal_C[0]);	//如果Cal_C[0]=0,说明读出的出厂校验值第一步成功
						//下面还需要通过Cal_C[7]验证CRC校验值,
						//如果CRC校验值通过,才能证明读出的出厂校验值正确。
}

        如果读出来的C0数值不为0,说明你的IIC时序有问题或者你硬件复位后延时时间不足时就着急读出了工厂校准数据。 

      如何验证自己读出的工厂校准值是正确的呢?

         可以通过调用BOOL MS5611_CRC(uint16_t *prom)函数进行验证。借助第8组数据C7进行CRC验证,如果函数返回TRUE,表示读出的工厂校准数据是正确的,如果返回FALSE,表示读出的工厂校准数据是错误的。

        验证工厂校准值CRC程序代码如下所示:(函数的入口参数就是读出的工厂校准数据)

BOOL MS5611_CRC(uint16_t *prom)
{
    int32_t i, j;
    uint32_t res = 0;
    uint8_t zero = 1;
    uint8_t crc = prom[7] & 0xF;
    prom[7] &= 0xFF00;

    // if eeprom is all zeros, we're probably fucked - BUT this will return valid CRC lol
    for (i = 0; i < 8; i++) 
	{
        if (prom[i] != 0)
            zero = 0;
    }

    if (zero)
        return (FALSE);

    for (i = 0; i < 16; i++) {
        if (i & 1)
            res ^= ((prom[i >> 1]) & 0x00FF);
        else
            res ^= (prom[i >> 1] >> 8);
        for (j = 8; j > 0; j--) {
            if (res & 0x8000)
                res ^= 0x1800;
            res <<= 1;
        }
    }

    prom[7] |= crc;
    if (crc == ((res >> 12) & 0xF))
        return (TRUE);
	return (FALSE);
}

3、D1 CONVERSION

        D1 CONVERSION是启动温度AD转换。

        因为传感器获得的气压数据,温度数据是模拟量,需要进行模数转换。D1,D2分别对应气压和温度的模数转换精度。支持从256到4096的转换精度,精度越大,转换时间越长,具体对应关系见图:

                   在这里插入图片描述

        可以观察到,如果采用OSR 4096采样频率,需要等待9.04ms后,才能读取AD转换结果,如果等待时间不足,读取的AD转换结果肯定是错误的。

4、D2 CONVERSION

        D2 CONVERSION是启动压力AD转换。

        因为传感器获得的气压数据,温度数据是模拟量,需要进行模数转换。D1,D2分别对应气压和温度的模数转换精度。支持从256到4096的转换精度,精度越大,转换时间越长,具体对应关系见图:

                   在这里插入图片描述

        可以观察到,如果采用OSR 4096采样频率,需要等待9.04ms后,才能读取AD转换结果,如果等待时间不足,读取的AD转换结果肯定是错误的。

5、READ ADC RESULT:

        当启动温度AD转换后,需要如果采用OSR 4096采样频率,必须要至少等待9.04ms以后读取AD结果。

        当启动压力AD转换后,需要如果采用OSR 4096采样频率,必须要至少等待9.04ms以后读取AD结果。

        读取气压和温度模数转换后的数据,就是我们需要的数据。

        说明:启动温度AD转换,然后读取温度AD转换结果,以及启动压力AD转换,然后读取AD转换结果共用下面的一段程序。

        读取AD转换结果的程序代码如下:

uint32_t MS561101BA_DO_CONVERSION(uint8_t command)
{
	uint32_t conversion = 0x00;
	uint32_t conv1,conv2,conv3; 
	
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
	MS5611_i2c_WaitAck();
	MS5611_i2c_SendByte(command);	
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	MS5611_delay_ms(12);	//根据数据手册,最大采集时间=9.04ms

	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
	MS5611_i2c_WaitAck();
	MS5611_i2c_SendByte(0x00);
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress + 1);
	MS5611_i2c_WaitAck();	
	conv1 = MS5611_i2c_ReadByte();	//带ACK的读数据  bit 23-16
	MS5611_i2c_Ack();
	conv2 = MS5611_i2c_ReadByte();	//带ACK的读数据  bit 8-15
	MS5611_i2c_Ack();
	conv3 = MS5611_i2c_ReadByte();	//带NoACK的读数据 bit 0-7
	MS5611_i2c_NoAck();
	MS5611_i2c_Stop();

	conversion = (conv1 << 16) + (conv2 << 8) + conv3;
	return (conversion);
}

        读取到温度AD转换结果和压力AD转换结果后,就可以根据手册上的公式进行计算大气压力P。

                    在这里插入图片描述

 当温度过低(低于20度)时,计算过程就多了T2,OFF2等步骤:

                                        在这里插入图片描述

  四、采样速率

        MS5611分别对D1 CONVERSION(启动温度AD转换)和D2 CONVERSION是启动压力AD转换的转换速率规定的控制字。参见下图。

                                              在这里插入图片描述

   D1 CONVERSION  OSR=256     ---> 控制字=0x40
   D1 CONVERSION  OSR=4096   ---> 控制字=0x48
   D2 CONVERSION  OSR=256     ---> 控制字=0x50
   D2 CONVERSION  OSR=4096   ---> 控制字=0x58

    D1 CONVERSION和 D2 CONVERSION采样速率控制字参见下面程序代码。

#define  MS561101BA_D1_OSR_256 		(0x40)
#define  MS561101BA_D1_OSR_512 		(0x42)
#define  MS561101BA_D1_OSR_1024 	(0x44)
#define  MS561101BA_D1_OSR_2048 	(0x46)
#define  MS561101BA_D1_OSR_4096 	(0x48)


#define  MS561101BA_D2_OSR_256 		(0x50)
#define  MS561101BA_D2_OSR_512 		(0x52) 
#define  MS561101BA_D2_OSR_1024 	(0x54) 
#define  MS561101BA_D2_OSR_2048 	(0x56) 
#define  MS561101BA_D2_OSR_4096 	(0x58) 

        警告:当选择OSR 4096采样速率时,启动AD转换后,必须要等待9.04ms以后才能读取AD转换结果;如果延时时间不足,读取的AD转换结果是错误的。

五、MS5611工作流程简介

        MS5611工作流程大致分为以下几个步骤:

               第一步:MS5611硬件复位,然后延时一段时间才可以读取出厂校准值。

               第二步:读出出厂校准值,然后检查读取的出厂校准值的CRC是否正确。

               第三步:启动温度AD转换,读取温度AD值。(警告:启动温度AD转换后,如果选择OSR 4096必须要等到9.04ms以后才能读取温度AD值,否则读取的温度AD值是错误的)

               第四步:启动气压AD转换,读取气压AD值。(警告:启动气压AD转换后,如果选择OSR 4096必须要等到9.04ms以后才能读取气压AD值,否则读取的气压AD值是错误的)

               第五步:把两个AD值按手册给的公式转成真实温度和气压,并根据温度补偿计算大气压力。

六、温度低于20度问题

         网上的程序没有考虑温度低于20度时的情况。网上程序代码统一采用如下方式计算温度,参见下图:

dT = D2_Temp - (((u32)Cal_C[5])<<8);
Temperature = (float)(2000 + dT*((u32)Cal_C[6])/(float)8388608.0);//算出温度值的100倍,2001表示20.01°

        上述代码有问题,因为D2_Temp和Cal_C[5]都是无符号整数,两个无符号整数D2_Temp和Cal_C[5]相减永远都无法得到负数。

        解决方法程序代码如下:

if (D2_Temp > (((uint32_t)Cal_C[5]) << 8 ))
	{
		dT	= D2_Temp - (((uint32_t)Cal_C[5]) << 8 );
	}
	else
	{
		dT	= ((( uint32_t)Cal_C[5]) << 8) - D2_Temp;
		dT *= -1;
	}
	Temperature = (float)(2000 + dT*((uint32_t)Cal_C[6])/(float)8388608.0);//算出温度值的100倍,2001表示20.01°

 

七、源程序

1、IIC.C


#define RCC_MS5611_I2C_PORT 		RCC_AHB1Periph_GPIOC
#define GPIO_MS5611_I2C_PORT		GPIOC
#define GPIO_MS5611_I2C_SCL_Pin		GPIO_Pin_3
#define GPIO_MS5611_I2C_SDA_Pin		GPIO_Pin_4



#define MS5611_I2C_SCL_1()  	GPIO_SetBits(			GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SCL_Pin)	// SCL = 1
#define MS5611_I2C_SCL_0()  	GPIO_ResetBits(			GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SCL_Pin)	// SCL = 0
#define MS5611_I2C_SDA_1()  	GPIO_SetBits(			GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SDA_Pin)	// SDA = 1
#define MS5611_I2C_SDA_0()  	GPIO_ResetBits(			GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SDA_Pin)	// SDA = 0
#define MS5611_I2C_SDA_READ()  	GPIO_ReadInputDataBit(	GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SDA_Pin)	// 读SDA口线状态
#define MS5611_I2C_SCL_READ()  	GPIO_ReadInputDataBit(	GPIO_MS5611_I2C_PORT, GPIO_MS5611_I2C_SCL_Pin)	// 读SCL口线状态




/*
*********************************************************************************************************
*	函 数 名: bsp_InitI2C
*	功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_bsp_InitI2C(void)
{	
	GPIO_InitTypeDef GPIO_InitStructure;	
	
	
	RCC_AHB1PeriphClockCmd(RCC_MS5611_I2C_PORT, ENABLE);// 打开GPIO时钟
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;;  	//输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;		//开漏输出,必须配置为开漏输出(GPIO_OType = GPIO_OType_OD)。如果配置为推挽,读取时钟时肯定会失败。
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Pin =  GPIO_MS5611_I2C_SCL_Pin | GPIO_MS5611_I2C_SDA_Pin;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIO_MS5611_I2C_PORT, &GPIO_InitStructure);

	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
	MS5611_i2c_Stop();
}



void MS5611_SDA_INPUT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;  		//输入模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;		//开漏输出,必须配置为开漏输出(GPIO_OType = GPIO_OType_OD)。如果配置为推挽,读取时钟时肯定会失败。
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_NOPULL;	
	GPIO_InitStructure.GPIO_Pin =  GPIO_MS5611_I2C_SDA_Pin;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIO_MS5611_I2C_PORT , &GPIO_InitStructure);
}



void MS5611_SDA_OUTPUT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;;  	//输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;		//开漏输出,必须配置为开漏输出(GPIO_OType = GPIO_OType_OD)。如果配置为推挽,读取时钟时肯定会失败。
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_InitStructure.GPIO_Pin =  GPIO_MS5611_I2C_SDA_Pin;
	GPIO_Init(GPIO_MS5611_I2C_PORT , &GPIO_InitStructure);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Delay
*	功能说明: I2C总线位延迟,最快400KHz
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_i2c_Delay(void)
{
	uint8_t i;
	/* 
		CPU主频168MHz时,在内部Flash运行, MDK工程不优化。用台式示波器观测波形。
		循环次数为5时,SCL频率 = 1.78MHz (读耗时: 92ms, 读写正常,但是用示波器探头碰上就读写失败。时序接近临界)
		循环次数为10时,SCL频率 = 1.1MHz (读耗时: 138ms, 读速度: 118724B/s)
		循环次数为30时,SCL频率 = 440KHz, SCL高电平时间1.0us,SCL低电平时间1.2us
		上拉电阻选择2.2K欧时,SCL上升沿时间约0.5us,如果选4.7K欧,则上升沿约1us
		实际应用选择400KHz左右的速率即可
	*/
	for (i = 0; i < 30; i++);
}



void MS5611_delay_us(uint16_t time)
{
	uint16_t i;
	
	
	for (i = 0 ; i < time ; i++)
	{
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
	}
	
}



void MS5611_delay_ms(uint8_t time)
{
	uint8_t i;
	
	
	for (i = 0 ; i < 10 * time ; i++)
	{
		MS5611_delay_us(255);
		MS5611_delay_us(255);
		MS5611_delay_us(200);		
	}
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线启动信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_i2c_Start(void)
{
	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */	
	MS5611_SDA_OUTPUT();
	MS5611_I2C_SDA_1();	
	MS5611_delay_us(20);	
	MS5611_I2C_SCL_1();
	MS5611_delay_us(50);	
	MS5611_I2C_SDA_0();	//START:when CLK is high,DATA change from high to low 
	MS5611_delay_us(50);	
	MS5611_I2C_SCL_0();	//钳住I2C总线,准备发送或接收数据 
	MS5611_delay_us(10);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Stop
*	功能说明: CPU发起I2C总线停止信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_i2c_Stop(void)
{
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */	
	MS5611_SDA_OUTPUT();
	MS5611_I2C_SDA_0();	//STOP:when CLK is high DATA change form low to high
	MS5611_delay_us(20);	
	MS5611_I2C_SCL_1();
	MS5611_delay_us(50);
	MS5611_I2C_SDA_1();
	MS5611_delay_us(30);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
//SCL在高电平期间,SDA被从设备拉为低电平表示应答
void MS5611_i2c_Ack(void)
{
	MS5611_SDA_OUTPUT();
	MS5611_I2C_SDA_0();	/* CPU驱动SDA = 0 */
	MS5611_delay_us(20);

	MS5611_I2C_SCL_1();	//拉高时钟
	MS5611_delay_us(50);
	MS5611_I2C_SCL_0();	//拉低钟钟,钳住I2C总线以便继续接收
	MS5611_delay_us(10);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_NoAck
*	功能说明: CPU产生1个NACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_i2c_NoAck(void)
{
	MS5611_SDA_OUTPUT();
	MS5611_I2C_SDA_1();	/* CPU驱动SDA = 1 */
	MS5611_delay_us(20);
	
	MS5611_I2C_SCL_1();	//拉高时钟
	MS5611_delay_us(50);
	MS5611_I2C_SCL_0();	//拉低钟钟,钳住I2C总线以便继续接收
	MS5611_delay_us(10);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_WaitAck
*	功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
*	形    参:  无
*	返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
/*    功    能: 提供I2C总线的时钟信号, 并返回在时钟电平为高期间SDA 信号线上状*/
/*              态。本函数用于数据发送时的确认检查。        				 */

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
BOOL MS5611_i2c_WaitAck(void)
{
	uint8_t ucErrTime = 0x00;

	
	MS5611_SDA_INPUT();	
	MS5611_I2C_SDA_1();
	MS5611_delay_us(30);
	
	MS5611_I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	MS5611_delay_us(30);	
		
	while (MS5611_I2C_SDA_READ())
	{
		ucErrTime++;
		if (ucErrTime > 250)
		{
			MS5611_I2C_SCL_0();
			MS5611_delay_us(30);
			MS5611_i2c_Stop();
			return (FALSE);
		}
	}
	MS5611_I2C_SCL_0();
	MS5611_delay_us(30);		
	return (TRUE);
}



/*
*********************************************************************************************************
*	函 数 名: i2c_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:  _ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
void MS5611_i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;

	
	MS5611_SDA_OUTPUT();
	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (_ucByte & 0x80)
			MS5611_I2C_SDA_1();
		else
			MS5611_I2C_SDA_0();

		_ucByte <<= 1;	/* 左移一个bit */
		
		MS5611_delay_us(10);		
		MS5611_I2C_SCL_1();
		MS5611_delay_us(30);
		MS5611_I2C_SCL_0();
		MS5611_delay_us(10);
	}
}



/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:  无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t MS5611_i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value = 0x00;

	
	/* 读到第1个bit为数据的bit7 */
	for (i = 0; i < 8; i++)
	{
		MS5611_SDA_OUTPUT();
		MS5611_I2C_SDA_1();		//使能内部上拉,准备读取数据
		MS5611_delay_us(20);
		
		MS5611_I2C_SCL_1();		//拉高时钟线
		MS5611_delay_us(50);
		MS5611_SDA_INPUT();
		//MS5611_delay_us(10);
		value <<= 1;
		
		if (MS5611_I2C_SDA_READ())
			value++;
		MS5611_delay_us(10);
		MS5611_I2C_SCL_0();		//拉低时钟线
		MS5611_delay_us(10);
	}
	return (value);
}

2、MS5611.c 

#include <math.h>



u16 Cal_C[8];  //用于存放PROM中的8组数据
/*
C0 等于0
C1 压力灵敏度 SENS|T1
C2  压力补偿  OFF|T1
C3	温度压力灵敏度系数 TCS
C4	温度系数的压力补偿 TCO
C5	参考温度 T|REF
C6 	温度系数的温度 TEMPSENS
C7  用于CRC校验值
*/	


uint32_t D1_Pres , D2_Temp; 	// 存放数字压力和温度
float Pressure;					//温度补偿大气压
float dT , Temperature , T2;	//实际和参考温度之间的差异,实际温度,中间值
double OFF , SENS;  			//实际温度抵消,实际温度灵敏度
float Aux , OFF2 , SENS2;  		//温度校验值


static float Alt_offset_Pa=0;
double paOffsetNum = 0;
uint16_t  paInitCnt=0;
uint8_t paOffsetInited=0;
float MS5611_Pressure;


#define  PA_OFFSET_INIT_NUM 		(50)  
#define  PROM_NB                 	(8)


#define  MS561101BA_SlaveAddress 	(0xEE)  //定义器件在IIC总线中的从地址
#define  MS561101BA_D1 				(0x40) 
#define  MS561101BA_D2 				(0x50)
#define  MS561101BA_RST 			(0x1E) 


#define  MS561101BA_D1_OSR_256 		(0x40)
#define  MS561101BA_D1_OSR_512 		(0x42)
#define  MS561101BA_D1_OSR_1024 	(0x44)
#define  MS561101BA_D1_OSR_2048 	(0x46)
#define  MS561101BA_D1_OSR_4096 	(0x48)


#define  MS561101BA_D2_OSR_256 		(0x50)
#define  MS561101BA_D2_OSR_512 		(0x52) 
#define  MS561101BA_D2_OSR_1024 	(0x54) 
#define  MS561101BA_D2_OSR_2048 	(0x56) 
#define  MS561101BA_D2_OSR_4096 	(0x58) 


#define  MS561101BA_ADC_RD 			(0x00)
#define  MS561101BA_PROM_RD 		(0xA0) 	//出厂校准值起始地址
#define  MS561101BA_PROM_CRC 		(0xAE) 	//出厂校准值CRC校验值地址



BOOL MS561101BA_RESET(void)
{	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);	//CSB接地,主机地址:0xEE,否则 0x77
	if (!MS5611_i2c_WaitAck())
	{
		MS5611_i2c_Stop();
		return (FALSE);
	}
	MS5611_i2c_Stop();
	MS5611_delay_us(50);
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_RST);			//发送复位命令
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	return (TRUE);
}



/
//从PROM读取出厂校准数据,共8组数据。
//其中:
//      C0---  等于0,如果不等于0,说明你的I2C时序有问题
//      C1---C6,参见下面叙述。
//      C7---用于计算CRC校验值
//      
/
/*
C0 等于0
C1 压力灵敏度 SENS|T1
C2  压力补偿  OFF|T1
C3	温度压力灵敏度系数 TCS
C4	温度系数的压力补偿 TCO
C5	参考温度 T|REF
C6 	温度系数的温度 TEMPSENS
C7  用于计算CRC校验值
*/	
BOOL MS561101BA_READ_PROM(void)
{
	uint16_t d1,d2;
	uint8_t i;
	
	
	for (i = 0 ; i < PROM_NB ; i++)		//读取PROM中的8组数据
	{
		MS5611_i2c_Start();
		MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
		MS5611_i2c_WaitAck();

		MS5611_i2c_SendByte((MS561101BA_PROM_RD + i*2));
		MS5611_i2c_WaitAck();
		MS5611_i2c_Stop();
		MS5611_delay_us(200);
		
		MS5611_i2c_Start();
		MS5611_i2c_SendByte(MS561101BA_SlaveAddress + 0x01);	//进入接收模式
		MS5611_i2c_WaitAck();
		d1 = MS5611_i2c_ReadByte();
		MS5611_i2c_Ack();		
		d2 = MS5611_i2c_ReadByte();
		MS5611_i2c_NoAck();
		MS5611_i2c_Stop();		
		Cal_C[i] = (d1 << 0x08) | d2;
	}
	return (!Cal_C[0]);	//如果Cal_C[0]=0,说明读出的出厂校验值第一步成功
						//下面还需要通过Cal_C[7]验证CRC校验值,
						//如果CRC校验值通过,才能证明读出的出厂校验值正确。
}



/
// MS5611 prom 数据校验(计算CRC校验值,验证出厂校准值是否正确)
//返回值:
//       TRUE = 读出的出厂校准值正确
//       FALSE = 读出的出厂校准值错误
/
BOOL MS5611_CRC(uint16_t *prom)
{
    int32_t i, j;
    uint32_t res = 0;
    uint8_t zero = 1;
    uint8_t crc = prom[7] & 0xF;
    prom[7] &= 0xFF00;

    // if eeprom is all zeros, we're probably fucked - BUT this will return valid CRC lol
    for (i = 0; i < 8; i++) 
	{
        if (prom[i] != 0)
            zero = 0;
    }

    if (zero)
        return (FALSE);

    for (i = 0; i < 16; i++) {
        if (i & 1)
            res ^= ((prom[i >> 1]) & 0x00FF);
        else
            res ^= (prom[i >> 1] >> 8);
        for (j = 8; j > 0; j--) {
            if (res & 0x8000)
                res ^= 0x1800;
            res <<= 1;
        }
    }

    prom[7] |= crc;
    if (crc == ((res >> 12) & 0xF))
        return (TRUE);
	return (FALSE);
}



BOOL MS561101BA_Init(void)
{
	if (MS561101BA_READ_PROM())
	{
		if (MS5611_CRC(Cal_C))
			return (TRUE);	//读出的出厂校验值是正确的
	}
	return (FALSE);		//读出的出厂校验值是错误的
}




//读取温度AD转换值或者读取压力AD转换值

uint32_t MS561101BA_DO_CONVERSION(uint8_t command)
{
	uint32_t conversion = 0x00;
	uint32_t conv1,conv2,conv3; 
	
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
	MS5611_i2c_WaitAck();
	MS5611_i2c_SendByte(command);	
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	MS5611_delay_ms(12);	//根据数据手册,最大采集时间=9.04ms

	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress);
	MS5611_i2c_WaitAck();
	MS5611_i2c_SendByte(0x00);
	MS5611_i2c_WaitAck();
	MS5611_i2c_Stop();
	
	MS5611_i2c_Start();
	MS5611_i2c_SendByte(MS561101BA_SlaveAddress + 1);
	MS5611_i2c_WaitAck();	
	conv1 = MS5611_i2c_ReadByte();	//带ACK的读数据  bit 23-16
	MS5611_i2c_Ack();
	conv2 = MS5611_i2c_ReadByte();	//带ACK的读数据  bit 8-15
	MS5611_i2c_Ack();
	conv3 = MS5611_i2c_ReadByte();	//带NoACK的读数据 bit 0-7
	MS5611_i2c_NoAck();
	MS5611_i2c_Stop();

	conversion = (conv1 << 16) + (conv2 << 8) + conv3;
	return (conversion);
}



//读取数字温度AD转换值,计算温度
void MS561101BA_GetTemperature(uint8_t OSR_Temp)
{   
	D2_Temp = MS561101BA_DO_CONVERSION(OSR_Temp);
	//MS5611_delay_ms(10);
	
	//dT = D2_Temp - (((u32)Cal_C[5])<<8);
	//Temperature = (float)(2000 + dT*((u32)Cal_C[6])/(float)8388608.0);//算出温度值的100倍,2001表示20.01°
	

//警告:当温度低于20度,计算的大气压力不正确的解决方法	
/	
	//上面2行代码,当温度值高于等于20度时,计算的压力值正确。
	//但是上面2行代码,当温度值低于20度时,有问题。因为D2_Temp和Cal_C[5] )都是无符号数,
	//无符号数之间使用减法,没法得到负值,所以必须要按照下述代码修改一下。
	
	if (D2_Temp > (((uint32_t)Cal_C[5]) << 8 ))
	{
		dT	= D2_Temp - (((uint32_t)Cal_C[5]) << 8 );
	}
	else
	{
		dT	= ((( uint32_t)Cal_C[5]) << 8) - D2_Temp;
		dT *= -1;
	}
	Temperature = (float)(2000 + dT*((uint32_t)Cal_C[6])/(float)8388608.0);//算出温度值的100倍,2001表示20.01°
}



//读取数字气压AD转换值,计算大气压力
void MS561101BA_GetPressure(uint8_t OSR_Pres)
{	
	D1_Pres = MS561101BA_DO_CONVERSION(OSR_Pres);
	//MS5611_delay_ms(10);
	OFF =  (uint32_t)(Cal_C[2] << 16) + ((uint32_t)Cal_C[4] * dT) / 128;
	SENS = (uint32_t)(Cal_C[1] << 15) + ((uint32_t)Cal_C[3] * dT) / 256;
	
	//温度补偿
	if (Temperature < 2000)	// second order temperature compensation when under 20 degrees C
	{
		T2 = (dT*dT) / 0x80000000;
		Aux = (Temperature - 2000)*(Temperature - 2000);
		OFF2 = (float)(2.5)*Aux;
		SENS2 = (float)(1.25)*Aux;
		if (Temperature < -1500)
		{
			Aux = (Temperature + 1500)*(Temperature + 1500);
			OFF2 = OFF2 + 7 * Aux;
			SENS2 = SENS + (float)(5.5)*Aux;
		}
	}
	else //(Temperature > 2000)
	{
		T2 = 0;
		OFF2 = 0;
		SENS2 = 0;
	}
	
	Temperature -= T2;
	OFF = OFF - OFF2;
	SENS = SENS - SENS2;
	Pressure = (D1_Pres * SENS / 2097152 - OFF)/32768;
	MS5611_Pressure = Pressure / (float)(1000000.0);
}



/*
 * 气压解算为高度值(cm)
 */
float getEstimatedAltitude(int32_t baroPressure)
{
    static float Altitude;

    if(Alt_offset_Pa == 0){ 
        if(paInitCnt > PA_OFFSET_INIT_NUM){
            Alt_offset_Pa = paOffsetNum / paInitCnt;
            paOffsetInited=1;
        }else
        paOffsetNum += baroPressure;  
        paInitCnt++; 
        Altitude = 0; 
return Altitude;

    }

    Altitude = 4433000.0f * (1 - powf((((float) baroPressure) / Alt_offset_Pa), 0.190295f));

return Altitude; 
}

3、main.c


int main(void)
{	
   if (SysTick_Config(SystemCoreClock / 1000))
   { 
      while (1); 
   }	
   	MS5611_bsp_InitI2C();
	MS561101BA_RESET();
    MS5611_delay_ms(200);    //延时200毫秒
    //警告:MS5611重启后如果延时时间不足,则读出的出厂校准值是错误的。
    if (!MS561101BA_Init())  //如果读出的出厂校准值是错误的,则单片机停止运行。  
        return (1);
   	while (1)
	{
        MS561101BA_GetTemperature(MS561101BA_D2_OSR_4096);
	    MS561101BA_GetPressure(MS561101BA_D1_OSR_4096);
    }
}

八、测试结果

       第一块MS5611气压计测试结果

 

 第二块MS5611气压计测试结果

                       

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32F407单片机移植MS5611气压计(基于IIC)---同时解决温度低于20度时计算得到的大气压错误的问题 的相关文章

  • stm32F407中arr与psc以及pwm之间的关系

    stm32F407pwm控制 A Stm32F407主频 xff08 即CPU的时钟频率 xff09 xff1a 168MHZ B arr是计数 xff0c 从0到设定值 xff0c 然后返回至0重新开始计数 xff08 也可以看成pwm的
  • STM32F407-用TB6600驱动器驱动57步进电机(代码+连线)

    一 硬件 1 硬件准备 57步进电机 xff08 型号57CM18 xff09 xff0c 驱动器TB6600 xff0c 开发板STM32F407ZGT6 2 电气特性 3 连线 驱动器右边分有两个区域 Signal xff1a 用于驱动
  • 飞控开发--气压计MS5611

    ms5611简介 xff1a 官方给出的最大分辨率 xff1a 10cm 工作电压 xff1a 1 8v 3 6v 气压 AD 精度 xff1a 24位 工作环境 xff1a xff0d 40 43 85 C xff0c 10 1200mb
  • 气压计MS5611

    经过几天的痛苦挣扎 终于搞定了 完成气压计的参数读写 xff0c 温度检测 xff0c 大气压计算 因为这款气压计精度高 xff0c 好多计算需要用到正负数 xff0c 整数小数 xff0c 浮点整形 xff0c 有的计算结果特别大 xff
  • STM32F407-串口数据传送

    一 串口基础 1 常用的串口相关寄存器 USART SR状态寄存器USART DR数据寄存器USART BRR波特率寄存器 2 串口操作相关库函数 xff08 省略入口参数 xff09 void USART Init 串口初始化 xff1a
  • STM32F407-SPI通信接口

    1 SPI概念 SPI xff0c 是一种高速的 xff0c 全双工 xff0c 同步的通信总线 xff0c 并且在芯片的管脚上只占用四根线 xff0c 节约了芯片的管脚 xff0c 同时为PCB的布局上节省空间 xff0c 提供方便 xf
  • 基于STM32F407的WIFI通信(使用的是ESP8266模块)

    基于STM32F407的WIFI通信 xff08 使用的是ESP8266模块 xff09 本次做的是WIFI通信所实现的功能是 xff1a 由单片机端向客户端发送数据 模式 xff1a STA模式 xff08 及连接路由器的方式 xff09
  • stm32f407 RTC不更新问题排查

    1 问题 在做stm32f407rtc实验时 xff0c 代码是用cubemx生成的 xff0c 通过串口打印出时间值 xff0c 1s打印一次 但是结果与料想中的不一致 发现打印出来的值一直不更新 按下复位键 xff0c 后时间会更新一次
  • STM32F407 Flash操作笔记

    简述 STM32F4XX的闪存擦除方式分为两种 xff1a 扇区擦除 xff08 最小单元16K xff09 和整片擦除 在实际应用中 xff0c 为满足重要信息的存储 xff0c 需将信息存入FLASH中 xff0c 针对以上两种擦除方式
  • BMP085气压传感器驱动 &MS5611经验

    BMP085是新一代的小封装气压传感器 主要用于气压温度检测 在四轴飞行器上可以用作定高检测 该传感器属于IIC总线接口 依然沿用标准IIC驱动程序 使用该传感器需要注意的是我们不能直接读出转换好的二进制温度数据或者气压数据 必须先读出一整
  • STM32F407IG单片机读写SD2405ALPI实时时钟程序(包括:读时钟时间、写时间到时钟、时间报警中断、倒计时中断)

    具体的IIC时序图和分析过程请参见下面网友的文章 https blog csdn net ybhuangfugui article details 52151835 本人在STM32F407单片机上亲测读时钟 写时钟 时间中断以及倒计时 秒
  • STM32F103C8T6单片机IAP升级

    关于IAP升级的方法和原理 网上已经有很多资料了 这块就不再说了 现在就将bootloader和app配置方法整理如下 APP程序就是一个简单的LED闪烁 APP设置为从FLASH中启动 STM32F103C8T6单片机flash有64K
  • STM32 之五 Core Coupled Memory(CCM)内存

    写在前面 今天在搞STM32F4时 用到了一部分特殊内存 CCM 搜了搜网上没多少介绍 索性自己查手册 某些芯片没有CCM 基本架构 废话少说 先看看这块内存特殊在哪里 官方的基本架构说明如下 The main system consist
  • PID算法与PID自整定算法

    PID算法与PID自整定算法 本文是由于研发恒温槽项目故需要了解PID控制算法和PID自整定算法 为方便本人日后需要故作此记录 直接粘贴代码吧 这是PID位置式控温算法 函数名 void Pid positional float speed
  • MS5607使用中的问题,温度低于20℃,数值不对

    MS5607使用中的问题 温度低于20 马后炮 因为是网上找的代码 懒得看手册 导致后面低于20摄氏度还会有问题 只好认真的看了手册 发现我的代码是ms5611的 照着手册重新改写后就木有问题了 不能省的地方就别省了 公司使用MS5607设
  • STM32+MS5611测气压温度例程详解,测试无误

    硬件平台 STM32F10X MS5611 JLink 软件平台 Keil 4 一 基础知识 首先 MS5611是什么 MS5611气压传感器是集合SPI和I C 高达20 MHz 总线接口的高分辨率气压传感器 分辨率可达到10cm 内部有
  • STM32F407 单片机+DMA+环形缓冲区+GPS报文解析

    本文采用DMA 环形缓冲区对GPS报文进行解析 思路是通过DMA中断接收到GPS报文后 存放到环形缓冲区 然后在主程序中解析GPS报文 解析GPS报文的关键是 将环形缓冲区中的字节转换成字符串 然后在字符串中查找GPS报文头标识 例如 GP
  • STM32F407移植FATFS文件系统(版本 R0.09b)支持长文件名和中文名称

    FatFs文件系统 默认是不支持长文件名和中文名称的 要想支持长文件名和中文名称 需要打开ffconf h文件进行配置 一 支持长文件名 FatFs文件系统 默认是不支持长文件名的 要想支持长文件名 需要打开ffconf h文件进行配置 找
  • SD卡 FATFS CSV 文件中的 逗号和换行

    RFC 4180 Common Format and MIME Type for Comma Separated Values CSV Files 要点有 1 CSV的换行符号要使用CRLF 即 回车符 换行符 的形式 2 文字可以使用双引
  • FATFS实现数据追加功能(原文不覆盖)

    在对FATFS的应用中我们经常需要把采集的数据存入的文件中 用作保存 也许我们的系统是一个长期的运行过程 但是我们的数据可能不是持续采集的 所以我们这样写代码 注册一个工作区域 f mount 0 fs 打开创建一个新文件 res f op

随机推荐