【004】基于STM32标准库的IMU9250数据读取

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lucky_dog_2018/article/details/79748055

基于STM32标准库的IMU9250数据读取

关键词:MPU9250,九轴,STM32,I2C

MPU9250简介

  MPU9250是一款九轴MEMS传感器,由两部分组成一组为三轴加速度传感器及三轴陀螺仪,另一组则是AKM公司的AK8963三轴磁力计。可以理解为将MPU6050和AK8963封装在一个芯片内。MPU9250中的加速度传感器(±2g,±4g,±8g,±16g)和陀螺仪(±250dps,±500dps,±1000dps,±2000dps)均支持量程可编程,加速度计和陀螺仪的测量结果以16数字方式输出,磁力计测量结果以16位或14位数字方式输出。MPU9250还内置温度传感器,可以用来进行不同温度下的校准。图1为MPU9250结构框图,通过结构框图可知,MPU9250通信方式为I2C或SPI。


图1 MPU9250结构框图

  MPU9250应用较为广泛比如:手势控制,体感游戏控制,平衡车,室内定位,可穿戴设备等。除了MPU9250之外,还有很多类似的传感器以满足不同的应用场景。

  要实现MPU9250的数据读取,我们需要了解三个方面的内容:I2C基本通信方式、MPU9250基本寄存器以及STM32的I2C控制模块。

I2C通信协议简介

  这里仅根据满足读取MPU9250需求进行I2C通信方式介绍,不涉及复杂和细致部分。关于细节信息将在后面第二次迭代中讲述。I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。主器件(Master)用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件(Slave)。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。SCL为时钟信号线,SDA为数据信号线。图2和图3分别是MPU9250读写单个字节的方式。


图2 MPU9250写单个字节方式


图3 MPU9250读单个字节方式

  对图2和图3中出现的符号进行说明

  S:START,开始信号,对应SCL处于高时,SDA由高到低跳变。

  AD:ADDRESS,从控制器的地址。

  W:WRITE,读写位设置为写,清零。

  R:READ,读写位设置为读,置一。

  ACK:ACKNOWLEDGE,告知,对应在第九个时钟周期,SDA为低,SCL为高。

  NACK:NOT-ACKNOWLEDGE,不告知,对应在第九个时钟周期,SDA为高,SCL为高。

  RA:REGISTER ADDRESS,寄存器地址。

  DATA:数据

  P:STOP,停止信号,对应SCL处于高时,SDA从低向高跳变。

  如果是使用引脚模拟I2C通信的方式,我们需要了解每个符号对应的引脚状态,如果使用STM32的I2C模块进行通信,我们只需要关心这些符号的意义即可,STM32中内置的I2C模块会帮助我们产生及接收需要的信号。这里着重对AD,AD+W,AD+R做一些说明。

  由MPU9250数据手册可知若MPU9250的AD0引脚接地,则MPU9250的地址为0x68,若MPU9250的AD0引脚接高,则MPU9250的地址为0x69。一般的I2C接口器件都有AD0引脚,在设置地址时一定要看AD0的状态。

  AD+W和AD+R,根据I2C协议I2C从机地址为7位,第8位为读写位。故AD+W对应AD<<1& 0xFE,AD+R对应AD<<1 | 0x01。

MPU9250基本寄存器

  Who Am I寄存器 地址0x75

  该寄存器用于告知用户当前正在访问的设备,其不受传感器状态影响,存储着固定值0x71也可以拿来判断我们的读取是否成功。

  Power Management 1 寄存器 0x6B

  [7]将该位置1,传感器将硬件重启并且自动清零该位

  [2:0]选择设备时钟源,0为使用内被20MHz时钟,1为自动选择可用的时钟

  Sample Rate Divider 寄存器 0x19

  [7:0]设置采样频率,计算公式:SAMPLE_RATE= internal_Sample_Rate / (1 + SMPLRT_DIV)

  Configuration 寄存器 0x1A

  [2:0]低通滤波器配置位,配置方式和结果如图4所示。关于FCHOICE将在后面讲到。


图4 DLPF_CFG配置方式

  Gyroscope Configuration 寄存器 0x1B

 [4:3]GYRO_FS_SEL,陀螺仪量程设置00b=+250dps,01b=+500dps,10b=+1000dps,11=+2000dps。

  [1:0]Fchoice_b。这里即出现了图4中的FCHOICE,值得注意的是“_b”在这里有取反的意思。

  Accelerometer Configuration 寄存器 0x1C

 [4:3]ACCEL_FS_SEL,设置加速度传感器的量程00b=±2g,01b=±4g,10b=±8g,11b=±16g

  Interrupt Status 寄存器 0x3A

  [0]RAW_SATA_RDY_INT,当有新的传感器数据存入并且可读时,该位置1。

  Interrupt Enable 寄存器 0x38

  [0]RAW_RDY_EN,当新的数据可读时,在传感器中断引脚上产生中断。使能该位可以利用中断来进去数据读取降低MCU负荷。

STM32F4XX I2C标准库函数

  voidI2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)

  用于产生I2C通信的START信号,只需要填入使用的I2C模块和ENABLE即可,例如:

I2C_GenerateSTART(I2C2, ENABLE);

  void I2C_GenerateSTOP(I2C_TypeDef* I2Cx,FunctionalState NewState)

  用于产生I2C通信的STOP信号,只需要填入使用的I2C模块和ENABLE即可,例如:

I2C_GenerateSTOP(I2C2, ENABLE);

  ErrorStatus I2C_CheckEvent(I2C_TypeDef*I2Cx, uint32_t I2C_EVENT)

 用于检测I2C模块事件,判断发送、读取是否成功等等,例如:I2C_CheckEvent(I2C2, EventFlag),如果事件发生则返回True,否则返回False

 void I2C_Send7bitAddress(I2C_TypeDef* I2Cx,uint8_t Address, uint8_t I2C_Direction)

  用于发送从设备的地址,注意7位地址在使用前需要向左移1位,例如:I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Transmitter);

  void I2C_Cmd(I2C_TypeDef* I2Cx,FunctionalState NewState)

  使能或者这失能指定的I2C总线,例如:I2C_Cmd(I2C1, ENABLE);

  void I2C_SendData(I2C_TypeDef* I2Cx,uint8_t Data)

  向指定的I2C总线发送数据,例如:I2C_SendData(I2C2, RegisterAddr);

  下面首先按照图2和图3中给出的时序实现基于I2C的单个字节读函数和写函数。这里有一点需要注意,如果使用MCU管脚模拟I2C,程序顺序执行的情况下,只有当前一个指令执行完成,后一条指令才能执行,这样不会出现问题。但是如果是异步或者使用I2C模块的方式实现I2C通信,指令虽然已经发出,但是I2C模块需要一定的时间来处理,我们需要确定I2C模块已经完成上一个指令处理,再进行下一个指令的发送,不然会产生与预期不符的现象,因此在函数中会看见MPU6050_Wait_Event()函数。

代码如下:

读函数

uint8_t MPU6050_I2C_Read_OneByte(const uint8_t RegisterAddr)
{
    //Prepare for read date
    MPU_Wait_Status(I2C_FLAG_BUSY);//Wait until the bus is idle
    I2C_AcknowledgeConfig(I2C2, ENABLE); //Enable acknowledgment

    I2C_GenerateSTART(I2C2, ENABLE);
    MPU6050_Wait_Event(I2C_EVENT_MASTER_MODE_SELECT);//Check ev5

    I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Transmitter); //Address + Write
    MPU6050_Wait_Event(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //Wait transmit finish and ACK ev6

    //Before setting PE again, please check the related registers status
    I2C_Cmd(I2C2, ENABLE);// Clear EV6 by setting again the PE bit
    //After setting PE, please check the related registers status
    //After checking it seems not use here

    I2C_SendData(I2C2, RegisterAddr);
    MPU6050_Wait_Event(I2C_EVENT_MASTER_BYTE_TRANSMITTED);//Wait transmit finish and ACK ev8

    I2C_GenerateSTART(I2C2, ENABLE);
    MPU6050_Wait_Event(I2C_EVENT_MASTER_MODE_SELECT);//Check ev5

    I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Receiver); //Address + Write
    MPU6050_Wait_Event(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//Wait transmit finish and ACK ev6

    I2C_AcknowledgeConfig(I2C2, DISABLE); //Disable acknowledgment
    MPU6050_Wait_Event(I2C_EVENT_MASTER_BYTE_RECEIVED); //Check ev7

    I2C_GenerateSTOP(I2C2, ENABLE);

    return I2C_ReceiveData(I2C2);
}

写函数

uint8_t MPU6050_I2C_Write_OneByte(const uint8_t RegisterAddr, const uint8_t RegisterValue)
{
	//Prepare for write date
	MPU_Wait_Status(I2C_FLAG_BUSY);//Wait until the bus is idle
	I2C_AcknowledgeConfig(I2C2, ENABLE); //Enable acknowledgment

	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_Wait_Event(I2C_EVENT_MASTER_MODE_SELECT); //Check ev5

	I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Transmitter); //Address + Write
	MPU6050_Wait_Event(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//Wait transmit finish and ACK ev6

	I2C_SendData(I2C2, RegisterAddr);
	MPU6050_Wait_Event(I2C_EVENT_MASTER_BYTE_TRANSMITTED);//Wait transmit finish and ACK ev8

	I2C_SendData(I2C2, RegisterValue);
	MPU6050_Wait_Event(I2C_EVENT_MASTER_BYTE_TRANSMITTED);//Wait transmit finish and ACK ev8

	I2C_GenerateSTOP(I2C2, ENABLE);
	return 0;
}

完成了单个字节的读写,我们就可以利用这两个函数对MPU9250进行基本的设置,主要是采样频率和量程的设置,基本设置代码如下:

        RegisterValue = MPU6050_I2C_Read_OneByte(MPU9250_WHO_AM_I);
	if (RegisterValue != 0x71) // Check read who am i register value
	{
		return 255;
	}
	MPU6050_I2C_Write_OneByte(MPU9250_PWR_MGMT_1, MPU9250_H_RESET_MASK); //Reset all internal registers value by default
	Wait_nms(500);
	MPU6050_I2C_Write_OneByte(MPU9250_PWR_MGMT_1, 0x01); //Choose internal clock
	//Set MPU9250 out date rate gyro 20Hz Accel 20Hz
	MPU6050_I2C_Write_OneByte(MPU9250_SMPLRT_DIV, 0x31);
	MPU6050_I2C_Write_OneByte(MPU9250_CONFIG, (0x01 << 0) & MPU9250_DLPF_CFG_MASK);
	MPU6050_I2C_Write_OneByte(MPU9250_GYRO_CONFIG, MPU9250_GYRO_FULL_SCALE_500DPS);
	MPU6050_I2C_Write_OneByte(MPU9250_ACCEL_CONFIG, MPU9250_FULL_SCALE_2G)

  在完成基本设置后,可以进行数据的读取,这里我们已经有读取单个字节的函数,可以逐一读取寄存器中的数值,也可以基于读单个字节的函数改写为读多个字节的函数。读取加速度数据和陀螺仪数据的代码如下:

void MPU6050_Get_RawData()
{
	uint8_t TempBuffer[14];
	uint8_t MPU_Status = 0;
	uint8_t BufferCnt = 0;
	MPU_Status = MPU6050_I2C_Read_OneByte(MPU9250_INT_STATUS);
	if ((MPU_Status & MPU9250_RAW_DATA_RDY_INT_MASK) == MPU9250_RAW_DATA_RDY_INT_MASK)
	{
		//ACC X Y Z TEMP GYRO X Y Z
		MPU6050_I2C_Read_nByte(MPU9250_ACCEL_XOUT_H, TempBuffer, 14);
		for (BufferCnt = 0; BufferCnt < 14; BufferCnt++)
		{
			PushQueue(TempBuffer[BufferCnt]);
		}
	}

}


 

 

联系作者

展开阅读全文

没有更多推荐了,返回首页