硬件I2C读写MPU6500
I2C外设
- 外设内容
①STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
②支持多主机模型
③支持7位/10位地址模式
④支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
⑤支持DMA
⑥兼容SMBus协议
⑦STM32F103C8T6 硬件I2C资源:I2C1、I2C2 - 主机发送&主机接收序列图
此序列图在主机利用硬件电路收发数据的过程中尤为重要,它配置了哪些时刻电路需要做的事件操作(EVx).也就是说,利用硬件实现I2C的收发数据实际上就是在控制EVx.
主机发送:

主机发送流程图:
主机接收:

主机接收流程图:
I2C_SR中最常用的一些状态标志位及其作用:

硬件I2C读写MPU6500
- 接线图与然健I2C读取MPU6500的接线图一致,如图:

- 控制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);
}
- 通过代码可以看到,在硬件控制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位主发送流程的结束。- MPU6500_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);函数是用来控制while循环的,防止因众多事件中的某一个事件错误而导致始终跳不出while循环而导致后续事件无法执行.
- 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数据就立即完成了下次准备,速度之快是软件模拟无法比拟的.
759

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



