当前位置: 首页 > news >正文

STM32 I2C通信完整教程:从协议原理到硬件实现

STM32 I2C通信完整教程:从协议原理到硬件实现

前言

I2C(Inter-Integrated Circuit)通信协议是嵌入式开发中最常用的通信方式之一。本教程将从I2C协议的设计原理开始,逐步深入到STM32的软件模拟和硬件实现,最终完成MPU6050六轴传感器的数据读取。

本教程分为两大部分:

  1. 软件模拟I2C:使用普通GPIO口手动翻转电平实现协议
  2. 硬件I2C:使用STM32内部I2C外设实现协议

通过对比学习,您将深入理解I2C通信的本质,掌握两种实现方式的优缺点。

第一章:I2C协议设计原理

在这里插入图片描述

1.1 通信协议的设计需求

假设我们需要设计一个通信协议,用于单片机与外部模块的数据交换。这个协议需要满足以下要求:

  1. 节省资源:将全双工改为半双工,减少通信线数量
  2. 应答机制:确保数据传输的可靠性
  3. 多设备支持:一条总线可以挂载多个设备
  4. 同步时序:降低对硬件的依赖,便于软件模拟

1.2 I2C协议的硬件规定

I2C总线只需要两根线:

  • SCL(Serial Clock):串行时钟线,由主机控制
  • SDA(Serial Data):串行数据线,半双工传输
硬件电路特点
// I2C硬件配置要求
// 1. 所有设备的SCL连接在一起
// 2. 所有设备的SDA连接在一起  
// 3. SCL和SDA都需要外接上拉电阻(通常4.7kΩ)
// 4. 所有设备的引脚都配置为开漏输出模式

开漏输出的优势:

  • 完全杜绝电源短路现象
  • 避免引脚模式的频繁切换
  • 实现"线与"功能:任何设备输出低电平,总线就是低电平

1.3 I2C协议的软件规定

基本时序单元

I2C协议由6个基本时序单元组成:

  1. 起始条件(Start):SCL高电平期间,SDA从高电平切换到低电平
  2. 终止条件(Stop):SCL高电平期间,SDA从低电平切换到高电平
  3. 发送一个字节:SCL低电平期间变换数据,高电平期间读取数据(高位先行)
  4. 接收一个字节:同发送,但数据方向相反
  5. 发送应答:主机发送应答位(0表示应答,1表示非应答)
  6. 接收应答:主机接收从机的应答位
完整时序结构

指定地址写时序:

起始条件 → 从机地址+写 → 应答 → 寄存器地址 → 应答 → 数据 → 应答 → 终止条件

指定地址读时序(复合格式):

起始条件 → 从机地址+写 → 应答 → 寄存器地址 → 应答 → 
重复起始 → 从机地址+读 → 应答 → 数据 → 非应答 → 终止条件

在这里插入图片描述

第二章:MPU6050传感器介绍

2.1 MPU6050简介

MPU6050是一款六轴姿态传感器,集成了:

  • 三轴加速度计:测量X、Y、Z轴的加速度值
  • 三轴陀螺仪:测量X、Y、Z轴的角速度值
  • 温度传感器:测量芯片温度

2.2 传感器工作原理

加速度计原理

加速度计本质上是一个测力计,通过测量重力在各轴上的分量来确定倾斜角度:

  • 静止状态下,只受重力影响,可以测量倾斜角度
  • 运动状态下,会受到惯性力影响,不适合直接计算角度
陀螺仪原理

陀螺仪基于角动量守恒原理,测量各轴的角速度:

  • 不受外部加速度影响,具有动态稳定性
  • 长时间积分会产生漂移,不具有静态稳定性
数据融合

通过互补滤波或卡尔曼滤波算法,融合加速度计和陀螺仪数据,可以获得精确稳定的姿态角。

2.3 MPU6050寄存器配置

// 重要寄存器地址定义
#define MPU6050_SLAVE_ADDRESS    0xD0  // 从机地址(包含读写位)
#define MPU6050_SMPLRT_DIV       0x19  // 采样率分频器
#define MPU6050_CONFIG           0x1A  // 配置寄存器
#define MPU6050_GYRO_CONFIG      0x1B  // 陀螺仪配置
#define MPU6050_ACCEL_CONFIG     0x1C  // 加速度计配置
#define MPU6050_PWR_MGMT_1       0x6B  // 电源管理1
#define MPU6050_PWR_MGMT_2       0x6C  // 电源管理2
#define MPU6050_WHO_AM_I         0x75  // 设备ID寄存器// 数据寄存器地址
#define MPU6050_ACCEL_XOUT_H     0x3B  // 加速度X轴高字节
#define MPU6050_ACCEL_XOUT_L     0x3C  // 加速度X轴低字节
// ... 其他数据寄存器

在这里插入图片描述

第三章:软件模拟I2C实现

在这里插入图片描述

在这里插入图片描述

3.1 GPIO配置

// 软件I2C初始化函数
void MyI2C_Init(void)
{// 开启GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置GPIO为开漏输出模式GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;  // PB10(SCL), PB11(SDA)GPIO_Init(GPIOB, &GPIO_InitStructure);// 释放总线,设置为高电平GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}

3.2 引脚操作封装

为了便于移植和修改,将引脚操作进行封装:

// 引脚操作封装函数
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);Delay_us(10);  // 软件延时,确保时序稳定
}void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);Delay_us(10);
}uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return BitValue;
}

3.3 基本时序单元实现

起始条件
void MyI2C_Start(void)
{MyI2C_W_SDA(1);  // 确保SDA为高电平MyI2C_W_SCL(1);  // 确保SCL为高电平MyI2C_W_SDA(0);  // SDA下降沿,产生起始条件MyI2C_W_SCL(0);  // 拉低SCL,准备数据传输
}
终止条件
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);  // 确保SDA为低电平MyI2C_W_SCL(1);  // 释放SCLMyI2C_W_SDA(1);  // SDA上升沿,产生终止条件
}
发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i++)  // 循环8次,发送8位数据{// SCL低电平期间变换数据(高位先行)MyI2C_W_SDA(Byte & (0x80 >> i));MyI2C_W_SCL(1);  // 释放SCL,从机读取数据MyI2C_W_SCL(0);  // 拉低SCL,准备下一位}
}
接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00;MyI2C_W_SDA(1);  // 主机释放SDA,防止干扰从机发送for (i = 0; i < 8; i++)  // 循环8次,接收8位数据{MyI2C_W_SCL(1);  // 释放SCL,从机变换数据if (MyI2C_R_SDA() == 1)  // SCL高电平期间读取数据{Byte |= (0x80 >> i);  // 如果读到1,对应位置1}MyI2C_W_SCL(0);  // 拉低SCL,准备下一位}return Byte;
}
应答机制
// 发送应答
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);  // 0表示应答,1表示非应答MyI2C_W_SCL(1);       // 产生时钟脉冲MyI2C_W_SCL(0);
}// 接收应答
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);       // 主机释放SDAMyI2C_W_SCL(1);       // 产生时钟脉冲AckBit = MyI2C_R_SDA(); // 读取应答位MyI2C_W_SCL(0);return AckBit;
}

3.4 MPU6050驱动实现

寄存器读写函数
// 写寄存器函数
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();                           // 起始条件MyI2C_SendByte(MPU6050_SLAVE_ADDRESS);   // 发送从机地址+写MyI2C_ReceiveAck();                      // 接收应答MyI2C_SendByte(RegAddress);              // 发送寄存器地址MyI2C_ReceiveAck();                      // 接收应答MyI2C_SendByte(Data);                    // 发送数据MyI2C_ReceiveAck();                      // 接收应答MyI2C_Stop();                            // 终止条件
}// 读寄存器函数
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;// 第一阶段:指定寄存器地址MyI2C_Start();                           // 起始条件MyI2C_SendByte(MPU6050_SLAVE_ADDRESS);   // 发送从机地址+写MyI2C_ReceiveAck();                      // 接收应答MyI2C_SendByte(RegAddress);              // 发送寄存器地址MyI2C_ReceiveAck();                      // 接收应答// 第二阶段:读取数据MyI2C_Start();                           // 重复起始条件MyI2C_SendByte(MPU6050_SLAVE_ADDRESS | 0x01); // 发送从机地址+读MyI2C_ReceiveAck();                      // 接收应答Data = MyI2C_ReceiveByte();              // 接收数据MyI2C_SendAck(1);                        // 发送非应答(结束传输)MyI2C_Stop();                            // 终止条件return Data;
}
MPU6050初始化
void MPU6050_Init(void)
{MyI2C_Init();  // 初始化I2C通信// 配置电源管理寄存器1:解除睡眠,选择陀螺仪时钟MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);// 配置电源管理寄存器2:所有轴正常工作MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);// 配置采样率分频器:采样率 = 陀螺仪输出频率 / (1 + 分频值)MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);// 配置数字低通滤波器:最平滑滤波MPU6050_WriteReg(MPU6050_CONFIG, 0x06);// 配置陀螺仪:最大量程 ±2000°/sMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);// 配置加速度计:最大量程 ±16gMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
数据读取函数
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;// 读取加速度X轴数据DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX = (DataH << 8) | DataL;// 读取加速度Y轴数据DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY = (DataH << 8) | DataL;// 读取加速度Z轴数据DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ = (DataH << 8) | DataL;// 读取陀螺仪X轴数据DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX = (DataH << 8) | DataL;// 读取陀螺仪Y轴数据DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY = (DataH << 8) | DataL;// 读取陀螺仪Z轴数据DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ = (DataH << 8) | DataL;
}// 获取设备ID
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

第四章:硬件I2C实现

4.1 STM32 I2C外设简介

STM32内部集成了硬件I2C收发电路,具有以下特点:

  • 支持多主机模型
  • 支持7位/10位地址模式
  • 支持标准速度(100kHz)和快速模式(400kHz)
  • 支持DMA传输
  • 兼容SMBus协议

4.2 硬件I2C配置

GPIO和时钟配置
void HardwareI2C_Init(void)
{// 开启I2C2和GPIOB时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置GPIO为复用开漏模式GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;    // 复用开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;  // PB10(SCL), PB11(SDA)GPIO_Init(GPIOB, &GPIO_InitStructure);
}
I2C外设配置
void HardwareI2C_Init(void)
{// ... GPIO配置代码 ...// 配置I2C外设I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;                    // I2C模式I2C_InitStructure.I2C_ClockSpeed = 50000;                     // 时钟频率50kHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;            // 时钟占空比2:1I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;                  // 使能应答I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址I2C_InitStructure.I2C_OwnAddress1 = 0x00;                     // 自身地址I2C_Init(I2C2, &I2C_InitStructure);// 使能I2C外设I2C_Cmd(I2C2, ENABLE);
}

4.3 事件等待机制

硬件I2C使用事件驱动的方式工作,需要等待相应的事件发生:

// 等待事件函数(带超时机制)
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout = 10000;while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS){Timeout--;if (Timeout == 0){break;  // 超时退出,防止程序卡死}}
}

4.4 硬件I2C读写实现

写寄存器函数
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{// 产生起始条件I2C_GenerateSTART(I2C2, ENABLE);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);  // 等待EV5事件// 发送从机地址+写I2C_Send7bitAddress(I2C2, MPU6050_SLAVE_ADDRESS, I2C_Direction_Transmitter);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);  // 等待EV6事件// 发送寄存器地址I2C_SendData(I2C2, RegAddress);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);  // 等待EV8事件// 发送数据I2C_SendData(I2C2, Data);MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);   // 等待EV8_2事件// 产生终止条件I2C_GenerateSTOP(I2C2, ENABLE);
}
读寄存器函数
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_SLAVE_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_SLAVE_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);  // 等待EV7事件Data = I2C_ReceiveData(I2C2);  // 读取数据// 恢复应答配置I2C_AcknowledgeConfig(I2C2, ENABLE);return Data;
}

第五章:数据处理与应用

5.1 数据转换

MPU6050输出的是16位有符号数据,需要根据配置的量程进行转换:

// 数据转换函数
float MPU6050_GetAccel(int16_t AccelRaw)
{// 量程配置为±16g时的转换return (float)AccelRaw / 32768.0 * 16.0;  // 单位:g
}float MPU6050_GetGyro(int16_t GyroRaw)
{// 量程配置为±2000°/s时的转换return (float)GyroRaw / 32768.0 * 2000.0;  // 单位:°/s
}

5.2 主函数应用示例

int main(void)
{int16_t AX, AY, AZ, GX, GY, GZ;uint8_t ID;// 系统初始化OLED_Init();        // OLED显示屏初始化MPU6050_Init();     // MPU6050初始化// 读取设备ID验证通信ID = MPU6050_GetID();OLED_ShowString(1, 1, "ID:");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);Delay_ms(100);  // 延时100ms}
}

第六章:软件I2C vs 硬件I2C对比

6.1 优缺点对比

特性软件I2C硬件I2C
引脚灵活性任意GPIO口固定引脚
资源占用占用CPU时间占用硬件外设
实现难度相对简单需要理解事件机制
传输效率较低较高
时序精度依赖软件延时硬件自动控制
多主机支持需要额外实现硬件原生支持
DMA支持不支持支持

6.2 选择建议

选择软件I2C的情况:

  • 硬件I2C资源不足
  • 需要多路I2C通信
  • 对时序要求不高
  • 希望代码简单易懂

选择硬件I2C的情况:

  • 对传输效率要求高
  • 需要多主机通信
  • 需要DMA传输
  • CPU资源紧张

第七章:常见问题与解决方案

7.1 通信异常处理

// 超时等待机制
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout = 10000;while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS){Timeout--;if (Timeout == 0){// 超时处理:复位I2C外设I2C_SoftwareResetCmd(I2Cx, ENABLE);I2C_SoftwareResetCmd(I2Cx, DISABLE);break;}}
}

7.2 常见错误及解决方法

  1. 读取ID失败

    • 检查接线是否正确
    • 确认上拉电阻是否连接(模块内置或外接4.7kΩ)
    • 验证从机地址是否正确
    • 检查电源供电是否正常
  2. 数据读取异常

    • 确认MPU6050已解除睡眠模式
    • 检查寄存器配置是否正确
    • 验证I2C时序是否符合要求
  3. 程序卡死

    • 添加超时等待机制
    • 检查I2C总线是否被占用
    • 确认中断配置是否冲突

7.3 调试技巧

// I2C总线扫描函数
void I2C_Scanner(void)
{uint8_t i;uint8_t ack;printf("I2C总线扫描结果:\r\n");for (i = 0; i < 128; i++){MyI2C_Start();MyI2C_SendByte(i << 1);  // 发送地址+写ack = MyI2C_ReceiveAck();MyI2C_Stop();if (ack == 0)  // 收到应答{printf("发现设备地址: 0x%02X\r\n", i);}Delay_ms(10);}
}

第八章:进阶应用

8.1 多字节连续读写

在这里插入图片描述

// 连续读取多个寄存器
void MPU6050_ReadMultiReg(uint8_t RegAddress, uint8_t *Data, uint8_t Length)
{uint8_t i;// 指定起始地址MyI2C_Start();MyI2C_SendByte(MPU6050_SLAVE_ADDRESS);MyI2C_ReceiveAck();MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();// 重复起始,切换到读模式MyI2C_Start();MyI2C_SendByte(MPU6050_SLAVE_ADDRESS | 0x01);MyI2C_ReceiveAck();// 连续读取数据for (i = 0; i < Length; i++){Data[i] = MyI2C_ReceiveByte();if (i == Length - 1){MyI2C_SendAck(1);  // 最后一个字节发送非应答}else{MyI2C_SendAck(0);  // 其他字节发送应答}}MyI2C_Stop();
}// 优化的数据读取函数
void MPU6050_GetDataFast(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t Data[14];  // 连续读取14个字节(包含温度数据)// 从加速度X轴高字节开始连续读取MPU6050_ReadMultiReg(MPU6050_ACCEL_XOUT_H, Data, 14);// 数据解析*AccX = (Data[0] << 8) | Data[1];*AccY = (Data[2] << 8) | Data[3];*AccZ = (Data[4] << 8) | Data[5];// Data[6]和Data[7]是温度数据,这里跳过*GyroX = (Data[8] << 8) | Data[9];*GyroY = (Data[10] << 8) | Data[11];*GyroZ = (Data[12] << 8) | Data[13];
}

8.2 中断驱动的I2C通信

// 使用中断方式的硬件I2C
volatile uint8_t I2C_TxBuffer[10];
volatile uint8_t I2C_RxBuffer[10];
volatile uint8_t I2C_TxIndex = 0;
volatile uint8_t I2C_RxIndex = 0;
volatile uint8_t I2C_Direction = 0;  // 0:发送, 1:接收void I2C2_EV_IRQHandler(void)
{if (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)){// EV5事件:起始条件已发送I2C_Send7bitAddress(I2C2, MPU6050_SLAVE_ADDRESS, I2C_Direction ? I2C_Direction_Receiver : I2C_Direction_Transmitter);}else if (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){// EV6事件:地址发送完成,进入发送模式I2C_SendData(I2C2, I2C_TxBuffer[I2C_TxIndex++]);}else if (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING)){// EV8事件:字节正在发送if (I2C_TxIndex < sizeof(I2C_TxBuffer)){I2C_SendData(I2C2, I2C_TxBuffer[I2C_TxIndex++]);}}else if (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){// EV8_2事件:字节发送完成I2C_GenerateSTOP(I2C2, ENABLE);}// ... 接收相关事件处理
}

8.3 DMA传输

// 配置DMA进行I2C数据传输
void I2C_DMA_Config(void)
{DMA_InitTypeDef DMA_InitStructure;// 开启DMA时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 配置DMA用于I2C发送DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&I2C2->DR;DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)I2C_TxBuffer;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;DMA_InitStructure.DMA_BufferSize = sizeof(I2C_TxBuffer);DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel4, &DMA_InitStructure);// 使能I2C的DMA请求I2C_DMACmd(I2C2, ENABLE);
}

第九章:姿态解算基础

9.1 数据融合原理

加速度计和陀螺仪各有优缺点,通过数据融合可以获得更准确的姿态信息:

// 互补滤波算法示例
typedef struct {float Roll;   // 横滚角float Pitch;  // 俯仰角float Yaw;    // 偏航角
} EulerAngle_t;EulerAngle_t ComplementaryFilter(int16_t ax, int16_t ay, int16_t az,int16_t gx, int16_t gy, int16_t gz,float dt)
{static EulerAngle_t angle = {0};float alpha = 0.98;  // 互补滤波系数// 加速度计计算角度(静态稳定)float acc_roll = atan2(ay, az) * 180.0 / 3.14159;float acc_pitch = atan2(-ax, sqrt(ay*ay + az*az)) * 180.0 / 3.14159;// 陀螺仪积分角度(动态稳定)angle.Roll += gx * dt;angle.Pitch += gy * dt;angle.Yaw += gz * dt;// 互补滤波融合angle.Roll = alpha * angle.Roll + (1 - alpha) * acc_roll;angle.Pitch = alpha * angle.Pitch + (1 - alpha) * acc_pitch;return angle;
}

9.2 卡尔曼滤波

// 简化的卡尔曼滤波器
typedef struct {float Q_angle;   // 过程噪声协方差float Q_bias;    // 过程噪声协方差float R_measure; // 测量噪声协方差float angle;     // 角度float bias;      // 偏差float rate;      // 角速度float P[2][2];   // 误差协方差矩阵
} KalmanFilter_t;float KalmanFilter_Update(KalmanFilter_t *kf, float newAngle, float newRate, float dt)
{// 预测步骤kf->rate = newRate - kf->bias;kf->angle += dt * kf->rate;kf->P[0][0] += dt * (dt * kf->P[1][1] - kf->P[0][1] - kf->P[1][0] + kf->Q_angle);kf->P[0][1] -= dt * kf->P[1][1];kf->P[1][0] -= dt * kf->P[1][1];kf->P[1][1] += kf->Q_bias * dt;// 更新步骤float S = kf->P[0][0] + kf->R_measure;float K[2];K[0] = kf->P[0][0] / S;K[1] = kf->P[1][0] / S;float y = newAngle - kf->angle;kf->angle += K[0] * y;kf->bias += K[1] * y;float P00_temp = kf->P[0][0];float P01_temp = kf->P[0][1];kf->P[0][0] -= K[0] * P00_temp;kf->P[0][1] -= K[0] * P01_temp;kf->P[1][0] -= K[1] * P00_temp;kf->P[1][1] -= K[1] * P01_temp;return kf->angle;
}

第十章:项目实战案例

10.1 平衡车控制系统

// 平衡车PID控制器
typedef struct {float Kp, Ki, Kd;float error, last_error, integral;float output;
} PID_Controller_t;float PID_Calculate(PID_Controller_t *pid, float setpoint, float measured_value)
{pid->error = setpoint - measured_value;pid->integral += pid->error;// 积分限幅if (pid->integral > 100) pid->integral = 100;if (pid->integral < -100) pid->integral = -100;float derivative = pid->error - pid->last_error;pid->output = pid->Kp * pid->error + pid->Ki * pid->integral + pid->Kd * derivative;pid->last_error = pid->error;return pid->output;
}// 平衡车主控制函数
void BalanceCar_Control(void)
{static PID_Controller_t angle_pid = {50.0, 0.0, 0.5, 0};  // 角度环PIDstatic PID_Controller_t speed_pid = {10.0, 0.1, 0.0, 0};  // 速度环PIDint16_t ax, ay, az, gx, gy, gz;float pitch_angle, pitch_rate;float motor_output;// 读取传感器数据MPU6050_GetData(&ax, &ay, &az, &gx, &gy, &gz);// 计算俯仰角和角速度pitch_angle = atan2(-ax, az) * 180.0 / 3.14159;pitch_rate = gy / 131.0;  // 转换为°/s// 角度环控制float angle_output = PID_Calculate(&angle_pid, 0, pitch_angle);// 速度环控制(这里简化处理)motor_output = angle_output;// 电机控制输出Motor_SetSpeed((int16_t)motor_output);
}

10.2 四轴飞行器姿态控制

// 四轴飞行器姿态控制
typedef struct {float roll, pitch, yaw;PID_Controller_t roll_pid;PID_Controller_t pitch_pid;PID_Controller_t yaw_pid;
} FlightController_t;void Quadcopter_Control(FlightController_t *fc)
{int16_t ax, ay, az, gx, gy, gz;EulerAngle_t current_angle;EulerAngle_t target_angle = {0, 0, 0};  // 目标姿态// 读取传感器数据MPU6050_GetData(&ax, &ay, &az, &gx, &gy, &gz);// 姿态解算current_angle = ComplementaryFilter(ax, ay, az, gx, gy, gz, 0.01);// 三轴PID控制float roll_output = PID_Calculate(&fc->roll_pid, target_angle.Roll, current_angle.Roll);float pitch_output = PID_Calculate(&fc->pitch_pid, target_angle.Pitch, current_angle.Pitch);float yaw_output = PID_Calculate(&fc->yaw_pid, target_angle.Yaw, current_angle.Yaw);// 电机混控输出int16_t motor1 = 1000 + roll_output + pitch_output + yaw_output;int16_t motor2 = 1000 - roll_output + pitch_output - yaw_output;int16_t motor3 = 1000 - roll_output - pitch_output + yaw_output;int16_t motor4 = 1000 + roll_output - pitch_output - yaw_output;// 输出到电机PWM_SetDutyCycle(1, motor1);PWM_SetDutyCycle(2, motor2);PWM_SetDutyCycle(3, motor3);PWM_SetDutyCycle(4, motor4);
}

总结

本教程详细介绍了STM32 I2C通信的完整实现过程,从协议原理到实际应用,涵盖了以下要点:

  1. I2C协议原理:深入理解协议的设计思想和硬件要求
  2. 软件模拟实现:使用GPIO手动实现I2C时序,灵活性高
  3. 硬件外设实现:利用STM32内置I2C外设,效率更高
  4. MPU6050应用:完整的传感器驱动开发流程
  5. 进阶应用:多字节传输、中断、DMA等高级功能
  6. 姿态解算:数据融合算法的基础应用
  7. 项目实战:平衡车和四轴飞行器的控制系统

通过本教程的学习,您应该能够:

  • 深入理解I2C通信协议的工作原理
  • 熟练掌握软件和硬件两种I2C实现方式
  • 独立完成传感器驱动程序的开发
  • 为后续的项目开发打下坚实基础

希望这份教程能够帮助您在嵌入式开发的道路上更进一步!


参考资料:

  • STM32F103数据手册
  • MPU6050数据手册
  • I2C总线规范
  • STM32标准外设库文档

作者声明:
本教程基于实际项目经验整理,所有代码均经过测试验证。如有疑问或建议,欢迎交流讨论。

http://www.dtcms.com/a/300994.html

相关文章:

  • 一文快速了解Docker和命令详解
  • 模拟实现python的sklearn库中的Bunch类以及 load_iris 功能
  • 文件权限标记机制在知识安全共享中的应用实践
  • minio 对象存储
  • java的break能加标签,return可以加标签吗
  • 从一副蓝牙耳机里get倍思的“实用而美”
  • Python 程序设计讲义(23):循环结构——循环控制语句 break 与 continue
  • 背包DP之多重背包
  • 边缘提取算法结合深度学习的肺结节分割预测
  • 「日拱一码」040 机器学习-不同模型可解释方法
  • 【机器学习】第七章 特征工程
  • 【机器学习-3】 | 决策树与鸢尾花分类实践篇
  • 探索 Linux 调试利器:GDB 入门与实战指南
  • 在分布式的远程调用中proxy和stub角色区别
  • C++ 多线程 std::thread::get_id
  • 数独求解器与生成器(回溯算法实现)
  • Python|OpenCV-实现对颜色进行检测(22)
  • PandasAI连接LLM进行智能数据分析
  • qt常用控件-06
  • 【人工智能】【Python】各种评估指标,PR曲线,ROC曲线,过采样,欠采样(Scikit-Learn实践)
  • PAT 甲级题目讲解:1010《Radix》
  • Spring之【Bean的生命周期】
  • [AI8051U入门第十一步]W5500-服务端
  • Linux实战:从零搭建基于LNMP+NFS+DNS的WordPress博客系统
  • (10)数据结构--排序
  • 设计模式(八)结构型:桥接模式详解
  • k8s的权限
  • Python队列算法:从基础到高并发系统的核心引擎
  • Cline与Cursor深度实战指南:AI编程助手的革命性应用
  • 【Canvas与标牌】优质资产六角星标牌