1. SPI协议基础与四种模式解析
SPI(Serial Peripheral Interface)作为一种同步串行通信协议,在嵌入式系统中扮演着重要角色。与I2C、UART相比,SPI以其全双工、高速率的特点在传感器、存储设备等场景中广泛应用。理解SPI协议的核心在于掌握其四种工作模式,这直接关系到设备间的正常通信。
1.1 SPI时钟极性与相位
SPI的四种模式由CPOL(Clock Polarity)和CPHA(Clock Phase)两个参数组合而成:
- CPOL=0:时钟空闲状态为低电平
- CPOL=1:时钟空闲状态为高电平
- CPHA=0:数据在时钟的第一个边沿采样
- CPHA=1:数据在时钟的第二个边沿采样
实际项目中,我曾遇到过因模式配置错误导致传感器数据读取全为0xFF的情况。通过逻辑分析仪捕获波形发现,主机与从机的时钟相位设置不匹配,调整CPHA后问题立即解决。
1.2 四种模式详解
| 模式 | CPOL | CPHA | 典型应用设备 |
|---|---|---|---|
| 0 | 0 | 0 | 多数SPI Flash |
| 1 | 0 | 1 | TI ADC芯片 |
| 2 | 1 | 0 | 某些RF模块 |
| 3 | 1 | 1 | STM32内置SPI |
注意:设备手册中通常会明确标注所需SPI模式,但有些国产芯片文档可能描述模糊,此时建议用逻辑分析仪验证实际通信波形。
1.3 模式选择实战技巧
- 交叉验证法:当不确定设备模式时,可遍历四种模式进行测试
- 上拉电阻配置:模式3(CPOL=1)时,建议SCK线上拉
- 首字节试探:发送0xAA或0x55等特征值观察响应
- 示波器诊断:测量CS下降沿后的第一个时钟边沿特性
在STM32F4系列MCU中,通过SPI_CR1寄存器的BR[2:0]、CPOL和CPHA位可灵活配置这些参数。我曾用以下代码快速切换模式进行设备枚举:
void SPI_Mode_Test(SPI_HandleTypeDef *hspi) { uint8_t modes[4] = {SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3}; for(int i=0; i<4; i++) { hspi->Init.CLKPhase = modes[i] & 0x01; hspi->Init.CLKPolarity = (modes[i]>>1) & 0x01; HAL_SPI_Init(hspi); if(SPI_Device_Detect()) { printf("Found valid mode: %d\n", i); break; } } }2. 通用SPI驱动框架设计
2.1 Linux SPI子系统架构
Linux内核的SPI子系统采用分层设计,主要包含:
- 控制器驱动:处理SoC特定的SPI控制器硬件
- 核心层:提供注册、消息传递等基础设施
- 协议驱动:实现具体设备的功能逻辑
- 用户空间接口:通过sysfs、字符设备等暴露API
在RK3566平台上移植SPI Flash驱动时,我发现其控制器驱动需要特别处理DMA对齐问题。当传输长度非4字节对齐时,必须切换为CPU模式,否则会导致数据错位。
2.2 设备树配置实例
现代Linux驱动普遍采用设备树描述硬件连接关系。一个典型的SPI设备节点如下:
&spi1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi1_pins>; cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; flash@0 { compatible = "winbond,w25q128"; spi-max-frequency = <50000000>; reg = <0>; spi-tx-bus-width = <1>; spi-rx-bus-width = <1>; }; };关键参数说明:
spi-max-frequency:需根据设备手册和PCB走线质量确定spi-tx-bus-width:支持1/2/4线模式(标准SPI/Dual/Quad SPI)reg:CS线编号,对应控制器物理片选或GPIO模拟片选
2.3 通用驱动实现要点
- 传输超时处理:
#define SPI_TIMEOUT_MS 100 int spi_transfer(struct spi_device *spi, struct spi_message *mesg) { unsigned long timeout = jiffies + msecs_to_jiffies(SPI_TIMEOUT_MS); // ... 传输逻辑 if (time_after(jiffies, timeout)) { dev_err(&spi->dev, "SPI transfer timeout"); return -ETIMEDOUT; } }- DMA缓冲区分配:
void *dma_buf = kmalloc(len, GFP_DMA | GFP_KERNEL); if (!dma_buf) { dev_warn(dev, "Fallback to non-DMA mode"); dma_buf = kmalloc(len, GFP_KERNEL); }- 电源管理集成:
static int __maybe_unused spi_drv_suspend(struct device *dev) { struct spi_device *spi = to_spi_device(dev); if (spi->controller->auto_runtime_pm) pm_runtime_disable(&spi->dev); return pinctrl_pm_select_sleep_state(dev); }3. 典型SPI设备驱动实现
3.1 SPI Flash驱动详解
以W25Q128JV为例,其操作指令集包括:
基本操作:
- WRITE_ENABLE (0x06)
- PAGE_PROGRAM (0x02)
- SECTOR_ERASE (0x20)
- READ_DATA (0x03)
性能优化技巧:
- 使用Quad SPI模式提升吞吐量
- 批量写入时保持WP#引脚低电平
- 合理规划擦除粒度(4KB/32KB/64KB)
坏块处理方案:
static int w25q_check_bad_block(struct spi_device *spi, uint32_t addr) { uint8_t cmd[5] = {READ_ID, addr>>16, addr>>8, addr, 0}; uint8_t id[2]; spi_write_then_read(spi, cmd, 5, id, 2); return (id[0] == 0x00 && id[1] == 0x00) ? 1 : 0; }3.2 传感器驱动案例(ICM42688-P)
这款6轴IMU传感器对SPI时序要求严格:
关键配置步骤:
- 上电后等待1ms再初始化
- 读取WHO_AM_I寄存器(0x75)验证通信
- 配置FIFO和中断引脚
数据读取优化:
void icm42688_read_fifo(struct spi_device *spi) { uint8_t cmd = READ_FIFO | 0x80; // 设置读位 struct spi_transfer xfer[2] = { {.tx_buf = &cmd, .len = 1}, {.rx_buf = fifo_data, .len = FIFO_SIZE} }; spi_sync_transfer(spi, xfer, 2); }- 实测注意事项:
- 避免在SPI时钟高于10MHz时读取FIFO
- 温度采样期间保持CS稳定
- 使用硬件NCS信号可提升稳定性
4. 高级SPI应用与调试
4.1 DMA传输优化
在STM32F4平台上实现SPI DMA需注意:
- 内存对齐问题:
__attribute__((aligned(4))) uint8_t tx_buffer[256];- 双缓冲技巧:
HAL_SPI_Transmit_DMA(&hspi1, buf1, len); while(SPI_Is_Busy()) { if(SPI_Tx_Complete) { HAL_SPI_Transmit_DMA(&hspi1, buf2, len); Process_Data(buf1); } }- 错误恢复机制:
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if(hspi->ErrorCode & HAL_SPI_ERROR_DMA) { HAL_SPI_DMAStop(hspi); SPI_Reinit(hspi); } }4.2 菊花链(Daisy Chain)配置
多个SPI设备串联时:
硬件连接:
- 前级DOUT连接后级DIN
- 共享SCK和CS信号
- 终端设备接50Ω匹配电阻
软件处理:
void daisy_chain_write(SPI_HandleTypeDef *hspi, uint8_t *data, uint8_t dev_num) { uint8_t *chain_buf = malloc(dev_num); // 数据按设备顺序排列 for(int i=0; i<dev_num; i++) chain_buf[dev_num-1-i] = data[i]; HAL_SPI_Transmit(hspi, chain_buf, dev_num, 100); free(chain_buf); }4.3 逻辑分析仪调试技巧
使用Saleae Logic分析SPI信号时:
触发设置:
- 下降沿触发CS信号
- 设置合理的采样率(至少4倍于SCK频率)
常见问题诊断:
- 时钟极性反相:波形显示数据在错误边沿采样
- 模式不匹配:首个数据位出现位置异常
- 时序违规:设备要求tCSH(CS保持时间)不满足
Python解析脚本示例:
def parse_spi(analyzer): for packet in analyzer.get_spi_packets(): if packet.cs.value == 0: # CS active print(f"MOSI: {packet.mosi} MISO: {packet.miso}")在最近一个车载项目调试中,通过逻辑分析仪发现SPI Flash在-40℃时会出现时钟抖动问题。最终通过降低时钟频率至原值的1/4并增加CS保持时间解决了该问题。这提醒我们SPI参数配置需要兼顾全温度范围的稳定性。