目录

1 SPI协议

1.1 SPI协议

1.2 四线传输

1.3 传输速率

2 SPI协议的基本操作

2.1 数据传输

2.2 起始/停止信号

2.3 数据的有效性(以CPHA=1为例)

2.4 时钟极性和时钟相位

3 SPI的四种模式

3.1 SPI模式“0”

3.2 SPI模式“1”

3.3 SPI模式“2”

3.4 SPI模式“3”

4 SPI主模式下全双工发送和接收过程模式

4.1 BSY标志

4.2 TXE标志

4.3 RXNE标志

5 软/硬件SPI

5.1 软件SPI

5.2 硬件SPI

5.3 SPI初始化(以SPI模式0为例)

5.3.1 软件SPI初始化

5.3.2 硬件SPI初始化

5.4 交换一个字节(以SPI模式0为例)

5.4.1 软件SPI

5.4.2 硬件SPI


1 SPI协议

1.1 SPI协议

一种高速、全双工的同步串行通信协议,主要用于短距离通信。

1.2 四线传输

NSS:从机选择信号线,当多个设备与主机相连并选择指定从机通信时,就把指定从机所连接的NSS信号线置为低电平便可进行通信,置高电平似为结束,主机产生信号。

SCK:时钟信号线,用于同步数据传输,主机产生信号。

MOSI:主设备输出/从设备输入引脚,主机输出信号,从机输入信号,主机产生信号。

MISO:主设备输入/从设备输出引脚,从机输出信号,主机输入信号,从机产生信号。

1.3 传输速率

远大于I2C通信协议传输速率(可高达10MHz)。

2 SPI协议的基本操作

2.1 数据传输

数据以同步数据移位寄存器的方式进行传输并且每个时钟周期交换1位(1个字节=8位)。在前面我们说到SPI是四线传输,其中MOSI和MISO连接在数据移位寄存器上。发送时,数据从“发送缓冲区”加载到发送移位寄存器,再逐位移出到MOSI线;接收时,MISO线上的数据被逐位移入接收移位寄存器,最终存入“接收缓冲区”(注意:数据的逐位移出或移入均为同步执行,而“接收缓冲区”中的数据去向取决于主机的软件配置和系统设计)。SCK时钟信号线用于同步数据位传输。

2.2 起始/停止信号

起始信号:NSS信号线由高电平变为低电平

停止信号:NSS信号线由低电平变为高电平

2.3 数据的有效性(以CPOL=0,CPHA=1为例)

MOSI和MISO的数据在SCK时钟信号的上升沿触发,在下降沿采样(数据在时钟下降沿时刻数据有效,其他时刻数据无效)高电平表示数据“1”,低电平表示数据“0”.

2.4 时钟极性和时钟相位

时钟极性CPOL:指SCK在空闲状态(无数据传输状态)下的电平高低;当CPOL=0时表示低电平开始,通信时由低电平跳变为高电平;CPOL=1时表示高电平开始,通信时由高电平跳变为低电平。

时钟相位CPHA:指MOSI和MISO数据的采样时刻,当CPHA=0时MOSI和MISO数据线上的数据将在SCK时钟线的“第一个边沿”被采样,“第二个边沿”进行数据切换当CPHA=1时MOSI和MISO数据线上的数据将在SCK时钟线的“第二个边沿”被采样,“第一个边沿”进行数据切换

3 SPI的四种模式

3.1 SPI模式“0”

3.2 SPI模式“1”

3.3 SPI模式“2”

3.4 SPI模式“3”

4 SPI主模式下全双工发送和接收过程模式

4.1 BSY标志

  • 在SPI通信中,BSY标志是由硬件自动设置和清除的状态标志位,用于指示SPI总线当前是否处于忙碌状态
  • 当BSY标志为1时,表示SPI总线正在传输数据,禁止操作SPI控制寄存器(如写入数据或修改配置)。
  • 当BSY标志为0时,表示SPI总线空闲,允许配置或启动新的传输。
  • 主机开始发送数据时,BSY自动置位。
  • 移位寄存器为空且无新数据传输时,BSY自动清零。

4.2 TXE标志

  • TXE=1表示可以安全写入下一个数据,但不会阻塞传输
  • 当发送缓冲区为空(可写入新数据)时,硬件自动置位TXE=1。
  • 数据从发送缓冲区转移到移位寄存器后,TXE自动置1。
  • 写入数据到SPI_DR寄存器会清除TXE标志(硬件自动清除)。

4.3 RXNE标志

  • RXNE=1表示接收缓冲器有数据,可以读出;未及时读取数据,新接收的数据会覆盖旧数据
  • 当接收缓冲区有数据可读时,硬件自动置位RXNE=1。
  • 数据从移位寄存器完全移入接收缓冲区后,RXNE置1。
  • 读取SPI_DR寄存器会清除RXNE标志(硬件自动清除)。

5 软/硬件SPI

5.1 软件SPI

通过‌软件模拟时序‌实现的SPI通信协议(即普通GPIO引脚模拟SPI信号,如NSS、SCK、MOSI、MISO)。软件SPI完全依赖CPU通过代码控制每个时钟周期和数据位的电平翻转。

优点:灵活性高,可用任意GPIO引脚;兼容性强,可模拟非标准SPI时序;不需要硬件资源,不依赖芯片SPI外设。

缺点:速度慢,受CPU处理速度限制无法满足高速设备需求;CPU占用高,影响实时性;时序不稳定,受中断、任务调度影响;多从机管理复杂,需手动控制多个NSS引脚。

5.2 硬件SPI

通过微控制器中的专用硬件模块实现SPI通信,通过配置寄存器来自动管理时序。硬件模块负责自动生成时钟信号(SCK)并处理数据的移位操作。

优点:高速通信,支持高频时钟适合高速设备;CPU占用低,数据传输由硬件自动处理;时序精准,时钟边沿和数据采样由硬件控制,稳定性高。

缺点:引脚固定,必须使用芯片指定的SPI引脚;资源有限,芯片的SPI外设数量有限;配置复杂,调试门槛较高。

5.3 SPI初始化(以SPI模式0为例)

5.3.1 软件SPI初始化
void Model_SPI_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /*设置默认电平*/
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
    GPIO_ResetBits(GPIOA, GPIO_Pin_5);  
}
5.3.2 硬件SPI初始化
void Model_SPI_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);                     
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /*SPI初始化*/
    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);

    /*设置默认电平*/
    GPIO_SetBits(GPIOA, GPIO_Pin_4);

}

SPI_Mode(模式选择)

  • SPI_Mode_Master(主模式)
  • SPI_Mode_Slave(从模式)

SPI_Direction(通信方向选择)

  • SPI_Direction_2Lines_FullDuplex:SPI设置为双线双向全双工
  • SPI_Direction_2Lines_RxOnly:SPI设置为双线单向接收
  • SPI_Direction_1Line_Rx:SPI设置为单线双向接收
  • SPI_Direction_1Line_Tx:SPI设置为单线双向发送

SPI_DataSize(数据帧格式选择)

  • SPI_DataSize_16b:SPI 发送接收 16 位帧结构
  • SPI_DataSize_8b:SPI 发送接收 8 位帧结构

    SPI_FirstBit(先行位选择)

  • SPI_FisrtBit_MSB:数据传输从 MSB(高)位开始
  • SPI_FisrtBit_LSB:数据传输从 LSB(低)位开始

SPI_BaudRatePrescaler(选择波特率预分频值)

  • SPI_BaudRatePrescaler2:选择波特率预分频值为2
  • SPI_BaudRatePrescaler4:选择波特率预分频值为4
  • SPI_BaudRatePrescaler8:选择波特率预分频值为8
  • SPI_BaudRatePrescaler16:选择波特率预分频值为16
  • SPI_BaudRatePrescaler32:选择波特率预分频值为32
  • SPI_BaudRatePrescaler64:选择波特率预分频值为64
  • SPI_BaudRatePrescaler128:选择波特率预分频值为128
  • SPI_BaudRatePrescaler256:选择波特率预分频值为256

SPI_CPOL(时钟极性选择)

  • SPI_CPOL_High:时钟悬空高(CPOL=1)
  • SPI_CPOL_Low:时钟悬空低(CPOL=0)

SPI_CPHA(时钟相位选择)

  • SPI_CPHA_1Edge:数据捕获于第一个时钟沿(CPHA=0)
  • SPI_CPHA_2Edge:数据捕获于第二个时钟沿(CPHA=1)

SPI_NSS(NSS控制模式选择)

  • SPI_NSS_Hard:硬件控制NSS
  • SPI_NSS_Soft:软件控制NSS

SPI_CRCPolynomial(CRC多项式设置)默认值为7

5.4 交换一个字节(以SPI模式0为例)

5.4.1 软件SPI
uint8_t Model_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00;
    for (i = 0; i < 8; i ++)
    {
        Model_SPI_W_MOSI(!!(ByteSend & (0x80 >> i)));  //写
        GPIO_SetBits(GPIOA, GPIO_Pin_5);
        if (Model_SPI_R_MISO()){ByteReceive |= (0x80 >> i);}  //读
        GPIO_ResetBits(GPIOA, GPIO_Pin_5);
    }

    return ByteReceive; 
}
5.4.2 硬件SPI
uint8_t Model_SPI_SwapByte(uint8_t ByteSend)
{
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
    SPI_I2S_SendData(SPI1, ByteSend);
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);

    return SPI_I2S_ReceiveData(SPI1);
}

注:本篇文章涉及位操作和结构体内容如不明白可阅读往期内容。

注:本篇文章主要讲解了什么是SPI和软/硬件SPI等理论及少部分实操,后续会继续发表关于SPI的具体应用。

注:以上内容仅个人理解,不具备唯一性和绝对正确性,仅供参考。

Logo

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

更多推荐