1. MPU6050简介
- MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
- 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
- 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
1.1 主要参数
- 16位ADC采集传感器的模拟信号,量化范围:-32768~32767
- 加速度计满量程选择:±2、±4、±8、±16(g)
- 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
- 可配置的数字低通滤波器
- 可配置的时钟源
- 可配置的采样分频
I2C从机地址:1101000(AD0=0)、1101001(AD0=1)
如果在程序中用十六进制表示,一般有两种表示方式,以1101000地址为例。第一种,直接把7位二进制数转换为十六进制,110 1000就是0x68,所以可以说MPU6050的从机地址是0x68。在I2C通信时序中,第一个字节的高7位是从机地址,最低位是读写位,所以如果认为0x68是从机地址,在发送第一个字节时,需要先把0x68左移1位,再按位或上读写位。第二种,把0x68左移1位后的数据当做从机地址。0x68左移1位后是0xD0,则MPU6050的从机地址就是0xD0,这时在实际发送第一个字节时,如果需要写,就直接把0xD0当作第一个字节,如果需要读,就或上0x01,即0xD1当作第一个字节。
1.2 硬件电路
MPU6050模块原理图
XCL、XDA:MPU6050的6轴传感器不够用,需要进行扩展时使用,通常用于外接磁力计或者气压计,MPU6050的主机接口可以直接访问这些扩展芯片的数据,在MPU6050内有DMP单元进行数据融合和姿态解算。
1.3 模块框图
2. 软件I2C读写MPU6050
2.1 接线图
SCL接PB10、SDA接PB11。由于本节代码使用的是软件I2C,即用普通的GPIO口手动翻转电平,不需要STM32内部的外设资源支持,所以这里端口可以任意指定,也可以SCL接PA8,SDA接PA9等。
根据I2C协议的硬件规定,SCL和SDA均应外挂上拉电阻,由于模块内部自带上拉电阻,所以不需要外接。XCL和XDA用于扩展的接口,不使用。AD0引脚修改从机地址的最低位,由于模块内接了下拉电阻,所以引脚悬空相当于接地。INT中断信号输出脚,不使用。
2.2 代码
程序的整体架构:
- 首先,建立I2C通信层的.c和.h模块。在通信层中,写好I2C底层的GPIO初始化和6个时序基本单元(起始、终止、发送一个字节、接收一个字节、发送应答、接收应答)。
- 再建立MPU6050的.c的.h模块。在此层,基于I2C通信模块实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置、读寄存器得到传感器数据。
- 最后在main.c中调用MPU6050模块,初始化,获取数据,显示数据。
由于使用的是软件I2C,所以stm32的I2C库函数就不需要看了,只需要使用GPIO的读写函数。
MyI2C.c(防止和库函数的函数重名)
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_Init(void)
{
// 需要完成2个任务:
// ① 把SCL和SDA均初始化为开漏输出模式;
// ② 把SCL和SDA置高电平。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;// 开漏输出(虽然名字是"输出",但开漏输出模式仍然可以输入)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;// SCL是PB10,SDA是PB11
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);// 置SCL和SDA高电平
}
// 对操作端口的库函数进行封装
void MyI2C_W_SCL(uint8_t BitValue)// 写SCL。参数给1或0,就可以释放或拉低SCL
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);// 如果单片机主频较快,可以加延时,防止从机跟不上
}
void MyI2C_W_SDA(uint8_t BitValue)// 写SDA。参数给1或0,就可以释放或拉低SDA
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)// 读SDA
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
// 接下来完成I2C的6个时序基本单元
//
// 1、起始条件
// 首先确保SCL和SDA释放,然后先拉低SDA,再拉低SCL
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);// 释放SDA
MyI2C_W_SCL(1);// 释放SCL
MyI2C_W_SDA(0);// 先拉低SDA
MyI2C_W_SCL(0);// 再拉低SCL
}
// 2、终止条件
// SCL高电平期间,SDA从低电平切换到高电平
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);// 先拉低SDA,避免SDA之前为高电平时释放无法产生上升沿
MyI2C_W_SCL(1);// 再释放SCL
MyI2C_W_SDA(1);// 再释放SDA
}
// 3、发送一个字节
// SCL低电平,SDA变换数据;SCL高电平,SDA保持数据稳定。放完1位后,释放SCL,拉低SCL,驱动时钟运转
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));// 首先趁SCL低电平,把Byte的最高位放在SDA线上。下一轮就是把次高位放在SDA线上
// 取Byte最高位为Byte&0x80,取次高位为Byte&0x40...在循环中,使用右移i位即可实现按位与数值的变换
MyI2C_W_SCL(1);// 再释放SCL。释放后从机会立刻把SDA的数据读走
MyI2C_W_SCL(0);// 拉低SCL
}
}
// 4、接收一个字节
// SCL低电平,从机把数据放到SDA上,为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA也相当于切换为输入模式
// 然后主机在SCL高电平期间读取SDA,再拉低SCL。这样重复8次,主机就能读到一个字节了。
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);// 主机释放SDA
for(i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);// 主机释放SCL
if(MyI2C_R_SDA() == 1)// 主机读取数据
{
Byte |= (0x80 >> i);// 如果读取到1,就从高到低把Byte对应位置1
}
MyI2C_W_SCL(0);// 读取1位后拉低SCL,这时从机会把下一位数据放到SDA上
}
return Byte;
}
// 5、发送应答
// 其实就是发送一个字节的简化
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);// 函数进入时,SCL低电平,主机把AckBit放到SDA上
MyI2C_W_SCL(1);// 再释放SCL。从机读取应答
MyI2C_W_SCL(0);// 拉低SCL,进入下一个时序单元
}
// 6、接收应答
// 其实就是接收一个字节的简化
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);// 函数进入时SCL低电平,主机释放SDA防止干扰从机。同时,从机把应答位放在SDA上
MyI2C_W_SCL(1);// 主机释放SCL,读取应答位
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);// 读取后拉低SCL,进入下一个时序单元
return AckBit;
}
MyI2C.h
cpp
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
MPU6050.c
cpp
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
// 指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址+读写位
MyI2C_ReceiveAck();// 接收应答
MyI2C_SendByte(RegAddress);// 指定寄存器地址
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);// 发送写入指定寄存器地址下的数据
MyI2C_ReceiveAck();
MyI2C_Stop();
}
// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址+读写位
MyI2C_ReceiveAck();// 接收应答
MyI2C_SendByte(RegAddress);// 指定寄存器地址
MyI2C_ReceiveAck();
MyI2C_Start();// 重复起始条件
MyI2C_SendByte(MPU6050_ADDRESS | 0xD1);// 读写位1,读出数据
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();// 从机发送数据,主机接收数据
MyI2C_SendAck(1);// 这里只想要1个字节,所以不给从机应答
MyI2C_Stop();
return Data;
}
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);// 配置PWR_MGMT_1寄存器,解除睡眠,选择X轴陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);// 采样率分频,决定了数据输出的快慢。10分频
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);// 数字低通滤波器给110
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);// 陀螺仪配置,选择最大量程11
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);// 加速度计配置,选择最大量程11
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
// 由于需要返回6个变量,使用指针的地址传递
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t Data_H, Data_L;
Data_H = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);// 加速度寄存器X轴高8位
Data_L = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (Data_H << 8) | Data_L;// 加速度计X轴的16位数据
Data_H = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
Data_L = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (Data_H << 8) | Data_L;
Data_H = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
Data_L = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (Data_H << 8) | Data_L;
Data_H = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);// 陀螺仪寄存器X轴高8位
Data_L = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (Data_H << 8) | Data_L;// 陀螺仪X轴的16位数据
Data_H = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
Data_L = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (Data_H << 8) | Data_L;
Data_H = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
Data_L = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (Data_H << 8) | Data_L;
}
MPU6050.h
cpp
#ifndef __MPU6050_H
#define __MPU6050_H
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif
MPU6050_Reg.h
cpp
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
#endif
main.c
cpp
#include "stm32f10x.h" // Device
#include "Delay.h"
#include "MPU6050.h"
int16_t AX, AY, AZ, GX, GY, GZ;
int main(void)
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1, 1, "ID:");
OLED_ShowHexNum(1, 4, MPU6050_GetID(), 2);
OLED_ShowString(1, 7, "Acc|Gyro");
OLED_ShowString(2, 1, "X |");
OLED_ShowString(3, 1, "Y |");
OLED_ShowString(4, 1, "Z |");
while(1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
OLED_ShowSignedNum(2, 3, AX, 5);
OLED_ShowSignedNum(3, 3, AY, 5);
OLED_ShowSignedNum(4, 3, AZ, 5);
OLED_ShowSignedNum(2, 10, GX, 5);
OLED_ShowSignedNum(3, 10, GY, 5);
OLED_ShowSignedNum(4, 10, GZ, 5);
}
}
其他引用的头文件和c代码可在此处查阅:Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)