硬件I2C读写MPU6500

硬件I2C读写MPU6500

I2C外设

  1. 外设内容
    STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
    ②支持多主机模型
    ③支持7位/10位地址模式
    ④支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
    ⑤支持DMA
    ⑥兼容SMBus协议
    STM32F103C8T6 硬件I2C资源:I2C1、I2C2
  2. 主机发送&主机接收序列图

此序列图在主机利用硬件电路收发数据的过程中尤为重要,它配置了哪些时刻电路需要做的事件操作(EVx).也就是说,利用硬件实现I2C的收发数据实际上就是在控制EVx.

主机发送:
在这里插入图片描述
主机发送流程图:

主机接收:
在这里插入图片描述
主机接收流程图:

I2C_SR中最常用的一些状态标志位及其作用:
在这里插入图片描述

硬件I2C读写MPU6500

  1. 接线图与然健I2C读取MPU6500的接线图一致,如图:
    在这里插入图片描述
  2. 控制I2C读写数据的代码与软件I2C读写MPU6500工程的代码一致,只需改写MPU6500.c文件代码
    MPU6500代码:
#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6500_Reg.h"

#define MPU6500_ADDRESS 0xD0

//处理while循环超时问题
void MPU6500_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout = 10000;
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
	{
		Timeout --;
		if(Timeout == 0)
		{
			break;
		}
	}
}


//写入从设备寄存器数据
void MPU6500_WriteReg(uint8_t RegAddress,uint8_t Data)
{
	//传输开始
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
	
	//指定从设备的地址
	I2C_Send7bitAddress(I2C2,MPU6500_ADDRESS,I2C_Direction_Transmitter);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	//指定寄存器的地址
	I2C_SendData(I2C2,RegAddress);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
	
	//写数据
	I2C_SendData(I2C2,Data);
	//注意当发送的数据只有一个字节或发送到最后一个字节时,要给标志位证明发送结束,即参数I2C_EVENT_MASTER_BYTE_TRANSMITTED
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	I2C_GenerateSTOP(I2C2,ENABLE);
	
}

//在从设备寄存器中读取数据
uint8_t MPU6500_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;

	//传输开始
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
	
	//指定从设备的地址
	I2C_Send7bitAddress(I2C2,MPU6500_ADDRESS,I2C_Direction_Transmitter);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	//指定寄存器的地址
	I2C_SendData(I2C2,RegAddress);
	//注意参数使用I2C_EVENT_MASTER_BYTE_TRANSMITTED表明要等待数据全部发送完毕后在执行下一句代码
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	//生成重复起始条件
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
	
	//指定读的从机地址,注意参数I2C_Direction_Receiver即改写最低位为1(读模式)
	I2C_Send7bitAddress(I2C2,MPU6500_ADDRESS,I2C_Direction_Receiver);
	//主机接收数据的EV6事件是I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,不同于主机读取数据的EV6事件
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);


    //从机在发送数据只有一个字节或发送数据的最后一个字节时,要提前告知主机
	//比如从机在发送最后一个字节时,要在发送该字节之前(倒数第二个数据发送后)告知主机这是最后一个字节
	I2C_AcknowledgeConfig(I2C2,DISABLE);
	I2C_GenerateSTOP(I2C2,ENABLE);
	
	MPU6500_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
	Data = I2C_ReceiveData(I2C2);
	
	//将ACK的值恢复为默认模式(默认是每收到一个字节就给ACK)
	I2C_AcknowledgeConfig(I2C2,ENABLE);
	
	return Data;
}

//初始化
void MPU6500_Init(void)
{
	//I2C2是APB1总线上的外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	//根据引脚定义表,I2C2的外设只能搭载在PB10和PB11的引脚上
	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);
	
	I2C_InitTypeDef I2C_InitStructure;
	//指定收到数据后是否给从机应答
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	//寻址位
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	//通信速度,标准速度(100KHZ及以下),快速(400KHZ及以下100KHZ以上)
	I2C_InitStructure.I2C_ClockSpeed = 50000;
	//通信占空比,在I2C_ClockSpeed快速状态下有用,标准状态下占空比为1:1
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	//给STM32一个默认地址,只要不喝总线上其他设备的地址重复就行
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;
	I2C_Init(I2C2,&I2C_InitStructure);
    
	I2C_Cmd(I2C2,ENABLE);
	
	
	
    //配置电源管理寄存器 1 (MPU6500_PWR_MGMT_1)
    //    - 清除睡眠模式(BIT6=0)
    //    - 选择 PLL 时钟源(X 轴陀螺仪参考时钟)
    //    - 温度传感器启用(默认已启用,但明确设置更安全)
    MPU6500_WriteReg(MPU6500_PWR_MGMT_1, 0x01);
    
    //配置电源管理寄存器 2 (MPU6500_PWR_MGMT_2)
    //    - 禁用加速度计和陀螺仪的低功耗模式(所有轴启用)
    MPU6500_WriteReg(MPU6500_PWR_MGMT_2, 0x00);
    
    //设置采样率分频器 (MPU6500_SMPLRT_DIV)
    //    - 公式:Sample Rate = Gyroscope Output Rate / (1 + SMPLRT_DIV)
    //    - 例如:Gyro 输出率默认 8kHz,分频值为 0x09 → 8000/(9+1) = 800Hz
    MPU6500_WriteReg(MPU6500_SMPLRT_DIV, 0x09);
    
    //配置扩展寄存器 (MPU6500_CONFIG)
    //    - 设置数字低通滤波器(DLPF)带宽(例如 0x06 对应 5Hz 低通滤波)
    //    - 降低噪声,但可能影响高频信号响应
    MPU6500_WriteReg(MPU6500_CONFIG, 0x06);
    
    //配置陀螺仪量程 (MPU6500_GYRO_CONFIG)
    //    - 0x18 → ±2000°/s(最大量程,适合剧烈运动场景)
    MPU6500_WriteReg(MPU6500_GYRO_CONFIG, 0x18);
    
    //配置加速度计量程 (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);
}


  1. 通过代码可以看到,在硬件控制I2C的读写时只需调用I2C_CheckEvent(I2Cx,I2C_EVENT);这个官方函数来将事件传入,函数根据传入的事件来控制I2C下一步该干什么.
    起始信号 (S) 与 EV5 中断
    主机动作:主设备(MCU)在I2C总线上发送一个起始条件 (S)。
    中断事件:起始条件成功发出后,状态寄存器会置位相应标志,触发 中断事件 EV5。关键标志是 SB=1(起始位已发送)。
    程序员操作:在EV5的中断服务程序中,程序员必须读取状态寄存器SR1以清除SB标志,并立即将从机地址(7位地址+写方向位0)​ 写入数据寄存器DR。这个写DR的操作会使硬件自动将地址发送到总线上。
    地址发送与 EV6 中断
    主机动作:硬件将DR寄存器中的从机地址发送到总线上。
    从机动作:匹配该地址的从机返回一个应答位 (A)。
    中断事件:收到从机的有效应答后,触发 中断事件 EV6。关键标志是 ADDR=1(地址已发送)。
    程序员操作:在EV6的中断服务程序中,程序员必须通过读取SR1寄存器,再读取SR3寄存器(顺序不可颠倒)来清除ADDR标志。此举也标志着主发送器模式正式激活。
    发送第一个数据字节与 EV8_1 中断
    主机动作:ADDR标志被清除后,主机硬件会释放时钟线,继续通信进程。程序员将第一个要发送的数据字节写入数据寄存器DR。
    中断事件:当DR寄存器中的数据被硬件加载到移位寄存器并开始发送时,DR寄存器变空,触发 中断事件 EV8_1。关键标志是 TxE=1(数据寄存器空)。
    从机动作:从机接收完整个字节后,会返回一个应答位 (A)。
    程序员操作:在EV8_1的中断服务程序中,程序员需要先读取SR1寄存器,然后紧接着将下一个数据字节写入DR寄存器,以清除TxE标志并为发送下一字节做准备。
    发送后续数据字节 (N) 与 EV8 中断
    循环开始:进入一个发送循环。每当一个数据字节发送完毕、从机返回应答后,都会触发 中断事件 EV8(因为DR在上一步已被写入,此时为空)。
    程序员操作:
    如果还有更多数据要发送 (Data2…N),程序员在EV8中断中,只需简单地将下一个数据字节写入DR寄存器即可清除TxE标志,通信继续。
    这个“发送-应答-触发EV8-写下一数据”的循环会持续进行,直到所有数据发送完毕。
    发送最后一个字节与 EV8_2 中断
    最终判断:当发送最后一个数据字节时,程序员在对应的EV8中断中的操作会有所不同。
    程序员操作:在最后一个数据的EV8中断服务程序中,程序员不再写入新的数据,而是等待该字节发送完成并从机返回最后一个应答位。
    中断事件:最后一个字节发送并收到应答后,触发 中断事件 EV8_2。此时TxE=1,并且BTF=1(字节传输完成)。
    结束通信:在EV8_2中断中,程序员需要通过软件控制,产生一个停止条件 §​ 在总线上。停止条件的发出,标志着整个7位主发送流程的结束。
  2. MPU6500_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);函数是用来控制while循环的,防止因众多事件中的某一个事件错误而导致始终跳不出while循环而导致后续事件无法执行.
  1. main.c代码:
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6500.h"

uint8_t ID;								//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	
	MPU6500_Init();
	
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		//显示静态字符串
	ID = MPU6500_GetID();				//获取MPU6500的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号
	
	while (1)
	{
		MPU6500_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取MPU6500的数据
		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);
	}
}

由此,硬件实现与软件一样的效果,但是硬件实现的SCL与SDA的波形图结合更紧密

软硬件波形图对比

在这里插入图片描述

可以看出硬件不用手动配置两条电平线的配合,在硬件波形图中(下面的波形图),SCL电平在处于下降沿时SDA数据就立即完成了下次准备,速度之快是软件模拟无法比拟的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

必胜的思想钢印

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值