总线通信接口I²C以及SPI入门指南:从原理到实践

目录

[1 I²C 通信原理](#1 I²C 通信原理)

[1.1 基本工作原理](#1.1 基本工作原理)

[1.1.1 信号线功能](#1.1.1 信号线功能)

[1.1.2 电气特性](#1.1.2 电气特性)

[1.2 通信过程](#1.2 通信过程)

[1.2.1 基本信号条件](#1.2.1 基本信号条件)

[1.2.2 数据传输流程](#1.2.2 数据传输流程)

[1.2.3 时序控制](#1.2.3 时序控制)

[1.3 高级特性](#1.3 高级特性)

[1.3.1 总线仲裁](#1.3.1 总线仲裁)

[1.3.2 从设备寻址](#1.3.2 从设备寻址)

[1.3.3 时钟同步](#1.3.3 时钟同步)

[1.4 常见问题及解决方案](#1.4 常见问题及解决方案)

[1.4.1 通信故障排查](#1.4.1 通信故障排查)

[1.4.2 性能优化](#1.4.2 性能优化)

[2 SPI (Serial Peripheral Interface) 通信接口](#2 SPI (Serial Peripheral Interface) 通信接口)

[2.1 基本概述](#2.1 基本概述)

[2.1.1 信号线定义](#2.1.1 信号线定义)

[2.1.2 硬件连接](#2.1.2 硬件连接)

[2.2 协议层详解](#2.2 协议层详解)

[2.2.1 时钟模式详解](#2.2.1 时钟模式详解)

[2.3 数据传输格式](#2.3 数据传输格式)

[2.3.1 基本传输](#2.3.1 基本传输)

[2.3.2 DMA传输](#2.3.2 DMA传输)

[2.4 高级特性](#2.4 高级特性)

[2.4.1 多从机控制](#2.4.1 多从机控制)

[2.4.2 时序优化](#2.4.2 时序优化)

[2.5 性能优化与故障排除](#2.5 性能优化与故障排除)

[2.5.1 性能优化技巧](#2.5.1 性能优化技巧)

[2.5.2 常见问题解决](#2.5.2 常见问题解决)

[3 两种协议的主要区别](#3 两种协议的主要区别)

[4 实际应用示例](#4 实际应用示例)

[4.1 I²C通信](#4.1 I²C通信)

[4.1.1 EEPROM读写](#4.1.1 EEPROM读写)

[4.1.2 传感器通信](#4.1.2 传感器通信)

[4.2 SPI通信接口](#4.2 SPI通信接口)

[4.2.1 SD卡通信](#4.2.1 SD卡通信)

[4.2.2 液晶显示器驱动](#4.2.2 液晶显示器驱动)


1 I²C 通信原理

I²C 总线采用开漏输出结构,需要外接上拉电阻(典型值4.7kΩ或10kΩ)。这种结构有以下优点:

  • 实现了线与功能,允许多个设备共享总线
  • 避免了总线竞争造成的器件损坏
  • 可以实现时钟同步机制
  • 支持不同电压等级设备的互联(需要使用电平转换器)

上拉电阻的选择计算公式:

R(min) = (Vdd - Vol_max) / Iol_max
R(max) = tr / (0.8473 × Cb)

其中:
- Vdd:电源电压
- Vol_max:最大低电平输出电压
- Iol_max:最大低电平输出电流
- tr:上升时间
- Cb:总线电容

1.1 基本工作原理

I²C 是一种双线半双工同步串行通信协议,其工作原理如下:

1.1.1 信号线功能

  • SCL(时钟线):由主设备产生,控制数据传输时序
  • SDA(数据线):双向数据传输线,可由主设备或从设备控制
  • 两条线都采用开漏输出方式,需要外接上拉电阻
  1. SCL(时钟线)
    • 主设备产生时钟信号
    • 控制数据传输的时序
    • 支持时钟同步(时钟拉伸)机制
  2. SDA(数据线)
    • 双向数据传输
    • 在时钟高电平期间必须保持稳定
    • 仅在SCL为低电平时才能改变电平

1.1.2 电气特性

  • 开漏输出结构使得多个设备可以连接到同一总线
  • 上拉电阻将总线拉至高电平(空闲状态)
  • 设备通过下拉信号线至低电平来发送数据
  • 实现了"线与"功能,避免了总线冲突

1.2 通信过程

1.2.1 基本信号条件

  1. 起始条件(START):
    • SCL 为高电平时,SDA 从高电平跳变到低电平
    • 表示通信开始
  2. 停止条件(STOP):
    • SCL 为高电平时,SDA 从低电平跳变到高电平
    • 表示通信结束
  3. 数据有效性:
    • SCL 为高电平期间,SDA 必须保持稳定
    • 只有在 SCL 为低电平时,才能改变 SDA 的电平

**起始条件(S)和停止条件(P)**的详细时序要求:

cpp 复制代码
// 起始条件的详细实现
void I2C_GenerateStart(void) {
    // 确保总线空闲
    while(I2C_GetBusState() != I2C_BUS_IDLE);
    
    // 产生起始条件
    SDA_HIGH();
    SCL_HIGH();
    delay_us(4);          // 建立时间至少4.7µs
    SDA_LOW();           // 在SCL高电平期间拉低SDA
    delay_us(4);          // 保持时间至少4µs
    SCL_LOW();           // 准备发送数据
}

// 停止条件的详细实现
void I2C_GenerateStop(void) {
    SCL_LOW();
    SDA_LOW();
    delay_us(4);
    SCL_HIGH();
    delay_us(4);          // 建立时间至少4.7µs
    SDA_HIGH();          // 在SCL高电平期间拉高SDA
    delay_us(4);          // 总线释放时间至少4.7µs
}

1.2.2 数据传输流程

  1. 地址帧:
    • 7位或10位设备地址
    • 1位读写控制位(R/W)
    • 1位应答位(ACK)
  2. 数据帧:
    • 8位数据
    • 1位应答位
    • 可以连续发送多个数据帧

7位地址格式:

|--------|------|-----|
| 7位设备地址 | 读/写位 | ACK |

10位地址格式:

|---------|-----|--------|-----|
| 11110xx | R/W | 剩余8位地址 | ACK |

cpp 复制代码
+---------------+----------------+---+
| 11110xx | R/W | 剩余8位地址 | ACK |
+---------------+----------------+---+

1.2.3 时序控制

  • 标准模式(100kbps)和快速模式(400kbps)具有不同的时序要求
  • 设备通过拉低 SCL 来实现时钟同步
  • 支持时钟延展功能,从设备可以通过拉低 SCL 来降低通信速率
  • 写操作时序:
cpp 复制代码
// 完整的写操作示例
uint8_t I2C_WriteData(uint8_t deviceAddr, uint8_t regAddr, uint8_t *data, uint16_t len) {
    uint16_t i;
    
    // 1. 发送起始条件
    I2C_GenerateStart();
    
    // 2. 发送设备地址(写)
    if(I2C_SendByte(deviceAddr << 1 | 0x00) == I2C_NACK)
        return I2C_ERROR;
        
    // 3. 发送寄存器地址
    if(I2C_SendByte(regAddr) == I2C_NACK)
        return I2C_ERROR;
        
    // 4. 发送数据
    for(i = 0; i < len; i++) {
        if(I2C_SendByte(data[i]) == I2C_NACK)
            return I2C_ERROR;
    }
    
    // 5. 发送停止条件
    I2C_GenerateStop();
    
    return I2C_SUCCESS;
}
  • 读操作时序:
cpp 复制代码
// 完整的读操作示例
uint8_t I2C_ReadData(uint8_t deviceAddr, uint8_t regAddr, uint8_t *buffer, uint16_t len) {
    uint16_t i;
    
    // 1. 发送起始条件
    I2C_GenerateStart();
    
    // 2. 发送设备地址(写)
    if(I2C_SendByte(deviceAddr << 1 | 0x00) == I2C_NACK)
        return I2C_ERROR;
        
    // 3. 发送寄存器地址
    if(I2C_SendByte(regAddr) == I2C_NACK)
        return I2C_ERROR;
        
    // 4. 重复起始条件
    I2C_GenerateStart();
    
    // 5. 发送设备地址(读)
    if(I2C_SendByte(deviceAddr << 1 | 0x01) == I2C_NACK)
        return I2C_ERROR;
        
    // 6. 读取数据
    for(i = 0; i < len; i++) {
        buffer[i] = I2C_ReceiveByte();
        // 除最后一个字节外,其他都需要发送ACK
        if(i < len - 1)
            I2C_SendAck();
        else
            I2C_SendNack();
    }
    
    // 7. 发送停止条件
    I2C_GenerateStop();
    
    return I2C_SUCCESS;
}

1.3 高级特性

1.3.1 总线仲裁

  • 支持多主机操作
  • 通过 SDA 线进行仲裁
  • 当检测到 SDA 线上的实际电平与期望发送的电平不同时,主机失去总线控制权
cpp 复制代码
uint8_t I2C_CheckArbitration(void) {
    if(SDA_READ() != SDA_LastState) {
        // 失去仲裁,释放总线
        SDA_HIGH();
        SCL_HIGH();
        return I2C_LOST_ARBITRATION;
    }
    return I2C_WIN_ARBITRATION;
}

1.3.2 从设备寻址

  • 每个从设备都有唯一的地址
  • 支持通用呼叫地址(0x00)
  • 可以实现广播通信

1.3.3 时钟同步

  • 所有设备都可以延长低电平时间
  • 实现了不同速度设备的兼容
  • 确保数据传输的可靠性

I²C支持时钟同步机制,从设备可以通过拉低SCL来延长时钟周期,实现速度匹配:

cpp 复制代码
void I2C_WaitClockSync(void) {
    uint16_t timeout = 0xFFFF;
    
    // 等待时钟线释放
    while(SCL_READ() == 0 && timeout--)
        delay_us(1);
}

1.4 常见问题及解决方案

1.4.1 通信故障排查

  • 总线死锁:
cpp 复制代码
void I2C_BusReset(void) {
    uint8_t i;
    
    // 产生9个时钟脉冲释放总线
    for(i = 0; i < 9; i++) {
        SCL_HIGH();
        delay_us(5);
        SCL_LOW();
        delay_us(5);
    }
    
    // 产生停止条件
    I2C_GenerateStop();
}
  • 超时处理:
cpp 复制代码
#define I2C_TIMEOUT_MAX 0xFFFF

uint8_t I2C_WaitFlag(uint8_t flag, uint8_t status) {
    uint16_t timeout = I2C_TIMEOUT_MAX;
    
    while((I2C_GetFlag(flag) != status) && timeout--)
        delay_us(1);
        
    return (timeout > 0) ? I2C_SUCCESS : I2C_TIMEOUT;
}

1.4.2 性能优化

  • 速率计算:
cpp 复制代码
// I2C速率计算公式
float I2C_CalculateSpeed(uint32_t PCLK1, uint16_t CCR) {
    return PCLK1 / (CCR * 2);  // 标准模式下的计算
}
  • DMA传输示例:
cpp 复制代码
void I2C_DMA_Config(void) {
    DMA_InitTypeDef DMA_InitStructure;
    
    // 配置DMA通道
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)TxBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
    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_Init(DMA1_Channel6, &DMA_InitStructure);
}

2 SPI (Serial Peripheral Interface) 通信接口

2.1 基本概述

SPI 是一种全双工同步串行通信协议,使用四根信号线:

  • MOSI (Master Out Slave In):主机输出从机输入
  • MISO (Master In Slave Out):主机输入从机输出
  • SCK (Serial Clock):时钟信号
  • NSS/CS (Slave Select):从机选择信号

其基本工作原理如下:

1.信号同步机制

  • 通过 SCK 时钟信号实现数据同步
  • 主设备产生时钟信号,控制数据的发送和接收时序
  • 数据在时钟的上升沿或下降沿被采样,具体取决于配置的时钟模式

2.数据传输原理

  • 采用移位寄存器原理,数据位按位依次移出和移入
  • 主机和从机同时发送和接收数据,实现全双工通信
  • 每发送一位数据,移位寄存器同时移入一位数据
  • 8个时钟周期完成8位数据交换

3.片选机制

  • 通过 CS/SS 信号线选择要通信的从设备
  • CS 信号低电平有效,高电平时从设备处于非活动状态
  • 多个从设备共享 MOSI、MISO、SCK 信号线,但需要独立的 CS 线

其数据传输过程为:

1.基本传输流程

  • 主机拉低目标从机的 CS 信号线
  • 主机产生时钟信号
  • 数据通过 MOSI 和 MISO 线同时传输
  • 传输完成后,主机拉高 CS 信号线

2.时序控制

  • CPOL(时钟极性)决定时钟空闲时的电平状态
  • CPHA(时钟相位)决定在第一个还是第二个时钟边沿采样数据
  • 这两个参数组合形成四种不同的传输模式,设备必须使用相同的模式才能正确通信

2.1.1 信号线定义

SPI总线包含四根基本信号线:

  1. MOSI (Master Out Slave In):
    • 主机发送数据线
    • 从机接收数据线
    • 空闲时可设置为高阻态
  2. MISO (Master In Slave Out)
    • 主机接收数据线
    • 从机发送数据线
    • 未选中的从机必须将此线设为高阻态
  3. SCK (Serial Clock):
    • 由主机产生的时钟信号
    • 控制数据传输的同步
    • 频率可达数十MHz
  4. NSS/CS (Slave Select):
    • 片选信号,低电平有效
    • 可以是硬件方式或软件方式
    • 每个从机需要独立的片选线

STM32 SPI 配置示例:

cpp 复制代码
void SPI_Init(void) {
    SPI_InitTypeDef SPI_InitStructure;
    
    // 配置SPI参数
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 双线全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                  // 8位数据帧
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                         // 时钟极性
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                       // 时钟相位
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                          // 软件管理NSS
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 波特率预分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                 // 高位先发
    SPI_InitStructure.SPI_CRCPolynomial = 7;                           // CRC多项式
    
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
}

2.1.2 硬件连接

基本连接示例:

cpp 复制代码
// 硬件SPI引脚定义
#define SPI_SCK_PIN     GPIO_Pin_5
#define SPI_MISO_PIN    GPIO_Pin_6
#define SPI_MOSI_PIN    GPIO_Pin_7
#define SPI_NSS_PIN     GPIO_Pin_4

// GPIO初始化配置
void SPI_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // SCK, MOSI配置为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = SPI_SCK_PIN | SPI_MOSI_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // MISO配置为浮空输入
    GPIO_InitStructure.GPIO_Pin = SPI_MISO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // NSS配置为推挽输出(软件控制方式)
    GPIO_InitStructure.GPIO_Pin = SPI_NSS_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

2.2 协议层详解

2.2.1 时钟模式详解

SPI 有四种工作模式 ,由**CPOL(时钟极性)和 CPHA(时钟相位)**决定:

模式 CPOL CPHA 说明
0 0 0 空闲低电平,第一个边沿采样
1 0 1 空闲低电平,第二个边沿采样
2 1 0 空闲高电平,第一个边沿采样
3 1 1 空闲高电平,第二个边沿采样
cpp 复制代码
typedef enum {
    SPI_MODE0 = 0,  // CPOL=0, CPHA=0
    SPI_MODE1 = 1,  // CPOL=0, CPHA=1
    SPI_MODE2 = 2,  // CPOL=1, CPHA=0
    SPI_MODE3 = 3   // CPOL=1, CPHA=1
} SPI_Mode_TypeDef;

void SPI_SetMode(SPI_TypeDef* SPIx, SPI_Mode_TypeDef mode) {
    // 配置时钟极性
    SPIx->CR1 &= ~SPI_CR1_CPOL;
    SPIx->CR1 |= (mode & 0x02) ? SPI_CR1_CPOL : 0;
    
    // 配置时钟相位
    SPIx->CR1 &= ~SPI_CR1_CPHA;
    SPIx->CR1 |= (mode & 0x01) ? SPI_CR1_CPHA : 0;
}

2.3 数据传输格式

2.3.1 基本传输

cpp 复制代码
// 发送并接收一个字节
uint8_t SPI_TransferByte(uint8_t data) {
    // 等待发送缓冲区空
    while(!(SPI1->SR & SPI_SR_TXE));
    
    // 发送数据
    SPI1->DR = data;
    
    // 等待接收完成
    while(!(SPI1->SR & SPI_SR_RXNE));
    
    // 返回接收到的数据
    return SPI1->DR;
}

// 发送多个字节
void SPI_TransferMultiBytes(uint8_t *txData, uint8_t *rxData, uint16_t size) {
    for(uint16_t i = 0; i < size; i++) {
        rxData[i] = SPI_TransferByte(txData[i]);
    }
}

2.3.2 DMA传输

cpp 复制代码
void SPI_DMA_Config(void) {
    DMA_InitTypeDef DMA_InitStructure;
    
    // 配置发送DMA通道
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)TxBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
    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_Init(DMA1_Channel3, &DMA_InitStructure);
    
    // 使能SPI的DMA请求
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
}

2.4 高级特性

2.4.1 多从机控制

cpp 复制代码
// 多从机选择结构
typedef enum {
    SPI_SLAVE_1 = 0,
    SPI_SLAVE_2,
    SPI_SLAVE_3,
    SPI_SLAVE_MAX
} SPI_Slave_TypeDef;

// 片选控制
void SPI_SelectSlave(SPI_Slave_TypeDef slave) {
    // 先禁用所有从机
    GPIOA->BSRR = (1 << 4) | (1 << 5) | (1 << 6);
    
    // 使能选中的从机
    switch(slave) {
        case SPI_SLAVE_1:
            GPIOA->BRR = (1 << 4);
            break;
        case SPI_SLAVE_2:
            GPIOA->BRR = (1 << 5);
            break;
        case SPI_SLAVE_3:
            GPIOA->BRR = (1 << 6);
            break;
        default:
            break;
    }
}

2.4.2 时序优化

cpp 复制代码
// 优化的数据传输函数
uint8_t SPI_FastTransfer(uint8_t data) {
    volatile uint8_t dummy;
    
    // 清空接收缓冲区
    while(SPI1->SR & SPI_SR_RXNE) {
        dummy = SPI1->DR;
    }
    
    // 发送数据
    SPI1->DR = data;
    
    // 等待传输完成
    while(!(SPI1->SR & SPI_SR_RXNE));
    
    return SPI1->DR;
}

2.5 性能优化与故障排除

2.5.1 性能优化技巧

  • 时钟速率优化:
cpp 复制代码
// 计算最优分频系数
uint16_t SPI_CalculatePrescaler(uint32_t targetFreq) {
    uint32_t pclk = SystemCoreClock / 2;  // APB1时钟
    uint16_t prescaler = 0;
    
    while(pclk > targetFreq && prescaler < 7) {
        pclk /= 2;
        prescaler++;
    }
    
    return prescaler << 3;
}
  • 中断方式优化:
cpp 复制代码
void SPI_IRQHandler(void) {
    if(SPI1->SR & SPI_SR_RXNE) {
        // 接收到数据
        rxBuffer[rxIndex++] = SPI1->DR;
        
        if(rxIndex >= rxSize) {
            // 传输完成,禁用中断
            SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, DISABLE);
        }
    }
}

2.5.2 常见问题解决

  • 通信故障检测:
cpp 复制代码
uint8_t SPI_CheckConnection(void) {
    uint8_t retry = 0;
    uint8_t response;
    
    while(retry < 3) {
        response = SPI_TransferByte(0xFF);
        if(response != 0xFF)
            return SPI_SUCCESS;
        retry++;
    }
    
    return SPI_ERROR;
}
  • 总线复位:
cpp 复制代码
void SPI_BusReset(void) {
    // 禁用SPI
    SPI_Cmd(SPI1, DISABLE);
    
    // 重置控制寄存器
    SPI1->CR1 = 0;
    SPI1->CR2 = 0;
    
    // 清空状态寄存器
    volatile uint16_t dummy = SPI1->SR;
    
    // 重新初始化
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
}

3 两种协议的主要区别

  • 传输速率:
    • SPI 可以达到更高的传输速率(几十 Mbps)
    • I²C 标准模式为 100kbps,快速模式为 400kbps
  • 信号线数量:
    • SPI 需要至少4根线(MOSI、MISO、SCK、CS)
    • I²C 只需要2根线(SCL、SDA)
  • 通信方式:
    • SPI 是全双工通信
    • I²C 是半双工通信
  • 设备寻址:
    • SPI 使用独立的片选线选择从设备
    • I²C 使用地址寻址方式选择从设备
  • 总线控制:
    • SPI 主从关系固定
    • I²C 支持多主机操作和总线仲裁

4 实际应用示例

4.1 I²C通信

4.1.1 EEPROM读写

基于24C02的读写操作示例:

cpp 复制代码
// 页写操作
uint8_t EEPROM_PageWrite(uint8_t addr, uint8_t *data, uint8_t len) {
    if(len > 8) return I2C_ERROR;  // 24C02每页8字节
    
    return I2C_WriteData(EEPROM_ADDR, addr, data, len);
}

// 随机读操作
uint8_t EEPROM_RandomRead(uint8_t addr, uint8_t *buffer, uint8_t len) {
    return I2C_ReadData(EEPROM_ADDR, addr, buffer, len);
}

4.1.2 传感器通信

以MPU6050为例的初始化和数据读取:

cpp 复制代码
// MPU6050初始化
void MPU6050_Init(void) {
    // 复位设备
    I2C_WriteData(MPU6050_ADDR, PWR_MGMT_1, 0x80);
    delay_ms(100);
    
    // 唤醒设备
    I2C_WriteData(MPU6050_ADDR, PWR_MGMT_1, 0x00);
    
    // 配置采样率
    I2C_WriteData(MPU6050_ADDR, SMPLRT_DIV, 0x07);
    
    // 配置数字低通滤波器
    I2C_WriteData(MPU6050_ADDR, CONFIG, 0x06);
}

// 读取加速度数据
void MPU6050_ReadAcc(short *accData) {
    uint8_t buffer[6];
    
    I2C_ReadData(MPU6050_ADDR, ACCEL_XOUT_H, buffer, 6);
    
    accData[0] = (buffer[0] << 8) | buffer[1];  // X轴
    accData[1] = (buffer[2] << 8) | buffer[3];  // Y轴
    accData[2] = (buffer[4] << 8) | buffer[5];  // Z轴
}

4.2 SPI通信接口

4.2.1 SD卡通信

cpp 复制代码
// SD卡初始化序列
uint8_t SD_Init(void) {
    uint8_t retry = 0;
    uint8_t response;
    
    // 先发送至少74个时钟周期
    for(uint8_t i = 0; i < 10; i++) {
        SPI_TransferByte(0xFF);
    }
    
    // 发送CMD0,使卡进入SPI模式
    do {
        response = SD_SendCommand(CMD0, 0, 0x95);
        retry++;
    } while(response != 0x01 && retry < 200);
    
    if(retry == 200) return SD_ERROR;
    
    // 发送CMD1初始化卡
    retry = 0;
    do {
        response = SD_SendCommand(CMD1, 0, 0xFF);
        retry++;
    } while(response != 0x00 && retry < 200);
    
    return response == 0x00 ? SD_SUCCESS : SD_ERROR;
}

// 读取数据块
uint8_t SD_ReadBlock(uint32_t addr, uint8_t *buffer) {
    // 发送读命令
    if(SD_SendCommand(CMD17, addr, 0xFF) != 0x00)
        return SD_ERROR;
    
    // 等待数据令牌
    if(SD_WaitDataToken() != 0xFE)
        return SD_ERROR;
    
    // 读取512字节数据
    for(uint16_t i = 0; i < 512; i++) {
        buffer[i] = SPI_TransferByte(0xFF);
    }
    
    // 读取CRC(通常忽略)
    SPI_TransferByte(0xFF);
    SPI_TransferByte(0xFF);
    
    return SD_SUCCESS;
}

4.2.2 液晶显示器驱动

cpp 复制代码
// LCD初始化
void LCD_Init(void) {
    // 硬件复位
    LCD_RESET_LOW();
    delay_ms(100);
    LCD_RESET_HIGH();
    delay_ms(50);
    
    // 发送初始化命令序列
    LCD_WriteCommand(0x11);  // 退出睡眠模式
    delay_ms(120);
    
    LCD_WriteCommand(0x29);  // 开启显示
    LCD_WriteCommand(0x3A);  // 设置像素格式
    LCD_WriteData(0x55);     // 16位/像素
}

// 写命令
void LCD_WriteCommand(uint8_t cmd) {
    LCD_DC_LOW();           // 命令模式
    LCD_CS_LOW();           // 选中LCD
    
    SPI_TransferByte(cmd);
    
    LCD_CS_HIGH();          // 取消片选
}

// 写数据
void LCD_WriteData(uint8_t data) {
    LCD_DC_HIGH();          // 数据模式
    LCD_CS_LOW();           // 选中LCD
    
    SPI_TransferByte(data);
    
    LCD_CS_HIGH();          // 取消片选
}
相关推荐
mftang10 分钟前
一套极简易的直流无刷电机(Deng FOC)开发套件介绍
单片机·嵌入式硬件
水水阿水水11 分钟前
第一章:C++是C语言的扩充(一)
linux·c语言·数据结构·c++·算法
老王WHH6 小时前
STM32——系统滴答定时器(SysTick寄存器详解)
stm32·单片机·嵌入式硬件
_周游6 小时前
【C语言】_指针与数组
c语言·开发语言
码力全開7 小时前
C 语言奇幻之旅 - 第14篇:C 语言高级主题
服务器·c语言·开发语言·人工智能·算法
矮油0_o7 小时前
30天开发操作系统 第 12 天 -- 定时器
c语言·汇编·算法·操作系统
KBDYD10108 小时前
单片机控制
stm32·单片机·嵌入式硬件
UtopiaYouth8 小时前
第五章 Linux 网络编程
linux·c语言·ubuntu·网络编程·系统编程
黄金右肾9 小时前
STM32之CAN通讯(十一)
stm32·单片机·can·嵌入式软件
番茄老夫子9 小时前
STM32H7的SPI总线基础知识备忘
stm32·单片机·嵌入式硬件