0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

原来SPI并没有想的那么简单

璟琰乀 来源:8号线攻城狮 作者:8号线攻城狮 2020-12-18 16:52 次阅读

先说串口之前写过一篇UART,通用串行异步通讯协议,感兴趣可以参考一下《我打赌!你还不会UART》;因为UART没有时钟信号,无法控制何时发送数据,也无法保证双发按照完全相同的速度接收数据。因此,双方以不同的速度进行数据接收和发送,就会出现问题。

如果要解决这个问题,UART为每个字节添加额外的起始位和停止位,以帮助接收器在数据到达时进行同步;

双方还必须事先就传输速度达成共识(设置相同的波特率,例如每秒9600位)。

传输速率如果有微小差异不是问题,因为接收器会在每个字节的开头重新同步。相应的协议如下图所示;

vqeU3a.png

串口传输的过程

如果您注意到上图中的11001010不等于0x53,这是一个细节。串口协议通常会首先发送最低有效位,因此最小位在最左边LSB。低四位字节实际上是0011 = 0x3,高四位字节是0101 = 0x5。

异步串行工作得很好,但是在每个字节发送的时候都需要额外的起始位和停止位以及在发送和接收数据所需的复杂硬件方面都有很多开销。

不难发现,如果接收端和发送端设置的速度都不一致,那么接收到的数据将是垃圾(乱码)。

下面开始讲一下SPI协议,会有哪些优点。

SPI通讯协议于是我们想有没有更好一点的串行通讯方式;相比较于UART,SPI的工作方式略有不同。

SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。

时钟是一个振荡信号,它告诉接收端在确切的时机对数据线上的信号进行采样。

产生时钟的一侧称为主机,另一侧称为从机。总是只有一个主机(一般来说可以是微控制器/MCU),但是可以有多个从机(后面详细介绍);

数据的采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低)。

具体要看对SPI的配置;

整体的传输大概可以分为以下几个过程:

主机先将NSS信号拉低,这样保证开始接收数据;

当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit);

由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要,尽管设备将具有可以运行的最高速度(稍后我们将讨论选择合适的时钟边沿和速度)。

主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI信号线上进行发送到从机;

主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO信号线发送;

具体如下图所示;

BjAr2i.png

SPI的时序

注意,SPI是“全双工”(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据,另外SPI的接收硬件可以是一个简单的移位寄存器。这比异步串行通信所需的完整UART要简单得多,并且更加便宜;

SPI特性SPI总线包括4条逻辑线,定义如下:

MISO:Master input slave output 主机输入,从机输出(数据来自从机);

MOSI:Master output slave input 主机输出,从机输入(数据来自主机);

SCLK :Serial Clock 串行时钟信号,由主机产生发送给从机;

SS:Slave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。

其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;

MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);

MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);

NSS也可以是CE,CS或SSEL;

SCLK也可以是SCK;

本文将按照以下命名进行讲解[MISO, MOSI, SCK,NSS]

下图显示了单个主机和单个从机之间的典型SPI连接。

qmyYVf.png

主从连接

时钟频率SPI总线上的主机必须在通信开始时候配置并生成相应的时钟信号。在每个SPI时钟周期内,都会发生全双工数据传输。

主机在MOSI线上发送一位数据,从机读取它,而从机在MISO线上发送一位数据,主机读取它。

就算只进行单向的数据传输,也要保持这样的顺序。这就意味着无论接收任何数据,必须实际发送一些东西!在这种情况下,我们称其为虚拟数据;

从理论上讲,只要实际可行,时钟速率就可以是您想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的SPI传输速率。

时钟极性 CKP/Clock Polarity除了配置串行时钟速率(频率)外,SPI主设备还需要配置时钟极性。

根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOL。时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据;

CKP可以配置为1或0。这意味着您可以根据需要将时钟的默认状态(IDLE)设置为高或低。极性反转可以通过简单的逻辑逆变器实现。您必须参考设备的数据手册才能正确设置CKP和CKE。

CKP = 0:时钟空闲 IDLE为低电平 0;

CKP = 1:时钟空闲 IDLE为高电平 1;

时钟相位 CKE /Clock Phase (Edge)除配置串行时钟速率和极性外,SPI主设备还应配置时钟相位(或边沿)。根据硬件制造商的不同,时钟相位通常写为CKE或CPHA;

顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿;

CKE = 0:在时钟信号 SCK的第一个跳变沿采样;

CKE = 1:在时钟信号 SCK的第二个跳变沿采样;

时钟配置总结综上几种情况,下图总结了所有时钟配置组合,并突出显示了实际采样数据的时刻;

其中黑色线为采样数据的时刻;

蓝色线为SCK时钟信号;

具体如下图所示;

qmmYNz.png

模式编号SPI的时钟极性和相位的配置通常称为 SPI模式,所有可能的模式都遵循以下约定;具体如下表所示;

SPI ModeCPOLCPHA

0 [00]00

1 [01]01

2 [10]10

3 [11]11

除此之外,我们还应该仔细检查微控制器数据手册中包含的模式表,以确保一切正常。

多从机模式前面说到SPI总线必须有一个主机,可以有多个从机,那么具体连接到SPI总线的方法有以下两种:

第一种方法:多NSS

通常,每个从机都需要一条单独的SS线。

如果要和特定的从机进行通讯,可以将相应的 NSS信号线拉低,并保持其他 NSS信号线的状态为高电平;如果同时将两个 NSS信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条 MISO线上传输数据,最终导致接收数据乱码。

具体连接方式如下图所示;

f6n2yu.png

多NSS连接

第二种方法:菊花链

在数字通信世界中,在设备信号(总线信号或中断信号)以串行的方式从一 个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链。

菊花链的最大缺点是因为是信号串行传输,所以一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了;

另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况;

具体的连接如下图所示;

菊花链连接

其中红线加粗为数据的流向;

所以最终的数据流向图可以表示为:

z6ZFNv.png

数据流图

SCK为时钟信号,8clks表示8个边沿信号;

其中D为数据,X为无效数据;

所以不难发现,菊花链模式充分使用了SPI其移位寄存器的功能,整个链充当通信移位寄存器,每个从机在下一个时钟周期将输入数据复制到输出。

优缺点SPI通讯的优势使SPI作为串行通信接口脱颖而出的原因很多;

全双工串行通信;

高速数据传输速率。

简单的软件配置;

极其灵活的数据传输,不限于8位,它可以是任意大小的字;

非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。

SPI的缺点没有硬件从机应答信号(主机可能在不知情的情况下无处发送);

通常仅支持一个主设备;

需要更多的引脚(与I2C不同);

没有定义硬件级别的错误检查协议;

RS-232和CAN总线相比,只能支持非常短的距离;

编程实现下面是通过STM32的cubemx自动生成的HAL库代码,比较简单,截取了其中一部分,具体如下;

static void MX_SPI1_Init(void){ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; //主机模式 hspi1.Init.Direction = SPI_DIRECTION_2LINES; //全双工 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //数据位为8位 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; //CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; //CPHA为数据线的第一个变化沿 hspi1.Init.NSS = SPI_NSS_SOFT; //软件控制NSS hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,32M/2=16MHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //最高位先发送 hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //TIMODE模式关闭 hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC关闭 hspi1.Init.CRCPolynomial = 10; //默认值,无效 if (HAL_SPI_Init(&hspi1) != HAL_OK) //初始化 { _Error_Handler(__FILE__, __LINE__); }} //发送数据HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 通信
    +关注

    关注

    18

    文章

    6084

    浏览量

    136551
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1724

    浏览量

    92285
  • 串行通信
    +关注

    关注

    4

    文章

    579

    浏览量

    35623
收藏 人收藏

    评论

    相关推荐

    ADS1198 DRDY并没有自动变成高电平,而是一直维持在低电平,为什么?

    手册上说DRDY会在SCLK的下降沿自动变成高电平(DRDY s pulled high at the falling edge of SCLK),但为什么我做了几次后发现DRDY并没有自动变成高电平,而是一直维持在低电平。
    发表于 02-06 07:14

    请问有没有ADS1293的SPI的各个寄存器的介绍?

    请问有没有ADS1293的SPI的各个寄存器的介绍,数据手册中并没有详细介绍,TI给的官方历程中涉及到很多与开发板相关的引脚,所以不太理解给出的例程。
    发表于 01-15 07:05

    ADS58B18通过SPI控制将其test pattern寄存器设置为001或010,为什么输出并没有全为0或1而是变化的?

    ADS58B18通过SPI控制将其 test pattern 寄存器设置为001或010,但其输出并没有全为 0或1,而是变化的。我通过SPI读该寄存器,ovr_sdout输出显示该寄存器已经
    发表于 01-15 07:04

    DAC5687没有设置差分,采用的等效单端,那么内部PLL可以工作吗?

    时钟模式采用PLL模式,datasheet要求输入时钟采用差分,但是我并没有设置差分,而是采用的等效单端,那么内部PLL可以工作吗? 如今情况是可以测到满偏电流,但是输出端一直是高电平,有个小波动,这是为什么啊?
    发表于 01-09 08:17

    编写好ADS1148读写数据的驱动时候,发现SPI总线上的数据并没有被读回来,是什么原因呢?

    在编写好ADS1148 读写数据的驱动时候,发现SPI总线上的数据并没有被读回来,从示波器上,可以发现SPI的read命令是没有问题的,可是SDO上面没数据,后来进一步查看是ADS11
    发表于 12-06 07:13

    使用ADS5281时,ADCLKN/P,CLKP/N的输出是高电平并没有1X,6X的正弦信号,为什么?

    在使用ADS5281时,输入电压正常,Vcm输出电压为1.48V,REFT和REFB输入如下: 但是ADCLKN/P,CLKP/N的输出是高电平,并没有1X,6X的正弦信号,非常期待解答,感谢!
    发表于 11-14 07:58

    进行ads1299短接噪声测试时,增益更改后短接噪声并没有发生变化,为什么?

    在进行ads1299短接噪声测试时,ads1299的增益更改,短接噪声并没有发生变化,而且在使用内部方波测试时,方波不对成,在增益为1时,短接噪声为0.35mV左右,这是为什么呢
    发表于 11-14 07:46

    TPA2005为什么实际测量输出波形根输入一样,并没有放大阿?

    我选用的TPA2005D1芯片,输入电容3300pF,输入电阻150K。输出33uH(160mA)电感,1uF电容。 为什么实际测量输出波形根输入一样,并没有放大阿? 输入波形:1k,1V的音频信号。
    发表于 10-12 08:46

    THS7530增益控制电压加到0.9V并没有放大到40多dB,为什么?

    按照数据手册上FIG.21的接法,输入100mV VPP以下的信号,增益控制电压加到0.9V并没有放大到40多dB,只放大到700mV VPP;下图是TINA里面的仿真模型,为什么增益控制电压
    发表于 09-06 08:18

    为什么INA188的GBW并没有1Mhz?

    请问下,为什么INA188的GBW并没有1Mhz, 但是在官方推荐的应用(TIDA-01063)中却可以适用于20Mhz的传感器的检测?
    发表于 08-02 10:39

    单独使用AFE031芯片并没有任何输出怎么解决?

    你好,在使用AFE031芯片与非C2000系列芯片连接时,按照C2000Ware中boostxl_afe031_f28379d_dacmode示例中初始化完成后,使用SPI任意向AFE031芯片发送一个数据,示波器中并没有数据变化,请问这种情况怎么解决呢?
    发表于 07-30 06:48

    编译运行ESP8266_RTOS_SDK-master,发现程序并没有正确执行,为什么?

    ,eagle.irom0text.bin---->0x20000烧写到相应地址,程序运行后,发现并没有正确执行,请问是否烧写地址错误,或者是配置FLASH错误
    发表于 07-12 08:21

    静态库中定义的INIT_DEVICE_EXPORT函数并没有被系统调用,为什么?

    1,将一段代码编译成静态库 2,主工程链接这个静态库 3,静态库里的函数并没有被主工程调用 4,静态库中定义了一些 INIT_DEVICE_EXPORT 函数 问题: 静态库中定义的 INIT_DEVICE_EXPORT 函数并没有被系统调用,百思不得其解,求各位大佬指
    发表于 07-04 06:49

    用一块fpga板通过杜邦线以SPI的方式控制AD5504,连接后发现并没有控制上AD5504,为什么?

    我使用一块fpga板通过杜邦线以SPI的方式控制AD5504,但是我连接后发现并没有控制上AD5504。我的程序是:一开始发送一次control寄存器的控制数据,然后就一直发送input寄存器的控制
    发表于 05-31 06:24

    在进行ad9626的配置后,测试DCO并没有时钟的输出的原因?

    在进行ad9626的配置后,测试DCO并没有时钟的输出,然后进行寄存器ID数据的读出,读出了ID地址的数据,然后再次进行了配置寄存器,DCO还是没有时钟的输出。
    发表于 02-27 07:25