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

发布于:2025-07-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

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);  // 释放SCL
    MyI2C_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);       // 主机释放SDA
    MyI2C_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°/s
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
    
    // 配置加速度计:最大量程 ±16g
    MPU6050_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;                     // 时钟频率50kHz
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;            // 时钟占空比2:1
    I2C_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};  // 角度环PID
    static PID_Controller_t speed_pid = {10.0, 0.1, 0.0, 0};  // 速度环PID
    
    int16_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标准外设库文档

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


网站公告

今日签到

点亮在社区的每一天
去签到