一.什么是SPI

SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。

二.SPI协议

就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。

物理层

首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:

NSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。
SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。

协议层

和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。

1.通讯时序图

如图所示是SPI的一种通信模式下的时序图:

所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。

2.起始和停止信号

前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。

3.数据有效性

SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。

4.通讯模式

SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.

时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。
如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.

四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:

三.STM32中的SPI

简介

STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/2
(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。

功能框图


STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。

1.通讯引脚

STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:

根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。

2.时钟控制逻辑

这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
时钟频率的配置:
波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:
(PS:fpclk为对应SPI挂载总线的时钟频率)
通讯模式的配置:
通过配置“控制寄存器CR”中的CPOL位CPHA位将通讯模式配置为上文所说的四种模式之一。

3.数据控制逻辑

这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
标志位”会被置 1,表示传输完一帧,接收缓冲区非空;
发送数据:
地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
接收数据:
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。
数据帧格式:
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。
先行位:
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。

4.整体逻辑控制

在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。

初始化结构体

库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:

  1. typedef struct
  2. {
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */
  12. } SPI_InitTypeDef;

这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
(1) SPI_Direction
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
(2) SPI_Mode
本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
(3) SPI_DataSize
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
(SPI_DataSize_16b)。
(4) SPI_CPOL 和 SPI_CPHA
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
(5) SPI_NSS
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
(6) SPI_BaudRatePrescaler
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
(7) SPI_FirstBit
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
(8) SPI_CRCPolynomial
这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。

初始配置函数

  1. void SPI_Config(void)
  2. {
  3. /* 初始化SPI和相对应的GPIO口 */
  4. SPI_InitTypeDef SPI_InitStruct;
  5. GPIO_InitTypeDef GPIO_InitStruct;
  6. /* 打开SPI1和GPIOA的时钟 */
  7. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
  8. /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
  9. GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;
  10. GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;
  11. GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;
  12. GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
  13. GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
  14. GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
  15. GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
  16. GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
  17. GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;
  18. GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
  19. GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
  20. GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
  21. GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;
  22. GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
  23. GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
  24. GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
  25. /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */
  26. SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
  27. SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
  28. SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
  29. SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
  30. SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
  31. SPI_InitStruct.SPI_Direction = SPIx_Direction;
  32. SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;
  33. SPI_InitStruct.SPI_Mode = SPIx_Mode;
  34. SPI_InitStruct.SPI_NSS = SPIx_NSS;
  35. /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
  36. SPI_Init(SPIx, &SPI_InitStruct);
  37. SPI_Cmd(SPIx, ENABLE);
  38. /* 拉高NSS */
  39. SPI_NSS_Stop();
  40. }
  41. }

这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。

发送、接收一个字节

  1. /* 发送一个帧数据,同时接收一个帧数据 */
  2. uint8_t SPI_SendData( uint8_t data)
  3. {
  4. uint16_t timeout=0x2710; //10,000
  5. while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
  6. if((timeout--)==0) return printf("发送等待失败!\n");
  7. SPI_I2S_SendData(SPIx, data);
  8. timeout=0x2710; //10,000次循环无果后为失败
  9. while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
  10. if((timeout--)==0) return printf("接收等待失败!\n");
  11. return SPI_I2S_ReceiveData(SPIx);
  12. }
  13. /* 读取一个帧数据 */
  14. uint16_t SPI_ReadData(void)
  15. {
  16. return SPI_SendData( 1);//此时发送的值可以为任意值
  17. }

发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。

头文件

  1. #ifndef __SPI_H
  2. #define __SPI_H
  3. #include "stm32f10x.h"
  4. #define SPIx SPI1
  5. #define SPI_Clock RCC_APB2Periph_SPI1
  6. #define SPI_GPIO_Clock RCC_APB2Periph_GPIOA
  7. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
  8. #define SPI_SCK_GPIO_PORT GPIOA
  9. #define SPI_SCK_GPIO_MODE GPIO_Mode_AF_PP
  10. #define SPI_SCK_GPIO_SPEED GPIO_Speed_50MHz
  11. #define SPI_SCK_GPIO_PIN GPIO_Pin_5
  12. #define SPI_MOSI_GPIO_PORT GPIOA
  13. #define SPI_MOSI_GPIO_MODE GPIO_Mode_AF_PP
  14. #define SPI_MOSI_GPIO_SPEED GPIO_Speed_50MHz
  15. #define SPI_MOSI_GPIO_PIN GPIO_Pin_7
  16. #define SPI_MISO_GPIO_PORT GPIOA
  17. #define SPI_MISO_GPIO_MODE GPIO_Mode_AF_PP
  18. #define SPI_MISO_GPIO_SPEED GPIO_Speed_50MHz
  19. #define SPI_MISO_GPIO_PIN GPIO_Pin_6
  20. #define SPI_NSS_GPIO_PORT GPIOA
  21. #define SPI_NSS_GPIO_MODE GPIO_Mode_Out_PP
  22. #define SPI_NSS_GPIO_SPEED GPIO_Speed_50MHz
  23. #define SPI_NSS_GPIO_PIN GPIO_Pin_4 //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
  24. /* 配置SPI信息 */
  25. #define SPIx_BaudRatePrescaler SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz
  26. #define SPIx_CPHA SPI_CPHA_2Edge//偶数边沿采样
  27. #define SPIx_CPOL SPI_CPOL_High//空闲时SCK高电平
  28. #define SPIx_CRCPolynomial 7//不使用CRC功能,所以无所谓
  29. #define SPIx_DataSize SPI_DataSize_8b//数据帧格式为8位
  30. #define SPIx_Direction SPI_Direction_2Lines_FullDuplex
  31. #define SPIx_FirstBit SPI_FirstBit_MSB//高位先行
  32. #define SPIx_Mode SPI_Mode_Master//主机模式
  33. #define SPIx_NSS SPI_NSS_Soft//软件模拟
  34. /***************************************************************************************/
  35. #define SPI_NSS_Begin() GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
  36. #define SPI_NSS_Stop() GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
  37. #endif /*__SPI_H*/

四.资源链接

附上野火有关SPI的教学视频:
链接:https://pan.baidu.com/s/1b5IEq8pjo00945l4EMsDvA
提取码:umek

STM32—SPI详解的更多相关文章

  1. stm32位操作详解

    stm32位操作详解 STM32位操作原理 思想:把一个比特分成32位,每位都分配一个地址,这样就有32个地址,通过地址直接访问. 位操作基础 位运算 位运算的运算分量只能是整型或字符型数据,位运算把 ...

  2. spi详解

    来源:https://www.sohu.com/a/211324861_468626 1. SPI简介 SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围 ...

  3. Java SPI详解

    1.什么是SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件. SPI的作用就是为这些被扩展的 ...

  4. SPI详解2

    串行外设接口 (SPI) 总线是一种运行于全双工模式下的同步串行数据链路.用于在单个主节点和一个或多个从节点之间交换数据. SPI 总线实施简单,仅使用四条数据信号线和控制信号线(请参见图 1). 图 ...

  5. STM32—ADC详解

    文章目录 一.ADC简介 二.ADC功能框图讲解 1.电压输入范围 2.输入通道 3.转换顺序 4.触发源 5.转换时间 6.数据寄存器 7.中断 8.电压转换 三.初始化结构体 四.单通道电压采集 ...

  6. STM32—中断详解(配合按键中断代码,代码亲测)

    在STM32中执行中断主要分三部分: 1.配置NVIC_Config()函数 2.配置EXTI_Config()函数 3.编写中断服务函数 (注:本文章所用代码为中断按键代码,实现了按键进入中断从而控 ...

  7. STM32—SPI读写FLASH

    目录 FLASH简介 W25Q64 W25Q64简介 FLASH控制指令 FLASH内部存储结构 代码讲解 读取芯片ID 发送写使能信号 等待FLASH不忙 擦除扇区 写入数据 读取数据 注 FLAS ...

  8. [SPI]SPI协议详解

    转自:https://my.oschina.net/freeblues/blog/67400 1.SPI协议简介 1.1.SPI协议概括 SPI,是英语Serial Peripheral interf ...

  9. STM32固件库详解

    STM32固件库详解   emouse原创文章,转载请注明出处http://www.cnblogs.com/emouse/ 应部分网友要求,最新加入固件库以及开发环境使用入门视频教程,同时提供例程模板 ...

随机推荐

  1. C语言:自增 自减

    一个整数类型的变量自身加 1 可以这样写: a = a + 1; 或者 a += 1; 不过,C语言还支持另外一种更加简洁的写法,就是: a++; 或者 ++a; 这种写法叫做自加或自增,意思很明确, ...

  2. C语言:按行读TXT文件

    //搂行读取TXT #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_L ...

  3. 交通规则:HOV车道

    多乘员车道的限行时间一般为工作日上下班高峰,车上只有一个人时不能走该车道

  4. qtscrcpy使用

    点击"USB线"一栏中的"刷新设备列表"按钮,随后设备序列号会显示出来: ·点击"获取设备IP",随后在"无线"一栏中会 ...

  5. 网页如何嵌套网页__HTML框架

    通过使用html框架,可以在一个浏览器窗口中展示多个页面.也就是一个html文件中可以引入多个html文件.在网页中框架使用比较少,但我们还是需要了解下. 方式1:iframe 使用iframe标签来 ...

  6. Maven工程 报 Diamond types are not supported at language level '5'

    Maven工程 报 Diamond types are not supported at language level '5' 出现这种信息,一般表示的是你的language level(IDEA下J ...

  7. Java 获取、删除Word文本框中的表格

    本文介绍如何来获取Word文本框中包含的表格,以及删除表格. 程序测试环境包括: IDEA JDK 1.8.0 Spire.Doc.jar 注:jar导入,可通过创建Maven程序项目,并在pom.x ...

  8. 搭建NFS服务

    说明:这里是Linux服务综合搭建文章的一部分,本文可以作为单独搭建yum仓库的参考. 注意:这里所有的标题都是根据主要的文章(Linux基础服务搭建综合)的顺序来做的. 如果需要查看相关软件版本和主 ...

  9. 第三篇 -- HTML基础

    买的课程学习 Web 前端标准 web前端技术指的不是某一项技术,而是一系列技术的集合,主要包括: html -- 结构标准:负责网页结构的搭建 css -- 样式标准/表现标准:负责网页的美化工作 ...

  10. 构建前端第8篇之---Webstom搭建ES6运行环境

    张艳涛 写于2021-1-22 一.在有webstorm和node.js前提下,安装全局的babel npm install babel-cli babel-eslint -g 二.在terminal ...