关键词:MPU9250,九轴,STM32,I2C
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控制模块。
这里仅根据满足读取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。
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负荷。
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]);
}
}
}
联系作者