UAV021: STM32F4 realizes IIC communication and reads 10DOF data (six axis sensor MPU6050 + magnetometer HMC5883 + barometer MS5611)

Keywords: angular

order

Words written in the front: This article is long, read patiently

UAV021 (I) This paper implements STM32F429 porting FreeRTOS system, on the basis of which IIC communication is realized and 10DOF data is read. In flight control, it refers to three-axis acceleration, three-axis angular velocity, three-axis magnetic force and one axis air pressure. These 10 sets of data are the core data of flight control, and their importance is self-evident.

Here, MPU6050 is used to measure acceleration and angular velocity, HMC5883 magnetometer and MS5611 barometer; IIC communication is used to read data.

In this paper, IIC communication protocol and implementation will be implemented first, then MPU6050 principle and data reading, HMC5883 principle and data reading, MS5611 principle and data reading respectively. There are many details in this article. I hope you will not only copy the code, but gradually understand it.

Code package download link (no Gitee account should be registered): https://gitee.com/gengstrong/UAV021/raw/master/UAV021_2-IIC_10DOF_MPU6050_HM5883_MS5611.zip

The code is compiled under Keil and passes the test. The final running result is as follows:

Finally, before writing the text, please allow me to make complaints about it. There are also codes for these sensors on the Internet, but either they need to be downloaded by points (I haven't seen how they are written in them), or they are poorly typeset, with few comments and poor readability. Both the domestic open source mentality and the quality of open source need to be improved. Therefore, this article will try to solve the above problems, of course, there may be many shortcomings, please correct!

1, IIC communication protocol and Implementation

1.1 seven basic operations

IIC communication protocol uses two signal lines SCL and SDA for level combination, and defines seven basic operations: start, end, reply, non reply, wait for reply, send one byte, receive one byte.

1. Start: SCL is 1, SDA jumps from 1 to 0.

void IIC_Start(void)
{
	SDA_OUT();     			// SDA output mode
	IIC_SDA = 1;	  	  
	IIC_SCL = 1;			// SCL is 1
	delay_us(4);
 	IIC_SDA = 0;			// Start: SDA from 1 to 0
	delay_us(4);
	IIC_SCL = 0;			//Clamp I2C bus and prepare to send or receive data 
}	

Some of them are defined in the header file as follows:

#define SDA_IN()  {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}	//PH5 input mode
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5 output mode

#define IIC_SCL   PHout(4) //SCL
#define IIC_SDA   PHout(5) //SDA
#define READ_SDA  PHin(5)  //Input SDA

2. End: SCL is 1, SDA jumps from 0 to 1.

It can be seen that when the SCL is 1, the SDA is not allowed to change at will, otherwise the start signal or the end signal will be generated, which is also the reason why the SCL=0 after the command (except the end).

void IIC_Stop(void)
{
	SDA_OUT();				
	IIC_SCL = 0;
	IIC_SDA = 0;				
 	delay_us(4);
	IIC_SCL = 1; 
	IIC_SDA = 1;
	delay_us(4);	
}

3. Reply: the meaning of reply is that after receiving a byte sent by the host, the host releases the SDA, and the slave pulls the SDA down (0) to indicate that the byte has been received.

void IIC_Ack(void)
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 0;		// SDA is 0 -- answer
	delay_us(2);
	IIC_SCL = 1;
	delay_us(2);
	IIC_SCL = 0;
}

4. Non response: I think non response is a bad translation and easy to be misunderstood. "Reply" is to pull down SDA (0), "reply not" is to pull up SDA (1), in fact, it is also to receive data.

void IIC_NAck(void)
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 1;		// SDA is 1 -- no response
	delay_us(2);
	IIC_SCL = 1;
	delay_us(2);
	IIC_SCL = 0;
}			

What are the connections and differences between response and non response? The first detail of IIC: "reply" and "non reply" are replies to the host after receiving data from the slave. "Reply" indicates that there is data to be transmitted next, "non reply" indicates that it is the last data, so it is not necessary to continue transmission.
For example, if the host sends one byte, then the slave will send a "non response" signal after reading it; if the host wants to send three bytes in succession, then the first two bytes will be sent, and the slave needs "response", and the third needs "non response".

The second detail of IIC needs to be explained by the way: the host refers to the device that sends data, and the slave refers to the device that receives data. For example, if STM32 reads MPU6050 data, then MPU6050 is the host and STM32 is the slave. After MPU6050 sends data, STM32 needs to make a reply (Ack or NAck).

5. Wait for response: after the host sends a data, it will wait for a while to see whether the slave pulls down the SDA, that is, to see whether the slave makes an Ack reply or a NAck reply. If the slave pulls down the SDA (answer Ack) and can continue to transmit data, the next Byte cannot be transmitted unless it is restarted.

uint8_t IIC_WaitAck(void)
{
	uint8_t cnt = 0;
	SDA_IN();      			//SDA set as input  
	IIC_SDA = 1;
	delay_us(1);
	IIC_SCL = 1;
	delay_us(1);
	while (READ_SDA)		// Read SDA and exit from the slave answering 0
	{
		cnt ++;
		if (cnt>250)
		{
			IIC_Stop();		// Timeout exit
			return 1;
		}
	}
	IIC_SCL = 0;			   
	return 0;  
} 

6. Send a byte. In the process of sending bytes, 1 has 0, SCL needs to be 0, otherwise it will become a start / end signal.

However, there is also a problem that the SCL is always 0. For example, when the SDA is always 1 in a period of time, how can we know how many 1s are contained in it? Is it one, two or three? So this is the third detail of IIC: when sending bytes, SCL also plays the role of clock. An SCL pulse reads SDA once, which means that there is a bit of SDA data. Look at the picture more directly:

It can be seen that when the SCL is 0, the SDA becomes 0 / 1, and then the SCL is pulled up to read the SCL data; after reading, the SCL is pulled down first, and then the SDA is changed.

void IIC_SendByte(uint8_t txd)
{                        
    uint8_t t;   
	SDA_OUT(); 	    
    IIC_SCL=0;						// Pull down the clock before data transmission
    for (t=0; t<8; t++)
    {              
		IIC_SDA = (txd&0x80)>>7; 	// Transmit the highest bit
		txd <<= 1; 	  				// Move left one bit
		delay_us(2);   				// All three delays are necessary for TEA5767
		IIC_SCL = 1;				// Pull up SCL to read data
		delay_us(2); 
		IIC_SCL = 0;				// Lower the clock to prepare for next bit transmission
		delay_us(2);
    }	
} 	    

7. receive a byte. Receiving a byte is the process of reading SDA.

uint8_t IIC_RecvByte(void)
{
	uint8_t i, recv=0;
	SDA_IN();							// SDA set as input
	for(i=0; i<8; i++ )
	{
		IIC_SCL = 0; 
		delay_us(2);
		IIC_SCL = 1;
		if(READ_SDA)					// Read 1, move recv left 1 bit and add 1
		{
			recv <<= 1;   
			recv += 0x01;
		}
		else							// Read 0, move recv left 1 bit and add 0
		{
			recv <<= 1;
			recv += 0x00;
		}
		delay_us(1); 
	}					 
    return recv;
}

These are the seven basic operations of IIC:

operation Corresponding function
start IIC_Start()
end IIC_Stop()
answer IIC_Ack()
Non response IIC_NAck()
Waiting for response IIC_WaitAck()
Send a byte IIC_SendByte()
Receive a byte IIC_RecvByte()

This section also describes three details of IIC:

  1. "Reply" and "non reply" are replies to the host after receiving data from the slave. The "reply" indicates that there is data to be transmitted next. The "non reply" indicates that it is the last data, so it is unnecessary to continue transmission.
  2. Host refers to the device that sends data, and slave refers to the device that receives data.
  3. When sending bytes, SCL also acts as a clock. SDA is read once in an SCL pulse, which means there is one bit of SDA data.

Of course, there are other details. Let's talk about them later.

1.2 several combined operations

IIC protocol defines the above seven basic, which can be used more easily through combination. For example, after sending data, you often have to wait for the response signal, and after receiving data, you also have to reply. Based on this, the following functions (3 writes, 3 reads) are defined for ease of use. Note that all function definitions have certain rules. Proper nouns are capitalized and initials are capitalized. I hope we can have a unified style.

Function name Function function
uint8_t IIC_WriteByte(uint8_t txd); Send a byte and + wait for reply
uint8_t IIC_WriteAddrRegByte(uint8_t addr, uint8_t reg, uint8_t dat); Write a given data in the given register of a given device
uint8_t IIC_WriteAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf); Write multiple given data in given register of given device
uint8_t IIC_ReadByte(uint8_t ack); IIC reads a byte + answer / no answer
uint8_t IIC_ReadAddrRegByte(uint8_t addr, uint8_t reg); Read a data in a given register of a given device
uint8_t IIC_ReadAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf); Read out multiple data in a given register of a given device

Each function is implemented as follows. If it is not very urgent, it is suggested that we should knock it once again, feel the rules, take the essence and remove the dross.

j next, the iic.h and iic.c files are given directly as follows:

#ifndef _MYIIC_H
#define _MYIIC_H
#include "sys.h"

/* For convenience, first put some macro definitions here	*/
#define SDA_IN()  {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=0<<5*2;}	//PH5 input mode
#define SDA_OUT() {GPIOH->MODER&=~(3<<(5*2));GPIOH->MODER|=1<<5*2;} //PH5 output mode

#define IIC_SCL   PHout(4) //SCL
#define IIC_SDA   PHout(5) //SDA
#define READ_SDA  PHin(5)  //Input SDA

#define IIC_GPIO   GPIOH
#define IIC_PIN    GPIO_PIN_4 | GPIO_PIN_5
#define IIC_ENCLK() __HAL_RCC_GPIOH_CLK_ENABLE()

/*	IIC Basic operation method	*/
void IIC_Init(void);                				// Initialize IO port of IIC	

void IIC_Start(void);								// Send IIC start signal
void IIC_Stop(void);	  							// Send IIC stop signal
void IIC_Ack(void);									// IIC sends ACK signal
void IIC_NAck(void);								// IIC sends NACK signal
void IIC_SendByte(uint8_t txd);						// IIC sends a byte (without waiting for reply)
uint8_t IIC_RecvByte(void);							// IIC receives a byte (no reply)
uint8_t IIC_WaitAck(void); 							// IIC waiting for ACK signal

/*	IIC Combined operation method	*/
uint8_t IIC_WriteByte(uint8_t txd);														// IIC sends a byte (with waiting response)
uint8_t IIC_WriteAddrRegByte(uint8_t addr, uint8_t reg, uint8_t dat);					// Write a given data in the given register of a given device
uint8_t IIC_WriteAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf); 	// Write multiple given data in given register of given device

uint8_t IIC_ReadByte(uint8_t ack);														// IIC reads a byte
uint8_t IIC_ReadAddrRegByte(uint8_t addr, uint8_t reg);									// Read a data in a given register of a given device
uint8_t IIC_ReadAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf);		// Read out multiple data in a given register of a given device

#endif
/* 1. Understanding of host and slave: the sender is the host, for example, STM32 reads MPU data, MPU sends data, and MPU is the host																		*/
/* 2. Understanding of reply: reply is to release SDA to make slave reply after master transmits data. The slave function pulls down SDA to 0, which is called response and non response														*/
/*    Do you want to answer? This is determined by the host. For example, read 6 consecutive Byte data of MPU6050, the first 5 need to answer, and the sixth need not 														*/
/* 3. SCL When it is 1, SDA should not jump randomly, because jumping from low to high means the end, and jumping from high to low means the end. When the SCL is 0, the data will be changed, so the SCL must be 0 when transmitting data											*/
/*	  But why not always 0? Because it's always 0, it's unknown whether it's one or two or even three consecutive zeros after reading SDA as 0. Therefore, an SCL is read once, which is similar to clock function. An SCL pulse has an SDA data	*/
/* 4. IIC The address has 7-bit address and 8-bit address. 8-bit address is actually 7-bit address shifted left by one bit. The 7-bit address of MPU6050 is 0x68, the actual read data address is 0x68 < < 1, and the write data address is 0x68 < < 1 | 0x01				*/
/*    Another example is that the 7-bit address of HMC5883 is 0x77, the 8-bit write address is 0x77 < < 1 = 0xEE, and the 8-bit read address is 0x77 < < 1 | 0x01 = 0xef. This is also the reason why some place bits 0x77 and some place bits 0xEE				*/

#include "iic.h"
#include "delay.h"

/****************************** IIC Hardware initialization***********************************/

/* IIC Hardware initialization 		*/
/* See IIC for pin definition_ PIN 	*/
void IIC_Init(void)
{			
	GPIO_InitTypeDef  GPIO_InitStructure;				// GPIO initialization structure
	IIC_ENCLK();										// Enable IIC clock
	
	GPIO_InitStructure.Pin 		= IIC_PIN;
	GPIO_InitStructure.Mode 	= GPIO_MODE_OUTPUT_PP;	// Push pull output
	GPIO_InitStructure.Speed 	= GPIO_SPEED_FAST;		// 100MHz
	GPIO_InitStructure.Pull 	= GPIO_PULLUP;			// Pull up
	HAL_GPIO_Init(IIC_GPIO, &GPIO_InitStructure);		// initialization
	
	IIC_SCL = 1;										
	IIC_SDA = 1;
}

/****************************** IIC Basic operation method***********************************/
/* IIC Start signal			*/
/* SCL=1 SDA from 1 to 0	*/
void IIC_Start(void)
{
	SDA_OUT();     			// SDA output mode
	IIC_SDA = 1;	  	  
	IIC_SCL = 1;			// SCL is 1
	delay_us(4);
 	IIC_SDA = 0;			// Start: SDA from 1 to 0
	delay_us(4);
	IIC_SCL = 0;			//Clamp I2C bus and prepare to send or receive data 
}	  

/* Generate IIC stop signal		*/
/* SCL=1 SDA from 0 to 1	*/
void IIC_Stop(void)
{
	SDA_OUT();				
	IIC_SCL = 0;
	IIC_SDA = 0;				
 	delay_us(4);
	IIC_SCL = 1; 
	IIC_SDA = 1;
	delay_us(4);	
}

/* Wait for the answer signal 							*/
/* Normal response from slave, SDA = 0						*/
/* Return value: 0, reply received						*/
/*        1,No response received, corresponding to Article 2 of start note	*/
uint8_t IIC_WaitAck(void)
{
	uint8_t cnt = 0;
	SDA_IN();      			//SDA set as input  
	IIC_SDA = 1;
	delay_us(1);
	IIC_SCL = 1;
	delay_us(1);
	while (READ_SDA)		// Read SDA and exit from the slave answering 0
	{
		cnt ++;
		if (cnt>250)
		{
			IIC_Stop();		// Timeout exit
			return 1;
		}
	}
	IIC_SCL = 0;			   
	return 0;  
} 

/* Generate ACK response			*/
/* Pull down SDA to indicate response		*/
void IIC_Ack(void)
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 0;		// SDA is 0 -- answer
	delay_us(2);
	IIC_SCL = 1;
	delay_us(2);
	IIC_SCL = 0;
}

/* Generate ACK response			*/
/* Pull SDA up to indicate non response	*/   
void IIC_NAck(void)
{
	IIC_SCL = 0;
	SDA_OUT();
	IIC_SDA = 1;		// SDA is 1 -- no response
	delay_us(2);
	IIC_SCL = 1;
	delay_us(2);
	IIC_SCL = 0;
}			

/* IIC Send a byte (no response waiting)*/
void IIC_SendByte(uint8_t txd)
{                        
    uint8_t t;   
	SDA_OUT(); 	    
    IIC_SCL=0;						// Pull down the clock before data transmission
    for (t=0; t<8; t++)
    {              
		IIC_SDA = (txd&0x80)>>7; 	// Transmit the highest bit
		txd <<= 1; 	  				// Move left one bit
		delay_us(2);   				// All three delays are necessary for TEA5767
		IIC_SCL = 1;				// Pull up SCL to read data
		delay_us(2); 
		IIC_SCL = 0;				// Lower the clock to prepare for next bit transmission
		delay_us(2);
    }	
} 	   

/* Receive 1 byte				*/
/* Return parameter: bytes received		*/
uint8_t IIC_RecvByte(void)
{
	uint8_t i, recv=0;
	SDA_IN();							// SDA set as input
	for(i=0; i<8; i++ )
	{
		IIC_SCL = 0; 
		delay_us(2);
		IIC_SCL = 1;
		if(READ_SDA)					// Read 1, move recv left 1 bit and add 1
		{
			recv <<= 1;   
			recv += 0x01;
		}
		else							// Read 0, move recv left 1 bit and add 0
		{
			recv <<= 1;
			recv += 0x00;
		}
		delay_us(1); 
	}					 
    return recv;
}

/****************************** IIC Combined operation method***********************************/

/* IIC Send a byte											*/
/* Return whether the slave responds, equivalent to IIC_SendByte() + IIC_WaitAck()	*/
/* ack=0 It means response, ack=1 means no response!!!						*/
uint8_t IIC_WriteByte(uint8_t txd)
{                        
    uint8_t ack;   
	IIC_SendByte(txd);
	ack = IIC_WaitAck();			// Wait for the slave to answer, t=0 for answer, t=1 for no answer!!!
	return ack;
} 

/* IIC Send a byte in the specified device specified register					*/
/* Return whether the slave responds or not										*/
/* 0 Indicates response (sending succeeded), 1 indicates no response (sending failed)!!!	*/
uint8_t IIC_WriteAddrRegByte(uint8_t addr, uint8_t reg, uint8_t dat)
{          
	IIC_Start();
	if (IIC_WriteByte(addr))
	{
		IIC_Stop();
		return 1;
	}
	if (IIC_WriteByte(reg))
	{
		IIC_Stop();
		return 1;
	}
	if (IIC_WriteByte(dat))
	{
		IIC_Stop();
		return 1;
	}
	IIC_Stop();
	return 0;
}

/* IIC Send a byte in the specified device specified register					*/
/* Return whether the slave responds or not										*/
/* 0 Indicates response (sending succeeded), 1 indicates no response (sending failed)!!!	*/
uint8_t IIC_WriteAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
	uint8_t i;
	IIC_Start();
	if (IIC_WriteByte(addr))
	{
		IIC_Stop();
		return 1;
	}
	if (IIC_WriteByte(reg))
	{
		IIC_Stop();
		return 1;
	}
	for (i=0; i<len; i++)
	{
		if (IIC_WriteByte(buf[i]))
		{
			IIC_Stop();
			return 1;
		}	
	}
	IIC_Stop();
	return 0;
}

/* Read 1 byte, equivalent to IIC_RecvByte() + IIC_(N)Ack()				    */
/* ack=0 Send ACK; ack=1, send NACK (no intentional anti logic, see Note 2 at the beginning for the reason) */
/* Return bytes read													*/
uint8_t IIC_ReadByte(uint8_t ack)
{
	uint8_t recv = 0;
	recv = IIC_RecvByte();
    if (ack)
        IIC_NAck();						// ack=1 send nACK
    else
        IIC_Ack(); 						// ack=0 send ACK   
    return recv;
}

/* Read a data in a given register of a given device	*/
uint8_t IIC_ReadAddrRegByte(uint8_t addr, uint8_t reg)
{
	uint8_t recv;
	IIC_Start();
	IIC_WriteByte(addr);
	IIC_WriteByte(reg);
	
	IIC_Start();
	IIC_WriteByte(addr | 0x01);
	recv = IIC_ReadByte(1);				// NAck
	IIC_Stop();
	return recv;
}

/* Read out multiple data in a given register of a given device	*/
uint8_t IIC_ReadAddrRegBytes(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
	uint8_t i;
	IIC_Start();
	if (IIC_WriteByte(addr))
	{
		IIC_Stop();
		return 1;
	}
	IIC_WriteByte(reg);
	
	IIC_Start();
	IIC_WriteByte(addr | 0x01);
	for (i=0; i<len; i++)
	{
		if (i == len-1)
			buf[i] = IIC_ReadByte(1);	// Last byte NAck
		else
			buf[i] = IIC_ReadByte(0);	// Ack when not the last byte
	}
	IIC_Stop();
	return 0;
}

2, MPU6050 related knowledge and data reading

2.1 MPU6050 related knowledge

First of all, MPU means Micro Processor Unit. Compared with Micro Control Unit (MCU), MPU is good at data processing. In fact, there are already logic units in MPU6050, which can read the data of accelerometer and gyroscope and process them, such as low-pass filtering, and finally send them through IIC protocol.

Another popular saying is that accelerometers are used to obtain acceleration, needless to say, gyroscopes are used to obtain angular velocity.

Now look directly at the schematic. In general, the MPU6050 on the market leads to the following pins:


The description of each pin is illustrated by the diagram of the positive point atom as follows:


The first four don't need to be mentioned, the fifth MPU_INT is the interrupt pin of MPU6050, which can be configured as roll, shake, touch, etc., but it is rarely used in flight control. 6th MPU_AD0 is used to configure IIC slave address, but I don't quite understand why this function is added. Is it to be able to access two MPU6050?

Let's skip this question for the moment. Let's take a look at a question about the address 0x86 (assumed to be in the air). IIC has 7-bit address and 8-bit address difference. 0x68 = 0110 1000 is actually a 7-bit address, 0x68 < 1 | 0 = 1101 0000 = 0xD0 is a write address, 0x68 < 1 | 1 = 1101 0001 = 0xD1 is a read address. Both 0xD0 and 0xD1 are 8-bit addresses. In fact, 0x68 moves one bit left and adds 0 (write address) or 1 (read address). So if you see 0xD0, don't be surprised, it's just the definition of 8-bit address. In fact, the high 7 bits represent the device and the lowest 1 bit represents the read / write operation.

2.2 data reading and conversion

(1) When reading MPU6050, the data read by acceleration and gyroscope are 16 bits (2 bytes), and the range is - 32767 ~ 32767. In this case, the stored variable is defined as bit short type. Short is also called short int. unlike int, short is 2 bytes, while int is 4 bytes. 65535 is a positive number for int types, but - 1 for short types. Therefore, we must pay attention to this point when reading MPU6050 data.

(2) So what is the relationship between the read data and the real data? This is related to the FSR (Full Scale Range) of the initial configuration. For accelerometers, the initial range can be configured as ± 2g, ± 4g, ± 8g and ± 16g, and G is the acceleration of gravity. If the configuration is ± 2g, reading to - 32767 represents - 2g, and reading to 32767 represents 2g. The conversion formula is:

True acceleration = read data * 2g / 32767

Gyroscope is similar to acceleration, and its range can be configured as ± 250 ° / s2, ± 500 ° / s2, ± 1000 ° / s2, ± 2000 ° / s2. For example, ± 2000 ° / s2 range is adopted, and the relationship between the real angular velocity and the data read is as follows:

True angular velocity = read data * 2000 / 32767 (in ° / s^2 ^)

2.3 procedure

The program includes mpu6050.h, mpu6050.c and main.c. Although main.c uses the operating system, it is easy to call without OS. If you still have difficulties, you can comment and leave a message. I will try to get a version that does not use OS.

// mpu6050.h mainly includes the definition of MPU6050 register and some user operations

#ifndef __MPU6050_H
#define __MPU6050_H
#include "iic.h"   												  	  

/************************** MPU6050 Register definition******************************/

#define MPU6050_ADDR				0X68<<1	// AD0 is grounded, 7-bit IIC address is 0x68; AD0 is connected to V3.3, 7-bit IIC address is 0x69. 8-bit address shift left one bit

#define MPU6050_SELF_TESTX_REG		0X0D	//Self check register X
#define MPU6050_SELF_TESTY_REG		0X0E	//Self check register Y
#define MPU6050_SELF_TESTZ_REG		0X0F	//Self check register Z
#define MPU6050_SELF_TESTA_REG		0X10	//Self check register A
#define MPU6050_SAMPLE_RATE_REG		0X19	//Sampling frequency divider
#define MPU6050_CFG_REG				0X1A	//Configuration register
#define MPU6050_GYRO_CFG_REG		0X1B	//Gyroscope configuration register
#define MPU6050_ACCEL_CFG_REG		0X1C	//Accelerometer configuration register
#define MPU6050_MOTION_DET_REG		0X1F	//Motion detection threshold setting register
#define MPU6050_FIFO_EN_REG			0X23	//FIFO enable register
#define MPU6050_I2CMST_CTRL_REG		0X24	//IIC host control register
#define MPU6050_I2CSLV0_ADDR_REG	0X25	//IIC slave 0 device address register
#define MPU6050_I2CSLV0_REG			0X26	//IIC slave 0 data address register
#define MPU6050_I2CSLV0_CTRL_REG	0X27	//IIC slave 0 control register
#define MPU6050_I2CSLV1_ADDR_REG	0X28	//IIC slave 1 device address register
#define MPU6050_I2CSLV1_REG			0X29	//IIC slave 1 data address register
#define MPU6050_I2CSLV1_CTRL_REG	0X2A	//IIC slave 1 control register
#define MPU6050_I2CSLV2_ADDR_REG	0X2B	//IIC slave 2 device address register
#define MPU6050_I2CSLV2_REG			0X2C	//IIC slave 2 data address register
#define MPU6050_I2CSLV2_CTRL_REG	0X2D	//IIC slave 2 control register
#define MPU6050_I2CSLV3_ADDR_REG	0X2E	//IIC slave 3 device address register
#define MPU6050_I2CSLV3_REG			0X2F	//IIC slave 3 data address register
#define MPU6050_I2CSLV3_CTRL_REG	0X30	//IIC slave 3 control register
#define MPU6050_I2CSLV4_ADDR_REG	0X31	//IIC slave 4 device address register
#define MPU6050_I2CSLV4_REG			0X32	//IIC slave 4 data address register
#define MPU6050_I2CSLV4_DO_REG		0X33	//IIC slave 4 write data register
#define MPU6050_I2CSLV4_CTRL_REG	0X34	//IIC slave 4 control register
#define MPU6050_I2CSLV4_DI_REG		0X35	//IIC slave 4 read data register

#define MPU6050_I2CMST_STA_REG		0X36	//IIC host status register
#define MPU6050_INTBP_CFG_REG		0X37	//Interrupt / bypass setting register
#define MPU6050_INT_EN_REG			0X38	//Interrupt enable register
#define MPU6050_INT_STA_REG			0X3A	//Interrupt status register

#define MPU6050_ACCEL_XOUTH_REG		0X3B	//Acceleration value, X-axis high 8-bit register
#define MPU6050_ACCEL_XOUTL_REG		0X3C	//Acceleration value, X-axis low 8-bit register
#define MPU6050_ACCEL_YOUTH_REG		0X3D	//Acceleration value, Y-axis high 8-bit register
#define MPU6050_ACCEL_YOUTL_REG		0X3E	//Acceleration value, Y-axis low 8-bit register
#define MPU6050_ACCEL_ZOUTH_REG		0X3F	//Acceleration value, Z-axis high 8-bit register
#define MPU6050_ACCEL_ZOUTL_REG		0X40	//Acceleration value, Z-axis low 8-bit register

#define MPU6050_TEMP_OUTH_REG		0X41	//Temperature value high octet register
#define MPU6050_TEMP_OUTL_REG		0X42	//Temperature value low 8-bit register

#define MPU6050_GYRO_XOUTH_REG		0X43	//Gyroscope value, X-axis high 8-bit register
#define MPU6050_GYRO_XOUTL_REG		0X44	//Gyroscope value, X-axis low 8-bit register
#define MPU6050_GYRO_YOUTH_REG		0X45	//Gyroscope value, Y-axis high 8-bit register
#define MPU6050_GYRO_YOUTL_REG		0X46	//Gyroscope value, Y-axis low 8-bit register
#define MPU6050_GYRO_ZOUTH_REG		0X47	//Gyroscope value, Z-axis high 8-bit register
#define MPU6050_GYRO_ZOUTL_REG		0X48	//Gyroscope value, Z-axis low 8-bit register

#define MPU6050_I2CSLV0_DO_REG		0X63	//IIC slave 0 data register
#define MPU6050_I2CSLV1_DO_REG		0X64	//IIC slave 1 data register
#define MPU6050_I2CSLV2_DO_REG		0X65	//IIC slave 2 data register
#define MPU6050_I2CSLV3_DO_REG		0X66	//IIC slave 3 data register

#define MPU6050_I2CMST_DELAY_REG	0X67	//IIC host delay management register
#define MPU6050_SIGPATH_RST_REG		0X68	//Signal channel reset register
#define MPU6050_MDETECT_CTRL_REG	0X69	//Motion detection control register
#define MPU6050_USER_CTRL_REG		0X6A	//User control register
#define MPU6050_PWR_MGMT1_REG		0X6B	//Power management register 1
#define MPU6050_PWR_MGMT2_REG		0X6C	//Power management register 2 
#define MPU6050_FIFO_CNTH_REG		0X72	//FIFO count register high octet
#define MPU6050_FIFO_CNTL_REG		0X73	//FIFO count register low octet
#define MPU6050_FIFO_RW_REG			0X74	//FIFO read write register
#define MPU6050_DEVICE_ID_REG		0X75	//Device ID register
 
/**************************** Operation function********************************/

uint8_t MPU6050_Init(void); 				//Initialize MPU60506050	
uint8_t MPU6050_SetGyroFsr(uint8_t fsr);
uint8_t MPU6050_SetAccelFsr(uint8_t fsr);
uint8_t MPU6050_SetLPF(uint16_t lpf);
uint8_t MPU6050_SetRate(uint16_t rate);

float MPU6050_GetTemperature(void);
uint8_t MPU6050_GetGyroscope(float *gx, float *gy, float *gz);
uint8_t MPU6050_GetAccelerometer(float *ax, float *ay, float *az);

/************************** User defined management*****************************/

enum MPU6050_GYRO_FSR								// Full Scale Range
{
	MPU6050_GYRO_FSR_250 = 0,
	MPU6050_GYRO_FSR_500 = 1,
	MPU6050_GYRO_FSR_1000 = 2,
	MPU6050_GYRO_FSR_2000 = 3
};

enum MPU6050_ACCEL_FSR			
{
	MPU6050_ACCEL_FSR_2g = 0,
	MPU6050_ACCEL_FSR_4g = 1,
	MPU6050_ACCEL_FSR_8g = 2,
	MPU6050_ACCEL_FSR_16g = 3
};


#define GYRO_FSR			MPU6050_GYRO_FSR_2000	// Accelerometer range ± 2000 (optional MPU6050_GYRO_FSR enumeration)
#define ACCEL_FSR			MPU6050_ACCEL_FSR_2g	// Accelerometer range ± 2g (optional MPU6050_ACCEL_FSR enumeration)
#define MPU_RATE			50						// Sampling rate 50Hz (optional 4-2000Hz)

#endif
// mpu6050.c initializes MPU6050 and reads and transforms related data

#include "mpu6050.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"   

/* Initialize MPU6050 	*/
/* Return value: 0, success		*/
/* Other, error code		*/
uint8_t MPU6050_Init(void)
{
	uint8_t res;
	res = IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_PWR_MGMT1_REG, 0x80);		// Reset MPU6050
	if (res)
	{
		printf("MPU6050 Reset failed \r\n");
		return 1;
	}
	delay_ms(100);
	IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_PWR_MGMT1_REG, 0x00);	 		// Wake up MPU6050
	
	MPU6050_SetGyroFsr(GYRO_FSR);												// Gyroscope sensor
	MPU6050_SetAccelFsr(ACCEL_FSR);												// Acceleration sensor
	MPU6050_SetRate(MPU_RATE);													// Set the sampling rate 50Hz (optional 4-2000Hz)
	IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_INT_EN_REG, 0X00);				// Turn off all interrupts
	IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_USER_CTRL_REG, 0X00);			// I2C main mode off
	IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_FIFO_EN_REG, 0X00);				// Close FIFO
	IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_INTBP_CFG_REG,0X80);				// INT pin low level valid
	res = IIC_ReadAddrRegByte(MPU6050_ADDR, MPU6050_DEVICE_ID_REG);				// Read device ID
	if(res == (MPU6050_ADDR >> 1))												// Device ID is correct
	{
		IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_PWR_MGMT1_REG, 0X01);		//Set CLKSEL,PLL X axis as reference
		IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_PWR_MGMT2_REG, 0X00);		//Acceleration and gyroscope work
		MPU6050_SetRate(MPU_RATE);												//Set the sampling rate to 50Hz
 	}
	else 
	{
		printf("MPU6050 Init failed \r\n");
		return 1;
	}
	printf("MPU6050 Init Success \r\n");
	return 0;
}


/* Set MPU6050 gyroscope sensor full range					*/
/* fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps	*/
/* Return value: 0, set successfully									*/
/*    Other, setting failed 									*/

uint8_t MPU6050_SetGyroFsr(uint8_t fsr)
{
	return IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_GYRO_CFG_REG, fsr<<3);	//Set the full range of gyroscope  
}

/* Set MPU60506050 acceleration sensor full range	*/
/* fsr:0,±2g;1,±4g;2,±8g;3,±16g		*/
/* Return value: 0, set successfully						*/
/*    Other, setting failed 						*/

uint8_t MPU6050_SetAccelFsr(uint8_t fsr)
{
	return IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_ACCEL_CFG_REG, fsr<<3);	//Set acceleration sensor full range  
}

/* Set the digital low pass filter of MPU60506050		*/
/* lpf:Digital low pass filter frequency (Hz)				*/
/* Return value: 0, set successfully						*/
/*    Other, setting failed 						*/
uint8_t MPU6050_SetLPF(uint16_t lpf)
{
	uint8_t data=0;
	if(lpf>=188)
		data=1;
	else if(lpf>=98)
		data=2;
	else if(lpf>=42)
		data=3;
	else if(lpf>=20)
		data=4;
	else if(lpf>=10)
		data=5;
	else 
		data=6; 
	return IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_CFG_REG, data);			//Set digital low pass filter  
}

/* Set the sampling rate of MPU6050 (assuming Fs=1KHz)		*/
/* rate:4~1000(Hz)						*/
/* Return value: 0, set successfully						*/
/*    Other, setting failed 						*/
uint8_t MPU6050_SetRate(uint16_t rate)
{
	uint8_t data;
	if(rate>1000)
		rate=1000;
	if(rate<4)
		rate=4;
	data = 1000 / rate - 1;
	data = IIC_WriteAddrRegByte(MPU6050_ADDR, MPU6050_SAMPLE_RATE_REG, data);	//Set digital low pass filter
 	return MPU6050_SetLPF(rate / 2);											//Automatically set LPF to half the sampling rate
}

/* Get the temperature value			*/
/* Return value: temperature value (° C)	*/
float MPU6050_GetTemperature(void)
{
    uint8_t buf[2]; 
    short raw;
	float temp;
	IIC_ReadAddrRegBytes(MPU6050_ADDR, MPU6050_TEMP_OUTH_REG, 2, buf); 
    raw = ((uint16_t)buf[0]<<8) | buf[1];  
    temp = 36.53 + ((double)raw) / 340.0;  
    return temp;
}

/* Get gyroscope value (original value)				*/
/* gx,gy,gz:Angular velocity of x,y,z axis of gyroscope		*/
/* Return value: 0, success						*/
/*    Other, error code					*/
uint8_t MPU6050_GetGyroscope(float *gx, float *gy, float *gz)
{
	uint8_t buf[6],res;  
	float fsr;
	short gyro[3];													// Here, short must be used. The value range is - 32767 ~ 32736, accounting for 2 bytes. int has the same value range, but takes up 4 bytes
	
	res = IIC_ReadAddrRegBytes(MPU6050_ADDR, MPU6050_GYRO_XOUTH_REG, 6, buf);
	if(res==0)
	{
		gyro[0] = ((uint16_t)buf[0]<<8) | buf[1];  					// Original gx
		gyro[1] = ((uint16_t)buf[2]<<8) | buf[3];  					// Primitive gy
		gyro[2] = ((uint16_t)buf[4]<<8) | buf[5];					// Original gz
	} 	
	
	if (GYRO_FSR == MPU6050_GYRO_FSR_250)
		fsr = 250.0;
	else if (GYRO_FSR == MPU6050_GYRO_FSR_500)
		fsr = 500.0;
	else if (GYRO_FSR == MPU6050_GYRO_FSR_1000)
		fsr = 1000.0;
	else if (GYRO_FSR == MPU6050_GYRO_FSR_2000)
		fsr = 2000.0;
	
	*gx = gyro[0] * (fsr / 3276.70) / 10.0;							// Multiply by fsr and divide by 32767. In order to produce too much error, this method is adopted
	*gy = gyro[1] * (fsr / 3276.70) / 10.0;
	*gz = gyro[2] * (fsr / 3276.70) / 10.0;
	
    return res;
}

/* Get the acceleration value (original value)			*/
/* gx,gy,gz:Data of x,y,z axis of gyroscope	*/
/* Return value: 0, success					*/
/*    Other, error code				*/
uint8_t MPU6050_GetAccelerometer(float *ax, float *ay, float *az)
{
    uint8_t buf[6], res;  
	short acc[3];													// short must be used here. The value range is - 32767 ~ 32736, accounting for 2 bytes. int has the same value range, but takes up 4 bytes
	float fsr;
	const float g = 9.8;											// Acceleration of gravity
	
	res = IIC_ReadAddrRegBytes(MPU6050_ADDR, MPU6050_ACCEL_XOUTH_REG, 6, buf);

	if(res==0)
	{
		acc[0] = ((uint16_t)buf[0]<<8) | buf[1];  
		acc[1] = ((uint16_t)buf[2]<<8) | buf[3];  
		acc[2] = ((uint16_t)buf[4]<<8) | buf[5];
	} 	

	if (ACCEL_FSR == MPU6050_ACCEL_FSR_2g)
		fsr = 2.0 * g;
	else if (ACCEL_FSR == MPU6050_ACCEL_FSR_4g)
		fsr = 4.0 * g;
	else if (ACCEL_FSR == MPU6050_ACCEL_FSR_8g)
		fsr = 8.0 * g;
	else if (ACCEL_FSR == MPU6050_ACCEL_FSR_16g)
		fsr = 16.0 * g;
	
	*ax = (acc[0]) / 100.0 * (fsr / 327.67);
	*ay = (acc[1]) / 100.0 * (fsr / 327.67);
	*az = (acc[2]) / 100.0 * (fsr / 327.67);
	
    return res;
}
// main.c calls related functions of mpu6050
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "mpu6050.h"
#include <math.h>

#define LED_TASK_PRIO 1
#define LED_STK_SIZE	32
TaskHandle_t LED_TASK_Handler;

#define MPU6050_TASK_PRIO 2
#define MPU6050_STK_SIZE	256
TaskHandle_t MPU6050_TASK_Handler;

void LED_Task(void *arg);				// LED task function
void MPU6050_Task(void *arg);			// MPU6050 task function

int main(void)
{
	HAL_Init();
	Stm32_Clock_Init(360, 25, 2, 8);	// 180M
	delay_init(180);
	uart_init(115200);
	
	IIC_Init();
	LED_Init();
	MPU6050_Init();	
	
	taskENTER_CRITICAL();
	
	xTaskCreate((TaskFunction_t	) 			LED_Task,												// Create LED task
				(const char*	)			"LED_Task",
				(uint16_t		)			LED_STK_SIZE,
				(void*			)			NULL,
				(UBaseType_t	)			LED_TASK_PRIO,
				(TaskHandle_t*	)			&LED_TASK_Handler
						 );
	xTaskCreate(MPU6050_Task, "MPU6050_Task", MPU6050_STK_SIZE, NULL, MPU6050_TASK_PRIO, &MPU6050_TASK_Handler);	// Create MPU6050 task		
	taskEXIT_CRITICAL();					
	vTaskStartScheduler();
				
	return 0;
}

/* Realize LED flashing, program running indicator	*/
void LED_Task(void *arg)
{
	while (1)
	{
		LED = 0;
		delay_ms(200);
		LED = 1;
		delay_ms(300);
	}
}

/* Realize MPU6050 to obtain angular velocity and acceleration	*/
void MPU6050_Task(void *arg)
{
	float accx, accy, accz;						// Get acceleration
	float gyrox, gyroy, gyroz;					// Get angular velocity
	float temperature;							// Obtain temperature
	
	while (1)
	{
		temperature = MPU6050_GetTemperature();
		MPU6050_GetAccelerometer(&accx, &accy, &accz);
		MPU6050_GetGyroscope(&gyrox, &gyroy, &gyroz);
		
		printf("\r\nMPU6050 measurement result ===============================\r\n");
		printf("temperature Temperature: %.2f°C\r\n", temperature);
		printf("acceleration ACC : [%.2f, %.2f, %.2f] m/s^2 \t|| ACC  ||: %.2f m/s^2\r\n", accx, accy, accz, sqrt(accx*accx + accy*accy + accz*accz));
		printf("angular velocity GYRO: [%.2f, %.2f, %.2f] °/s^2\t\t|| GYRO ||: %.2f °/s^2\r\n", gyrox, gyroy, gyroz, sqrt(gyrox*gyrox + gyroy*gyroy + gyroz*gyroz));
		
		delay_ms(500);
	}
}

3, Principle and data reading of HMC5883

3.1 principle of hmc5883

Learning to read MPU6050 data is much easier to read HMC5883 data. The 7-bit address of HMC5883 is 0x1E, and the 8-bit address is 0x3c (write) and 0x3d (read).

The schematic diagram of HMC5883 is as follows:

Among them, DRDY's explanation in the data manual is: data ready, interrupt pin. Internally pulled high. Optional connection. It was not used this time.

3.2 HMC5883 data reading and conversion

Like MPU6050, the data read from MPU is also of 16 bit integer type, and short type is required. The read data is the magnetic force of the magnetometer in x, y and z directions, which is recorded as mx, my and mz. In flight control, the magnetometer is used to measure the yaw angle, and the conversion formula is as follows:

yaw = atan2(my, mx) * (180 / 3.14159) + 180 (in °)

Among them, the yaw is 0 ° to the north, increasing to the East until 360 ° after one circle.

3.3 HMC5883 procedure

Like MPU6050, the program consists of three parts, hmc5883.h, hmc5883.c, and main.c, as follows:

#ifndef __HMC5883_H
#define __HMC5883_H
#include "iic.h"   												  	  

//============HMC5883 register address=========================
#define HMC5883_ADDR				0X1E<<1		// HMC address in IIC bus

#define HMC5883_CONFIG_REG_A		0x00		// Configuration register A
#define HMC5883_CONFIG_REG_B		0x01		// Configuration register B
#define HMC5883_MODE_REG			0x02		// Mode register
#define HMC5883_MAG_XOUTH_REG		0x03		// Data output X MSB register
#define HMC5883_MAG_XOUTL_REG		0x04		// Data output X LSB register
#define HMC5883_MAG_ZOUTH_REG		0x05		// Data output Z MSB register
#define HMC5883_MAG_ZOUTL_REG		0x06		// Data output Z LSB register
#define HMC5883_MAG_YOUTH_REG		0x07		// Data output Y MSB register
#define HMC5883_MAG_YOUTL_REG		0x08		// Data output Y LSB register
#define HMC5883_STA_REG				0x09		// Status register
#define HMC5883_IDEN_REG_A			0x10		// Identification register A
#define HMC5883_IDEN_REG_B			0x11		// Identification register B
#define HMC5883_IDEN_REG_C			0x12		// Identification register C

//====================User function=========================
#define HMC5883_MODE				0x00		// 0x00 continuous measurement mode, 0x01 single measurement mode (default)

void HMC5883_Init(void);
float HMC5883_GetAngle(void);

#endif
#include "iic.h"
#include "hmc5883.h"
#include <math.h>

/* Magnetometer 5883 initialization	*/
void HMC5883_Init(void)
{
	if (IIC_WriteAddrRegByte(HMC5883_ADDR, HMC5883_MODE_REG, HMC5883_MODE))
		printf("HMC5883 Init failed\r\n");
	else
		printf("HMC5883 Init Success\r\n");
}

/* Calculate the angle according to the magnetometer value						*/
/* Return value: angle								*/
/* 0 ° in the north direction, increasing to 360 ° in the east direction		*/
float HMC5883_GetAngle(void)
{
	uint8_t buf[6] ={0}, res;
	short mx, my, mz;							// Raw data read 16 bits
	float angle;
	
	res = IIC_ReadAddrRegBytes(HMC5883_ADDR, HMC5883_MAG_XOUTH_REG, 6, buf);
	
	if (res == 0)
	{
		mx = ((short)buf[0]<<8) | buf[1];		// To cast to the short type, short is 2 bytes, and the read buf[0],buf[1] is also 2 bytes. int is four bytes
		mz = ((short)buf[2]<<8) | buf[3];		// (int)65534=65534, (short)65534=-2
		my = ((short)buf[4]<<8) | buf[5];
	}

	angle = atan2((double)my, (double)mx) * (180.0/3.1415926) + 180;	// Get the angle according to the original data
	return angle;
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "hmc5883.h"
#include <math.h>

#define LED_TASK_PRIO 		1
#define LED_STK_SIZE		32
TaskHandle_t LED_TASK_Handler;

#define HMC5883_TASK_PRIO 	3
#define HMC5883_STK_SIZE	256
TaskHandle_t HMC5883_TASK_Handler;

void LED_Task(void *arg);			// LED task function
void HMC5883_Task(void *arg);		// HMC5883 task function

int main(void)
{
	HAL_Init();
	Stm32_Clock_Init(360, 25, 2, 8);	// 180M
	delay_init(180);
	uart_init(115200);
	
	IIC_Init();
	LED_Init();
	HMC5883_Init();
	
	taskENTER_CRITICAL();
	
	xTaskCreate((TaskFunction_t	) 			LED_Task,																// Create LED task
				(const char*	)			"LED_Task",
				(uint16_t		)			LED_STK_SIZE,
				(void*			)			NULL,
				(UBaseType_t	)			LED_TASK_PRIO,
				(TaskHandle_t*	)			&LED_TASK_Handler
				);
	xTaskCreate(HMC5883_Task, "HMC5883_Task", HMC5883_STK_SIZE, NULL, HMC5883_TASK_PRIO, &HMC5883_TASK_Handler);	// Create HMC5883 task
	taskEXIT_CRITICAL();					
	vTaskStartScheduler();
				
	return 0;
}

/* Realize LED flashing, program running indicator	*/
void LED_Task(void *arg)
{
	while (1)
	{
		LED = 0;
		delay_ms(200);
		LED = 1;
		delay_ms(300);
	}
}

/* Achieve the angle between HMC5883 and the north, 0 ° in the north, increasing to 360 ° in the East*/	
void HMC5883_Task(void *arg)
{
	float angle;
	while (1)
	{
		angle = HMC5883_GetAngle();
		printf("\r\nHMC5883 measurement result ===============================\r\n");
		printf("angle = %f°\r\n", angle);
		delay_ms(500);
	}
}

4, MS5611 principle and data reading

4.1 principle of ms5611

The 7-bit address of MS5611 is 0x77.

The schematic diagram of MS5611 is as follows:

PS communication protocol selection, 1: IIC communication protocol, 0: SPI communication protocol
CSB chip selection, effective at low level
SDO serial data output, used for SPI communication, not used for IIC

4.2 MS5611 data conversion

When reading the internal data of MS5611, first read six 16 digit numbers, which are some coefficients:

1. C[0] pressure sensitivity: SENS_T1
 2. C[1] pressure offset: OFF_T1
 3. Temperature coefficient of C [2] pressure sensitivity: TCS
 4. Temperature coefficient of C [3] pressure shift: TCO
 5. C[4] reference temperature: T_REF
 6. Temperature coefficient of C [5] temperature: TEMPSENS

The second is to read the digital values of air pressure and temperature, which are all 24 bits (3Bytes) data.

Finally, according to the algorithm of data manual, calculate the temperature and air pressure. The algorithm flow of data manual is as follows. The first and second diagrams are originally together, and give the temperature calculation method (expanded by 100 times, unit ° C). The air pressure calculation also needs to be iterated once through the third diagram, and then use the formula P = D1 * SENS - OFF. The barometer is in Pa.



The temperature is increased by 100 times, so it is easy to judge whether it is normal. But how much normal pressure data is normal? High school chemistry said that a standard atmospheric pressure is the atmospheric pressure at sea level, 101.325kPa. High school physics also said that with the rise of altitude... Don't look at the specific relationship. Use a map to explain the problem. According to your altitude, you can preliminarily judge whether the data is right or not.


In order to better understand the process, you can check the algorithm in ms5611.c. The name of the variable in the above flow chart is used.

4.3 MS5611 procedure

There are still three files, ms5611.h, ms5611.c and main.c. There are a lot of comments in them, which will not be described in detail.

// ms5611.h defines MS5611 address and some operations

#ifndef _MS5611_H
#define _MS5611_H
#include "sys.h"

/*============ MS5611 Register address========================*/
#define MS5611_ADDR			0x77<<1	// MS5611 8-bit address

#define MS5611_RST			0x1E	// Reset command
#define MS5611_D1_OSR_256	0x40	// MS5611 air pressure (D1) over sampling rate 256
#define MS5611_D1_OSR_512	0x42
#define MS5611_D1_OSR_1024	0x44
#define MS5611_D1_OSR_2048	0x46
#define MS5611_D1_OSR_4096	0x48

#define MS5611_D2_OSR_256	0x50	// MS5611 temperature (D2) over sampling rate 256
#define MS5611_D2_OSR_512	0x52
#define MS5611_D2_OSR_1024	0x54
#define MS5611_D2_OSR_2048	0x56
#define MS5611_D2_OSR_4096	0x58

#define MS5611_ADC_RD		0x00	// ADC read command
#define MS5611_PROM_RD		0xA2	// PROM read command, manual from 0xa0, 0xa2,..., 0xae, 8 bits in total
#define MS5611_RROM_CRC		0xAE    // Actually, C1-C6 corresponds to 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC

/*=================== User defined management========================*/
#define MS5611_D1_OSR		MS5611_D1_OSR_256		// Air pressure (D1) oversampling 256
#define MS5611_D2_OSR		MS5611_D2_OSR_256		// Temperature (D2) oversampling 256

uint8_t MS5611_Init(void);							// MS5611 initialization
uint32_t MS5611_DoConv(uint8_t cmd);				// MS5611 get digital pressure and temperature
void MS5611_GetTemperaturePressure(float *temperature, float *pressure); // Get real pressure and temperature

#endif

There is also a proper term in the program - oversampling. Oversampling is to sample analog signals at a frequency much higher than twice the signal bandwidth. Oversampling can push the quantization noise to a higher frequency, so that the system can choose a lower-pass filter with a higher cut-off frequency, which makes the attenuation of the signal we are concerned about smaller. (the preceding sentence is just a passing check. It doesn't matter if you can't understand it.)

// ms5611.c is mainly based on MS5611 datasheet to realize the calculation of temperature and pressure

#include "ms5611.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"   
#include "iic.h"

/**************************************************************************/
// 					MS5611 get air pressure and temperature
// 					  Algorithm reference data book						
/**************************************************************************/

/* Prom It stores six 16 bit data, meaning as follows:
   1. C[0] Pressure sensitivity: SENS_T1
   2. C[1] Pressure offset: OFF_T1
   3. C[2] Temperature coefficient of pressure sensitivity: TCS
   4. C[3] Temperature coefficient of pressure shift: TCO
   5. C[4] Reference temperature: T_REF
   6. C[5] Temperature coefficient of temperature: TEMPSENS
*/
static uint16_t C[6];

/* Initialize MS5611, including resetting and obtaining the value in Prom	*/
/* Return value 0 -- initialization succeeded						*/
/*		  1--initialization failed						*/
uint8_t MS5611_Init(void)
{
	u8 i;
	u8 d[2];
	IIC_Start();
	if (IIC_WriteByte(MS5611_ADDR))			// Address of transmitting device
	{
		printf("NOT find MS5611\r\n");	
		return 1;
	}
	IIC_WriteByte(MS5611_RST);				// Send reset signal
	IIC_Stop();	
	printf("MS5611 Init Success\r\n");
	delay_ms(100);
	
	for (i=0; i<6; i++)						// Read data in Prom
	{
		IIC_ReadAddrRegBytes(MS5611_ADDR, MS5611_PROM_RD + i*2, 2, d); // Read 6 numbers, 16 bits each, so + i*2
		C[i] = ((uint16_t)d[0] << 8) | d[1];
		delay_ms(5);
	}
	delay_ms(100);
	return 0;
}

/* MS5611 Conversion for obtaining digital value of temperature or air pressure		*/
/* cmd: MS5611_D1_OSR Obtain air pressure			*/
/*		MS5611_D2_OSR Obtain temperature			*/
uint32_t MS5611_DoConv(uint8_t cmd)
{
	uint32_t result = 0;
	uint8_t conv[3];
	IIC_Start();
	IIC_WriteByte(MS5611_ADDR);
	IIC_WriteByte(cmd);
	IIC_Stop();
	delay_ms(100);
	
	IIC_ReadAddrRegBytes(MS5611_ADDR, MS5611_ADC_RD, 3, conv);
	
	result = (uint32_t)conv[0] * 65535 + (uint32_t)conv[1] * 256 + (uint32_t)conv[2];
	return result;
}

/* MS5611 Conversion for obtaining true value of temperature or air pressure		*/
/* temperature -- Temperature (° C)				*/
/* pressure -- Air pressure (kPa)				*/
/* In order to keep consistent with the formula in the data manual, the variable name does not change	*/
void MS5611_GetTemperaturePressure(float *temperature, float *pressure)
{
	uint32_t D1, D2;												// D1--Pressure digital value, D2--Temperature digital value
	float dT, TEMP;
	double OFF, SENS;
	float P;
	float T2, OFF2, SENS2;
	
	D1 = MS5611_DoConv(MS5611_D1_OSR);								// Obtain the digital value of air pressure
	D2 = MS5611_DoConv(MS5611_D2_OSR);								// Get the numerical value of air temperature
		
	dT = D2 - (((uint32_t)C[4]) << 8); 								// dT = D2 - T_REF = D2 - C5 * 2^8
	TEMP = 2000.0 + ((float)dT / 8192.0) * ((float) C[5] / 1024.0);	// TEMP = 20°C + dT * TEMPSENS = 2000 + dT * C6 / 2^23
	OFF = ((int64_t)C[1]<<16) + ((double)C[3] * dT) / 128.0;		// OFF = OFF_T1 + TCO*dT = C1*2^15 + (C3*dT)/2^8
	SENS = ((int64_t)C[0]<<15) + ((double)C[2] * dT) / 256.0; 		// SENS = SENS_T1 + TCS*dT = C1*2^15 + (C3*dT)/2^8
	
	if (TEMP < 2000)												// Low temperature < 20 ° C
	{
		T2 = (float)((dT / 32768.0) * (dT / 65536.0));				// T2 = dT^2 / 2^31
		OFF2 = (float)(2.5 * (TEMP - 2000) * (TEMP - 2000));		// OFF2 = 5*(TEMP-2000)^2 / 2
		SENS2 = (float)(1.25 * (TEMP - 2000) * (TEMP - 2000)); 		// SENS2 = 5*(TEMP-2000)^2 / 4
		if (TEMP < -1500)											// Ultra low temperature < - 15 ° C
		{
			OFF2 += 7.0 * ((TEMP+1500) * (TEMP+1500));				// OFF2 = OFF2 + 7*(TEMP+1500)^2
			SENS2 += 5.5 * ((TEMP+1500) * (TEMP+1500));				// SENS2 = SENS2 + 11*(TEMP+1500)^2/2
		}	
	}
	else															// high temperature
	{
		T2 = 0;
		OFF2 = 0;
		SENS2 = 0;
	}
	
	TEMP -= T2;														// TEMP = TEMP - T2
	OFF -= OFF2;													// OFF = OFF - OFF2
	SENS -= SENS2;													// SENS = SENS - SENS2
	
	P = (float)(((float)D1 / 32768.0) * (SENS / 2097152.0) - (OFF / 32768.0));// P = D1*SENS-OFF = (D1*SENS/2^21 - OFF) / 2^15
	
	*temperature = TEMP / 100.0;									// The temperature is increased by 100 times, divided by 100 here
	*pressure = P /1000.0;											// The air pressure is in kilopascals, divided by 1000 here
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "ms5611.h"
#include <math.h>

#define LED_TASK_PRIO 		1
#define LED_STK_SIZE		32
TaskHandle_t LED_TASK_Handler;

#define MS5611_TASK_PRIO 	4
#define MS5611_STK_SIZE		256
TaskHandle_t MS5611_TASK_Handler;

void LED_Task(void *arg);				// LED task function
void MS5611_Task(void *arg);			// MS5611 task function

int main(void)
{
	HAL_Init();
	Stm32_Clock_Init(360, 25, 2, 8);	// 180M
	delay_init(180);
	uart_init(115200);
	
	IIC_Init();
	LED_Init();
	MS5611_Init();
	
	taskENTER_CRITICAL();
	
	xTaskCreate((TaskFunction_t	) 			LED_Task,																// Create LED task
				(const char*	)			"LED_Task",
				(uint16_t		)			LED_STK_SIZE,
				(void*			)			NULL,
				(UBaseType_t	)			LED_TASK_PRIO,
				(TaskHandle_t*	)			&LED_TASK_Handler
				);
	xTaskCreate(MS5611_Task, "MS5611_Task", MS5611_STK_SIZE, NULL, MS5611_TASK_PRIO, &MS5611_TASK_Handler);			// Create MS5611 task	
	taskEXIT_CRITICAL();				
	vTaskStartScheduler();
				
	return 0;
}

/* Realize LED flashing, program running indicator	*/
void LED_Task(void *arg)
{
	while (1)
	{
		LED = 0;
		delay_ms(200);
		LED = 1;
		delay_ms(300);
	}
}

/* Realize MS5611 to obtain temperature and air pressure	*/
void MS5611_Task(void *arg)
{
	float temperature, pressure;
	while (1)
	{

		MS5611_GetTemperaturePressure(&temperature, &pressure);
		printf("\r\nMS5611 measurement result ===============================\r\n");
		printf("tempereture = %.2f°C\r\n", temperature);
		printf("pressure = %.3fkPa\r\n", pressure);
		delay_ms(500);
	}
}

5. Summary

The above is IIC communication protocol and 10DOF data reading. With these data, we can move on to the next step, data fusion.

If there is any problem in the program test, please leave a message in the comment area. If you really can't understand the program with the operating system, you can also leave a message.

The article inevitably appears the fallacy, please correct!

- end -

Posted by jwrightnisha on Sat, 13 Jun 2020 19:30:00 -0700