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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI- \, h# J( d. ?+ A8 J
SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。
. L' @. `9 L9 {% ^
) l- b; p; h; V1 N" q0 W: D
二.SPI协议
  Z# ~. V/ g/ C9 L  h就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。! X2 x0 f( D  w3 C
3 Z! ^- X( y4 g$ ^) C/ }
物理层
- g' k0 Z, q7 N3 ]9 n3 ?首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:. o3 X3 J$ i0 h& O

& e' p- g( g3 N1 Y: V
20190809114531658.png
* T: G, o9 o; |# Y

; ?9 ~% Q; L* w0 ^) FNSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。
/ h2 h* h& t  x0 W# l& TSCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
( O6 i% Y7 r  W: C% {6 eMOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。% h6 a. y) j9 \/ Q
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。5 V+ D9 e) v0 t7 R# i' k" q

  n+ B4 Q9 n8 C- o3 S7 _

1 |& D6 ?- j% n& o* c. h0 R; |协议层
# q! S7 ?/ `8 X8 O, J和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。
2 T) l+ g9 G' j5 g5 Q
8 m# f; e1 b: I3 P( ]! \
# @2 X0 n: M$ ~
1.通讯时序图
4 p# \# e4 P& b8 [* n5 b# i6 y如图所示是SPI的一种通信模式下的时序图:0 N: V- K" k& b: n/ H
20190809121651274.png 0 K$ `$ M3 O7 _
/ I5 x' s  e+ B
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。! z0 w) u. }4 X% [# Z# t
+ Z  ]& |+ j, F8 e

3 Y* a8 P+ }& C6 G& {! y% d2.起始和停止信号
# I/ }4 J! w6 {  \" l% t2 T前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。
  s& |0 ^- r. O# q4 i* @% D8 H, a3 d

7 d: [% m+ I  G- U0 J& z( \! I6 M3.数据有效性% t# b* f5 i2 O5 X" ~  L9 s
SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。: b, v/ P1 `) x( p
( c) c; S7 k  o
4.通讯模式7 e! z8 A% B9 ?& \
SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。3 F5 o  ]' O, I( o3 x9 O$ P- L# b1 `
时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.
+ g; ]- Z( b" v' Y2 S6 a# |  \; F# t
20190809124918344.png 0 F8 l+ h2 y) v" C% n! T: S9 `

6 @0 _4 v" F; F( w时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。& e1 \: q8 P0 n- O
如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.! z% \8 X& R' H: m; W
% c6 k) M, A3 B) x2 L
20190809125537352.png
! H! d# C2 u  Q( R: V" `
) f0 Y3 A6 T+ }: L
四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
7 R2 z3 ]' u  _& U6 A0 b; p3 B" ?- n; ^6 k( B- {6 G# o
20190809133146530.png
- E: U8 L- Z$ [7 O# a  ^
- u* s' V) C& a( {, U
' l, u' }  J6 J$ @( ^, X) m
三.STM32中的SPI
2 v1 v5 E. I9 g9 g简介
- I. p$ h3 O% U3 B' C- Z
STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/29 x: _" f) N6 b/ \% |
(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。4 d$ h, B  L" P4 \2 S
- D( F9 U: J8 t' Y; \& o3 K
: E9 f3 {* C/ y4 N4 e
功能框图
/ l, P# \9 H* ~. a! Q2 A
. `% p2 d; O( p
20190809131145870.png : H4 c$ Z- {' l$ z- h

/ q9 b' ~, x- `- z. c) FSTM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。% f$ p" O: x6 D5 Y; S) j5 Y
# L9 r, ^* B# m5 D0 L2 H
1.通讯引脚% X. E; C+ m7 m3 {9 x7 G  }' I- s8 \
STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:- A& Y1 W; L1 I& ]/ `# c/ b
! V, f3 b, v! E$ u0 g
20190809132131865.png 0 Q7 Z! Q% ^8 b. f* h0 H5 D
& }1 t9 v+ K$ F# }5 }. H' b
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
/ R4 b" w0 y% s2 ^: t
8 d) {; K+ a! U# l1 r. B
2 B# ]4 ]- O! L- A4 B- k" F5 G. q1 X
2.时钟控制逻辑

. ]$ v* y# Y* G- v: I这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
, r0 `& j& x& q  B时钟频率的配置:
, _: s6 x! t# J' E7 b( @4 x4 z+ Q1 O. ?波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:5 r( k& @2 g+ _) `0 }
. u; x3 T( T) C. V4 x

/ O  V5 k# s6 m, M, I2 F+ l6 S+ B3 G! N+ a+ j/ r
' J: C7 F  f+ v
(PS:fpclk为对应SPI挂载总线的时钟频率). Q- U2 [; P9 d5 E6 C
通讯模式的配置:( y! Y3 ?- ~' Q; }. V/ Q  i
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
- `+ i6 u) B/ R/ [5 p$ J
) @8 ~6 O- T& I" h% [
3.数据控制逻辑
' U4 V9 {% Y' C$ X这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表* m# r5 Y$ M3 G* g5 U
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
/ Z$ n* C, a+ N6 u5 f* Y) [# U+ x" f4 ?标志位”会被置 1,表示传输完一帧,接收缓冲区非空;
9 m) Q$ u* E4 ?- K9 y. ?+ q发送数据:
6 w& l5 X! F$ h+ E7 |* L地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。% h6 g4 v" i' d( v* X7 t! |- q8 O% _
接收数据:
# |, h/ O6 e+ o7 B2 f$ j/ Q' c接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。
- n+ t( e. x! V: x数据帧格式:
. B) x  d3 d0 a8 |通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。/ F6 o  k: l/ }
先行位:
4 \; \( ^  C7 S5 O5 g1 ?通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。
  F  L5 C/ d6 s& t" t% A
( M  w# u! H0 r
4.整体逻辑控制9 g/ w% |  s8 u! |' k$ }" E7 O
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。% E) Q6 t  x' ^5 h0 J; C/ \* @

, f& S6 D; C( x/ F5 o初始化结构体
) ^5 \& S9 S, ?& m0 W+ W1 c3 \* ]库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:
& |. |& {( A8 J7 _; q8 a% |" `& S
  1. typedef struct. b9 Y# K$ Q4 v1 ~9 ?
  2. {
    - S  }8 w! |& Z5 B9 T) V
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */$ Z1 K- S0 _7 W& _  s" `$ t
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */; c4 w9 w9 t+ w/ ]& \) H5 y, k& b+ G
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
    ' F# w1 f1 A$ t# V! W
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/. o5 y9 q- d6 D& q. _! P
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */5 _1 C; ]# J- o2 z
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
    & G2 m$ B4 T7 v" g  v
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */" ?& g4 u: {* H! k' I  b! e7 R
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
    . h4 ]% J1 t( s6 A0 M+ c3 D3 |  _. [8 K' u
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */, q- v9 d& i3 h0 J
  12. } SPI_InitTypeDef;" H/ q3 U' ~: ]8 ]2 \1 {! m! R
复制代码
: W' w; O' X: c) h/ {6 R! H
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:: D1 ~3 }5 J$ k" I, t. x0 t7 m2 L
(1) SPI_Direction& ]* P" F$ k; `5 Z& B
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
6 q  a5 O$ c. J: A% A, g(2) SPI_Mode
. `- v; w& S7 N本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。/ q# S. Y( Z! }4 `
(3) SPI_DataSize  j2 s' u' t9 U  F9 S( \# {
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
3 t! v' _$ F  Q(SPI_DataSize_16b)。. g; j( f/ J1 H/ i+ j
(4) SPI_CPOL 和 SPI_CPHA; q: _* y* W! N% n
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
# t. H# j/ f7 u7 ?7 l(5) SPI_NSS6 y- w8 n3 {! d$ k8 [/ s4 }
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
8 x$ l$ v7 C* V, a" V(6) SPI_BaudRatePrescaler
4 t# V. `; J5 _$ ?$ C9 f# @本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。; ~& ]7 V7 x$ v3 s7 F3 `4 x
(7) SPI_FirstBit/ f4 [8 K. g9 z
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。# ]4 V: E$ B3 z2 G4 C# c8 I3 {
(8) SPI_CRCPolynomial7 q% q" N2 D" t$ g& }' A; Y. w
这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。
" W/ m9 N2 l- i' e
" ~4 W2 Z; E* Y
# O% F7 ~  F1 p: U
初始配置函数0 E8 }% G* V' ]8 m( _8 m' X
  1. void SPI_Config(void); ]8 A. F2 V% h2 Y/ W
  2. {9 A8 k7 s% c' m0 U- I& o) Q' K
  3.         /* 初始化SPI和相对应的GPIO口 */4 A; \, p: k! n9 Z) s
  4.         SPI_InitTypeDef  SPI_InitStruct;" v7 q1 ]; ?* }  T2 k" R: ?
  5.         GPIO_InitTypeDef GPIO_InitStruct;+ w1 m* p1 J& J
  6.         /* 打开SPI1和GPIOA的时钟 */( J1 J. [- G* g) ]; I
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);% P* F7 ?2 M& k( b; s! f& e9 |
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */, i$ n% S1 S! j0 p
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;) _( F! K, i. ^3 m; N
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;% B' J: c6 w+ p6 W- c
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;/ J$ N8 `; Q! G& d% n
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
    9 P* V! l7 n" }: K
  13.        
    6 M# t# p* [2 V3 D. i3 Y
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
    9 s: _! z; J9 E8 R7 h
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;; V8 ]1 K$ a& \6 z( t3 `
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
    4 P5 ~+ g7 `$ n0 e! W" q
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
    # w3 y0 R8 W7 N2 F( O4 c
  18.        
    ! V  E3 p% E: ^0 [1 Z
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;/ _) W7 Q( `5 w, p
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
    ) U" ^% |- v: Q' R3 T/ V  S, j
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
    ) H- e& T' b" _8 J0 A$ J2 R
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
    5 C( M0 _% A# i; i9 d& @$ O" M* T
  23.        
    ; u% ~# D  i/ v  A
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;7 w8 r3 _# `6 J2 v. K& v1 `+ m4 k
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;% }! k3 N1 q7 [3 _' w- T/ a
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;5 ^; e1 u) e) w' o3 _# J. L1 l
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    3 y  I8 v1 T. x" H( l5 f0 b9 w
  28.        
    * y( o# l/ U7 v7 A  x
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */3 K0 N1 q5 Q  A8 {, l3 _( Z7 F
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    : G) M8 m0 v& q3 @- ^# k
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
    - C! [- V. b+ v; _( S" R
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;: {' P& S0 D- h1 o' a2 l" m/ s
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
    ' c8 Q" v3 x0 N( d! @3 g
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;& d4 d" v( k- o" D, i& A4 ?6 W
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;. N6 ]+ n, }5 ?2 v
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;
    6 \: C" G7 F8 b. A7 M) k0 F
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    6 z0 X- L" O% N3 n" s
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;
    ( \! x9 d4 I" N: G2 Y9 P
  39.         $ y8 n$ X- G1 s
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
    ; w6 I1 H! X/ |! @% ?1 w
  41.         SPI_Init(SPIx, &SPI_InitStruct);9 `$ N: U! q) O( N0 Y6 S' V6 |
  42.         SPI_Cmd(SPIx, ENABLE);
    5 f7 |: K% e% U! w" @8 M
  43.         /* 拉高NSS */1 }  W% O2 A2 _8 d8 n  J5 @- B
  44.         SPI_NSS_Stop();- R- r: T. [, i1 X$ V
  45. }
      z' Q3 \8 P5 |# z& J2 ^

  46. 3 N; _  P7 S) `9 s# I
  47. }1 g; ^5 n1 F* ?7 n
复制代码

' C/ x8 E& k( o, T: M这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。
& \) a  z" G3 m+ [
8 K$ I5 s1 {4 [
4 {, I' g7 T" \5 N
发送、接收一个字节! I& n0 ^; _6 ?$ g
  1. /* 发送一个帧数据,同时接收一个帧数据 */
      y- l" e+ r7 i: B
  2. uint8_t SPI_SendData( uint8_t data)9 u1 h$ G3 {- ~5 ~
  3. {       
    9 R# c2 \; ~- B( g. z
  4.         uint16_t timeout=0x2710;   //10,000+ N6 w) k: J$ U0 b9 A1 i" W
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    / C1 W% ]2 j8 M4 r9 p) d8 q
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");
    # w: f' E. F, V3 V- r
  7.   o$ d, w5 x/ s. z  J) m0 h
  8.         SPI_I2S_SendData(SPIx, data);
    / G9 |. E$ K- }, Y  Z# m
  9.         timeout=0x2710;          //10,000次循环无果后为失败
    5 e% o. q- e5 e
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)% {8 k( k; n1 x/ f
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");
    7 l3 n$ `7 D$ f: T$ g) G
  12.                 ' X4 P3 c, f0 M( W
  13.         return SPI_I2S_ReceiveData(SPIx);
    2 G+ y2 B3 `/ Y& P
  14. }4 h4 m: K. V+ u3 P

  15. $ B( A3 b8 X0 ~8 ^% {4 @  d+ b

  16. 2 A; u$ C! I3 c
  17. /* 读取一个帧数据 */8 L6 M" G2 q! R0 d+ {6 e$ t' P
  18. uint16_t SPI_ReadData(void)9 }9 q- o( y7 H4 x7 w9 y% @: \
  19. {
    8 |$ `: U0 X8 A: _* h0 W4 _
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值
    5 U- M# l  J; u9 j' j+ O
  21. }
    & \1 Q3 I4 Z0 R2 z

  22. , y2 q$ v0 L) T1 ~$ y
复制代码

; E9 W1 J7 w' A( R5 M5 w) i发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。2 C2 D$ k, I+ G! s& h3 o" \7 D
0 R+ n! a( ?2 z

8 I2 d) S0 ~- V" g* Z) F$ H! S头文件
2 o/ [0 A; T8 Q  N+ m" r
  1. #ifndef __SPI_H
    - K6 [2 J. Y" Q9 K8 `
  2. #define __SPI_H
    & K3 c/ {) M0 @( z

  3. 9 q, Z$ g) @; Y( X& U  Y+ p/ Z  Q
  4. #include "stm32f10x.h"- P, D; Q8 ]+ G) U/ @9 C
  5.   {3 N) `  \* G+ ~; q3 j  b
  6. #define SPIx                        SPI12 J$ n8 h* l% \: k7 |
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1' e" v* a, ]+ ^' _1 ^' F, o
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA0 w9 v2 g1 W6 |/ d1 F& \
  9.   R3 a# f9 q5 D4 H  T

  10. 2 l9 G# e% H% h+ S8 O5 a
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
    * q. |0 l! M* g+ }5 S% O
  12. #define SPI_SCK_GPIO_PORT           GPIOA, X* w- I' X0 W2 [
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
    9 j  S( J9 g% B1 @4 e; x6 Q
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
    * X# G) u7 s# _* Y( e: Y; U$ r( z
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5
    2 `- p; m. F" e& r+ A, r  z8 V( D5 i
  16. 3 n/ y' O3 [' ?1 _& |
  17. #define SPI_MOSI_GPIO_PORT          GPIOA$ G' s0 Y2 R9 u( J" J
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    8 x  A# B% h, G3 R) V
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz
    0 H5 E: H- ^0 O5 {; T2 {) `$ K
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7) \5 O# t" H7 u7 K
  21. 2 j  J- r: j! D3 V- v
  22. #define SPI_MISO_GPIO_PORT          GPIOA, X$ \6 [4 o9 V
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP
    $ m3 w6 e5 [( f2 P0 V
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz/ E0 P! W' s$ z* ]6 u3 q
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_61 @% }' d5 O. m1 \% R

  26. - W+ Y8 S% ]0 \2 N! _( K
  27. #define SPI_NSS_GPIO_PORT          GPIOA( N8 [. U+ x4 `$ h+ r9 n% u9 [1 o
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP. v" ?- V$ J  V* G' Q
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz
    7 ?+ z4 b6 g6 x) o
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
    , {& I+ A% ]+ Q) A

  31. 6 X' x- V, T! n; j( ]8 r
  32. /* 配置SPI信息 */
    1 e4 h" c. i  Y! E' [
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz/ r, m; [" M7 o5 X+ y8 \! p
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样
    7 K8 `9 h5 f! l6 S( m8 I# r
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
    ! Z8 h4 Q4 d/ X
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓
    ( t, s; \( L4 f2 M7 w, S
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位" T( p1 p: I" K1 h
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex
      Z) L! ~, {' h, _  ?2 P' h
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行4 ]' L+ a" N4 c2 c0 x/ p' K1 r) u
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式
    2 f3 ^( Y- ^( w8 k
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟# j) [% v/ F& H, T- @9 @
  42. 4 m) N! G$ y0 c) ?" k, y2 |, W
  43. /***************************************************************************************/: ?% s# R* s+ A/ O4 {
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)- O9 }$ z9 [/ v
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)& z2 o% W  Y# M! ^' C6 s9 H9 k/ v6 `
  46. #endif  /*__SPI_H*/
    - o& ~5 h9 h6 k0 _& Q7 [0 d

  47. 1 v1 p6 `9 Z0 o" O' y& |$ ?; b
  48. ; G  n7 ]  h$ b) B
复制代码
7 g* l5 J& E6 D. o: r' m4 ?4 B) ^; U
————————————————. g5 ?. h" W9 u9 J& W9 \  u
版权声明:Aspirant-GQ$ R/ F" s9 t' R; t  ^
如有侵权请联系删除3 f* b: `: _: ~# D; _2 `
) F7 A( k4 |9 B7 {( H5 ~8 X7 v: O

' N0 b$ h+ b8 i' L2 E' |5 M) \8 |8 ?& n4 P& o" ?' c: y
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版