目录
[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(数据线):双向数据传输线,可由主设备或从设备控制
- 两条线都采用开漏输出方式,需要外接上拉电阻
- SCL(时钟线) :
- 主设备产生时钟信号
- 控制数据传输的时序
- 支持时钟同步(时钟拉伸)机制
- SDA(数据线) :
- 双向数据传输
- 在时钟高电平期间必须保持稳定
- 仅在SCL为低电平时才能改变电平
1.1.2 电气特性
- 开漏输出结构使得多个设备可以连接到同一总线
- 上拉电阻将总线拉至高电平(空闲状态)
- 设备通过下拉信号线至低电平来发送数据
- 实现了"线与"功能,避免了总线冲突
1.2 通信过程
1.2.1 基本信号条件
- 起始条件(START):
- SCL 为高电平时,SDA 从高电平跳变到低电平
- 表示通信开始
- 停止条件(STOP):
- SCL 为高电平时,SDA 从低电平跳变到高电平
- 表示通信结束
- 数据有效性:
- 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 数据传输流程
- 地址帧:
- 7位或10位设备地址
- 1位读写控制位(R/W)
- 1位应答位(ACK)
- 数据帧:
- 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总线包含四根基本信号线:
- MOSI (Master Out Slave In):
- 主机发送数据线
- 从机接收数据线
- 空闲时可设置为高阻态
- MISO (Master In Slave Out) :
- 主机接收数据线
- 从机发送数据线
- 未选中的从机必须将此线设为高阻态
- SCK (Serial Clock):
- 由主机产生的时钟信号
- 控制数据传输的同步
- 频率可达数十MHz
- 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(); // 取消片选
}