SPI通信详解

目录

SPI通信详解

1. SPI通信介绍

2. SPI和I2C的区别

3. SPI的硬件电路规定

4. SPI的移位示意图

1.边接收边发送

2.只接收,不发送

3.只发送,不接收

5. SPI软件设计(时序基本单元)

5.1 起始条件与终止条件的时序

5.2 主机与从机交换一个字节的时序

6. SPI通信举例(软硬件波形、控制W25Q64方法)

6.1. 举例:SPI通信控制W25Q64使能

6.2. 举例:SPI通信控制W25Q64指定地址写

6.3. 举例:SPI通信控制W25Q64指定地址读

7.SPI基本时序编写

7.1编写时需要注意的点

7.2程序文件简要说明:

MySPI.c

MySPI.h


STM32软件SPI通信读写W25Q64存储器模块可以看我的这篇博客:STM32通过SPI软件读写W25Q64-CSDN博客

STM32硬件SPI通信读写W25Q64存储器模块可以看我的这篇博客STM32通过SPI硬件读写W25Q64-CSDN博客

SPI通信详解

1. SPI通信介绍

SPI(Serial Peripheral Interface)和I2C一样,都是实现主控芯片和外挂芯片之间的数据交流。

I2C 与 SPI 的比较

I2C的缺点:

  • 驱动能力差: I2C接口由于线路上拉电阻的电路结构,使得通信总线的驱动能力较弱。当通信线路电压较高时,电阻上的损耗就越大,限制了I2C的最大通信速度。在标准模式下,I2C的最高时钟频率仅为100kHz,相比于SPI要慢得多。

SPI的优点:

  • 传输速度高: SPI在设计上更注重速度,其设计简单且灵活,能够支持更高的传输速率。

SPI是一种串行同步通信协议。它通过四根通信线(SCK、MOSI、MISO、SS)进行数据传输。

  • 全双工: SPI支持全双工通信,可以同时进行数据发送和接收。
  • 多设备支持: SPI支持多设备挂载(主多从),但不支持多主多从。
  • 时钟线: SPI使用一根时钟线(SCK)来提供时钟信号。数据的输出和输入都在SCK的上升沿或下降沿进行,由主机提供时钟信号,数据接收由从机来完成。

SPI通信线

  • SCK(Serial Clock): 提供时钟信号,由主机控制。数据传输依赖于SCK的时钟信号。
  • MOSI(Master Out Slave In): 主机输出,从机输入。用于主设备向从设备发送数据。
  • MISO(Master In Slave Out): 从机输出,主机输入。用于从设备向主设备发送数据。
  • SS(Slave Select): 片选信号,用于选择从设备。每个从设备都有一个独立的SS线,通过拉低SS信号来选择相应的从设备进行通信。

SPI通信线别称

  • SCK: SCLK, CLK, CK
  • MOSI: MI, SIO, DO(Data Output)
  • MISO: DI(Data Input)
  • SS: NSS(Not Slave Select), CS(Chip Select)

时序和数据传输

  • 无应答机制: SPI没有应答机制,这意味着主机发送的数据没有确认信号,数据传输的完整性和正确性需要其他手段保障。
  • 时钟信号同步: SPI通信依赖时钟信号(SCK)进行数据同步,确保数据位在SCK的时钟沿(上升沿或下降沿)传输。

2. SPI和I2C的区别

I2C

  • I2C的硬件电路和软件时序更加复杂
  • I2C通信的性价比高:在消耗最低硬件资源的情况下,实现最多的功能
  • I2C的弱上拉,会限制最大通信速度(标准100KHZ以下,快速400KHZ以下)

SPI

  • SPI传输更快。这一般取决于芯片厂商设计时的需求(比如W25Q64存储器芯片最大速度为80MHZ)
  • SPI设计的功能少,学习简单
  • SPI的硬件开销大,通信线的个数比较多。并且通信时经常会出现资源浪费的情况
  • SPI不会有I2C的应答机制

3. SPI的硬件电路规定

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 所有设备的输出引脚配置为推挽输出 !但是,SPI协议规定,为了防止从机全都是推挽输出导致冲突。 !规定从机在未被点名时,应该将输出设置为高阻态(引脚断开)
  • 所有设备的输入引脚配置为浮空或上拉输入

主机通过MOSI输出、从机通过MOSI输入

主机已通过MISO输入,从机通过MISO输出

主机(输出)想和谁通信,就把他的控制线输出为低电平。然后被对应的从机接收(输入)(同一时间只能选一个从机)

4. SPI的移位示意图

SPI在通信时有三种情况

  • 边发送边接收
  • 只接收,不发送
  • 只发送,不接收

1.边接收边发送

SPI的主机和从机可以理解为这个框图。

  • 波特率发生器

    波特率发生器就是主机的时钟。波特率发生器会控制主机和从机的移位寄存器的移位操作(左移)

  • 在进行边接收边发送时,比如我们写入了1 0 1 0 1 0 1 0这个数据希望发送到从机、而从机西方发送0 1 0 1 0 1 0 1这个数据,希望发送到主机。

    主机和从机的移位寄存器就会根据时钟的上升沿和下降沿(上升沿发送,下降沿接收)一位一位的发送和接收数据。

    在下降沿没来到之前,暂存在发送寄存器中。

    在下降沿来到之后,接收器就会接收主机/从机发送来的数据,并保存在移位寄存器的最低位。

    如此循环八次,就可以得到一个字节的数据。

    如下图所示

2.只接收,不发送

在只接收时,接收方还是会发送的,只是发送由我们指定发送0x00或者0xFF。 并且接收方发来的数据,发送方是不会去读取的。

3.只发送,不接收

在发送时,接收方还是会发送的,只是接收由我们指定发送0x00或者0xFF。 并且接收方发来的数据,发送方是不会去读取的。

5. SPI软件设计(时序基本单元)

5.1 起始条件与终止条件的时序

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

5.2 主机与从机交换一个字节的时序

SPI有两个配置位:CPOLCPHA

他们的不同值,对应了SPI交换一个字节的不同方式

  • CPOL = 0时:空闲状态时,SCK为低电平
  • CPOL = 1时:空闲状态时,SCK为高电平
  • CPHA =0时:SCK第一个边沿移入数据,第二个边沿移出数据
  • CPHA =1时:SCK第一个边沿移出数据,第二个边沿移入数据
  • 其中CPHA表示的是时钟相位,决定第一个时钟采样移入还是第二个时钟采样移入

一、交换一个字节(模式1)CPOL = 0 CPHA = 1

  1. 点名:SS信号线被置为低电平,表示从机已被选中并准备接收或发送数据。
  2. SCK空闲状态:SCK(Serial Clock)信号线保持为低电平,这是CPOL=0模式下的空闲状态。
  3. 坐下:当数据交换完成后,SS信号线被置回高电平,从机不再被选中。
  4. 第一个边沿来临:SCK的第一个上升沿到来,主机和从机开始发送高位数据。
  5. 第二个边沿来临:SCK的第二个下降沿到来,主机和从机接收到数据到最低位。
  6. 不断循环:这个过程不断重复,直到完成一个字节的数据交换。 如果想接收多个数据,就让SS一直为0就OK
  7. 主机输出后的电平没有硬性要求:在数据交换结束后,SCK可以继续保持低电平也可以恢复到空闲状态。
  8. 但是从机恢复推挽输出:从机在未被点名前应是高阻态。

二、交换一个字节(模式0)CPOL = 0 CPHA = 0

在实际应用中。模式0的应用是最多的。

  1. 点名:SS信号线被置为低电平,表示从机已被选中并准备接收或发送数据。
  2. 在第一个边沿来临的时候就要读取数据了,所以在此之前要提前发出最高位。
  3. 主机和从机在第一个边沿到达时开始接收信号了, 此时开始捕获数据到自己的移位寄存器的最低位。
  4. 主机和从机在第二个边沿开始发送信号, 送出自己的次高位
  5. 如此循环……,在接收到一个字节之后。 还会有一个低电平回到SCK静态。此时如果主机不打算交流了(SS返回高电平) 此时MOSI,主机输出端 可以上拉也可以保持,然后停止点名。 而MISO,从机的输出端 要回到高阻态
  6. 如果想要继续发送字节,就保持SS为0,主机从机的端口也不变。再来一遍这个波形。

三、交换一个字节(模式2)CPOL = 1 CPHA = 0

只不过是把CLK的波形取反一下就可以了。其他的和模式0一样

四、交换一个字节(模式0)CPOL = 0 CPHA = 0

只不过是把CLK的波形取反一下就可以了。其他的和模式1一样

6. SPI通信举例(软硬件波形、控制W25Q64方法)

6.1. 举例:SPI通信控制W25Q64使能

SPI不像I2C那样,有效数据流第一个字节是寄存器地址、之后依次是读写的数据。 使用的是读写寄存器的模型

SPI使用的是指令码+读写数据的模型:在SPI起始之后,第一个交换给从机的数据就是指令码。在从机中,会有一个对应的指令集。指令集会对应从机的不同功能。不同的功能对应不同的字节个数,比如控制W25Q64 存储器 使能,就需要一个字节。而控制写数据时,就需要多个字节。

W25Q64存储器的使能的指令为 0x06

在这里可以参考W25Q64使能的波形图

这实际上就是主机和从机交换了0x06和0xFF的一个字节。

在从机经过对比之后。发现0x06为自己的指令集中的使能,那么他就会开启。

  • 并且SPI是没有I2C的应答机制的

6.2. 举例:SPI通信控制W25Q64指定地址写

指定地址写同样的也需要写指令集。不过这次的就不同于使能只有一位字节就可以了。

  • 需要先发送写指令(0x02)、
  • 随后在指定地址(W25Q64有8M的存储空间,所以他的地址长度为24位 [23::0],也就是我们要发送三个字节长的地址)
  • 最后再发送指定数据。(Data)

波形如下:

这也是一次次的交换数据

这段波形的意思是:我要在地址为0x12 34 56的寄存器下写入0x55

从机的输出一直为1 也就是主机一直跟从机交换0xFF

这里的和I2C的一样,每次读写都会使地址指针++、如果在一次发送之后不终止,后续发送或者读取的字节就会一次指到后边的字节中

6.3. 举例:SPI通信控制W25Q64指定地址读

指定地址读

  • 向SS指定的设备
  • 发送读指令(0x03)
  • 随后在指定地址(Address[23:0])下,读取从机数据(Data)

这个波形的意思是:

从前4个字节一直都是0xFF.第五个字节开始,从机开始发送字节。此时主机的输出为0xFF。从机开始发送这个寄存器中的值,从机的输出就不再是0xFF。而主机交换给从机的值是0XFF

这里的和I2C的一样,每次读写都会使地址指针++、如果在一次发送之后不终止,后续发送或者读取的字节就会一次指到后边的字节中

  • 并且还可以看出,由于从机回复 是硬件执行。所以从机回复的高低电平是紧贴着下降沿的。

  • 而软件,在下降沿后 主机的输出并没有紧贴着,稍有延迟。 这就是软件模拟的一些延迟。不过因为有同步时钟,这些都问题不大

  • 并且。在W25Q64发送数据时,他的数据是在上一个波形的下降沿提前发送(移出)的。 因为这是SPI的 模式0

7.SPI基本时序编写

7.1编写时需要注意的点

SPI的基本时序有三个

  • 起始
  • 结束
  • 交换字节

起始和结束只需要控制SS片选信号的高低电平即可

交换字节分为两种方法

  • 掩码依次提取法

    掩码提取其实就是通过 & 操作来提取出一个字节中的某一位

  • 移位模型法

    移位模型法正是SPI通信时用到的方法。这个方法更加高效

7.2程序文件简要说明:

  • MySPI:为了防止与stm32函数重名,所以添加了My前缀。 主要是为了完成SPI的三个基本时序
  • MySPI.h:函数声明

MySPI.c

#include "stm32f10x.h"                  // Device header

/*所用引脚列表*/
#define RCCPeriph       RCC_APB2Periph_GPIOA
#define SCK_Port        GPIOA
#define SCK_Pin         GPIO_Pin_6
#define SS_Port         GPIOA
#define SS_Pin          GPIO_Pin_5
#define MOSI_Port       GPIOA
#define MOSI_Pin        GPIO_Pin_4
#define MISO_Port       GPIOA
#define MISO_Pin        GPIO_Pin_3

/**
  * 函    数:写片选信号SS
  * 参    数:BitValue:输入1片选信号SS为高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_SS (uint8_t BitValue)
{
    GPIO_WriteBit(SS_Port,SS_Pin,(BitAction)BitValue);
}

/**
  * 函    数:写时钟信号SCK
  * 参    数:BitValue:输入1时钟信号SCK为高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_SCK (uint8_t BitValue)
{
    GPIO_WriteBit(SCK_Port,SCK_Pin,(BitAction)BitValue);
}

/**
  * 函    数:写 主机输出,从机输入信号MOSI
  * 参    数:BitValue:输入1时主机输出高电平
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_W_MOSI (uint8_t BitValue)
{
    GPIO_WriteBit(MOSI_Port,MOSI_Pin,(BitAction)BitValue);
}

/**
  * 函    数:读 主机输入,从机输出信号MISO
  * 参    数:无
  * 返 回 值:BitValue
  * 注意事项:无
  */
uint8_t MySPI_R_MISO (void)
{
    return GPIO_ReadInputDataBit(MISO_Port,MISO_Pin);
}

/**
  * 函    数:MySPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Init(void)
{   
    /*配置时钟与引脚*/
    RCC_APB2PeriphClockCmd(RCCPeriph,ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = SCK_Pin | SS_Pin | MOSI_Pin;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MOSI_Port,&GPIO_InitStructure);   //时钟、片选、MOSI都是推挽输出
    
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = MISO_Pin;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MISO_Port,&GPIO_InitStructure);   //MISO 为上拉输入
    
    MySPI_W_SS(1);  //片选默认为高
    MySPI_W_SCK(0); //时钟默认为低
}

/*******************/
/*SPI的三个时序单元*/
/*******************/

/**
  * 函    数:起始信号
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Start(void)
{   
    MySPI_W_SS(0);
}

/**
  * 函    数:终止条件
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
void MySPI_Stop(void)
{   
    MySPI_W_SS(1);
}

/**
  * 函    数:交换一个字节(模式0)(方法1)
  * 参    数:SendByte      待发送的字节
  * 返 回 值:ReceiveByte   接收到的字节
  * 注意事项:这是使用掩码依次提取数据中的每一位保存或发送
              好处是不会改变传入参数本身,但是效率不高
              如果要改为模式1,则先上升沿再发送。先下降沿再接收(2、3则直接改时钟极性就ok了)
  */
//uint8_t MySPI_WarpByte(uint8_t SendByte)
//{
//    uint8_t ReceiveByte = 0x00;
//    
//    uint8_t i = 0;
//    for(i = 0; i < 8; i++)
//    {
//        MySPI_W_MOSI(SendByte & (0x80 >> i));  //发送第一个bit
//        MySPI_W_SCK(1);//第上升沿来临
//        if (MySPI_R_MISO() == 1)
//        {
//            ReceiveByte |= (0x80 >> i);    //按照从高往低接收数据
//        }
//        MySPI_W_SCK(0); //下降沿来临
//    }
//    return ReceiveByte;
//} 

/**
  * 函    数:交换一个字节(模式0)(方法2)
  * 参    数:SendByte      待发送的字节
  * 返 回 值:ReceiveByte   接收到的字节
  * 注意事项:这是使用了移位模型的方式。效率更快
              如果要改为模式1,则先上升沿再发送。先下降沿再接收(2、3则直接改时钟极性就ok了)
  */
uint8_t MySPI_WarpByte(uint8_t SendByte)
{
    uint8_t i = 0;
    for(i = 0; i < 8; i++)
    {
        MySPI_W_MOSI(SendByte & 0x80);  //发送第一个bit
        SendByte <<= 1; //发送数据左移一位
        MySPI_W_SCK(1); //第上升沿来临
        if (MySPI_R_MISO() == 1)
        {
            SendByte |= 0x01;    //保存收到的数据到发送寄存器的最低位
        }
        MySPI_W_SCK(0); //下降沿来临
    }
    return SendByte;
} 

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

//初始化
void MySPI_Init(void);
//起始
void MySPI_Start(void);
//终止
void MySPI_Stop(void);
//交换
uint8_t MySPI_WarpByte(uint8_t SendByte);

#endif

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值