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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI" `8 E' P/ J8 M4 w4 L* A5 b: J
SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。
; n/ _( z  H" I. ~0 [
' [, K$ M4 Z' X# Q* S& D- o- X
二.SPI协议9 n/ u! p/ a& [+ |4 r/ D
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
' B3 J  a4 L; I" w- l* h

. x" b* K  ?4 y' f8 N9 A物理层
* \( _9 n( a( H* t; Q. s首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:
/ E  ]  ^" K- D/ F5 U; E8 f* a# h1 S$ w
20190809114531658.png
) Q. Y! \: L+ h" b( c/ D) Z
0 [/ W' p8 x& T" P. K; x; }
NSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。0 {8 W3 X$ F* E% e3 E
SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。% J, C: u1 t4 K3 _4 P4 `0 s) X
MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。+ k/ W9 b9 l6 M/ }' j4 m
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。
; @# o. r8 v0 c- Q, Q: W, f6 q! M; m' i, W. S8 \# h
9 k' B: x/ I) X5 u
协议层  w% X- C+ ?* _$ ?6 f; G4 D
和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。
; U: b' k- W  a& p0 o
$ r& r" w: o$ \( h& ]4 K

& F) T) u1 \$ h# d* V8 {$ S1.通讯时序图* \& F0 U- `1 O; q5 @0 d4 L
如图所示是SPI的一种通信模式下的时序图:: x6 m) G  R, k2 T$ G
20190809121651274.png & k* |/ a: y" p/ w+ p) Z) I

& I# j! K$ Y) Z* @所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
  |5 \- J# H& P7 i" R. h6 L+ d. J! ]2 Q8 z

5 L) b. R9 O# R- _; U( h$ I- o2.起始和停止信号
4 u2 @% i0 F4 y+ y) Y7 H- V前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。
0 G: X( K: h. J/ v4 F8 G! a% y! q' @
+ [8 |, j9 A+ l* D, l
3.数据有效性
# X" y: w3 ~6 B& s/ A3 D5 Y4 ?: ?SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。
3 u9 J. P9 ^/ M0 Q

" _) Q/ S( D6 T  C+ b$ a4.通讯模式
% _# k* s+ k1 M( d3 x' X7 o; JSPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
1 \% d$ ]% q9 G0 o; D时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.
% q+ j$ K9 b* R' |/ ^
' m, @/ y5 D- q7 u5 g
20190809124918344.png + S  z- x+ |3 e5 ?

( |, ^! c8 G2 Y' O2 Y7 G9 k" S6 X时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。
9 z7 V1 s  g* q/ ^3 k如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.
, w- r% M! e! I; o- q' u
7 u% Q0 r' t; ]1 g( d( e6 o  K; A
20190809125537352.png # S# X# k5 W& f, L/ |

) G  d) A" ?& k1 K; G四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
; p9 }- T; V. k* Y/ {
8 f, c0 P6 K& \  h$ `, f
20190809133146530.png / D1 J2 j; N0 d, l$ }1 M
7 C2 y$ y& C( p* \9 w. H
) C5 ]* B1 W; G# \( B" A) y$ F
三.STM32中的SPI
4 A: }$ V) [- F0 F- U简介
- h5 ^) S: @- f+ }
STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/2
# w) r& k3 |* `, _- Y% ^4 N% l& o(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。
. g  r( E7 a- c: T* u5 n9 p. H3 i+ X7 p: G* }& u. R
2 E4 k$ \6 v# }8 q' V4 x# s; j" I. P
功能框图% a$ Z* M& v" X/ z9 g. ~7 b

& |- o% U( W8 k) R* j6 u1 v
20190809131145870.png
2 W2 V/ K* s, J# R. d* T
/ Y1 N: E7 ?" k$ x
STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。5 [, ?$ H- G( E% g# S. Z- c) K

) d) z6 q! x/ Y) D# T  U8 w7 w1.通讯引脚
1 u7 L0 K% j8 i7 o8 s( wSTM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:
) A3 b7 A1 j: }9 ]7 l
; @" |: k/ @5 w9 r8 t
20190809132131865.png $ n2 _! R0 u9 G; F

  S' C6 E' i) u7 M7 M根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
  Q& P0 [4 O" U& [% x9 x3 e7 e* p* j: W& z
, ~" U: l1 [0 ?: E# T! X3 L$ C
2.时钟控制逻辑
' ?$ T1 G" q# G
这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
* f2 @- T$ b# N. y0 T+ C: q时钟频率的配置:. @$ j( e5 f1 r+ Q3 Z* ^9 Z
波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:
. _) Z% C1 z9 b  r! J% {$ r, D; E
# K2 v" v! J' O, E7 T
/ `3 e: L0 ~& c1 l& u0 G

: r. L3 E* l) k* y% o+ t) p$ k
: r! Y3 b- }) D$ U& A
(PS:fpclk为对应SPI挂载总线的时钟频率)
8 L( ^# J# z$ Z# \2 c  ^& d通讯模式的配置:5 O7 g# c5 k; m4 N
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
6 v1 ^) ?3 {) H: \3 l
* y. Q2 p7 a8 B  }2 C: ?
3.数据控制逻辑/ u- t) }5 d2 b' S$ o: G' K9 m
这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表7 V! ]5 J) B8 I1 m2 U
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
) W9 `6 p6 }8 |. M" D( S标志位”会被置 1,表示传输完一帧,接收缓冲区非空;
$ D* g, F% ]& ^/ a* t6 Y发送数据:: e% t* `2 m1 ?% _* z
地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。1 }, P+ ^5 H8 Q3 C0 q. G
接收数据:# q$ M3 _0 n  n4 @6 U3 O$ T9 y
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。
+ U+ d6 H& y! n% w数据帧格式:
6 l$ o! l$ Y0 ^2 `% \# `7 p通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。
. }$ K" x1 f+ P" F3 K5 k# f先行位:. a- S) d; I4 r% }" H
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。  F' n, L( K  k1 t( p9 I9 E
# x( y4 K6 l0 y% r+ b
4.整体逻辑控制
- o% P% {6 q% l' @1 W: }" y在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
" x9 Z" S. g; B
' V: \8 y; |' `0 U6 h  z
初始化结构体3 F4 v. O7 u/ s) }! c5 e- C
库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:' r# R: P! q+ o* `1 E. i9 ^
  1. typedef struct
    3 i& {+ T) S8 v+ g
  2. {
    , i+ `, q$ @) y4 r/ D& ^& Z5 l
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */
    ; s' M; C6 ~4 m, J% c
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
    2 M8 m  f- Q. e# }7 f. ^
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
    # B. @# @6 `& e% h  ~
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/
    5 n- G* P7 U5 b0 F" F
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */# ^+ F% \5 N. L& v  |- h: W
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
    - H* |' W) W/ v1 Y% H; M' g1 ^
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */: L8 H1 [7 h* o, P, k. X: {; d
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */7 l: W! {6 h  X+ G, R' ^
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */
    # `+ @+ I5 C" q) [
  12. } SPI_InitTypeDef;2 d  Z5 J; s2 F5 A7 A2 ]
复制代码
& e, O8 H; v9 D4 H# B) V
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
- y" R* Q- {" k8 V(1) SPI_Direction
: Z: ]9 z1 x+ z3 k/ U' G4 V7 y- P+ J本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
) d0 H" c* P" Y4 W8 {(2) SPI_Mode
1 e' x8 V7 e, w! x) b. t本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
$ R& t6 B, S5 }9 Y* Y: O(3) SPI_DataSize1 v, E9 o, C* P) ?
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
! c3 T/ Q, h6 f7 s(SPI_DataSize_16b)。0 }! }! }6 w& O3 r" N, d, k
(4) SPI_CPOL 和 SPI_CPHA
0 Z% a. I4 z! J- @这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
# J: R( g' n' A4 Y* m" o) M4 k5 F(5) SPI_NSS' L( c+ M0 c7 Q2 o$ {  l7 k; X  [
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。/ f4 u, {2 x; B
(6) SPI_BaudRatePrescaler2 U% l, S% b6 b- L- m: [$ ~0 z  V
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
& `; B2 K( w" X' ^) ?$ G(7) SPI_FirstBit; H3 l+ Q- n8 S* ~
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
) W: U* ~+ T# S4 q' T(8) SPI_CRCPolynomial
' |% F* C+ ^( b. q4 D0 F" i这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。# ?: _8 }% C( f5 E2 L' l+ \/ p" D

+ C, Q2 Y4 W2 a! z# |4 v  a# t+ V  G

8 Q; }) C! v& b) Q# z初始配置函数2 [3 _- v1 n( J- l  ?3 D5 ~6 A
  1. void SPI_Config(void)3 b5 T8 B/ d& x  N5 i) r% {. z
  2. {
    ; U. D( w' r* `
  3.         /* 初始化SPI和相对应的GPIO口 */
    4 Q: h. b1 g% j# Z6 |8 W% r
  4.         SPI_InitTypeDef  SPI_InitStruct;. \2 q# m. L8 p* L, A
  5.         GPIO_InitTypeDef GPIO_InitStruct;
    9 z/ n* ]; _6 U
  6.         /* 打开SPI1和GPIOA的时钟 */4 z4 t) j, d& a- o
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);& T( I4 }; t0 K6 Q
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
    * q2 i/ d* _8 `
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;( D& O6 N6 X' \; v
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;% K- i- y  g7 _
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;
    3 S$ h+ u/ w7 O) e5 o- g. F/ P" J
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
    , w7 j5 {5 I; S$ u- B  w1 ^5 @
  13.        
    , L$ _3 z. _+ ^7 ~* \/ R
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;. r3 H# @# ?$ h  T7 d* j# [
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;% ]5 x; I1 p/ z; y+ E7 z
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;5 T1 ~) j" Y* B5 ~
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);8 U8 T/ ?/ r( X- u/ z: |
  18.         8 f( b2 v# V7 L1 V) b
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;
    3 B+ E/ K6 V1 Q
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
    4 O3 T7 r8 H9 i
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
    : ~' \- Z) \' e
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);. `3 E' r) |+ X8 M
  23.         3 j4 W1 H9 ]: D( m9 @7 I8 m) k7 q
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;
    ' Q* R! G8 z, [
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
    : l+ V# [2 L8 l* ]# Y
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
    8 W" z8 _5 g6 }/ r, t  ^% t
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    , K) \& X' ?2 q
  28.        
    0 m% {, H2 i: R' E1 i! _) a; V/ m) v
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */
    " f" Z4 j! ?. p" v
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    2 @5 F3 @, S" t, ~% b
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;2 g0 S/ M7 d. J
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;8 ]6 W& V% X+ }
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;: R. t6 n! [1 h2 e
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
    9 e' d% _4 f$ e+ b( E! D
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;
    , u, [" ?! @% x- u7 T/ I; ?
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;3 \$ S0 ?4 ^; P: n7 W8 h
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    / G$ R# L: C7 l
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;, f3 P, ~5 g  d* b# K4 P' E
  39.         # Q9 d7 l6 s& y. v/ Q, T
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
    * F. `9 @& m/ b5 `" W0 Q
  41.         SPI_Init(SPIx, &SPI_InitStruct);
    9 m( j+ |4 `- A2 K1 z
  42.         SPI_Cmd(SPIx, ENABLE);; I1 g; ?/ C( |* {. t) s
  43.         /* 拉高NSS */
    ; i# d5 b# q! l5 y
  44.         SPI_NSS_Stop();+ f* ?+ z( C# u' @  c
  45. }6 L, R3 W$ i" H4 v, `- n  t
  46. % ?  I* E+ e8 t3 m8 o3 x
  47. }; o9 g: a( g5 t
复制代码

, E4 p- ]  u8 ?9 O6 B4 f这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。
, Y5 s& N: x$ v
/ o' ^* p/ _: O$ b

. v- Y, t& D; P& S; @0 G发送、接收一个字节- S  e" Z  h6 c
  1. /* 发送一个帧数据,同时接收一个帧数据 */" Q0 H) V  D4 A( y5 R$ t- ?9 N4 j3 |
  2. uint8_t SPI_SendData( uint8_t data)5 }2 `+ A  u3 X, t1 u1 E; h# j1 Z
  3. {       
      t  y% Z8 M! X) b, b
  4.         uint16_t timeout=0x2710;   //10,000
    5 k7 S/ u" B2 \6 ]) F
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    ( ^8 w5 ~; r% s" `3 Q
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");
    9 D- x5 g: j9 u; C' |% q/ F/ F

  7. 8 e6 Q/ s+ ~: y7 O% w* m7 C# l1 u/ i
  8.         SPI_I2S_SendData(SPIx, data);
    . ^% I* j* Q$ L2 f
  9.         timeout=0x2710;          //10,000次循环无果后为失败. w# m1 B% x8 O" {
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)) v9 s, Y# }: I- a! v- G
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");
    ! P0 J. V; ^2 l* S' S
  12.                 ( w5 F4 Q" W/ Z, q
  13.         return SPI_I2S_ReceiveData(SPIx);2 }. s) B4 l, Z  c6 H2 e
  14. }
    ! d, w7 X+ l- W0 d# z0 G

  15. 4 c. G/ f3 v0 u, G5 S" D2 j) B
  16. ! f8 x6 `3 x( Q" x+ l: j
  17. /* 读取一个帧数据 */
    ! z: Z2 Q  q4 ~- Z" S
  18. uint16_t SPI_ReadData(void)
    . s# n0 C2 }8 j2 o+ j
  19. {
    8 k1 r$ Z. R. r3 O5 X
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值# v5 z1 V! Y8 j" @2 @9 q* k  y5 E
  21. }  G9 v0 o! y4 |" m

  22. # @8 K) G! F* P# E( I& L
复制代码

/ Q8 p; j5 Z" T5 q% t2 B& p发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。0 G% @$ s( H- C2 i- k9 }2 I* v- u
/ M, d9 G% ?7 ?
( R& @: R. T& }+ k& i: c
头文件: Q$ f: r  X/ ?" I: F7 X
  1. #ifndef __SPI_H
    7 P: M! a3 k+ b
  2. #define __SPI_H0 @% O1 h5 {' X4 {: q+ Y9 F  N

  3. ( n% I% i) m9 P- l
  4. #include "stm32f10x.h"- ]6 d* @* _0 L4 E

  5. % T$ Z3 T5 D) j6 i, Z! ?
  6. #define SPIx                        SPI1
    9 j' ?* }+ K9 `4 ^6 `! e8 V
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1
    % c# e0 k( Y1 j; h0 V- O  w
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA
    2 u3 n9 A2 d$ e) M& ?! P- f0 A5 }8 a
  9. " k6 L$ [1 W/ P
  10. & j7 [: i2 @/ _) I5 V
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
    . R! u1 N$ C: I; P) N* b
  12. #define SPI_SCK_GPIO_PORT           GPIOA
    ( |4 i  f- N7 }& v# R% {& Q# \: R) @
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP$ Q( M# A# ?- w2 x( a3 o
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz
    % C( H, [3 ?6 r; e- C& W+ z
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5! `, V  v' [! _$ ^; g" P
  16. ( c' j& Z0 w3 O9 k4 b
  17. #define SPI_MOSI_GPIO_PORT          GPIOA8 N, |' U. A$ ?9 P
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    - u% v/ C+ g) ?/ ?
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz
    . y( l6 T: h7 g9 J, @
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7
    0 p, i3 ]$ O1 n6 ?9 x! l

  21. . a1 L/ e! S  M% Q7 q
  22. #define SPI_MISO_GPIO_PORT          GPIOA/ U6 R6 }' D7 I$ A
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP
    4 f" q- G0 }. @* d5 Q9 C! r- r# x( T9 V
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz
    # Y- i" F' A+ I
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_6
    $ `9 P; z" F6 }
  26.   }. \! R( b# @$ K. X7 d- t0 W
  27. #define SPI_NSS_GPIO_PORT          GPIOA
    ( R( R6 Q+ f& l  g" }
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP
      q% o  \* y4 [" V: k5 \
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz
    ! U9 J* B2 [8 K4 h3 G, P: _4 x
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
    7 |2 N& E$ x" T6 g& H9 ]

  31. ' O! A. {( J# T
  32. /* 配置SPI信息 */* V1 f, m" Y2 ]1 d  A/ B
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz
    $ V$ T+ z9 M# c& p" p2 l
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样2 T1 ]+ C  V! n0 N4 `9 I
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平
    1 D7 \# A. G1 Z9 l/ [
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓3 l* N% i, {: p- E, @/ c: K# |/ @1 {2 r
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位& I) P* j2 }3 ~% @. a
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex2 w* j/ M9 U+ |, r8 o# C5 q& U* d
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行
    ) m9 _+ I6 N1 ]4 K# L
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式
    , f3 k' F% u! l- j! @6 J
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟
    4 T  a+ T, L1 p

  42. & K8 a- N8 [4 W; `% ]  F. `
  43. /***************************************************************************************/
    7 }7 u) A" f: J: U) _: }8 N3 a
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)
    ; ~" ~" s: [( F/ c4 y- W
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)1 F/ g; f" a# R% Q+ l; U
  46. #endif  /*__SPI_H*/) l9 w3 S" m4 c1 T. d
  47. + j  e- {6 Q5 b" E9 q# M# u

  48. ) L9 \  w) F6 O" p" O# I( U  w1 ~
复制代码
  N: x& ]: c% Q/ Q: {) N
————————————————
, ~' e- I( M% m* i1 S版权声明:Aspirant-GQ
4 M& x1 F7 |. I# d# x5 ^如有侵权请联系删除
* D: J. Y& v7 ?5 r& J0 i5 V+ _
( L* j% {& e* a% t( q0 s, z0 h% ~' F8 ?& M: K6 d& T9 @% |8 l

) S9 [' d6 a' v, R3 b& {; c( Z' Q
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版