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

发布于:2025-02-10 ⋅ 阅读:(57) ⋅ 点赞:(0)

目录

1 I²C 通信原理

1.1 基本工作原理

1.1.1 信号线功能

1.1.2 电气特性

1.2 通信过程

1.2.1 基本信号条件

1.2.2 数据传输流程

1.2.3 时序控制

1.3 高级特性

1.3.1 总线仲裁

1.3.2 从设备寻址

1.3.3 时钟同步

1.4 常见问题及解决方案

1.4.1 通信故障排查

1.4.2 性能优化 

2 SPI (Serial Peripheral Interface) 通信接口

2.1 基本概述

2.1.1 信号线定义

 2.1.2 硬件连接

2.2 协议层详解

2.2.1 时钟模式详解

2.3 数据传输格式

2.3.1 基本传输

2.3.2 DMA传输

2.4 高级特性

2.4.1 多从机控制

2.4.2 时序优化

2.5 性能优化与故障排除

2.5.1 性能优化技巧

2.5.2 常见问题解决

3 两种协议的主要区别

4 实际应用示例

4.1 I²C通信

4.1.1 EEPROM读写

4.1.2 传感器通信

4.2 SPI通信接口

4.2.1 SD卡通信

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)的详细时序要求:

// 起始条件的详细实现
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
+---------------+----------------+---+
| 11110xx | R/W | 剩余8位地址 | ACK |
+---------------+----------------+---+

1.2.3 时序控制

  • 标准模式(100kbps)和快速模式(400kbps)具有不同的时序要求
  • 设备通过拉低 SCL 来实现时钟同步
  • 支持时钟延展功能,从设备可以通过拉低 SCL 来降低通信速率
  •  写操作时序:
// 完整的写操作示例
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;
}
  •  读操作时序:
// 完整的读操作示例
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 线上的实际电平与期望发送的电平不同时,主机失去总线控制权
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来延长时钟周期,实现速度匹配:

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

1.4 常见问题及解决方案

1.4.1 通信故障排查

  • 总线死锁:
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();
}
  •  超时处理:
#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 性能优化 

  • 速率计算:
// I2C速率计算公式
float I2C_CalculateSpeed(uint32_t PCLK1, uint16_t CCR) {
    return PCLK1 / (CCR * 2);  // 标准模式下的计算
}
  •  DMA传输示例:
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 配置示例:

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 硬件连接

基本连接示例:

// 硬件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 空闲高电平,第二个边沿采样
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 基本传输

// 发送并接收一个字节
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传输

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 多从机控制

// 多从机选择结构
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 时序优化

// 优化的数据传输函数
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 性能优化技巧

  • 时钟速率优化:
// 计算最优分频系数
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;
}
  •  中断方式优化:
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 常见问题解决

  • 通信故障检测:
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;
}
  •  总线复位:
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的读写操作示例:

// 页写操作
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为例的初始化和数据读取:

// 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卡通信

// 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 液晶显示器驱动

// 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();          // 取消片选
}


网站公告

今日签到

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