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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI( W9 n4 v6 r, C% |
SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。& U8 a% I) L% q4 x  g* X

* H! W% ]' S# d' l3 q二.SPI协议7 }; E1 ]& g+ [/ i, m! v9 @3 J, B+ K
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。( V% p: o5 K+ k; M
- o" ^& ]& K. Y; f0 {) S
物理层7 Q7 K0 u0 s1 w$ Y" V& `# K
首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:
% `7 |& k8 z/ O& g5 Z9 E/ q; R! `. \- m% Z* H& v
20190809114531658.png
. j) G0 T1 @' K' K2 D$ Y$ p
: P2 @2 J$ i8 j; Y4 r3 \) O. K* Z& \! i
NSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。2 E7 x& w" }! e$ V, B. [
SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
9 j( O- B5 P7 I  v( [MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。
4 U* A+ P4 I, h4 D8 |4 B  ?MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。- b9 o" Y0 y7 D- V, E( S
9 S7 q, ]" g! \6 ]( @! n. D2 N

& K0 H4 j, h# z6 ]7 w4 Y; F协议层2 }+ x- Q2 P% O" z% H; G+ @! ?. Z
和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。
8 z" P4 r" `/ C" b( Q2 n; d
) u9 M; K* B0 Z

' p0 e7 O' g! o1.通讯时序图
" L1 O3 W" M+ C6 ?9 e8 n! I! G如图所示是SPI的一种通信模式下的时序图:
$ |$ v% H5 R8 T( G7 M
20190809121651274.png . Z4 ~2 a8 c. ]9 w
, i6 |) M) C. M  }
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。: r6 n* d2 t" C# p# b" d0 U

  F1 e: p0 A  F
, G$ ]! K1 p9 G5 t
2.起始和停止信号7 k& E- v0 R+ S) C
前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。
2 E. }; S" g! |& A! B- q  ~
* R, q5 R+ y4 ?+ ~
3.数据有效性* ]' E4 u. M& X( T+ `
SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。) @- K5 Y2 j0 [- m4 z# f
& @. [+ j1 q7 z' ]3 |6 e% p5 c4 m5 y
4.通讯模式
8 e2 E# D! i2 e8 Z! q; V+ ^SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
9 z0 a) ~9 h7 M  j' b时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0./ S9 A: x: v2 S5 Z
0 x5 V: @/ W: X1 \5 Y
20190809124918344.png ' H" A) P- `  W2 V
$ n4 I: f5 t! g' W0 {; j
时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。6 ?) Z7 n* d; j/ }9 P
如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.
% ]  D& G# R3 P" _9 J0 S! [( [9 N2 Y
20190809125537352.png
9 Y, C6 J, L5 C& W/ `5 l! d2 Z, e

* t. S& j, E/ l% t/ s; ?: J: B$ T7 ?四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
0 A! a$ S$ ]9 ?! e; \9 u% P
) t3 q; J1 S& n  F5 L( z
20190809133146530.png ' s  A' m2 G4 ^# m% L( r" k, U. j
, F8 L+ A+ h% N1 Q: n2 w5 V5 s
8 G4 L8 v/ B" a) e
三.STM32中的SPI1 I$ P: p' j2 `4 h0 D
简介

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

6 O1 B$ r0 Z5 H3 ^6 o

- b' A- z  ]/ A+ c8 d) |4 x功能框图
/ V$ {$ q: N' z8 c1 k/ t
: d6 [! w& ?, }) e+ [" f
20190809131145870.png 8 |4 G* l2 N: S3 B) n6 ~
9 T5 f3 J  b' i* L# Y; m9 c( X* [
STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。
7 G2 h# d8 _* [9 w  f' @0 B0 f( V% K8 B8 k% B8 @! U( N
1.通讯引脚
+ V* G: q8 V, ]$ E/ `! HSTM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:
$ m" o9 o8 u+ ?* L4 z& Z& y7 y  n1 b6 Q" u% Z
20190809132131865.png 3 e; P6 q" l/ `, R8 ?6 z
5 q% r" d, V. S( h  O9 G% y; I
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。3 w+ Q, G+ `' r2 N& u

9 H( z! P, j2 [. n+ C$ q# k
( p! L& b2 `( n9 Q" x* i6 h
2.时钟控制逻辑

( ]' n* F' e# l" ~5 Q0 T+ m; ]  }这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。, j: o' V- P; K- B2 g
时钟频率的配置:& X% R. x, p' Z) @/ P2 P
波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:6 u9 y, {. ?9 _: \6 O1 {. H
; ^& ~: V/ N: q+ o8 n

3 q/ Q0 q, @7 c/ k
+ D; f' D& I0 \# i8 \
$ N5 `% m7 x- K$ ^9 k' G+ k( o
(PS:fpclk为对应SPI挂载总线的时钟频率)
4 U8 M# E9 t" p; I: h通讯模式的配置:
" A; }" M9 x; J' ~0 j通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。: W" [: h& E4 W: U0 T* L4 U
  M& m7 o- S' T) o4 C$ x" `2 \
3.数据控制逻辑; A3 \9 Y5 ^7 K2 C
这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表
+ ]5 I) x, Y5 S; H1 B示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
/ y, K: Z8 S1 R0 a& K+ r( p标志位”会被置 1,表示传输完一帧,接收缓冲区非空;7 f4 V" a& `! J2 [1 U2 O( D
发送数据:8 o* l9 h2 a2 R
地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
  ~' S- G# d( Q( m! @% V8 i7 N接收数据:
3 H6 n$ a8 I& ^+ k7 V" q( }接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。) E; h" {" U4 O/ d2 G4 g
数据帧格式:& @, p5 l: F8 |; a4 W
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。, I) I' j, G- h! P0 @3 O0 f; G
先行位:
$ o* B# D4 q# Y; N+ H& M通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。- C! |- J; {* [

& ~+ S$ _- u6 g3 R% e9 Z4.整体逻辑控制- H5 c) L9 Z- K, w" B6 y( J% Q
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
$ Q0 p" M) I; ~# b3 o
; T$ W4 [6 q/ J) K( n
初始化结构体
" b' q* K2 \, Q! i$ G' g: ^3 l# V库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:6 a8 O9 `  ?% B3 S% U6 C
  1. typedef struct
    ) T' l# v" ~' H, P6 \$ O
  2. {
    ; l3 u1 D7 M; c7 a8 o
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */
    6 W- S6 I; D3 N  z7 j; b
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
    * C1 d0 S- ?& E. H, b* F
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
    " y' S" ^: S$ t0 g) [6 ]. b
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/! {$ l2 d1 g' X, }' d& O- _; ^
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
    / m( A6 P& d6 x7 R" |7 K
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/; o; w4 m" {7 ^  V& Q
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */
    4 p$ Y5 E1 F$ F/ D: Z! O* J
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */$ D: \. T1 J  G
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */, _6 z* B: R1 g7 b6 I% E/ A3 Y
  12. } SPI_InitTypeDef;
    1 D+ ^  v. f" w2 U
复制代码
+ ?: j1 Z1 q) m* [0 X; L4 m
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
0 E# h* f* ~7 Y' x5 Y(1) SPI_Direction
4 i# U0 J# Q, `( l本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
& i/ j' z( Y: m7 u: s/ T(2) SPI_Mode. s4 e6 g, y/ B, t1 J
本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
% _' P! \4 Y4 A(3) SPI_DataSize/ Q) A3 j% n3 m. G
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位: O/ ]" e9 r6 _3 x+ o1 m& m; M  @4 ?
(SPI_DataSize_16b)。1 F: Y- m6 ?: e+ b1 Z1 H
(4) SPI_CPOL 和 SPI_CPHA
% f3 r6 u+ n# c" W9 I8 ~这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。( M! F3 L: ~2 z! q
(5) SPI_NSS
3 k$ d* v. W# s/ a4 U: O本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。  f# t! @6 n( c# ^1 \9 r. x
(6) SPI_BaudRatePrescaler0 W* m2 D) m5 b, @9 k5 V3 _
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。5 X, c8 T0 [: W+ y: @/ d
(7) SPI_FirstBit! ~1 N# Y7 I& X# f
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
: q( m6 H8 K+ o, E(8) SPI_CRCPolynomial
) g5 p$ R' s# q  K+ t' q这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。
! ^4 m/ W. k: X
( ~' K* Z/ u2 k  l8 d) g
9 n4 j7 W8 ]% Q$ c$ w, a" V
初始配置函数. C) Q  @# t: P0 F+ M! }
  1. void SPI_Config(void)  t/ Y1 }4 p4 e, A) j
  2. {
    ; C% t4 j! ~+ r4 Y+ J' \
  3.         /* 初始化SPI和相对应的GPIO口 */
    0 b9 p* U  j' v: F0 h2 l2 }  q
  4.         SPI_InitTypeDef  SPI_InitStruct;
    % t! `1 W0 Q$ G! _9 R' n2 [5 ?
  5.         GPIO_InitTypeDef GPIO_InitStruct;
    ; }$ E  T- P" [8 Y% Y1 y
  6.         /* 打开SPI1和GPIOA的时钟 */
    0 R( a) r9 x. K2 u( z
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
    $ E, `* ]% o8 X. X0 t& ?1 `
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
    & a# d* ]; H- B; L! w
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;6 N0 o8 n6 P" Y: N9 K
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;# J3 {) s7 {" i7 g# j7 h+ o9 M
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;1 ^- H+ L' N# k/ O6 M3 j
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
    ) w6 h. v6 u6 @9 {! `) @" q+ {
  13.        
      [) @2 ^% E& |+ J6 R$ b' k
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;
    ( b* v$ x& V  S0 X+ D
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;0 E5 y! ]0 @! p4 y- m7 D1 e
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
    4 j; M3 ]9 X  R% n' }: e! H
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);+ S# Z, \$ ]4 F( J4 C
  18.         8 m. x! f+ }) N  U. z- [$ q
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;
    6 x4 }; K+ I' v6 E6 }1 y
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
    2 D) ?' f4 r& x9 B9 l
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;+ K4 J( n0 G+ l/ j0 T: r
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
    ) R. n/ x! i; u9 z
  23.        
    : v7 m$ o8 t- p
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;. M/ h1 E7 {) [' n- ?, F. V+ o3 u
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;, {- n# w; t  S4 V. `
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
    ( g, L! v2 ~7 E
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    # T9 t/ q( ^! n  O& M; ~
  28.        
    , M6 v( ]6 y% ~) f, O% L( C
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */
    * [0 W" g% x) w' F" x
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    7 [+ e. n+ L, @
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;" S- n' M' L) c) w- ~2 |- T
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;4 r/ h5 D( R: Q
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;& W) x) _1 m' Y0 Z. q3 c* j! W) ^  y
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
    0 _2 G/ I9 |- }! b: g
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;
    " P" G% l+ |" {. Q9 a2 z* f3 k4 F/ b2 Q
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;
    , C& ?6 |  ]0 d5 ]
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    " i9 R  C8 k( ]9 z& Y, I9 j$ O
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;4 `2 H/ h$ P- }; o
  39.         1 z0 K- q! f) y% d& t, u  Z7 }
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */# [; I( U2 L: u, Y4 B: Q
  41.         SPI_Init(SPIx, &SPI_InitStruct);5 p9 P' _/ f+ {' `1 L2 p2 s
  42.         SPI_Cmd(SPIx, ENABLE);4 L0 Y0 {3 F0 x/ R& `' b/ ^; o5 q2 g1 B
  43.         /* 拉高NSS */
    " ^- X# I- }" K: U, V
  44.         SPI_NSS_Stop();
    ) }, a) W  {# X0 ~6 Q5 t. b
  45. }+ k$ c/ {. Z5 G: ^& h) q+ C, _/ A- u. X) v

  46. . K, K" |6 L$ O5 S
  47. }/ q! H8 W0 Y+ ?- b: V! u
复制代码

, N: R8 [7 I# u( v. A+ }/ F这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。' P3 g, p- J3 j) ^( [8 e+ `) H: _
- ]' k2 X, X. p( S+ H1 n6 {
' b8 R6 b1 B: i/ x
发送、接收一个字节
- z. i% O$ g7 q8 m3 K
  1. /* 发送一个帧数据,同时接收一个帧数据 */% E! }- P5 x& a- x
  2. uint8_t SPI_SendData( uint8_t data)
    : Y6 T/ ~# P7 A/ u4 a( K
  3. {       
    - H. g1 Q8 Q  \$ J# f3 |
  4.         uint16_t timeout=0x2710;   //10,000, H6 A! d1 A7 H7 c* ]
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    ( X7 t4 I6 v. m$ n4 v6 ?
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");5 ^; U( e+ A. J: ~4 |! u7 R
  7. 6 F  Q( c% q: D' W! f: ?9 p5 i
  8.         SPI_I2S_SendData(SPIx, data);
    0 i' ?2 {# d9 p6 L4 @
  9.         timeout=0x2710;          //10,000次循环无果后为失败9 a8 R' ^+ y6 ~2 T; ]. t( b
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)" w* _# Z; H1 S
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");
    ' V. G4 j4 q4 d& A$ Q
  12.                 9 h% P4 ?4 o; ]3 b: h# w
  13.         return SPI_I2S_ReceiveData(SPIx);
    / o$ [% X/ f/ B/ K& Q
  14. }7 a* ~2 m& m- ^' u

  15. 6 H) S7 m6 q! H) e/ M, |1 ~3 V
  16. ) O5 T9 E- w1 {/ h! {
  17. /* 读取一个帧数据 */
    8 _6 K, ^; S  V  ^
  18. uint16_t SPI_ReadData(void). J% T, f: K' r( W5 \8 p! p* H
  19. {
    " Q* }& }* t9 X! U/ g- t- G$ h! b
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值
    2 d7 w! J( A9 m- i: z! r6 @
  21. }
    9 f( j& B& g1 t3 g" j9 c" Y
  22. # s. C& v, w7 n# N
复制代码
: ~, ]2 P0 S. P# @$ F9 V7 B; f, o- ~
发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。
; V" B, A* Y; \  b; S3 h1 v; [( Y) _3 C8 h2 {

: R; k7 s7 f2 }+ M- ~# M头文件
9 M. E8 n9 X8 M# S* s
  1. #ifndef __SPI_H. T  W1 S2 ?6 M) u
  2. #define __SPI_H" d  u4 [2 b& W
  3. 4 \' G; ^7 T' I$ z: a, I$ H
  4. #include "stm32f10x.h"
    ( K- J' P% X+ J3 A! T- n

  5. # B% o8 a0 A1 C% L. H) D
  6. #define SPIx                        SPI1
    ( o# I* Z" Z7 Q; T
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1* Y6 l! D/ |  s, m
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA1 H7 I! D; x/ D- G% ?
  9. 3 h) o/ q# u, ~9 J# u
  10. ! g) u( y* n) g# ?% O  h
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */6 w( F+ Z8 n6 p9 K. r) {
  12. #define SPI_SCK_GPIO_PORT           GPIOA' P( g" K" O) F6 a
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP9 R  j: W! `* q! n- f
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
    & M2 M0 G4 p" Y6 p. F6 b7 h
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5# {6 s0 `7 h( i, r, T1 I7 `: ?9 E

  16. 3 s: Z8 p- X, y; B
  17. #define SPI_MOSI_GPIO_PORT          GPIOA
    9 o+ J( U7 R2 b  s& V  Y' D+ i  q
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    # d% t% f" x9 @1 S4 U; k; b
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz
    ' w6 j+ `% U* T  s( O. @
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7
    " C) \% `; h- j
  21. % t4 q) j" A8 {% f, }) g
  22. #define SPI_MISO_GPIO_PORT          GPIOA
    2 g$ s3 w6 i1 u0 ]5 S
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP  j1 z: z' ]/ }: R* [& \$ D1 v
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz
    % J0 p# q' P  @7 p
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_63 u: d7 f, q& D% }$ R# a7 @/ ?
  26. ' U) t, S& ^; i5 O1 s5 G  X$ J* O
  27. #define SPI_NSS_GPIO_PORT          GPIOA* }) \$ S0 E. B& P- g0 H! t
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP* `4 v3 J, K" z' D
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz/ l; Z; _; G) u( r' i0 \! d; u
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致" j9 z* [; o+ B, b$ V
  31. . K5 r- H2 R9 M& G
  32. /* 配置SPI信息 */( V+ ?6 F& b2 T- @) e, J
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz4 Q2 k0 F; i  P7 Y
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样+ f7 d1 c1 a. t- l9 w' {" s
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
    * \( V6 P0 o' s" ~& j
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓& U$ I* J% `4 g' {
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位8 e8 x1 {  J! {1 E# D# Z) ?
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex% k7 b* [% H* h* R& H
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行
    1 y: r4 }$ b- Y8 Q
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式
    9 P9 k4 F$ V$ D. N. Q) k
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟
    1 u* X9 g+ v/ B; {
  42. ( {- Z( L# E1 R7 J+ D$ M
  43. /***************************************************************************************// t- h& ]8 g5 o) D
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    8 m( S/ H2 ^% t" n4 d* Q
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    ' s. k7 C; K; Z# I& u3 C! q
  46. #endif  /*__SPI_H*/. c1 Q3 X' c2 P5 ]8 f
  47. 8 V6 Q9 Q+ Q- o; U' X* a; W3 A
  48. * B" i& Y: B3 }% P/ z# }- g
复制代码
- a3 [% x9 E7 [' p" W
————————————————
2 {+ R7 E7 J; }! i2 B: j6 p, w版权声明:Aspirant-GQ* z0 Z6 Q( J7 h* p
如有侵权请联系删除- ?! f/ F+ C0 t" q9 f' s% z. m

4 b5 K9 _! A' X- g. {0 Q- X1 O2 H) M

5 X& O: ?" N7 U* F4 l+ n& 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 手机版