请选择 进入手机版 | 继续访问电脑版

你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32的经验分享—SPI详解

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI
* c7 o% F/ [& U, |+ S4 ~4 N% G9 y# {SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。) M/ ~6 }% S8 W8 V5 ?8 u$ H
0 _9 R' j1 m2 R; [, ~9 n4 F" C0 s% d
二.SPI协议
* v- i7 Q! T4 z; i  B+ W* p就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。& E3 m% P: g4 z0 R- w
2 i8 C2 E( v' n7 K" ^% x0 ^8 e. c. A
物理层
2 E# N: ?" t1 g. O. C. S5 Q首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:
4 _; c1 j/ K* S( ^7 }1 a
( q% b$ t) M  W# N% K+ g
20190809114531658.png : q9 n, F! y" E7 }9 k6 F

9 f- B, n" @; c: w4 e$ yNSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。
" C$ p5 ^! F& m! b: WSCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。$ I' m- C  x0 B9 V
MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。
" j% f, m% |8 C- Z+ z5 p0 rMISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。
/ X- ]( u8 b; A; G3 H  N- |: T" b% M0 x, c) E9 n' q

. `* l$ M. \0 `协议层- Y# S5 T, s; ^- M2 W5 M: {$ }
和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。" `, b% r8 }" h$ F

( O9 z: S- m3 e( t& |

% E% m, }: O, N3 q1.通讯时序图0 E3 Q) T* d1 Q
如图所示是SPI的一种通信模式下的时序图:
- V, W% Q% L( @9 J$ X( C" _; o8 F) [
20190809121651274.png
! l! c6 v. o% R7 n) C
+ o& _4 e: r9 l) d6 G
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
: Y6 |, P5 V( e  y( t
9 `# a( J# g3 W6 g8 r0 E9 W2 s

5 d( u9 w" _5 T$ y) i1 ]( F3 t; @7 D2.起始和停止信号6 X! O+ u4 m2 F
前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。
8 r9 S7 ~% U+ j$ Y7 {/ t

; d& f( C1 T- w8 k& L1 \3.数据有效性
. f4 x2 s& G" X' |5 l! sSPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。4 |. ~- X& n1 A8 ~9 A( K. S+ }7 Q& B
6 D' _9 B# f" K9 a1 ~
4.通讯模式# Z+ \0 j( y; u+ I# n# _5 X
SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
8 `# M1 A0 @& a. j4 h) S时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.! g. M$ D' G* k; W* j, |/ ^0 _1 i
2 c8 Y4 W/ d0 y: c
20190809124918344.png # k, x1 I! q2 V/ d3 ~+ s& ]
( ]6 E0 `/ `8 @7 ]7 ^3 r: u/ r
时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。
$ J- d9 S: W7 R" }8 s如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.2 g$ l) ^% K( R3 \

" G9 `$ j2 N8 m
20190809125537352.png
) `. ?6 R- M$ l1 J

8 `# K6 b( {  H1 Q. N+ q# c四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:6 _/ y6 [& q; Y: d/ K  q8 }
" p, L3 K7 k( j4 X7 {5 @  Z( |8 X
20190809133146530.png
% w. \' f) u/ p; E7 s% \+ _  n# |; f, J$ M( o
! B+ h1 ]1 \& D# [, Z
三.STM32中的SPI3 w/ A9 o0 ^, {. I+ Z/ b
简介

6 N$ V& x2 Q$ i1 J  Z2 wSTM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/25 b5 V5 e6 `) [( A3 D" r
(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。1 `! w. Y# e. e) A# A

. r/ ^7 {' c3 u- U; N: J% V
# A6 b' X( c0 i% ~
功能框图
5 @5 ^* K3 D& _. I. A9 X& t- |8 s* v/ e! g( U' [9 d8 c
20190809131145870.png
! h- Q  k+ n8 V- N
7 d! G0 P9 }2 e, a7 z
STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。
5 ]# w* Y3 T% [5 t$ X% K3 |
( a" j2 ^3 X8 r8 A, K1.通讯引脚; T# F  |- S; S: b
STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:" ~# q) B( s/ r$ _- c
3 L8 `& g& m/ s* R1 H8 I" n
20190809132131865.png
0 i0 J( q; k5 i! b/ `7 m9 @
6 e( X+ c- T, f0 v6 ]0 M7 W4 Y
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
2 n# p- H; D/ s7 }1 ?7 ?8 \9 a' @  Q; y( v/ e5 ]2 m  P# s' }, _

9 L4 u# @3 a+ n- P# }) h+ ]2.时钟控制逻辑

8 H) }2 K% `& @这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。; c9 u, ^0 |0 G2 D  C
时钟频率的配置:
1 g  Z# h; h8 a+ \0 ?; s波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:
2 W6 U! L  w* S' b- ~* u
7 S4 y7 H( i0 ^( s! X3 @9 ^% l
1 N3 x" L6 A6 H- i; j! h& r- D
* P2 ?+ Q9 |: ]' ]! l
4 g3 o( g; |$ z$ d$ e+ d' J
(PS:fpclk为对应SPI挂载总线的时钟频率)9 Q5 ?) Q3 e' F
通讯模式的配置:
& M% Z; @& _& \2 u2 g- b8 i通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
  C- \& c( G! N$ v7 e

& g% z$ W  l/ g3 Z3.数据控制逻辑
% r9 d6 M* n* H这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表
7 `- F9 K! t" n# d$ m; }! V" t3 \示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE, F* E  x: V4 M8 r2 @# `
标志位”会被置 1,表示传输完一帧,接收缓冲区非空;& F& R3 \0 j& `, g$ L, U* O/ w
发送数据:
" m2 B* s7 e3 z+ k地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
  N3 C, L3 L% ?5 Y. x接收数据:7 W7 x! b7 ]: ^7 L5 h. P% b8 _
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。5 \* H& _  y7 z, \: [; O2 T
数据帧格式:, j6 w: m' `2 D8 V
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。9 N7 j4 h" u5 U5 E4 A
先行位:: n, J" P: _4 j$ Z$ @
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。) G' S$ T# N: [$ I8 [9 R( b/ Q
/ u9 F3 I: X9 i8 ~8 ]8 F! }0 h6 M
4.整体逻辑控制4 e/ L3 Q& \. k+ M$ D6 T; K7 S
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。' {4 \  T2 M3 E% }: @/ g" \

+ j) e! U! t3 g$ \3 C初始化结构体7 O4 B) A5 n' _2 j: z. p# V; i
库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:/ f/ l0 K  I& a3 `8 r
  1. typedef struct" {9 p* g+ e$ e1 n" j- M$ A; P
  2. {
    8 ~5 [; P9 X: w1 S: _
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */8 {; C. b0 S+ f0 s7 x' z
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
    * A+ g0 t3 p: V
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
    8 l- H2 l8 j2 Z1 X( O4 u' ?
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/. P& z% t3 J6 K
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */6 @  ~3 G2 K4 j/ j8 Y- E7 i* U6 [5 |
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/! e$ k9 c# [1 F" I) `/ x( C' A
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */
    " V) f' J% Z7 P, C
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */5 U4 U+ H7 {  `( B9 p6 E# s
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */- h0 F# Z0 @2 c1 X9 H6 A5 |' ~
  12. } SPI_InitTypeDef;
    ) F+ B9 Y: c7 T* L  P  i( C
复制代码

  s6 |( A# N  p5 T; }5 P' Z* p  V+ d这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:! |6 F, M4 A1 r% T9 g/ M1 M
(1) SPI_Direction* v4 }7 E4 A* P3 I9 y5 {
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
$ I# k: \8 n8 ^* S$ r! X(2) SPI_Mode
  Q9 C. A5 ]* {0 Q  N本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
! j# K. v9 f7 |* i8 [& Y(3) SPI_DataSize
- R9 Q% M3 x) P% |本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
* H- Q  l: [, C; e(SPI_DataSize_16b)。0 A1 Q% ]  h% o
(4) SPI_CPOL 和 SPI_CPHA- N* U. Y% F8 L" @
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
% p9 m4 D2 C" D(5) SPI_NSS
* @! }' s/ _. k- i5 x& C- p- [+ T本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。7 z. E3 ^: u0 n. G
(6) SPI_BaudRatePrescaler0 u" s2 d% l! t$ I
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
+ Q( S3 p; w; Z) t(7) SPI_FirstBit  p7 w; ^7 q, Q. ^: n
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
# s& u8 W$ j8 Z# c5 u% P(8) SPI_CRCPolynomial
) `8 V5 ^' p4 _' U这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。/ d# l% ~2 `& o

: f0 ]9 P2 Q0 E6 l1 }: A
; X* E, H' [/ J1 B) `* P5 X& c
初始配置函数( l0 u" m3 k- W
  1. void SPI_Config(void)
    ! m, e( d: a( _; H# a
  2. {3 `0 [2 O' V8 a0 b. ^3 J+ @
  3.         /* 初始化SPI和相对应的GPIO口 */
    # e8 m, C- O- n1 |" V; z. F* u
  4.         SPI_InitTypeDef  SPI_InitStruct;
    2 m0 j. c/ q7 l7 j. t2 T
  5.         GPIO_InitTypeDef GPIO_InitStruct;. B9 W7 d- W9 ^4 S
  6.         /* 打开SPI1和GPIOA的时钟 */
    ; x; e8 u5 S/ E( _5 {% c
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
    3 s9 j3 j& W+ {" \' M2 B
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
    + w+ j( l& U- V( {
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;. s% u: R  j7 h1 x4 Q
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;: i+ U, E9 M# _. x
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;9 u# m8 g4 O8 O5 d* [
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);4 {% s0 j3 ?1 l7 P: N' P; U
  13.         # }) J7 T- s5 i- \: I
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
    5 c: I1 J1 |) g1 t
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
    6 `$ b, o& S% t5 X+ R- g
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
    + {! C+ z4 a. y4 A  L4 F: w
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
    % h$ O( _4 Y  g! `2 G4 b. i
  18.        
    : M: Q' i4 P$ {: f* ?8 m2 I: _
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;# Q( f" p8 u7 R1 A( d8 e0 Q
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;9 J1 _) G& P$ W' X5 F- {# p% y
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;. p1 n; i! `+ D7 j0 q& O4 ?* u6 y
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
    " p8 h4 _3 @3 I" P  g- P& H
  23.        
    , P2 {1 J/ q( |2 ~, X3 a
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;
    + g" l; |/ I& m6 `/ b
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
      S  ]' ?* ^5 P1 t
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;4 Y& }9 y5 p$ r3 ^1 p1 B( u
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    - X3 ~9 O, s: D3 @
  28.         9 G$ c7 z! S/ m; Z8 a1 p6 Z0 {
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */2 q" c- p8 f/ G7 |1 u# q
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    . R4 u2 p1 p' f' e
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;* L8 y: R6 g$ e6 W; p, y
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
    + V# ~! @+ C4 D  V$ s
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;# ?, [- J3 A, n! h" @
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;! l8 U! N* u6 \4 D
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;
    ! h& x' d' B0 }: P  A
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;8 {5 U& U4 D1 f
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    8 w+ J+ N6 K* ~6 v" Y, y1 R+ c
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;
    4 B; \% q9 c4 F, K: L. k
  39.         ' w2 k9 R/ n' b' h" l' E8 v' G' q
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */: r7 i# L* q7 E0 a# O; b, o
  41.         SPI_Init(SPIx, &SPI_InitStruct);' \' E' }* j0 n
  42.         SPI_Cmd(SPIx, ENABLE);
    " a) k# e  p! X1 F* F( e/ j
  43.         /* 拉高NSS */
    : a% m+ l3 ^" ~# O: C. `% o# B
  44.         SPI_NSS_Stop();' ]( I& b& Q( W  y
  45. }
    9 ]4 P0 D$ X) i
  46. 2 S! o  z3 }0 [" F. d
  47. }
    ! @8 W: o% _- C  u. a) C
复制代码
4 q" J8 N3 g6 A9 J% U$ ~
这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。! A" c: O: g, e2 u! [- B# P
$ W$ T; b; }% B) J; |8 m

0 |9 B2 n" Z" F: b0 F& {, U发送、接收一个字节
1 \* a4 ]& I# {2 z& \# m' o
  1. /* 发送一个帧数据,同时接收一个帧数据 */" s  s" @. l( _3 ?4 Z, F! _- l
  2. uint8_t SPI_SendData( uint8_t data)
    # S" q0 W+ z5 P/ ^! }2 f. R
  3. {       
    ) A6 M9 B3 d. O0 O9 ]+ c& [
  4.         uint16_t timeout=0x2710;   //10,000
    4 |0 }! V) w; G$ i' ]6 g8 _
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    2 Y0 h+ x& {3 N$ i  |
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");
    # E- f' {" Q; `. _
  7. % g) f# ^& q5 e- p
  8.         SPI_I2S_SendData(SPIx, data);7 ^) a0 H/ b/ c4 S
  9.         timeout=0x2710;          //10,000次循环无果后为失败1 P" u/ v" u6 R* ?. P
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
    ( B8 i: [. F( q- C% o
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");0 ~, y& E9 u; N
  12.                
    + Q) d' d& K; o& Z; A
  13.         return SPI_I2S_ReceiveData(SPIx);
    ( P. d- i0 O2 q' O9 v: r  @$ b: Q
  14. }
    . K* a8 y! H8 z# }4 S+ r

  15. 6 O5 p7 R: T# i4 R! o' ~

  16. ' a6 l2 t" ?; i& i; f/ h# P" E
  17. /* 读取一个帧数据 */
    9 J7 C8 `( A2 d+ L# a" g0 r6 N
  18. uint16_t SPI_ReadData(void)
    5 ^, G5 s5 v0 I$ e7 _( O" o
  19. {5 v! h' {+ @- ^0 X; r
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值+ {* {, L9 U7 ?* L
  21. }
    4 }" B0 n4 H* t; |3 n( s
  22. $ n# ^" F: y2 k! O# o1 A7 }
复制代码

8 K  S3 o" P5 d0 @$ j发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。& S$ v- D4 v; C+ Q3 G1 J# D

  O* g: X: e7 v5 g( A* \  u0 h
% c8 _- {- O7 S1 Y1 p' H9 G8 [
头文件, n4 ]8 C3 T6 y& ~0 p
  1. #ifndef __SPI_H: x( ?# d7 H# q; [2 B/ w
  2. #define __SPI_H6 O  h3 Q. E4 j  b1 R8 l# D) t

  3. & K6 ]! A, M8 |3 K8 f& Q* [
  4. #include "stm32f10x.h"
    " V8 V: e: e$ Y! u/ a: D0 o, C

  5. # O+ }, V. k. H) B% Z! N2 o- w2 f
  6. #define SPIx                        SPI1
    - r* T: R( u4 m
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1
    ; U0 ^' {% E1 w6 E% b; J
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA( O, K& S% v5 M8 U6 B  O! P
  9. - O7 ?/ M/ N$ L( P9 Q

  10. ' a, t! e* x# m, ?
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
    9 E6 l# v! I4 r) x
  12. #define SPI_SCK_GPIO_PORT           GPIOA
    " b7 t3 u+ i  j( [
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
    ) Z9 [. k0 d/ P
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
    1 Z& ?' v( o! a3 a; O3 I( X  e' t, R
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5
    " \. x" O- h! l7 Y& s* u: r

  16. & P% Q. h# p) }- A2 r
  17. #define SPI_MOSI_GPIO_PORT          GPIOA
    $ O# |# w& b* \3 `$ ~
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP* [- y" W( `8 c+ p- S
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz) T/ d" z8 b- g  `" S$ C" {! H
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7: G# a$ C$ e/ {0 o2 Q. [
  21. 3 {1 x# P! M5 Y9 g
  22. #define SPI_MISO_GPIO_PORT          GPIOA/ L) G9 D9 R& t" z; R
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP: I* U5 ~4 q9 ~3 f7 G( u4 ?6 I
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz- T' Q5 ?3 c5 O- k1 [" S  {' [
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_6! Z( K" k. |! K9 t
  26. & B9 N* S) K  R! @' O' W6 Z
  27. #define SPI_NSS_GPIO_PORT          GPIOA& g/ _8 T& g8 _% p
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP8 M/ |  a) R4 }/ Q- m
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz
    ( z' T, q9 h: r$ t" D2 _
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致) j3 n0 u: z9 M+ K; J

  31. ; h/ ?1 |+ C% S$ _
  32. /* 配置SPI信息 */, a/ v: A; \" R3 U) x8 \; |
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz
    : K* T  k* \) \% _
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样7 L! M3 U  ]  U$ W3 w. \
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
    * g% K7 _1 _; s3 q( r
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓* ^$ s" s4 O* t: k8 y4 L6 w* v* o
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位
    . \* p. \+ m$ N# M4 b1 d0 X- w* X
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex
    , w4 f: m; `8 m/ ~5 U
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行' I1 a1 `( W( {9 L  A
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式
    ) o" j% }# N$ {
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟2 Y- ]. g0 r  p( K: G. U$ @- Y

  42. 8 T( G) s) c1 H4 }
  43. /***************************************************************************************/
    : `+ S* E+ t; p
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    : `8 \# x% U3 y8 @; V9 B
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    ; d: @/ \8 v: ^" ]: J7 W5 s9 _
  46. #endif  /*__SPI_H*/
    & y; x) O# v6 }' b) t5 O+ J. \
  47. 5 n9 t- C( L0 k  v

  48. 8 D7 R6 l7 E) L! Q; Y1 ~
复制代码

' d" M3 M8 d  X0 w' u0 R4 J————————————————' d! u- h" E) S$ p3 v$ v( ^7 X, s
版权声明:Aspirant-GQ6 @% z+ I% K2 X: M" l' F
如有侵权请联系删除7 _+ P& L* q& ~0 ]

$ |, K0 \$ D7 E8 M' z9 z2 B# H' V( S! i' q

  [6 [, x5 n2 B* H+ q: F
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版