STM32(十九)——软件/硬件IIC读写MPU6050

一、软件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

读取数据的函数和软件的一样

相关推荐
悠哉悠哉愿意11 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
Lester_110111 天前
STM32霍尔传感器输入口设置为复用功能输入口时,还能用GPIO函数直接读取IO的状态吗
stm32·单片机·嵌入式硬件·电机控制
LCG元11 天前
低功耗显示方案:STM32L0驱动OLED,动态波形绘制与优化
stm32·嵌入式硬件·信息可视化
三佛科技-1873661339711 天前
120W小体积碳化硅电源方案(LP8841SC极简方案12V10A/24V5A输出)
单片机·嵌入式硬件
z203483152011 天前
STM32F103系列单片机定时器介绍(二)
stm32·单片机·嵌入式硬件
古译汉书11 天前
【IoT死磕系列】Day 7:只传8字节怎么控机械臂?学习工业控制 CANopen 的“对象字典”(附企业级源码)
数据结构·stm32·物联网·http
Alaso_shuang11 天前
STM32 核心输入、输出模式
stm32·单片机·嵌入式硬件
脚后跟11 天前
AI助力嵌入式物联网项目全栈开发
嵌入式硬件·物联网·ai编程
2501_9181269112 天前
stm32死锁是怎么实现的
stm32·单片机·嵌入式硬件·学习·个人开发
z203483152012 天前
STM32F103系列单片机定时器介绍(一)
stm32·单片机