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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI7 c* A6 @" j7 C4 c% L) S% i
SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。* ~# s. v" T+ f) W: B% M
2 y  U2 l( V6 j+ m9 V
二.SPI协议
! M' @: r4 N0 @1 m$ r就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
; l" |4 D9 O9 Q+ s! G9 S1 K

8 b% R6 k9 Y. i* j物理层
' p3 J% Z) `0 T3 X  h# E首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:( R- ~% U( o  }0 v4 c/ r0 l& H
' A2 x9 J8 M1 y+ T1 ]9 A3 }; }. S
20190809114531658.png
. a7 Y2 @; E! I! D+ J

& }! X+ X5 x  F5 ^3 o0 A: g: JNSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。
; m, W3 j: \" F3 ?& ^  qSCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
! i9 `" B$ D1 W: ?% PMOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。; S; D; y4 i' L# A
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。- n: x! X5 M5 d

; r' e9 l  ]+ S
' a" a$ D* m" h7 t5 e+ D- f
协议层/ H0 `, y" q# S
和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。* b+ ]+ }  ]8 Q4 W  s0 n3 x4 ~5 K
! U4 U8 M. y/ |2 S5 h; [9 e# t7 S
. Z% B' u% |! w% V. r3 K1 |" d
1.通讯时序图5 `0 k! j8 G  ]- T
如图所示是SPI的一种通信模式下的时序图:
; s" {: O. m2 j$ U
20190809121651274.png + y% e1 D; b+ y7 I, s* `

4 F$ l- C2 l* N& l3 F( z, y所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
. \. L0 o/ w) l+ a7 v) \  H
- w+ n' v# ~4 u/ n7 D
  B/ T7 }! Q. _' v7 q4 F) L
2.起始和停止信号6 |( |  [3 n* N9 ^. ~0 y( z3 L
前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。
! y" `' H) z& d5 l
2 o# h) P( g) ]
3.数据有效性; \+ f* X7 x' S$ X5 S' X
SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。
7 D* M6 o: z- V& ]$ b& }3 |/ l5 p5 Q  ^

! b# X% O) S* w% s1 t4.通讯模式4 @1 v* D: D: G4 m2 M
SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
) l$ y2 o8 C1 L$ J* j( h; G时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.
% ?6 A; U1 h1 F; _5 m# ]1 X1 o0 @1 ^6 ]0 e
20190809124918344.png
' e4 @$ z3 ~( P/ ^4 O: K
+ s8 e6 w0 @7 r0 g; P# T  T6 J4 [5 v
时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。
7 A/ P/ @. }" I7 E1 Z( ^如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.1 V: R! }- Q0 W* C, E9 z' B6 @( E
& s9 U% c" d/ N% k& x
20190809125537352.png
4 Z/ K6 j1 K6 e2 ~0 ~2 @( ]# a0 K

0 W& a5 i4 P7 l) ~* p( c四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
( e2 ^# P: t1 o* J
5 U' u" {. n0 B
20190809133146530.png 5 B2 ], X* o" H

0 \9 A. i* ~/ r* }% F2 R
- G: m8 s. l+ `9 y5 n. h& F9 a
三.STM32中的SPI
: w  f# Y+ [$ [0 Q2 o/ _) V简介

5 q, x4 F0 B! r/ T; ]STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/2$ G7 ]0 E# k1 I9 W" \. K. D
(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。- ^/ l7 _* M$ i6 b+ f3 T
' j1 t. t$ ^9 j" M
- ~2 }( H4 {/ O$ _) \6 ?$ O  I9 Y
功能框图' ?! l/ _8 R( `& w7 I% x$ T& g

  r0 M- s0 u+ w+ S
20190809131145870.png 9 d+ B) W" q8 D1 ^

3 H& B4 |5 e: c# [  {/ [. o; JSTM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。6 [8 u8 C/ e. S1 L+ J" F# N9 d4 n
* |& m  ^) E& d( v6 ]( C
1.通讯引脚
! s0 c+ `$ |& }) ISTM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:
3 c) T3 W( ]" Y, a. |6 Q+ Q( }0 |
/ f0 g; U9 S+ q9 p, s
20190809132131865.png * P8 D3 c/ g8 r7 D* E, e
9 s5 K4 o# K9 ?( Z/ W) n
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
- I( L- T  h1 k8 r, |, C& F
" e4 b& m6 i+ N+ y

* P4 u! f: i0 s* h4 Q( h5 s4 V2.时钟控制逻辑

( I' f. I$ ]4 `1 a这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
4 X: X; l8 p- H时钟频率的配置:
) G. O) M2 Z: J: @波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:+ a8 X; ^3 K) P5 J2 E; X$ L% V

  L' X' U3 V, J/ D2 m

" f% \" t0 X' a* l/ L' X( s
; {3 I* a2 x  W
5 W' c$ v1 J% s; Z2 r8 y/ X  `) o
(PS:fpclk为对应SPI挂载总线的时钟频率)" J2 r. k# p1 I: W
通讯模式的配置:: l- F6 C( z4 p" j% E
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
5 c: P$ W! e3 `5 \* C$ [
9 L; n- u, e" S5 f' c
3.数据控制逻辑" p, v& N: D; y; Z6 d! R
这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表2 W1 I- G) B! q* a$ c: i  u
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
- n, A5 g% ~* B+ b2 H* u  d3 x/ N标志位”会被置 1,表示传输完一帧,接收缓冲区非空;
* J$ Z9 U0 J2 V! t( U, V8 g4 E发送数据:
/ l5 c# d6 u# h; T3 C地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
, u3 ^) U# N) \$ h. h  e接收数据:( m4 F3 O  P+ N  h! @# |
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。: M  F$ W: I2 P  W% w) J
数据帧格式:
7 R' T" t  y1 F! f' s2 h' j) P# U通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。9 l( I8 W2 P  b$ ^% \9 F
先行位:9 u7 t3 p! r) B. |
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。& ^  j/ f. }! G2 _; i1 B. Y) y
7 c" A8 h7 ~2 h" p9 ]3 u6 h
4.整体逻辑控制8 i( [& O. U8 t. W! _4 a  y
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
9 c/ g2 a+ x5 {( g8 M  e, q

$ d* E% \5 I  k- s% B初始化结构体
8 R, x) f+ \1 i/ g0 h) F库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:3 N; l0 u  r- q8 U1 \
  1. typedef struct
    , K1 \9 T3 \4 Q, i# I3 y: s& W
  2. {" U/ ]6 h; a5 d$ ~( A
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */
      @$ `8 {% m8 L
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
    . \" I4 E. v& Q* u# J: L3 _9 k
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */7 L2 I0 u  @* V# h7 [- h. Z
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/
    ( A$ Y6 s% @+ J. Y- }
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */  A8 K3 H1 \" T7 T
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
      w% ]  U8 @: e+ K/ n8 v& A
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */
    6 H  U) v# h+ h' r4 F; B: f+ O9 s
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
    ! o4 }2 Z3 u0 n- }
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */6 e3 q" j0 @7 n4 U# u  q
  12. } SPI_InitTypeDef;% S* Y) B& f) v' F3 m( Y
复制代码
7 |2 n/ \8 o0 x5 H# Q
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
5 e' t1 a5 g5 B% Y' x) }- ~(1) SPI_Direction9 B1 n/ ]1 V  i; ~9 Y
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。/ c! `, U3 S0 j
(2) SPI_Mode
$ i8 x5 l  c( F* K" L) A: h* W本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。: l( i; n& E( R7 h( W9 U. @
(3) SPI_DataSize8 }/ T! H& w$ `8 v4 T' }/ Q
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
, z+ j* f( q+ {(SPI_DataSize_16b)。
# Q0 q# S$ c; {$ i5 E$ ?(4) SPI_CPOL 和 SPI_CPHA# k. t3 f! m& S1 [# b
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。8 S4 M$ c2 u6 I! |9 G5 ?) a
(5) SPI_NSS
4 k% W& D' A; y- K- S4 q+ `4 {本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
0 `' T7 h& e6 `; G0 z* b$ M(6) SPI_BaudRatePrescaler; m9 Y3 f- w" b& R* _
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
% h/ J, }% ^$ k& Y; _+ i6 v(7) SPI_FirstBit
/ E1 x% @9 H4 C) {; r* f3 D3 H5 B5 K所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
( t% t% e& Z( y0 T(8) SPI_CRCPolynomial7 F" w9 [3 t% j2 {. _
这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。9 r5 N4 L; F. e! y' u8 k6 |
# m  ~* Z) j  Z

! N% o% ~8 y( j初始配置函数) R. C4 o) F$ a( ~
  1. void SPI_Config(void)
    2 E9 u  C$ g. S) G7 C8 b" D; w
  2. {# I+ C; i5 `  T
  3.         /* 初始化SPI和相对应的GPIO口 */
    1 W5 L( T2 S* A* y
  4.         SPI_InitTypeDef  SPI_InitStruct;
    4 U% h( d- X5 @# e$ e
  5.         GPIO_InitTypeDef GPIO_InitStruct;
    4 p6 O5 x% X2 b! k9 p
  6.         /* 打开SPI1和GPIOA的时钟 */# _; g3 t) m( d7 W+ J) p5 E% o
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);! a8 h5 t" F, _8 I4 ]( M
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
    # c* m) M0 Y: W: k3 J
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;4 }7 U( J4 H: K0 L2 H3 T
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;
    & T* C* U+ L$ P, u5 x) t* y
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;( N5 T  N3 Z/ P. N! t9 _8 r- A
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
    : R3 U7 K9 s& t. G9 ~( U
  13.        
    2 O, a- B) i" ]0 w$ B
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
    1 ^8 C! g: L# {. ?$ ~
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
    # _; O. d7 w5 ^/ a
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;9 }6 [' @$ L$ q! b: `
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);2 D. x  p( y+ H: K8 s8 e& S
  18.         ) v+ f8 r1 q* e0 z  X( X  g7 I
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;  x+ V  ~: z) i1 {  l* n
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;% ]% ~. F/ I- j3 a. k
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
    ; {, h* {) i. @: P2 O+ v) q
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);1 v$ h' w2 M: K# P
  23.         1 [; E' Q. ?- W
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;
    9 ]) P8 l2 K; g# u
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;" P) z$ z& X! t0 X
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
    8 d6 m1 Z  \  X# O8 i, Y. X
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    / Y4 |3 z% F  G; M0 F" B7 j
  28.         6 y8 ~- s. \1 s9 y" l; T
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */) N3 C! z9 f1 b8 n; a, Q- b
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    % z7 N) k$ w0 J6 {$ X4 o8 @
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
    + l# `) A3 s4 i% r
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
    7 k( ^* i% F+ i$ Z  N  V0 p; z; b
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;3 G+ o0 S# o" w0 g. s; @' {
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
    3 o# K4 h# X2 {6 ~& x3 a! C
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;
    # P# `' S5 A5 u- h% D8 R
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;( l, P  ]/ i1 a) H
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;9 p0 x! r6 c' Y4 H2 W) o5 z
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;
    5 c4 J4 g. f1 p+ p
  39.         $ i% Z+ |; M! [) V7 J
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
    4 T$ I# O6 j3 _* W8 {  D7 u
  41.         SPI_Init(SPIx, &SPI_InitStruct);
    . j2 Y7 L; m# S& a
  42.         SPI_Cmd(SPIx, ENABLE);, @0 _8 Q3 B& I& s% c6 ?+ v; V) y
  43.         /* 拉高NSS */$ Q% r; Q3 X* Q" B! v' @
  44.         SPI_NSS_Stop();
    # p/ h3 ]  @1 U5 e& `% U, F
  45. }
    + |$ p6 k' S: ~3 T3 h/ q% L5 x; k

  46. " u4 H$ B. m7 g2 g
  47. }
    + Q1 T: S) E2 w4 s2 `$ c4 a: R- ^
复制代码
0 G2 t' @) M; v/ D9 r
这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。  X' A! B, e: _( \% b
. W' j- f; g2 U$ F5 l

: ^/ t" I' x* B+ K$ Q( Y; Q发送、接收一个字节( Z0 D4 b: \. ]/ {$ P
  1. /* 发送一个帧数据,同时接收一个帧数据 */
    + D- f4 \+ F4 n
  2. uint8_t SPI_SendData( uint8_t data)
    - G4 J  J* j/ J# U
  3. {       
    " f, i# o9 j8 K' D6 g9 t) v6 j4 ]
  4.         uint16_t timeout=0x2710;   //10,000- c2 E& Z! T' u5 _1 P6 P  q
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    ' H$ |) G- _' _. Z, ~
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");
    4 o# h2 n# V+ M  z* V8 d1 t
  7. - E- s, E' A( X" {: V$ Z
  8.         SPI_I2S_SendData(SPIx, data);1 L, S, @- [" j& ^1 F) E
  9.         timeout=0x2710;          //10,000次循环无果后为失败& q8 O6 E; m( D8 p
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)+ v4 x, H) X7 {: r0 G4 w. V2 m
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");
    3 a6 W+ d8 q' u4 U; t
  12.                 ) M/ c9 m4 {$ y: d& Z) a8 B
  13.         return SPI_I2S_ReceiveData(SPIx);
    1 O4 c" D& B& K( h6 [, O" K# A( Q
  14. }$ b4 x7 _/ ~8 F: ]* k

  15. ' x2 k( p: b, e8 m3 {1 S
  16. - [7 ^, C/ _2 E* q" m
  17. /* 读取一个帧数据 */% S" v: D$ X& j3 J0 g$ e/ J
  18. uint16_t SPI_ReadData(void)
    " t! r) ~7 i) m
  19. {3 B6 z2 t/ I1 p' e0 N
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值0 d$ ^' g# F/ \/ S+ I( ]) m3 W
  21. }' f$ y/ e2 Z' l

  22. 8 H+ a: x( q$ y) d/ }% K, n
复制代码

0 I5 _* Z: z8 h: w  @发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。
  P2 b" b3 T4 B2 |0 w8 i$ c9 A; r3 N
+ v# ^8 ?: C! @; X! t% S9 {
头文件
1 B: j6 o* ^0 i4 K( i! P- G
  1. #ifndef __SPI_H4 J; j' D' }1 G  G4 a' @
  2. #define __SPI_H  q+ Y- B8 W2 n2 {
  3. ! V6 x/ t: F4 L+ u
  4. #include "stm32f10x.h"
      V2 O# U6 w# y. N# Z
  5. $ _8 Y& u4 ^: n  o- o* I0 _' a$ b
  6. #define SPIx                        SPI15 B/ M1 F$ x$ q
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1
    1 j& v1 i( w1 X8 p, Q) L' ]
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA. }% A* L8 d) Y5 Y2 g5 t: [

  9. * X. K+ v+ H6 b2 ^
  10. * e! g! L1 a+ o
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
    9 q; a8 v- j; Z- c0 p# d$ G) L
  12. #define SPI_SCK_GPIO_PORT           GPIOA
    4 z; |8 o+ @! E8 D+ g3 I' [
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
    2 X0 U" F$ `3 W. z
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
    ' C/ O9 ]) g& }1 o# T# V& I" D: I
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_53 J- d1 b2 i& R8 D1 \* s. A+ c7 q

  16. 9 H6 n; [' z3 g  j5 f6 }# Q
  17. #define SPI_MOSI_GPIO_PORT          GPIOA! x3 Y5 U2 `- c7 U3 C7 L+ s
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    ; g. c! P7 I- m7 u+ h. q
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz5 R8 w8 Q1 V% V- m, V
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_75 J% [1 Y. w/ J1 R2 D

  21.   y' J6 X0 I# M2 N- ~8 o
  22. #define SPI_MISO_GPIO_PORT          GPIOA) Z+ I: w/ a: Z/ t" V7 p& A$ J
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP3 ~8 \1 ], h+ a3 D. z4 |1 z
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz; i0 S# Q/ j+ e8 {# ?
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_6
    ; G2 E/ O7 H& H! l* `; r8 n8 n5 Y
  26. ; c, _7 C# [1 E, l2 l& s
  27. #define SPI_NSS_GPIO_PORT          GPIOA
    1 A1 S: ?$ Z1 }! F# o3 V0 s
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP. ?: u% E" @! H& ^' L/ ^
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz& ?0 \  s- Y; r9 w6 t( f% c2 b
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
    8 |: t& w7 n% k0 A; o

  31. 7 F; L, }$ r+ j4 E. y
  32. /* 配置SPI信息 */5 t, I& w' R: y9 B8 ?3 K' m9 L
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz
    5 p1 v$ {3 r) g8 U2 m* v
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样( s* ~6 O# W' D, N5 z
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
    . k& j# p% B+ B& \  e) i2 a2 J" C
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓& c) m% M  K! X9 V* L, }% [
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位
    , @  z* c* M; C' r
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex
    7 g5 Q  E6 c& J1 u
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行
    # J; K  n( G2 k( e2 q) U8 h
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式& C( x/ |- r* F- `; A) s& s
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟
    * O8 C$ I0 S' _6 e! N, j5 o

  42. 8 M, Z1 t1 x7 t: b7 I; _1 D0 e
  43. /***************************************************************************************/1 T5 b, Z0 h3 R5 H
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)1 |; W0 e( |% T* h
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    + s  _' m+ {$ C1 U
  46. #endif  /*__SPI_H*/9 ?. s+ E0 C4 `3 V7 a
  47. " T+ s$ r$ f& ~+ \

  48. ! p/ X3 B- V# X1 R2 O( v) j& g2 Z+ l
复制代码
' X4 A0 z# P/ b3 o( u
————————————————9 ?- O) i. N" u4 |- z6 W
版权声明:Aspirant-GQ, v+ Q* v- c4 t# p' E+ j$ }. f
如有侵权请联系删除: H# B8 C+ f8 C. a
$ u% s) [# F7 t# h, t
. _! s& A0 T; J, w  W) \4 b
6 V8 _+ D6 }+ b- _1 i
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

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