1. MPU6050
1.1 运动学概念
1.2 工作原理
2. 参数
2.1 量程选择
2.2 I2C从机地址配置
3. 硬件电路
4. 框架图
5. 软件和硬件波形对比
6. 软件I2C读写MPU6050
6.1 程序整体构架
6.2 一些需要注意的点:
6.3 MPU6050初始化配置
6.4 传感器模型
7. 代码实现
7.1 MyI2C.c
7.2 MyI2C.h
7.3 MPU6050.C
7.4 MPU6050.H
7.5 MPU6050_Reg.h
7.6 main.c
对于I2C通信和MPU6050的详细解析可以看下面这篇文章
STM32单片机I2C通信详解-CSDN博客
对于STM32通过I2C硬件读写MPU6050的代码,可以看下面这篇文章
STM32通过I2C硬件读写MPU6050-CSDN博客
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
ADC和数据存储
加速度计满量程选择:±2、±4、±8、±16(g)
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
可配置的数字低通滤波器
允许用户根据应用需求配置滤波器,滤除噪声和干扰。
可配置的时钟源
支持多种时钟源,包括内部振荡器、外部参考时钟等。
采样率
采样率可以通过配置寄存器设置,决定数据采集的频率。
I2C地址配置
满量程选择
加速度计满量程示例:
测量分辨率:
二进制地址转换为十六进制:
1101000
为例。1101000
转换为十六进制,即分割低4位和高3位:0110 1000
,转换后为0x68
。I2C时序中的地址格式:
1101000
加上1位读写位。0x68
是从机地址,需要将0x68
左移1位,再加上读写位(0或1)。0x68
左移1位:1101 0000
(即0xD0
)。(0x68 << 1) | 0
或 (0x68 << 1) | 1
。实际应用:
0xD0
。0xD1
。引脚 | 功能 |
VCC、GND | 电源3.3v |
SCL、SDA | I2C通信引脚 |
XCL、XDA | 主机I2C通信引脚 |
AD0 | 从机地址最低位 |
INT | 中断信号输出 |
软件I2C实现
硬件I2C实现
首先建立I2C通信层的.c和.h模块,再建立MPU6050.c, 最后是main.c
起始和终止条件
起始条件:在SCL高电平期间,SDA由高电平变为低电平,产生起始条件(Start Condition)。这表示一次I2C通信的开始。
终止条件:在SCL高电平期间,SDA由低电平变为高电平,产生终止条件(Stop Condition)。这表示一次I2C通信的结束。
数据传输过程
数据位传输:在SCL低电平期间,主机可以改变SDA的电平,即在SCL的每一个低电平周期内,主机将要传输的数据位放在SDA线上。
数据位读取:在SCL高电平期间,从机读取SDA线上的数据。此时,SDA线上的电平表示当前传输的数据位。
应答位的发送和接收
发送应答:在主机发送完一个字节数据后,从机会在下一个SCL高电平期间将SDA线拉低,表示已接收到数据(ACK)。
接收应答:主机在发送完数据后,将SDA线释放为高电平,然后在SCL高电平期间读取SDA的电平,判断是否收到从机的应答。
I2C引脚配置
开漏输出+弱上拉:I2C引脚配置为开漏输出,并启用弱上拉电阻。这意味着,当引脚输出高电平时,实际上是释放引脚,由上拉电阻将其拉高。当引脚输出低电平时,引脚被拉低。
接收Byte程序
配置电源管理寄存器1:0000 0001
设备复位:值:0 含义:设备不复位。
睡眠模式:值:0 含义:解除睡眠。
循环模式:值:0 含义:不需要循环。
无关位:值:0
温度传感器失能:值:0 含义:温度传感器不失能。
时钟源选择:值:001 含义:选择X轴的陀螺仪时钟。
配置电源管理寄存器2:0x00
循环唤醒模式:值:00 含义:不需要循环唤醒模式。
待机位:值:每个轴的待机位全部给0。
电源管理寄存器1和2主要用于控制MPU6050的电源状态和工作模式。通过设置这些参数,可以确保MPU6050在最佳状态下运行,并且根据需要调整其功耗表现。
采样率分频:值:0x09 含义:10分频,8位决定了数据输出的快慢,值越小越快。
配置寄存器:寄存器值:0x06
外部同步:全部给0,不需要。
数字低通滤波器:110,最平滑的滤波。
陀螺仪配置寄存器:0x18
自测使能:值:0 含义:不自测使能。
满量程选择:值:11 含义:选择最大量程。
无关位:后三位无关。
加速度计配置寄存器:0x18
这里借用一张图片
陀螺仪旋转检测
加速度计检测
数据计算
软件I2C读写MPU6050
#include "stm32f10x.h" // Device header #include "Delay.h" /*引脚配置层*/ /** * 函 数:I2C写SCL引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平 */ void MyI2C_W_SCL(uint8_t BitValue) { GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平 Delay_us(10); //延时10us,防止时序频率超过要求 } /** * 函 数:I2C写SDA引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平 */ void MyI2C_W_SDA(uint8_t BitValue) { GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性 Delay_us(10); //延时10us,防止时序频率超过要求 } /** * 函 数:I2C读SDA引脚电平 * 参 数:无 * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1 * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1 */ uint8_t MyI2C_R_SDA(void) { uint8_t BitValue; BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平 Delay_us(10); //延时10us,防止时序频率超过要求 return BitValue; //返回SDA电平 } /** * 函 数:I2C初始化 * 参 数:无 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化 */ void MyI2C_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出 /*设置默认电平*/ GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态) } /*协议层*/ /** * 函 数:I2C起始 * 参 数:无 * 返 回 值:无 */ void MyI2C_Start(void) { MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平 MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平 MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号 MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接 } /** * 函 数:I2C终止 * 参 数:无 * 返 回 值:无 */ void MyI2C_Stop(void) { MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平 MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平 MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号 } /** * 函 数:I2C发送一个字节 * 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF * 返 回 值:无 */ void MyI2C_SendByte(uint8_t Byte) { uint8_t i; for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位 { MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线 MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDA MyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据 } } /** * 函 数:I2C接收一个字节 * 参 数:无 * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF */ uint8_t MyI2C_ReceiveByte(void) { uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到 MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送 for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位 { MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量 //当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0 MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA } return Byte; //返回接收到的一个字节数据 } /** * 函 数:I2C发送应答位 * 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答 * 返 回 值:无 */ void MyI2C_SendAck(uint8_t AckBit) { MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线 MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位 MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块 } /** * 函 数:I2C接收应答位 * 参 数:无 * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答 */ uint8_t MyI2C_ReceiveAck(void) { uint8_t AckBit; //定义应答位变量 MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送 MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA AckBit = MyI2C_R_SDA(); //将应答位存储到变量里 MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块 return AckBit; //返回定义应答位变量 }
#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
#include "stm32f10x.h" // Device header #include "MyI2C.h" #include "MPU6050_Reg.h" #define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址 /** * 函 数:MPU6050写寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF */ void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) { MyI2C_Start(); //I2C起始 MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入 MyI2C_ReceiveAck(); //接收应答 MyI2C_SendByte(RegAddress); //发送寄存器地址 MyI2C_ReceiveAck(); //接收应答 MyI2C_SendByte(Data); //发送要写入寄存器的数据 MyI2C_ReceiveAck(); //接收应答 MyI2C_Stop(); //I2C终止 } /** * 函 数:MPU6050读寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF */ uint8_t MPU6050_ReadReg(uint8_t RegAddress) { uint8_t Data; MyI2C_Start(); //I2C起始 MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入 MyI2C_ReceiveAck(); //接收应答 MyI2C_SendByte(RegAddress); //发送寄存器地址 MyI2C_ReceiveAck(); //接收应答 MyI2C_Start(); //I2C重复起始 MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //发送从机地址,读写位为1,表示即将读取 MyI2C_ReceiveAck(); //接收应答 Data = MyI2C_ReceiveByte(); //接收指定寄存器的数据 MyI2C_SendAck(1); //发送应答,给从机非应答,终止从机的数据输出 MyI2C_Stop(); //I2C终止 return Data; } /** * 函 数:MPU6050初始化 * 参 数:无 * 返 回 值:无 */ void MPU6050_Init(void) { MyI2C_Init(); //先初始化底层的I2C /*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/ MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率 MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPF MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g } /** * 函 数:MPU6050获取ID号 * 参 数:无 * 返 回 值:MPU6050的ID号 */ uint8_t MPU6050_GetID(void) { return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值 } /** * 函 数:MPU6050获取数据 * 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 返 回 值:无 */ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) { uint8_t DataH, DataL; //定义数据高8位和低8位的变量 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据 *AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据 *AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据 *AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据 *GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据 *GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据 *GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回 }
#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); uint8_t MPU6050_GetID(void); void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ); #endif
#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
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "MPU6050.h" uint8_t ID; //定义用于存放ID号的变量 int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量 int main(void) { /*模块初始化*/ OLED_Init(); //OLED初始化 MPU6050_Init(); //MPU6050初始化 /*显示ID号*/ OLED_ShowString(1, 1, "ID:"); //显示静态字符串 ID = MPU6050_GetID(); //获取MPU6050的ID号 OLED_ShowHexNum(1, 4, ID, 2); //OLED显示ID号 while (1) { MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //获取MPU6050的数据 OLED_ShowSignedNum(2, 1, AX, 5); //OLED显示数据 OLED_ShowSignedNum(3, 1, AY, 5); OLED_ShowSignedNum(4, 1, AZ, 5); OLED_ShowSignedNum(2, 8, GX, 5); OLED_ShowSignedNum(3, 8, GY, 5); OLED_ShowSignedNum(4, 8, GZ, 5); } }