Skip to content

第十一章 SPI

11.1 SPI通信

11.1.1 SPI通信简介

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
  • 同步,全双工
  • 支持总线挂载多设备(一主多从,不支持多主机)

11.1.2 硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为 推挽输出 ,输入引脚配置为 浮空上拉输入

1706277356750

关于SPI引脚:

  1. MOSI为主机发送从机接收;MISO则是主机接收从机发送;SCK为时钟线,与I2C的SCL功能一样,都是为了实现同步通信;SS控制线的作用是选择从机,哪根线为低电平哪根线对应的从机就与主机通信,且同一时间只能用一个从机的SS为低电平。
  2. 未被选择的从机对应的MISO线,应为高阻态,防止主机接收数据错乱。
  3. 有几个从机就开辟几条SS线。

11.1.3 SPI移位结构

image-20250104194727114

SPI的移位原理:

当SPI同时接收和发送一个字节数据时,以高位先行为例(SPI硬件外设可以配置高位先行或低位先行);在第一个边沿,主机的最高位放到MOSI同时从机的最高位放到MISO,其他位一起往左移;在第二个边沿,主机的高位移到从机的低位,同时从机的高位移到主机的低位,依此八次仅完成一个字节数据的交换。

若SPI仅接收或发送时,忽略对方发送或接受的数据即可,一般这个无用的数据位0x00或0xFF。

11.1.4 SPI时基单元

  • 起始条件: SS从高电平切换到低电平
  • 终止条件: SS从低电平切换到高电平

1706280340445

SPI交换字节有多种模式可以选择,主要取决于CPOL(时钟极性)和CPHA(时钟相位),一个四种组合方式。

Danger

四种模式中,最常用的时模式 0 ,而最符合移位模型示例的是模式 1 ,主要学习一种常用的模式 0 就行了。

交换一个字节 (模式0)

  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
Danger

由于SCK第一个边沿就要移入数据,所以在起始条件生成以后,MOSI和MISO就要立刻移位数据,从而不会使得SCK第一个边沿移入的数据为空。

1706280519452

交换一个字节 (模式1)

  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

1706280584972

交换一个字节 (模式2)

  • CPOL=1:空闲状态时,SCK为高电平
  • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

1706280625411

交换一个字节 (模式3)

  • CPOL=1:空闲状态时,SCK为高电平
  • CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

1706280660766

11.1.5 SPI时序

主要根据芯片手册要求。

11.2 软件模拟SPI

GPIO引脚配置如下,三个输出为推挽输出,一个输入为上拉输入

image-20250105142619350

sw_spi.c

C
#include "sw_spi.h"


void SPI_Start(void)
{
    SS_L();
}

void SPI_Stop(void)
{
    SS_H();
}

uint8_t SPI_SwapByte(uint8_t SendByte)
{
    uint8_t i, ReceiveByte=0x00;
    for (i=0; i<8; i++)
    {
        // 高位先行
        MOSI_Write(SendByte & (0x80 >> i));
        // 移入数据
        SCK_H();
        // 读取MISO,并与ReceiveByte交换位
        if (MISO_Status == 1) {ReceiveByte |= MISO_Status;}
        SCK_L();
    }

    return ReceiveByte;
}

sw_spi.h

C
#ifndef SW_SPI_SW_SPI_H
#define SW_SPI_SW_SPI_H

#include "gpio.h"

void SPI_Start(void);
void SPI_Stop(void);
uint8_t SPI_SwapByte(uint8_t SendByte);

/* 这里用一种不同于I2C模拟的函数定义方式,不要感觉这样多此一举,
 * 很多商家给你的代码就有很多这样的定义,我们应当可以看懂
 */
#define MOSI_Write(BitValue)  HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, ((BitValue) ? GPIO_PIN_SET : GPIO_PIN_RESET))
#define SS_H()          HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_SET)
#define SS_L()          HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_RESET)
#define SCK_H()         HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET)
#define SCK_L()         HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET)
#define MISO_Status     HAL_GPIO_ReadPin(MISO_GPIO_Port, MISO_Pin)

#endif //SW_SPI_SW_SPI_H

11.3 SPI硬件外设

11.3.1 SPI外设简介

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8位/16位数据帧、高位先行/低位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

11.3.2 SPI框图

1706282048455

11.3.3 SPI基本结构

image-20250104201302433

这里的数据控制器主要时用于与LSBFIRST寄存器配合,实现SPI低位或高位先行的配置。