一、软件IIC读写MPU6050
接线图

MyI2C
初始化
开漏输出(Open-Drain)本身就自带 "输入能力"
- 输出 0 → 内部 MOS 导通 → 引脚拉低
- 输出 1 → 内部 MOS 关断 → 引脚浮空 ,由外部上拉电阻拉高→ 这时引脚状态完全由外部决定 → 单片机可以直接读取引脚电平
这就是:输出 1 = 相当于输入模式
void MyI2C_Init(void)
{
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;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}
封装函数
把硬件引脚抽象成宏,后续修改引脚时,只改这几行即可,不用改函数内部。
将翻转SCL、SDA电平的函数以及读取SDA数据的函数封装起来,拉低/释放时直接调用相应函数。
BitAction是 STM32 标准库(ST 官方库)预先定义好的枚举,源码在stm32f10x_gpio.h(以 F1 系列为例)中,取值:只有两个 ------Bit_RESET(低电平)、Bit_SET(高电平)。
#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_10
#define SDA_PIN GPIO_Pin_11
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(SCL_PORT, SCL_PIN, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(SCL_PORT, SDA_PIN, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(SCL_PORT, SDA_PIN);
Delay_us(10);
return BitValue;
}
时序
起始条件
先释放SCL、SDA,再拉低。这里因为要兼容重复起始条件Sr,SCL一开始是低电平,SDA不确定,所以保险起见先释放SDA。

void MyI2C_Start(void) //起始条件
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
发送数据
将 1 个 8 位的字节数据(uint8_t Byte),从高位到低位逐位通过 SDA 数据线发送出去,每发送 1 位都配合 SCL 时钟线的高低电平变化,保证数据被从机稳定采样。
前面MyI2C_W_SCL函数里有延时10ms,所以SCL 的高电平、低电平各保持至少 10μs,这完全满足 I2C 协议的最低要求。
void MyI2C_SendByte(uint8_t Byte) //发送数据
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
接收数据
定义Byte用来装读取到的数据,主机释放SDA,主机循环读取,返回Byte。
uint8_t MyI2C_ReceivByte(void) //接受数据
{
uint8_t i,Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
}
MyI2C_W_SCL(0);
}
return Byte;
}
接收应答
定义Ackbit用来接收应答位,主机释放SDA,主机读取SDA,返回Ackbit。
uint8_t MyI2C_ReceivAck(void) //接收应答
{
uint8_t Ackbit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
Ackbit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return Ackbit;
}
发送应答
void MyI2C_SendAck(uint8_t AckBit) //发送应答
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
终止条件
在时序单元开始时SDA的电平不固定,所以为了确保释放SDA能产生上升沿,要在时序单元开始之前,先拉低SDA。然后再释放SCL、SDA。
void MyI2C_Stop(void) //终止条件
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
MPU6050
寄存器表
#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
初始化
步骤 1:底层通信准备
初始化软件 I2C,配置 SCL/SDA 引脚为开漏输出 + 上拉,建立与 MPU6050 的 I2C 通信基础。
步骤 2:唤醒设备 + 配置时钟源
写入电源管理寄存器 1(0x6B),关闭睡眠模式,选择陀螺仪 X 轴作为时钟源(提升稳定性)。
步骤 3:开启全轴工作
写入电源管理寄存器 2(0x6C),关闭加速度计 / 陀螺仪所有轴的待机模式,确保全轴正常采样。
步骤 4:设置采样率
写入采样率分频寄存器(0x19),分频值设为 9,最终采样率 = 8000/(1+9)=800Hz。
步骤 5:配置低通滤波
写入配置寄存器(0x1A),设置低通滤波带宽为 5Hz,过滤高频噪声,提升数据平滑性。
步骤 6:设置陀螺仪量程
写入陀螺仪配置寄存器(0x1B),量程设为 ±2000°/s(最大量程,适配高速运动场景)。
步骤 7:设置加速度计量程
写入加速度计配置寄存器(0x1C),量程设为 ±16g(最大量程,适配高加速度场景)。
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}
指定位置写
#define MPU6050_ADDRESS 0xD0
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)//指定位置写
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceivAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceivAck();
MyI2C_SendByte(Data);
MyI2C_ReceivAck();
MyI2C_Stop();
}
指定位置读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//指定位置读
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceivAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceivAck();
MyI2C_Start();//重复起始
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
MyI2C_ReceivAck();
Data = MyI2C_ReceivByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
获取加速度计 / 陀螺仪 数据
读取 MPU6050 的加速度计 / 陀螺仪 16 位原始数据,通过 "高字节 + 低字节拼接" 还原完整数值
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;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
获取MPU6050ID
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
调用
uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;
int main(void)
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1, 1, "IS:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
while(1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
OLED_ShowSignedNum(2, 1, AX, 5);
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);
}
}
二、硬件IIC读写MPU6050
接线图
I2C2对应的引脚是PB10和PB11


初始化
时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
配置GPIO
配置为复用开漏模式,让 GPIO 引脚由硬件 IIC 外设控制。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
配置IIC
配置为标准IIC模式、5KHZ、占空比2:1、使能应答功能、地址识别7位地址、主机(STM32)在IIC的地址(0x00)
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);
使能IIC2
I2C_Cmd(I2C2, ENABLE);
配置MPU6050
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
等待事件完成
事件完成后结束循环,如果一直完成不了等Tineout减到0时跳出循环。
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
指定位置写
起始信号 → 等待EV5 → 发设备地址(写) → 等待EV6 → 发寄存器地址 → 等待EV8 → 发数据 → 等待EV8_2 → 停止信号
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
指定位置读
起始信号 → 等待EV5 → 发设备地址(写) → 等待EV6 → 发寄存器地址 → 等待EV8_2 → 重复起始信号 → 等待EV5 → 发设备地址(读) → 等待EV6_1 → 禁用应答 → 发送停止信号 → 等待EV7 → 读取数据 → 重新使能应答 → 返回数据
-
读操作多了重复起始信号(步骤 7),而非直接发停止信号,这是 I2C"先写寄存器地址、再读数据" 的标准流程;
-
读操作等待的 EV 事件多了
EV6_1(主机接收模式)和EV7(字节接收完成); -
单字节读取时需先禁用应答、再发停止信号,避免传感器继续发送数据。
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C2, RegAddress); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); I2C_GenerateSTART(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); I2C_AcknowledgeConfig(I2C2, DISABLE); I2C_GenerateSTOP(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); Data = I2C_ReceiveData(I2C2); I2C_AcknowledgeConfig(I2C2, ENABLE); return Data;}
两个EV6
写 : I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED : EV6
读: I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED : EV6
读取数据的函数和软件的一样