适合初学者的SPI讲解

1.1 SPI介绍(硬件配置)

  • 首先介绍一下SPI缩写的意思

    SPI是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的、全双工、同步通信总线,标准的SPI也仅仅使用4个引脚,常用于单片机和 EEPROM、FLASH、实时时钟、数字信号处理器等器件的通信。SPI是全双工同步通信,所以他的读写数据是同时发生的,所以下面的代码把读写数据放到一个函数里了

  • SPI通信原理

    工作原理:

    SPI通信,它主要是主从方式通信。这种模式通常只有一个主机和一个或者多个从机,标准的SPI是4根线,分别是SS(片选信号)、SCLK(时钟信号)、MOSI(主机发送数据 Master Output/Slave Input) MISO(主机接受数据Master Input/Slave Output)。我们,就是通过这四根线,来进行SPI通信的,首先由SCS(片选信号)选择设备,然后由主机产生时钟信号,最后通过传输和读取数据线进行数据的读取,现在这里简单介绍一下,下面会通过代码进行详细的解释

1.2 SPI的代码讲解

  • 介绍一下他的结构体
    {  
      uint16_t SPI_Direction;           /*!< 指定SPI为单向或双向数据模式。
                                             该参数可以是 @ref SPI_data_direction 中的一个值 */
    ​
      uint16_t SPI_Mode;                /*!< 指定SPI工作模式。
                                             该参数可以是 @ref SPI_mode 中的一个值 */
    ​
      uint16_t SPI_DataSize;            /*!< 指定SPI数据大小。
                                             该参数可以是 @ref SPI_data_size 中的一个值 */
    ​
      uint16_t SPI_CPOL;                /*!< 指定串行时钟的稳定状态。
                                             该参数可以是 @ref SPI_Clock_Polarity 中的一个值 */
    ​
      uint16_t SPI_CPHA;                /*!< 指定时钟有效边沿用于位捕获。
                                             该参数可以是 @ref SPI_Clock_Phase 中的一个值 */
    ​
      uint16_t SPI_NSS;                 /*!< 指定NSS信号是由硬件(NSS引脚)管理还是由软件使用SSI位管理。
                                             该参数可以是 @ref SPI_Slave_Select_management 中的一个值 */
     
      uint16_t SPI_BaudRatePrescaler;   /*!< 指定时钟预分频器值,该值将用于配置发送和接收SCK时钟。
                                             该参数可以是 @ref SPI_BaudRate_Prescaler 中的一个值。
                                             @note 通信时钟来源于主时钟。从机时钟不需要设置。 */
    ​
      uint16_t SPI_FirstBit;            /*!< 指定数据传输是从MSB位开始还是从LSB位开始。
                                             该参数可以是 @ref SPI_MSB_LSB_transmission 中的一个值 */
    ​
      uint16_t SPI_CRCPolynomial;       /*!< 指定用于CRC计算的多项式。也就是检验位是第几位 */
    }SPI_InitTypeDef;

  • 时序问题

    SPI通信的主机就是我们的单片机,在读写数据时序的过程中,有四种模式。要了解这四种模式,首先我们得学习两个名词,也就是我们写代码时进行操作的寄存器的名字

CPOL(时钟极性) 用于控制空闲状态下的电平状态 若CPOL被清'0',则表示空闲状态下SCLK为低电平,反之 SCLK则为高电平

CPHA(时钟相位)

若CPHA被置为1,SCLK时钟的第二个边沿(0为下降沿,1为上升沿),进行数据位的采集,数据则在第二个时钟边沿 被锁存

若CPHA被置为0,SCLK时钟的第1个边沿(0为下降沿,1为上升沿),进行数据位的采集,数据则在第1个时钟边沿被 锁存

  • 下面这张图详细的展示了如何这两个寄存器的相互使用

根据这个图片我们可以详细的看出来四种时序,画箭头的地方就是表示采集数据

根据这个图对端口进行配置

  • SPI进行初始化配置了

void SPI2_Init()
{
    GPIO_InitTypeDef GPIO_Init_Struct;  //首先对GPIO口进行初始化
    SPI_InitTypeDef SPI_Init_Struct;    //对SPI进行初始化
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);  //使能GPIOB的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI1,ENABLE);   //使能SPI2的时钟
​
    GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_AF_PP;    //对这三个引脚都设置成复用推挽输出
    GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_4 |GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_Init_Struct);
    
    GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_IPU;    //配置MISO
    GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_Init_Struct);
    
    //设置波特率预分频为256
    SPI_Init_Struct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; 
    //第二个边沿捕获
    SPI_Init_Struct.SPI_CPHA                = SPI_CPHA_2Edge;
    //一般都选择为高电平
    SPI_Init_Struct.SPI_CPOL                = SPI_CPOL_High; 
    //校验位是第几位 
    SPI_Init_Struct.SPI_CRCPolynomial       = 7; 
    //数据模式为8位
    SPI_Init_Struct.SPI_DataSize            = SPI_DataSize_8b; 
    //选择双向数据模式,双向都可以收发
    SPI_Init_Struct.SPI_Direction           = SPI_Direction_2Lines_FullDuplex;  
    SPI_Init_Struct.SPI_FirstBit            = SPI_FirstBit_MSB;         //设置SPI为高位先行
    SPI_Init_Struct.SPI_Mode                = SPI_Mode_Master;     //选择主模式
    SPI_Init_Struct.SPI_NSS                 = SPI_NSS_Soft;         //设置为软件模式
    //这个NSS就是用来选择设备的,因为是软件控制所以不用连接SSC的端口了
    SPI_Init(SPI2,&SPI_Init_Struct);
    
    SPI_Cmd(SPI2,ENABLE);  //使能
    //初始化时把SS拉高让他不工作,当他拉低是就开始工作了
    GPIO_SetBits(GPIOB,GPIO_Pin_6);  
    
}
​
  • 想必大家还不算了解主模式和从模式的区别吧,下面来介绍一下

  • 主模式
  1. 控制通信:在主模式下,SPI设备负责启动和终止数据传输,并且控制整个SPI通信的速度和时序。

  2. 时钟信号:主设备产生SPI所需的时钟信号(SCK),并将其发送给从设备。

  3. 数据传输:主设备可以决定何时开始或结束数据交换。

  • 从模式
  1. 响应通信:在从模式下,SPI设备响应来自主设备的命令。它不主动发起通信。

  2. 同步时钟:从设备接收由主设备产生的时钟信号(SCK),并根据该信号来同步自己的数据传输。

  3. 被动操作:从设备只能在接收到主设备的有效信号后才能执行数据传输。

为什么通常选择主模式

  1. 控制权:在主模式下,主设备负责产生时钟信号(SCK),从而控制数据的传输速率和整个通信流程。这意味着主设备可以自由地选择何时开始和结束数据传输,以及选择与哪个从设备进行通信。

  2. 初始化:主设备通常是一个微控制器或处理器,它可以初始化SPI通信,并根据需要选择不同的从设备进行交互。从设备则处于等待状态,直到被主设备选中。

  3. 灵活性:主模式下的设备可以根据需要更改通信速度、数据大小和其他配置参数,以适应不同类型的从设备或应用需求。

  4. 简化设计:对于从设备而言,无需复杂的时钟产生电路,只需要响应主设备产生的时钟信号即可。这有助于简化从设备的设计和降低成本。

  • 接下来就是写读字节,因为SPI通信时全双工同步通信,所以读写字节要同时发生

uint8_t SPI_ReadWriteByte(uint8_t data)
{
    u8 i = 0;
    //SPI_I2S_GetFlagStatus这个是获取当前状态的函数,下面则是再询问当前状态寄存器是否为空
    //为空则发送数据,不为空则等待
    while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == 0){
​
        i++;
        if(i>200)
        {
        return 0;  //超时处理,假如读取失败,跳出防止堵塞程序
        
        }
    }
        //当寄存器为空时,发送数据
        SPI_I2S_SendData(SPI2,data);
        //读取数据
        i = 0;
        while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == 0){
        i++;
        if(i>200)
            
        {
        return 0;  //超时处理
        
        }
        //当寄存器为空时  返回收到的数据
   
    
    }
​ return SPI_I2S_ReceiveData(SPI2);
}
//接下来还得有一个控制传输速率的函数
​
void Set_Speed(uint8_t SPI_BaudRatePrescaler)
{
        //检查输入的波特率 是否符合分频值
        assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));   
        //
        SPI2->CR1&=0XFFC7;//比特位 BR[2:0] 清零,确保它们的初始状态为0
        SPI2->CR1 |= SPI_BaudRatePrescaler;  //对寄存器进行位操作
        SPI_Cmd(SPI2,ENABLE);  //使能
}
​
![](D:\typora_picture\image-20240818231919861.png)
    
    

这两张图即可说明为什么控制波特率的函数那样写

过几天,会介绍一下SPI在实际项目中的应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值