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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI
: S5 F0 o8 T* s+ n; \9 @SPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。1 U5 {0 x& m# a4 G* y9 n* N. W
3 b2 J( ?7 o4 ]7 f5 R, H$ o
二.SPI协议0 U; m' s4 E# j
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
8 n7 Z* }: A# n! u* R. T
" x( O7 `$ H! K6 n& |+ q1 z
物理层0 B5 e5 W9 R1 J1 f7 H( B
首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:% C. o  F9 g' ~
- Q. a( r" {( c/ K* g6 g6 U$ G
20190809114531658.png 9 p9 u  A7 n& o# u

+ g0 I, P( s  b; d0 CNSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。
" u7 i3 F" B1 G: ^SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。5 G$ O. R' o8 b+ l
MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。9 N( x3 N7 P# v) T6 h! ^
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。$ z1 k% _3 U; F
& ~) L/ w9 u+ G. ]4 y, T

3 s4 t. d  A/ d) H协议层
! Z2 _: \/ I0 V6 @$ w  T和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。
/ k  p; ]$ R: ~# I2 d
9 v, ~! m0 X6 B
, q9 ]- P3 ]/ c6 o4 h) o- I2 ^
1.通讯时序图* T% O' h$ I8 ?5 t
如图所示是SPI的一种通信模式下的时序图:
4 w0 k4 P  H$ t/ l+ {  Y
20190809121651274.png ) m# \8 d2 @) V: {% u' \

* }. \; y2 L( x; Q+ H9 O所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。6 S0 X4 P" P1 g- }/ W
  I6 ?% o1 F- |; x
. h( C# k, a1 Q8 [" W
2.起始和停止信号; K' o5 I9 @) F7 U" A9 y6 P* _
前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。# L7 {# u  e9 H( X! e! z5 `
7 g) j* j3 g) N8 u8 |' W4 m( y! H
3.数据有效性; X5 @7 A6 M' i0 P4 Q7 |, U* N, l
SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。- L+ D$ J+ i# Y. s+ y+ e0 i

) u0 E& t% Z' P4.通讯模式
( g2 i& `/ H. g" H/ H  CSPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
) U4 V& u' s5 t4 a4 a) f时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.' ^% M2 k  t1 |5 {
; v& X& F+ j, e6 z. y
20190809124918344.png
2 {. {- Y/ I$ P' J4 u

) R2 t: s6 L9 h7 F7 x4 y时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。# N3 d( W% i# n
如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.
' l$ h" Z% \2 ~  _7 B0 z
& e. r* E2 s7 U  r
20190809125537352.png - O/ D* G1 q# m) S
5 a7 g+ D5 a+ |$ `2 w% N
四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
: z/ U8 x6 K) V- A! Z* E6 ]
# l6 [% P& _. y2 Q- w7 n
20190809133146530.png
. K( l2 T* l, D
0 ^4 |0 F' ]6 p# p% Y) ^6 V: E) n$ \

, ]! g) o/ R- a; k三.STM32中的SPI4 Y9 N0 y( c3 r; x. B: p6 [, K
简介

7 I& N0 I2 ^, n* V! c$ N+ H5 eSTM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/27 c: G9 t" [  F: u+ D- v, e3 X
(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。- B: v# d+ s1 t/ t/ e9 O* [2 G; Y
0 B: R$ W: Q! ^) U
! q, N7 w1 r6 q3 e, V9 f3 l) S
功能框图. i8 m! U7 t2 J

% Q- {' E7 T, q5 M' o! C
20190809131145870.png 3 W2 b* j; o5 \, W6 [! ]! Y3 r
$ }* b* U0 w: g6 @9 w. v
STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。5 ?* H1 `6 U5 c7 s8 q" D7 R* e5 f
4 d% g) H0 e3 D
1.通讯引脚
0 U# s! i% H1 F4 g: }8 ^STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:7 f! K* O. M1 d- U8 J0 Q4 E: Q+ Y

' O8 N0 I" _+ Y3 M* V
20190809132131865.png 5 J8 x0 j; G$ @! `, r

0 c! X* b4 h  e- g4 z9 H7 I根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
5 x6 ^8 D9 `0 X' g: ^+ P4 z4 H  d; B% I

' n0 J* F; e. _! o3 _5 E: H/ P2.时钟控制逻辑
9 Q6 K5 W6 Y$ O
这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
9 I' s, W. N0 E% B3 E' j! s# [时钟频率的配置:$ o+ w# E  u) s: [
波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:
1 j# \4 ?5 G% a) ^- t. ~
4 R- |: S! l- _6 x
8 B0 Y. E& z! B1 q3 S) T+ E

! j3 E& N; B' T
6 c' |- N' a' ~5 N
(PS:fpclk为对应SPI挂载总线的时钟频率)
" k/ Z6 I9 ]' ?3 p0 u8 I* b通讯模式的配置:5 p, F0 R* P5 J
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。* _' ^9 H9 D2 a  N: }+ \
5 t# w5 L# C. U; G$ H0 z2 k
3.数据控制逻辑
# r+ P' [: n7 c# \$ p' I  U这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表" A; P' J2 r! ]: N. S$ A+ e
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
$ n0 T& d- w5 ~3 o5 K  ~标志位”会被置 1,表示传输完一帧,接收缓冲区非空;$ ~: j! R# F4 Z3 `
发送数据:
9 z" f; c/ j  q- V& K2 Y- d地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
9 w8 {1 {! L4 Q8 Z2 e接收数据:$ X9 K0 o0 @1 V. V+ O, e+ Z
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。
7 u) P) T  n0 g; b" i数据帧格式:, c' t; C! o& D6 F' N0 {( ^0 ]
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。0 o1 C6 n6 p) F; ^4 ~5 E0 I2 r$ ^
先行位:8 r6 F& ?* m" S! n0 @
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。
3 H6 {) D9 Y+ P; n1 u

! f( L4 }2 l" r& {7 }  A# c5 Z4.整体逻辑控制, h' p0 |3 c5 T0 G
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
( }: O- }; O" g0 w- H
+ i, c" M. ^; V0 S
初始化结构体
2 q2 F0 ?; d* @% ?库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:( v/ m. Z) q& _
  1. typedef struct
    . ~' o- @) C% A3 h" o3 i- N
  2. {! r$ x2 E; c0 M; G, x7 q' c2 W. u/ X
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */, E8 ?( N* p, L, Y5 `/ j
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
    4 H7 l  `, p6 P- Q
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
    ! [7 w" u: f5 u9 R
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/: g# E2 m2 ^/ G; f; o2 C8 [
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
    . W8 d" B* v6 ?  f5 Y
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
    / J, d+ w& \# Z
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */* D6 P. F' }& p, B) _& Y
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
    $ Y# |) A: l1 W$ w' ~5 d
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */4 ]  M" \3 ?- U- E- b9 @) h
  12. } SPI_InitTypeDef;
    , }1 U! F* I5 E
复制代码
9 k% h- R( p$ |  B1 G+ h: f9 V9 T9 \
这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
# A+ X2 [' ~1 N9 U/ u1 `! t8 r(1) SPI_Direction8 w; Z: ~: [$ n& \
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
3 K+ ~# C0 P: Z! h0 x! K(2) SPI_Mode
/ d1 H* A, O) |& l3 f本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
+ C" N7 ]9 l$ _# P5 H, c7 y(3) SPI_DataSize( |3 o9 M* r/ x: j  ^+ i
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
) E, T+ Y8 Q( T9 y8 N(SPI_DataSize_16b)。
% r6 \1 f. W) k% c(4) SPI_CPOL 和 SPI_CPHA6 L- v* ?  @) _7 F
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
  x# x* d! S" Z8 O) R  A(5) SPI_NSS1 G# o" \4 ~( q7 d. ~, C1 K
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
4 W- d2 f. |. g" @, Y2 ^(6) SPI_BaudRatePrescaler
  S0 F6 |4 E/ K本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
$ T7 Y4 F' [8 M(7) SPI_FirstBit. F1 Z+ q* a, F* I0 g: H6 }: t
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
4 T4 Q7 `0 _. M  n/ f5 g, _(8) SPI_CRCPolynomial
" c1 f2 I5 }- P, _6 l0 r这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。% r2 @# x" C8 j+ m7 Z5 p  a/ q

5 l, h  D  c# h0 K3 Y! T  s
5 O. L, V+ ^+ u- H0 F
初始配置函数
5 B# g8 S$ H" ^3 C7 F  b. K- k
  1. void SPI_Config(void)
      o# `; {# S! K- S; K  ^
  2. {
    - o$ e/ K! P! `( p0 |: x1 l
  3.         /* 初始化SPI和相对应的GPIO口 */
    ) ~, B# H0 B1 [& c& h9 v
  4.         SPI_InitTypeDef  SPI_InitStruct;
    : E8 N% ^* h4 {/ }$ P  b
  5.         GPIO_InitTypeDef GPIO_InitStruct;4 ]0 r% }8 B( l& Q: H! c
  6.         /* 打开SPI1和GPIOA的时钟 */
    , Z, _: N4 r* K( V
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
    ' m: Z) M1 ^2 `: ?* h: P6 @9 F; ]
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
    8 a! l- {( X4 e6 S
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;
    " n# h/ h2 h" }9 d5 U
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;/ ?3 j3 |' W+ s8 ]3 Y
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;- |; _  S; h3 A
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);$ U4 B7 I% P! J3 W$ N/ _
  13.        
    ; ~& `6 |( \: c! N, P
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;; w# `5 h% m" Z
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;2 ?! S( P* P* ?6 r( B, S0 _
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;6 k& B2 @. q2 O: @
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
    0 l* R3 j! `& _) T
  18.        
    * z4 P" u3 Q7 f  O  s5 m& J: |% k
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;. x7 E/ ?; Q; D# @0 T0 V# ^% a4 K/ r
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
    8 }0 A" Q# D# [$ j' f
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
    : I) ^% p. w# c
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
    6 w6 U6 c: ~& l$ `7 N
  23.        
    1 _2 a( s, w1 Y$ n8 @0 g# C
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;$ ]$ V( h0 r- {
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
    4 Y/ E* A9 z1 q% L$ T& E
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
    + @9 f, M  S$ u% i2 W3 q
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);, n) \4 L& y4 X! o2 T
  28.         % [7 a% }7 v/ O* N7 s, {- a. Q7 K
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */8 i1 f. E% V/ v  h# T
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    ! s7 [6 e, J1 n: j: s; r
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
    : G2 h' \$ T7 x3 @' i! x
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
    : y0 r5 z5 V" T7 _+ y
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
    # a6 J( F+ l  d# U$ N" M
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
    % n$ T, Z( g$ G( o
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;, W1 y& [' Q2 w& A2 A, E
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;, {: J/ n9 F; }' p
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    , u  ?' R' D- g4 G* \
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;
    ) x' Q$ E3 i4 ]5 ?5 M  m: V
  39.         % T: |" t% N+ Q$ S, L1 J
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */2 a0 s. J9 D+ D$ f7 |, L
  41.         SPI_Init(SPIx, &SPI_InitStruct);
    ) {5 e- _/ I: b6 T
  42.         SPI_Cmd(SPIx, ENABLE);) ?% S% k4 f, |& |" [
  43.         /* 拉高NSS */
    % K8 u* W6 I1 H' \+ M0 K
  44.         SPI_NSS_Stop();& I. D( v8 z: v. J
  45. }
    ( f( T* Y6 ^3 ~
  46. & P1 W$ F0 b6 t2 l8 L* O
  47. }
    6 W( h2 ~3 }3 `" [6 u$ F  k3 b
复制代码

3 y4 n" Y- e2 V2 h8 C3 ^这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。, V3 R0 u/ R4 B' U9 |

- Y4 |) v' b- j$ ?& t, Y: }

+ J, d1 s6 }. n7 M发送、接收一个字节% T" S7 r: a* \3 n2 T+ r& R/ a, ^
  1. /* 发送一个帧数据,同时接收一个帧数据 */
    0 P! O7 a& w$ Q! A% ]6 n
  2. uint8_t SPI_SendData( uint8_t data)6 a0 P; M, z. l8 x& i" c; K. u; ]
  3. {        3 e, ~, u5 ^( O$ \5 i# }7 {/ F" C
  4.         uint16_t timeout=0x2710;   //10,000
    : S" s1 D+ z+ J# e  l
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
    $ e  i9 F7 O8 i9 d* n6 n" g
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");( T9 \/ v, |0 E3 F' Z; ]

  7. ) E" y7 }* l8 V4 Z+ k/ Z
  8.         SPI_I2S_SendData(SPIx, data);/ y3 r. l, S; J
  9.         timeout=0x2710;          //10,000次循环无果后为失败/ s1 A3 y1 B. V" |: B) p) l
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
    7 h' M  Z( K! n
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");" j/ C- {* s1 c' G9 z
  12.                
    % Q  A" V6 X# W; j
  13.         return SPI_I2S_ReceiveData(SPIx);2 i) H  q, H, I2 Z1 t* ~
  14. }
    % @$ }5 [' {. {; `' h: A
  15. 9 \, O9 P0 X# x2 L) V) _% X5 T
  16. 6 N! f/ {3 X) S$ T/ p
  17. /* 读取一个帧数据 */
    + J* K3 X; _: z" |
  18. uint16_t SPI_ReadData(void)2 b9 Z  \3 {5 c; @5 K) l2 {4 r
  19. {# H7 z" B! u) V9 z: C2 N- T
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值5 i" A! E: `1 ~  n! |# ^4 @
  21. }9 d, @' @, a; D8 p# H: z* p

  22. - Q0 ~$ i) U/ W: H
复制代码

; }9 m# k; G' u/ Y% @5 a发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。
  J7 v) I5 @+ r3 @9 M# o4 N! q
% t7 p" {8 p, _- y" X
头文件3 @3 j* e) ]2 |! F; B; z, c+ k
  1. #ifndef __SPI_H" T" ^) l$ ~2 x, d! H
  2. #define __SPI_H
    $ P, a* r6 s+ r9 ]
  3. 4 x2 r: @9 H! A2 u
  4. #include "stm32f10x.h"3 y8 d7 b! f) W6 o. X+ N
  5. + X3 g* Q9 {# t& |2 i
  6. #define SPIx                        SPI1
    & h7 _+ \3 A) Y) \& E9 M
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1
    1 B  {% i' H6 H0 x' v9 q/ S+ @' l
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA
    2 q( |+ w  p0 P& e: E
  9. 9 F3 _1 U9 a8 U" }1 @

  10. 0 l# ]. a% r2 G
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
    - ?7 H/ k9 r/ v6 B9 T
  12. #define SPI_SCK_GPIO_PORT           GPIOA
    8 e+ W! q9 l) w
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
    , O# y( C3 ~9 E' ?  T1 h- M
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz8 z3 v' |5 Y# I* \4 x' W
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5
    # S0 \: \9 h5 G1 \

  16. 3 k9 @' M6 q8 w+ R6 R5 [$ J2 q/ k
  17. #define SPI_MOSI_GPIO_PORT          GPIOA
    # z( e; W$ u" s
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    9 j& Q2 Q9 A: s9 b4 L
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz
    & i3 u2 [0 E0 E
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7
    $ R- m& S1 x0 b3 ~( ~

  21.   ^# H  ?3 E8 d9 N7 M- j
  22. #define SPI_MISO_GPIO_PORT          GPIOA3 w8 {! v5 w0 k& C" r3 Z
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP
    ( s" i$ W! N+ [4 ]
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz
    " k, L) r  i3 o- o6 h. ?
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_66 Y5 M4 h" B- e2 D4 u+ m* w4 x
  26. 0 U0 p5 e" j$ e* T; J/ V
  27. #define SPI_NSS_GPIO_PORT          GPIOA
    + d+ o+ p9 u$ r9 Z9 R' r
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP; ~9 d2 l: H3 W: z
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz- E& s- k! b; w& R! n% H
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
    6 s2 L: n5 P. v& \" h4 V  Y& x
  31. # s7 A. n! |) B* j. O  J
  32. /* 配置SPI信息 */
    / u; e5 Q- U* l0 c2 p& O8 O
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz+ D; }* C# [3 v
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样1 z7 i& u' e4 \% c
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平/ f8 K% l8 n8 j0 F# I5 H
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓
    ) O. L0 q9 d4 c, V4 A1 d- \
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位
    $ i, O0 F" w/ r* m$ [" Q
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex2 N; J7 V+ c; v0 m1 y, {/ s( L
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行2 |* M( P+ j; K& C. r
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式: [9 M/ \; f% F3 U" D
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟
    2 S' E1 D2 R# o3 }

  42. 8 M3 L8 Y7 z# {" Q. A% ^3 h# J# }
  43. /***************************************************************************************/) w& R: S- O+ v# Y" S/ Y. R, d
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)" R7 {* n% m+ q7 D$ b1 n
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)* b4 Y/ m/ o/ l3 P
  46. #endif  /*__SPI_H*/9 Q3 c" \" r, X' R: y  {
  47. + `1 n4 ~0 T. o: u3 M, \  G

  48.   u; Y5 p& d) B8 Q2 B! n- \9 J% Q( w
复制代码
4 r9 S! g! K5 Z. b
————————————————7 \7 d" i0 s9 H  J- k( c
版权声明:Aspirant-GQ
9 F' D( O) W  Y; G5 ^# k4 I如有侵权请联系删除- F% S0 ]6 m6 y! _

# V+ v3 K' ^% |+ E4 }8 b# g! o5 m9 z! c3 P
- ]% h9 f1 l) b9 ~5 D4 {! c
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版