一、SPI通信基础

1.1 什么是SPI?

SPI(Serial Peripheral Interface)是由Motorola提出的全双工同步串行通信协议,广泛用于微控制器与外围设备(Flash、传感器、显示屏等)的高速数据传输。

核心特性:

  • 全双工通信(同时收发)

  • 最高时钟频率可达数十MHz

  • 主从架构(一主多从)

  • 硬件连接简单(4线制)

1.2 SPI物理接口

信号线 全称 作用
SCK Serial Clock 同步时钟(由主机产生)
MOSI Master Out Slave In 主机发送,从机接收
MISO Master In Slave Out 主机接收,从机发送
NSS Slave Select 从机片选(低电平有效)

二、SPI工作原理及时序

2.1 四种工作模式

由CPOL(时钟极性)CPHA(时钟相位)组合决定:

模式 CPOL CPHA 特性
0 0 0 时钟空闲低电平,第一个边沿采样
1 0 1 时钟空闲低电平,第二个边沿采样
2 1 0 时钟空闲高电平,第一个边沿采样
3 1 1 时钟空闲高电平,第二个边沿采样

关键点:

  • CPOL=0:SCK空闲时为低电平

  • CPOL=1:SCK空闲时为高电平

  • CPHA=0:在SCK第一个边沿(奇数边沿)采样数据

  • CPHA=1:在SCK第二个边沿(偶数边沿)采样数据

2.2 典型时序分析(模式0)

以主机发送0xAA(10101010)为例:

  1. NSS拉低:选中从机

  2. SCK产生脉冲:CPOL=0,空闲低电平

  3. 数据采样

    • MOSI在SCK上升沿改变数据

    • 从机在SCK下降沿采样数据

  4. 数据传输:高位(MSB)先发


三、STM32 SPI外设配置

3.1 硬件设计

以STM32F103C8T6与W25Q64 Flash芯片通信为例:

  • SCK -> PA5

  • MOSI -> PA7

  • MISO -> PA6

  • NSS -> PA4(软件控制)

3.2 CubeMX配置步骤

  1. 启用SPI1(模式选择为全双工主模式)

  2. 配置参数:

    • 时钟分频(PCLK2为72MHz时,分频256得281.25kHz)

    • 数据宽度:8位

    • 模式选择:Mode 0(CPOL=0, CPHA=0)

    • NSS选择:软件模式

  3. 生成代码

3.3 关键代码解析

初始化代码
SPI_HandleTypeDef hspi1;

void SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    HAL_SPI_Init(&hspi1);
}
数据收发函数
// 发送单字节并接收返回
uint8_t SPI_TransmitReceive(uint8_t data)
{
    uint8_t rx_data;
    HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1, 100);
    return rx_data;
}

// 读取Flash芯片ID(以W25Q64为例)
uint16_t W25Q_ReadID(void)
{
    uint8_t cmd = 0x9F; // JEDEC ID命令
    uint8_t id[3];
    
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低NSS
    HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
    HAL_SPI_Receive(&hspi1, id, 3, 100);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 释放NSS
    
    return (id[1] << 8) | id[2]; // 返回厂商ID和设备ID
}

四、调试技巧与常见问题

4.1 示波器诊断

  • 检测SCK波形:确认频率和极性是否符合预期

  • 观察MOSI/MISO:验证数据发送是否正确

  • 检查NSS信号:确保片选信号正常拉低

4.2 常见问题排查

  1. 无数据通信

    • 检查硬件连接是否短路/断路

    • 验证SPI模式(主/从)设置是否正确

    • 确认NSS信号是否有效

  2. 数据错位

    • 检查MSB/LSB设置

    • 确认时钟相位设置是否匹配从机要求

  3. 通信速率问题

    • 降低波特率测试

    • 检查电源稳定性


五、进阶应用:DMA+SPI

5.1 使用DMA提升效率

// 启用DMA传输
void SPI_DMA_Transmit(uint8_t *pData, uint16_t Size)
{
    HAL_SPI_Transmit_DMA(&hspi1, pData, Size);
    while (hspi1.State != HAL_SPI_STATE_READY); // 等待传输完成
}

// 接收数据
void SPI_DMA_Receive(uint8_t *pData, uint16_t Size)
{
    HAL_SPI_Receive_DMA(&hspi1, pData, Size);
    while (hspi1.State != HAL_SPI_STATE_READY);
}

六、总结

本文详细解析了SPI协议的工作原理,并通过STM32实战代码演示了通信流程。实际开发中需注意:

  1. 严格匹配主从设备的SPI模式

  2. 合理选择通信速率(兼顾稳定性和速度)

  3. 复杂场景建议使用DMA减轻CPU负担


创作不易,如果本文对您有帮助,请点赞收藏支持!
关于SPI的更多问题,欢迎在评论区留言讨论

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐