SPI 时序分析:主从模式与时钟特性(CPOL/CPHA)
一、SPI 主从模式基础
SPI(Serial Peripheral Interface)是一种全双工同步串行通信协议,支持 1 主多从 架构。
1. 主模式配置(Host as Master)
o 生成时钟信号(SCK),控制数据传输速率。
o 通过 MOSI 发送数据,通过 MISO 接收数据。
o 管理从机片选信号(SS/CS,低电平有效)。
2. 从模式配置(Device as Slave)
• 核心职责:
o 响应主机时钟信号,在时钟边沿采样 / 发送数据。
o 仅当片选信号(SS/CS)有效时,参与数据传输。
• 关键特性:
o 从机无法主动发起传输,必须由主机通过 SS/CS 激活。
o 从机时钟频率由主机决定,需支持主机的最大时钟速率。
二、时钟极性(CPOL)与相位(CPHA)
SPI 定义了 4 种时序模式,通过 CPOL(Clock Polarity) 和 CPHA(Clock Phase) 配置,决定时钟空闲状态和数据采样时机。
1. 时钟极性(CPOL,SPI_CR1_CPOL)
• 定义:时钟信号在空闲状态(无传输时)的电平。
o CPOL = 0:空闲时 SCK 为 低电平(默认)。
o CPOL = 1:空闲时 SCK 为 高电平。
2. 时钟相位(CPHA,SPI_CR1_CPHA)
• 定义:数据在时钟的 第几个边沿 被采样。
o CPHA = 0:数据在 第一个时钟边沿(上升沿或下降沿,取决于 CPOL)采样,第二个边沿移位。
o CPHA = 1:数据在 第二个时钟边沿 采样,第一个边沿移位。
三、时序分析与配置步骤
主机配置示例(STM32 HAL)
// 配置 SPI 为模式 0(CPOL=0,CPHA=0)
SPI_HandleTypeDef hspi;
hspi.Instance = SPI1;
hspi.Init.Mode = SPI_MODE_MASTER;
hspi.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0(第一个边沿采样)
hspi.Init.NSS = SPI_NSS_SOFT; // 软件片选
HAL_SPI_Init(&hspi);
// 片选使能(拉低 SS 引脚)
HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_RESET);
// 发送数据(主发从收)
uint8_t tx_data = 0xAA;
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi, &tx_data, &rx_data, 1, 1000);
// 片选禁用(拉高 SS 引脚)
HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_SET);
从机时序适配(以自定义从机为例)
时钟同步:从机需在主机时钟边沿(根据 CPHA/CPOL)锁存数据
// Verilog 从机时序逻辑(模式 0)
always @(posedge sck) begin // CPOL=0,上升沿采样(CPHA=0)
if (cs_n == 0) begin // 片选有效
data_in <= mosi; // 主机发送数据,从机接收
miso <= data_out; // 从机发送数据,主机接收
end
end
项目 3:SPI Flash 读写
- 实现页编程与擦除操作
- 验证数据完整性(CRC 校验)
1. 硬件无关性设计
• 通过spi_flash_dev_t结构体配置 SPI 参数(时钟频率、CPOL/CPHA),支持不同硬件平台
• 依赖底层spi_transfer_byte()函数实现具体 SPI 通信(需根据实际平台如 STM32/ESP32 实现)
2. 安全机制
• 地址对齐检查:页编程要求地址按页对齐(address % PAGE_SIZE == 0)
• 操作超时处理:spi_flash_wait_ready()包含超时机制,避免死锁
• 写使能强制:所有写操作(编程 / 擦除)前必须调用写使能
// 写入流程(带CRC校验)
bool flash_write_with_crc(uint32_t address, const uint8_t *data, uint32_t len) {
// 1. 计算原始数据CRC
uint32_t crc = crc32_calculate(data, len);
// 2. 分页编程数据
for (uint32_t i = 0; i < len; i += SPI_FLASH_PAGE_SIZE) {
uint32_t page_addr = address + i;
uint16_t page_len = MIN(len - i, SPI_FLASH_PAGE_SIZE);
if (!spi_flash_page_program(page_addr, data + i, page_len)) {
return false;
}
}
// 3. 单独存储CRC值(建议存放在数据块末尾或专用区域)
uint8_t crc_bytes[4] = {
(crc >> 24) & 0xFF,
(crc >> 16) & 0xFF,
(crc >> 8) & 0xFF,
crc & 0xFF
};
return spi_flash_page_program(address + len, crc_bytes, 4);
}
// 读取校验流程
bool flash_verify_with_crc(uint32_t address, const uint8_t *expected_data, uint32_t len) {
uint8_t read_data[len + 4]; // 包含数据和CRC
if (!spi_flash_read_data(address, read_data, len + 4)) {
return false;
}
// 分离数据和存储的CRC
uint32_t stored_crc = ((uint32_t)read_data[len] << 24) |
((uint32_t)read_data[len+1] << 16) |
((uint32_t)read_data[len+2] << 8) |
read_data[len+3];
// 计算读取数据的CRC
uint32_t current_crc = crc32_calculate(read_data, len);
return (stored_crc == current_crc);
}