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

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

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:39
一.什么是SPI
9 a* v8 v- s/ V- D& F6 ySPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。
% V. @- T0 c+ h: E& V
& L! u. Y6 d0 _8 r! l, O) Q
二.SPI协议1 F, j9 H8 {: X* a( C% K' R
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
  ~( v' _1 D' E% E
: i* S7 D# c7 l6 f: W
物理层
2 b+ Y( ^. ?* d+ m首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:# g: @/ I# q+ _, v. n" S

) P6 R) m6 d+ G1 _) Y
20190809114531658.png
! j4 |& F. X7 Y# b- K) t' d$ i; i8 d
# A( z# v% ?: b. O+ u! V4 w5 W  a7 w
NSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。5 U6 m( p2 N( n% O
SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。
8 B" h: z4 e# L3 G7 X) @MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。& \3 b6 |% T, L' b$ d( [
MISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。
5 x3 M# k* ?- |& b) y
9 W' Z4 |* y3 C& x* v9 G# a# E

5 b1 O. y7 c- ?) F4 Q: f协议层. Y, C) H, V& _+ \8 k0 q4 I
和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。! K* T4 Q$ o) l- \$ H2 ^3 d
, Y. ^) J1 q9 a/ G% M
) V9 q. K0 k' q( r: F" g6 y
1.通讯时序图) `. d6 D! V: R
如图所示是SPI的一种通信模式下的时序图:7 `8 f3 C9 s7 B* d5 s- d" D, M
20190809121651274.png
4 s$ D* N* a# O! Z: \' C  c
3 i! R+ Z0 h9 L% O9 }
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
' `$ W5 J6 o, }6 O2 }. C& X' F; A* l5 u  W
6 F, g* U' I4 K2 S7 J
2.起始和停止信号( Y2 {7 w- [6 e4 S1 F1 s/ `
前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。, C# x% {+ S! r9 s( k

2 l* {# `% e1 v5 n* i, M. i% Y* P3.数据有效性
: F5 m* T) v+ h' |SPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。; z4 F: I9 b# ?" r

& k% w  p' P8 k4.通讯模式% a0 e$ Q/ W0 }4 g
SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
% e$ a5 l. i# q* G3 p时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.
; S  z# `2 H: T
% f! V' p- _) _* o3 V: h
20190809124918344.png
- a7 r* ?3 U/ m8 \) _7 m

6 O3 f( s4 x+ j, H; w- R时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。% H3 V, i* |4 h) |
如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.
: L5 j  h) ^5 W  {7 B0 c# A4 @  }& [2 ?2 ^; N
20190809125537352.png 0 \( z; T' i, o. }; e" g
7 z: x- Q$ k: M  {8 O4 E# b
四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:3 y# }2 y' f2 A- L
6 @" P# H# C3 I
20190809133146530.png
, d/ t  M; j# i/ k8 P6 F- V$ ]/ P% |8 g

# H% {3 P: @( T% g  `三.STM32中的SPI1 A8 |7 K9 U! q: E! f
简介

4 }) U+ Y9 n9 Y% B8 [STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/2
1 s/ l- V* J# l+ w" C2 ]: K8 N" s(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。# J# \" i, [6 X2 T" s
5 d& {5 k8 N3 B* }* \
# p6 s4 l' c' l5 F" G7 Y) L$ V
功能框图
6 y/ R0 @. w# v8 X/ E; }
7 a( \* k% l! [' H
20190809131145870.png
* Z, n8 g/ v  G5 j* C( @
  P4 v' A9 V6 R8 v. j1 p) Y' K- T# k
STM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。
% ^5 n6 }6 c, Q4 M, D
  J! w$ u7 y$ `- h1.通讯引脚% A/ @" }3 C7 `" w
STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:
( Z3 i' T2 y. ?9 A1 n
) L7 l+ u( s3 v1 D+ U  h2 w
20190809132131865.png , z& R4 f1 p5 k2 Q

4 o# E4 P9 P; ]8 {根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
* ?" M2 a* W% r; H8 t7 Z" X. O1 V4 e; q7 K: O$ q

) P+ K' k0 r) O$ e2.时钟控制逻辑
$ J5 t% J, @3 @8 G& H& y$ ]) ?
这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
) P# Q: }3 z. a  ?' _! k时钟频率的配置:
$ c: u3 U" V1 r, W2 S# w波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:
, S: g, t8 u% o5 R$ z" O7 c# b: {6 I( n/ ^! W9 Y* Q* e* \
- h/ D( \; R, t; _7 x. C9 a

. D: U4 `) L* |" k8 j' d, x# t  q/ }$ S* `5 y) x1 E$ g
(PS:fpclk为对应SPI挂载总线的时钟频率)1 {8 G- n  b- \. j: m
通讯模式的配置:1 l; Q# a- q; k
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
$ E% M: l( e  `6 D9 B1 _/ z  U

% L7 V( y. p: v, I( o3.数据控制逻辑
) C) c0 X' ]$ b9 W# L这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表
" I! {. U5 M: v- F示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
8 o4 u3 o: Y% U; p( V% P6 u标志位”会被置 1,表示传输完一帧,接收缓冲区非空;5 H- f- m; S+ X6 [+ z
发送数据:
' t2 E8 {" u+ v) N( U地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。
# S. v/ Y# U$ y& s" G. Z接收数据:# i# v& D) g  f2 U6 [4 b6 b. @
接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。: v2 w$ z* i) L1 H, m  c
数据帧格式:1 D! h! J! n$ o1 z3 y/ G; }
通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。" U9 j5 ?% L* n5 C
先行位:" i+ z, }% o% s+ v& n/ v0 @' J; f
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。
8 M! S) n: g8 X& N
) y* s* c. q. |- Z
4.整体逻辑控制
& ]& @5 z: }6 y; n+ r在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
: ~, F. N0 C1 ?, R" M% v" K

+ I0 |* T, Y! t0 W, w8 i" H初始化结构体
# |$ @, ?4 G1 _0 C* Y" r# _4 q库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:
9 H3 B6 p- f( y9 ~: p8 j; T
  1. typedef struct
    , F( V6 d; W& Y( _: Y
  2. {4 k7 |4 B7 S" b4 A" `7 R, [
  3. uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */( F  }0 R- }0 b5 Q- v' Y
  4. uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */1 }/ C" {! i1 q/ w2 R# W
  5. uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */1 j8 p% o: x8 ^3 j' g( a! B  c; Q
  6. uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*// g" B- E4 Z9 d- ~) ]7 j0 p
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */$ C2 ]; t" D: p6 H0 Z  k2 m- }
  8. uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/+ ?9 f9 D. m) u" a2 a3 R
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */1 |. \8 U! E4 e& L6 y- }9 w
  10. uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */& H4 s2 T- n8 c# U
  11. uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */
    $ X: O! f7 V. l% C: t
  12. } SPI_InitTypeDef;$ ^' }- Q' y6 ?4 X" C# Z! k6 |
复制代码

3 b( x) J2 X) _; X9 l/ I: Q$ X这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
; U- [* Z. I6 V4 n2 w- \( ]- ~(1) SPI_Direction; s# V* P& c6 }8 x* H. V
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。2 `9 H, E6 ?) {
(2) SPI_Mode& F1 y8 J5 T4 }
本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
9 w- k/ T/ o- }5 `(3) SPI_DataSize3 A! A* K( u/ ?2 M- e
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位
# D5 v7 j# n5 A9 m4 T9 g(SPI_DataSize_16b)。9 f  @' z" |9 F# W# G
(4) SPI_CPOL 和 SPI_CPHA4 ^8 A: T. M3 w
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
, J7 m' C% K# I" \) h(5) SPI_NSS) U) G& k# n" Z* a. T! M
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。; l: @& M) y, n$ W5 j. \0 I
(6) SPI_BaudRatePrescaler/ c' q& l* s, J+ v
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
' o& q5 z. z3 d1 M(7) SPI_FirstBit
. B. n4 k/ N; y5 `. [* F: T: v所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
7 b+ M* i4 L7 e+ \! B$ \, }. t(8) SPI_CRCPolynomial' R) n! v5 g1 P7 S; Y: A6 r
这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。0 Q# E" s; r# S$ {1 M8 g; @

3 l" @' i) n+ P2 Y) F/ O5 }

8 p$ `4 P  w; J- e. b初始配置函数
5 m" U) k+ Q' `) U6 a
  1. void SPI_Config(void)5 b% Z* j- w" `: }* s
  2. {8 n8 x$ P. u. T3 D% {
  3.         /* 初始化SPI和相对应的GPIO口 */: i( L$ F; B+ s+ `& O% n: z
  4.         SPI_InitTypeDef  SPI_InitStruct;' `5 D+ O, l" Y0 z5 o
  5.         GPIO_InitTypeDef GPIO_InitStruct;. q8 V0 l* E4 S! w4 @7 K6 i8 [
  6.         /* 打开SPI1和GPIOA的时钟 */
    & F! i$ h) l% Y+ x$ |
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);; w9 b5 K  I5 ^  p. w9 y6 @
  8.         /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */3 C+ Q: m$ @: |
  9.         GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;- {/ Q! ?( e& b  N) ?# S- {( `8 O
  10.         GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;
    1 u9 {, g; A; L" @9 z- w5 c+ d5 P
  11.         GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;, L! X+ l$ ?: @* n* Q
  12.         GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
    ) q& N7 x, e- \
  13.        
    - r! w+ Q# d! ?0 I( d' f: w
  14.         GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;/ {9 P8 p' P, [. [$ ^
  15.         GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
    ( t! p: u# O: a) i
  16.         GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;# A* h+ a  ~& C0 f: p4 {: H
  17.         GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
    & [9 j* ~) h$ [4 x" t( i
  18.        
    4 i  Y& H' M/ u6 H
  19.         GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;: l( ]% x. W) o  F
  20.         GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
    7 v, @+ k  Q* o! d
  21.         GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;- k; a# ?& [# M
  22.         GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
    2 d3 s" m0 j, ~
  23.         2 O" ]8 \$ Y7 h5 u
  24.         GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;1 z$ X9 J+ K& H2 o
  25.         GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;  L) w( h: h( d! Q0 p1 [5 T# N
  26.         GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
    ( @  `  E& r) P5 R0 z4 m: N
  27.         GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
    / ?( Q3 u. k. {/ o* c! G$ d
  28.        
    8 [5 r: W5 g" y& U5 T+ E5 L) |' b
  29.         /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */+ h2 _7 n5 X! x
  30.         SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
    % w; ]  r9 F, }8 G4 H- ]) R3 Z0 V
  31.         SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
    - L& F: E  o% S
  32.         SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
    ; Y, c0 ~& V4 y3 x* E8 j( t2 G
  33.         SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
    ! e: w3 k$ U* U! L& j
  34.         SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
    6 e9 ]- {3 ]2 l+ e
  35.         SPI_InitStruct.SPI_Direction = SPIx_Direction;2 B* s6 }( t/ ]  N
  36.         SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;* \5 P6 Y4 z8 J' y
  37.         SPI_InitStruct.SPI_Mode = SPIx_Mode;
    : \' n9 L* i* Y$ L& E
  38.         SPI_InitStruct.SPI_NSS = SPIx_NSS;
    & ^6 c& L0 r$ F+ k6 U/ r: m' I
  39.         / d# i: z& ~8 ], d5 L
  40.         /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */! F8 l. l, L6 E: P
  41.         SPI_Init(SPIx, &SPI_InitStruct);
    2 B) U4 n+ ?  f+ B3 j" _
  42.         SPI_Cmd(SPIx, ENABLE);
    ; N3 J; u  D8 G3 D$ x
  43.         /* 拉高NSS */  M- L2 J( \; b. c0 h
  44.         SPI_NSS_Stop();- O( |! N6 N% H# G
  45. }0 {' G2 s6 M' ~/ F7 l& y

  46. : w4 T; S) @6 o( f) C+ d( E2 R
  47. }
    % m, A* C5 n# Z( \
复制代码
) w# b/ ^- @# N. ]
这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。
! r0 ]$ Y2 F$ t5 l* q# e$ _! W* f4 N& C3 k
4 J4 c! E7 [1 ~4 J* |, Z
发送、接收一个字节" ?' }. T$ y3 q! _) S# B1 a/ w
  1. /* 发送一个帧数据,同时接收一个帧数据 */
    5 a) I! A9 f9 D1 ]# n3 E
  2. uint8_t SPI_SendData( uint8_t data)  S; e$ ^9 g- H" S# a( ]) j0 n7 i: t
  3. {        ! }: D: N" |5 y) X+ Y
  4.         uint16_t timeout=0x2710;   //10,000
    7 B6 U0 A+ ~+ u  x* ?+ G: N
  5.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响" u: c. X8 V6 m0 p1 a' w7 ]0 I4 k
  6.                 if((timeout--)==0) return printf("发送等待失败!\n");
      p: N6 @. l+ N  J: U; Z4 h

  7. 5 X. X% M. B8 a$ }. M9 B
  8.         SPI_I2S_SendData(SPIx, data);, v/ c5 T: U! l7 n4 k2 L
  9.         timeout=0x2710;          //10,000次循环无果后为失败6 j- O: [8 M3 Y6 Q% @. X1 w
  10.         while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
    7 u5 Z: S. G4 d2 `
  11.                 if((timeout--)==0) return printf("接收等待失败!\n");& F& Y7 z- t% `* D5 d
  12.                
    7 i, I7 y* e- T: D2 M- U
  13.         return SPI_I2S_ReceiveData(SPIx);2 W% ?7 ~" s; U5 r: ~
  14. }! L7 J; [1 M/ @5 G

  15. 8 c4 x( _3 k4 T2 Q2 b
  16. 4 w0 K- t/ m# F+ f: x' X
  17. /* 读取一个帧数据 */" j2 Y7 p9 o/ i6 O+ R! p  q. j1 A
  18. uint16_t SPI_ReadData(void)+ [! G; C" e" r* [/ l
  19. {2 x* N' ?6 g/ K: ?" B( g
  20.         return SPI_SendData( 1);//此时发送的值可以为任意值
    ! X$ y% I6 ]% B$ p! b5 G
  21. }% M* J9 x7 O" P: ~7 z9 L

  22. 4 P% N( N6 o/ S! j8 P8 u
复制代码

6 r8 u$ ?! J5 B8 r$ E发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。' c& t, h5 T& z( B$ n3 i4 O- N
; t) \1 |# \0 _/ w
  {  y% f7 ?! _" P# L" y
头文件+ t/ T$ R" M2 \3 l; `
  1. #ifndef __SPI_H
    6 d- ^$ q8 {/ B& ?
  2. #define __SPI_H1 [/ M8 l' r5 x0 B

  3. 0 U4 Y$ O/ ]8 a: h
  4. #include "stm32f10x.h"' p" y3 O- R: t! B

  5. . v+ i. ^" _6 h6 S- l( [, m, r
  6. #define SPIx                        SPI1! s- X) e8 o! h: i
  7. #define SPI_Clock                   RCC_APB2Periph_SPI1
    ( |6 R  N' P& i7 \) a6 T; x
  8. #define SPI_GPIO_Clock              RCC_APB2Periph_GPIOA
    % n% T9 \% n. o0 i
  9. 4 N" u/ E: v* d3 l

  10. 6 W! n! h; L& m9 b
  11. /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */$ c- r5 V# O4 M6 q4 H
  12. #define SPI_SCK_GPIO_PORT           GPIOA
    ( e+ L2 Z1 D" M$ |' W" }
  13. #define SPI_SCK_GPIO_MODE           GPIO_Mode_AF_PP
    8 S8 D& Q: Y4 ~
  14. #define SPI_SCK_GPIO_SPEED          GPIO_Speed_50MHz1 H) N# y. A! ]8 ]4 C- r* ~+ ?
  15. #define SPI_SCK_GPIO_PIN            GPIO_Pin_5' e$ Q& ]+ ~! K/ P+ H' b
  16. 4 x- q9 r, v7 R- w. X. w& X
  17. #define SPI_MOSI_GPIO_PORT          GPIOA2 H6 I0 B3 a9 B6 c, m2 p! ?
  18. #define SPI_MOSI_GPIO_MODE          GPIO_Mode_AF_PP
    / a- b  F" x. i" d
  19. #define SPI_MOSI_GPIO_SPEED         GPIO_Speed_50MHz! C* S/ M) C8 f
  20. #define SPI_MOSI_GPIO_PIN           GPIO_Pin_7
    , o$ M% N/ k, |

  21. + K/ M2 k9 y" m; a! w
  22. #define SPI_MISO_GPIO_PORT          GPIOA
    3 I& j7 }# A. l$ s' p5 U2 `: o
  23. #define SPI_MISO_GPIO_MODE          GPIO_Mode_AF_PP
    2 L. s2 }; t. U2 ^
  24. #define SPI_MISO_GPIO_SPEED         GPIO_Speed_50MHz
    * e/ r* r8 x% U/ r3 B% x- |
  25. #define SPI_MISO_GPIO_PIN           GPIO_Pin_6; Z  E( f; A# o! Q$ ?

  26. 8 t$ d8 ~9 O* P
  27. #define SPI_NSS_GPIO_PORT          GPIOA2 U  A7 ]6 Q$ n. t
  28. #define SPI_NSS_GPIO_MODE          GPIO_Mode_Out_PP
      y( Z+ L+ [- m
  29. #define SPI_NSS_GPIO_SPEED         GPIO_Speed_50MHz
    - @) d# h" K9 G2 x  ?& B
  30. #define SPI_NSS_GPIO_PIN           GPIO_Pin_4         //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
    ; _7 E, D! u4 T' Y: Z( u+ B8 M
  31. ) R: T. B3 g0 b
  32. /* 配置SPI信息 */) Q9 x( U0 R/ [8 n) U: ~
  33. #define SPIx_BaudRatePrescaler     SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz( s: L; ]5 z* I! t7 r. ?
  34. #define SPIx_CPHA                  SPI_CPHA_2Edge//偶数边沿采样
    0 l* ?; K" ~0 g
  35. #define SPIx_CPOL                  SPI_CPOL_High//空闲时SCK高电平" ^- {9 j5 ]4 i- P- c" W  q' d
  36. #define SPIx_CRCPolynomial         7//不使用CRC功能,所以无所谓! g% ?8 W3 ?' {, X( {& d/ p
  37. #define SPIx_DataSize              SPI_DataSize_8b//数据帧格式为8位
    $ R4 m1 [& h' n' e/ y4 g# X# Q
  38. #define SPIx_Direction             SPI_Direction_2Lines_FullDuplex8 X- K0 b5 g' a' R3 U4 @
  39. #define SPIx_FirstBit              SPI_FirstBit_MSB//高位先行
    + ~3 m% l; Q: L
  40. #define SPIx_Mode                  SPI_Mode_Master//主机模式
    8 [8 [) ~. |+ c. @/ ]- j
  41. #define SPIx_NSS                   SPI_NSS_Soft//软件模拟5 G5 d# P/ C+ U( o/ V
  42. - r% n9 f6 [0 A! x% D( {
  43. /***************************************************************************************/! l9 G/ l! q* V3 c5 G
  44. #define SPI_NSS_Begin()            GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)" o7 b* ?. }2 J' J# H
  45. #define SPI_NSS_Stop()             GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)6 p7 K# l# I' I: ]
  46. #endif  /*__SPI_H*/8 I5 z- A: K  y1 l

  47. ; n. f) z, z4 v5 G+ K2 |% |

  48. 5 V5 i% T) k* Q! X6 ^
复制代码

: t$ Y; C; ^- G9 ?4 T5 h3 l7 {————————————————
* R* _, m/ q" ?版权声明:Aspirant-GQ
: b' B9 Y) i! t如有侵权请联系删除8 ~' _! b: t: u# [( ]/ F/ `
; X, Q: c2 b3 A; {, d
8 m% Y' [: H  r; l# |

8 B- {3 i( ?9 U/ m: s& d: j, \7 \
20190809125923104.png
收藏 评论0 发布时间:2023-3-18 15:39

举报

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