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

        最近一个工程项目需要使用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气压计测试结果

                       

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值