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

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

相关推荐
风痕天际4 小时前
ESP32-S3开发教程6:硬件定时器
单片机·嵌入式硬件·嵌入式·esp32·freertos·esp32s3
Godspeed Zhao5 小时前
现代智能汽车中的无线技术97——NearLink(4)
stm32·单片机·汽车
z20348315206 小时前
如何用状态机解决按键状态识别问题(一)
c语言·单片机
之歆8 小时前
Heartbeat 高可用集群完全指南
单片机·嵌入式硬件
浩子智控9 小时前
提升linux串口通信实时性的编程实践
linux·单片机·嵌入式硬件
Tyrion.Mon9 小时前
5脚188数码管驱动
单片机
国科安芯1 天前
高可靠性电源方案的高温降额设计与热管理策略——基于ASP3605的温域特性实证研究
单片机·嵌入式硬件·安全威胁分析·安全性测试
白太岁1 天前
操作系统开发:(9) 从硬件复位到程序执行:如何编写符合硬件动作的启动文件与链接脚本
c语言·汇编·嵌入式硬件·系统架构
逻辑流1 天前
《精准测量的起点:STM32中的电压电流有效值计算算法》
stm32·单片机·嵌入式硬件·算法