软件I2C读写MPU6050
I2C
- 什么是I2C?

所有I2C设备的SCL连在一起,SDA连在一起,设备的SCL和SDA均要配置成开漏输出模式(因为I2C规定SDA和SCL的默认状态是高电平),SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右.I2C总线的示意图如下:

- I2C时序基本单元
-
起始条件&终止条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平

终止条件:SCL高电平期间,SDA从低电平切换到高电平.

-
发送一个字节
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

-
接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA).

-
发送应答&接收应答
发送应答: 主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答.

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA).

- 基本时序单元总结:

可以点击该链接I2C基本时序单元详解进一步学习I2C基本时序单元.
- I2C时序图
- 指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data).

- 当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data).

- 指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data).

- I2C基本结构

MPU6500
- 什么是MPU6500?
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景.在6轴姿态传感器中:
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度.
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度.

- MPU6500参数
①16位ADC采集传感器的模拟信号,量化范围:-32768~32767
②加速度计满量程选择:±2、±4、±8、±16(g)
③陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
④可配置的数字低通滤波器
⑤可配置的时钟源
⑥可配置的采样分频
⑦I2C从机地址:1101000(AD0=0)、 1101001(AD0=1)
编写软件程序读写MPU6050
-
接线图如下:

-
在Hardware文件夹下新建一个MyI2C.c和MyI2C.h文件
MyI2C.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
//BitAction是嵌入式开发(尤其STM32等微控制器平台)中常见的自定义枚举类型
//主要用于GPIO(通用输入输出)引脚的电平状态控制
void MyI2C_W_SCL(uint8_t BitVlaue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitVlaue);
//I2C标准模式(100kHz)要求每个时钟脉冲的建立时间(如SCL高电平稳定时间、SCL低电平保持时间)不小于4.7微秒,
//10微秒的延时确保了信号的稳定性,避免因时序过快导致的通信错误。
//STM32 F1系列即使不加延时函数也可以
Delay_us(10);
}
//控制数据线的写入函数
void MyI2C_W_SDA(uint8_t BitVlaue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitVlaue);
Delay_us(10);
}
//控制数据线的读函数
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitVlaue;
BitVlaue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitVlaue;
}
//初始化
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//将pB10和PB11初始化为开漏输出模式,此模式不只能输出,还能输入
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初始化后的默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
//I2C的启动条件函数
void MyI2C_Start(void)
{
//在这个启动函数开始时,SDA并不一定是高电平,所以要先将SDA设置为高电平,防止被误认为是终止信号
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
//I2C终止条件函数
void MyI2C_Stop(void)
{
//在这个终止函数开始时,SDA并不一定是低电平,所以要先将SDA设置为低电平,防止被误认为是开始信号
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
//发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i =0;i < 8;i ++)
{
//先将SDA的数据高位写入
MyI2C_W_SDA(Byte & (0x80 >> i));
//手动控制SCL,高位读取SDA(读取期间SDA不能有变化),低位让SDA变换数据
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//读取一个字节
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i,Byte = 0x00;
// 主机释放SDA(设置为高阻态),让从机驱动SDA
MyI2C_W_SDA(1);
for(i = 0;i < 8;i ++)
{
MyI2C_W_SCL(1);
// 读取SDA电平,MyI2C_R_SDA() == 1表示主机暂时解除对SDA线的控制(从机发送的当前位数据)
if(MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
}
// 主机拉低SCL,结束当前时钟周期(从机准备下一位数据)
MyI2C_W_SCL(0);
}
return Byte;
}
//发送一个应答位
void MyI2C_SendACK(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
//读取一个应答位
uint8_t MyI2C_ReceiveACK(void)
{
uint8_t AckBit;
// 主机释放SDA(设置为高阻态),让从机驱动SDA
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
将I2C的各个关键部分配置完成后在调用函数即可实现数据的发送与接收了,这就要对I2C的时序图和基本时序单元有一个比较深入的了解.
- 在Hardware文件夹下编写MPU6500.c(我的芯片是MPU6500,与MPU6050的芯片的参数基本吻合)和MPU6500.h文件.
MPU6500.c代码:
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6500_Reg.h"
#define MPU6500_ADDRESS 0xD0
//写入从设备寄存器数据
void MPU6500_WriteReg(uint8_t RegAddress,uint8_t Data)
{
//传输开始
MyI2C_Start();
//指定写入的从设备
MyI2C_SendByte(MPU6500_ADDRESS);
MyI2C_ReceiveACK();
//指定写入的从设备的寄存器
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveACK();
//指定写入的从设备的寄存器的数据
MyI2C_SendByte(Data);
MyI2C_ReceiveACK();
//传输完成,停止
MyI2C_Stop();
}
//在从设备寄存器中读取数据
uint8_t MPU6500_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
//传输开始
MyI2C_Start();
//指定写入的从设备
MyI2C_SendByte(MPU6500_ADDRESS);
MyI2C_ReceiveACK();
//指定写入的从设备的寄存器
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveACK();
//重复开始以读取数据
MyI2C_Start();
//指定读数据的从设备的地址,这里将从设备的地址的第八位改为1表示读数据
MyI2C_SendByte(MPU6500_ADDRESS | 0x01);
MyI2C_ReceiveACK();
Data = MyI2C_ReceiveByte();
//只读取一次数据,所以在读取完这此数据将ACK置为1,表示读取暂停
MyI2C_SendACK(1);
//传输完成,停止
MyI2C_Stop();
return Data;
}
//初始化
void MPU6500_Init(void)
{
// 1. 初始化 I2C 通信接口(确保能与 MPU6500 通信)
MyI2C_Init();
// 2. 配置电源管理寄存器 1 (MPU6500_PWR_MGMT_1)
// - 清除睡眠模式(BIT6=0)
// - 选择 PLL 时钟源(X 轴陀螺仪参考时钟)
// - 温度传感器启用(默认已启用,但明确设置更安全)
MPU6500_WriteReg(MPU6500_PWR_MGMT_1, 0x01);
// 3. 配置电源管理寄存器 2 (MPU6500_PWR_MGMT_2)
// - 禁用加速度计和陀螺仪的低功耗模式(所有轴启用)
MPU6500_WriteReg(MPU6500_PWR_MGMT_2, 0x00);
// 4. 设置采样率分频器 (MPU6500_SMPLRT_DIV)
// - 公式:Sample Rate = Gyroscope Output Rate / (1 + SMPLRT_DIV)
// - 例如:Gyro 输出率默认 8kHz,分频值为 0x09 → 8000/(9+1) = 800Hz
MPU6500_WriteReg(MPU6500_SMPLRT_DIV, 0x09);
// 5. 配置扩展寄存器 (MPU6500_CONFIG)
// - 设置数字低通滤波器(DLPF)带宽(例如 0x06 对应 5Hz 低通滤波)
// - 降低噪声,但可能影响高频信号响应
MPU6500_WriteReg(MPU6500_CONFIG, 0x06);
// 6. 配置陀螺仪量程 (MPU6500_GYRO_CONFIG)
// - 0x18 → ±2000°/s(最大量程,适合剧烈运动场景)
MPU6500_WriteReg(MPU6500_GYRO_CONFIG, 0x18);
// 7. 配置加速度计量程 (MPU6500_ACCEL_CONFIG)
// - 0x18 → ±16g(平衡量程与灵敏度)
MPU6500_WriteReg(MPU6500_ACCEL_CONFIG, 0x18);
}
/**
* 函 数:MPU6050获取数据
* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 返 回 值:无
*/
void MPU6500_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 = MPU6500_ReadReg(MPU6500_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据
*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6500_ReadReg(MPU6500_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据
*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6500_ReadReg(MPU6500_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据
*AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6500_ReadReg(MPU6500_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据
*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6500_ReadReg(MPU6500_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据
*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6500_ReadReg(MPU6500_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据
DataL = MPU6500_ReadReg(MPU6500_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据
*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
}
//读取芯片的ID号
uint8_t MPU6500_GetID(void)
{
return MPU6500_ReadReg(MPU6500_WHO_AM_I);
}
值得注意的是:
①在读取寄存器和写入寄存器要严格按照I2C的时序来操作.
②在函数void MPU6500_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);中,虽然DataH定义为8位,但是在执行代码DataH << 8后的8为并不会丢失,因为该函数的形参是16为的int16_t.
- 在Hardware文件夹下编写MPU6500_Reg.c文件,此文件用来存储从设备MPU6500的重要功能单元(如陀螺仪和重力加速度的测量器)的寄存器地址
MPU6500_Reg.c代码:
#ifndef __MPU6500_REG_H
#define __MPU6500_REG_H
#define MPU6500_SMPLRT_DIV 0x19
#define MPU6500_CONFIG 0x1A
#define MPU6500_GYRO_CONFIG 0x1B
#define MPU6500_ACCEL_CONFIG 0x1C
#define MPU6500_ACCEL_XOUT_H 0x3B
#define MPU6500_ACCEL_XOUT_L 0x3C
#define MPU6500_ACCEL_YOUT_H 0x3D
#define MPU6500_ACCEL_YOUT_L 0x3E
#define MPU6500_ACCEL_ZOUT_H 0x3F
#define MPU6500_ACCEL_ZOUT_L 0x40
#define MPU6500_TEMP_OUT_H 0x41
#define MPU6500_TEMP_OUT_L 0x42
#define MPU6500_GYRO_XOUT_H 0x43
#define MPU6500_GYRO_XOUT_L 0x44
#define MPU6500_GYRO_YOUT_H 0x45
#define MPU6500_GYRO_YOUT_L 0x46
#define MPU6500_GYRO_ZOUT_H 0x47
#define MPU6500_GYRO_ZOUT_L 0x48
#define MPU6500_PWR_MGMT_1 0x6B
#define MPU6500_PWR_MGMT_2 0x6C
#define MPU6500_WHO_AM_I 0x75
#endif
858

被折叠的 条评论
为什么被折叠?



