一.什么是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
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
) 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
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
- 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
. 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
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
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 x8 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& _
- typedef struct
. ~' o- @) C% A3 h" o3 i- N - {! r$ x2 E; c0 M; G, x7 q' c2 W. u/ X
- uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */, E8 ?( N* p, L, Y5 `/ j
- uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */
4 H7 l `, p6 P- Q - uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */
! [7 w" u: f5 u9 R - uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/: g# E2 m2 ^/ G; f; o2 C8 [
- uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
. W8 d" B* v6 ? f5 Y - uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
/ J, d+ w& \# Z - uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */* D6 P. F' }& p, B) _& Y
- uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
$ Y# |) A: l1 W$ w' ~5 d - uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */4 ] M" \3 ?- U- E- b9 @) h
- } 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 s5 O. L, V+ ^+ u- H0 F
初始配置函数
5 B# g8 S$ H" ^3 C7 F b. K- k- void SPI_Config(void)
o# `; {# S! K- S; K ^ - {
- o$ e/ K! P! `( p0 |: x1 l - /* 初始化SPI和相对应的GPIO口 */
) ~, B# H0 B1 [& c& h9 v - SPI_InitTypeDef SPI_InitStruct;
: E8 N% ^* h4 {/ }$ P b - GPIO_InitTypeDef GPIO_InitStruct;4 ]0 r% }8 B( l& Q: H! c
- /* 打开SPI1和GPIOA的时钟 */
, Z, _: N4 r* K( V - RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
' m: Z) M1 ^2 `: ?* h: P6 @9 F; ] - /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */
8 a! l- {( X4 e6 S - GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;
" n# h/ h2 h" }9 d5 U - GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;/ ?3 j3 |' W+ s8 ]3 Y
- GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;- |; _ S; h3 A
- GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);$ U4 B7 I% P! J3 W$ N/ _
-
; ~& `6 |( \: c! N, P - GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;; w# `5 h% m" Z
- GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;2 ?! S( P* P* ?6 r( B, S0 _
- GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;6 k& B2 @. q2 O: @
- GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
0 l* R3 j! `& _) T -
* z4 P" u3 Q7 f O s5 m& J: |% k - GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;. x7 E/ ?; Q; D# @0 T0 V# ^% a4 K/ r
- GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;
8 }0 A" Q# D# [$ j' f - GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
: I) ^% p. w# c - GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
6 w6 U6 c: ~& l$ `7 N -
1 _2 a( s, w1 Y$ n8 @0 g# C - GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;$ ]$ V( h0 r- {
- GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
4 Y/ E* A9 z1 q% L$ T& E - GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
+ @9 f, M S$ u% i2 W3 q - GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);, n) \4 L& y4 X! o2 T
- % [7 a% }7 v/ O* N7 s, {- a. Q7 K
- /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */8 i1 f. E% V/ v h# T
- SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
! s7 [6 e, J1 n: j: s; r - SPI_InitStruct.SPI_CPHA = SPIx_CPHA;
: G2 h' \$ T7 x3 @' i! x - SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
: y0 r5 z5 V" T7 _+ y - SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;
# a6 J( F+ l d# U$ N" M - SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
% n$ T, Z( g$ G( o - SPI_InitStruct.SPI_Direction = SPIx_Direction;, W1 y& [' Q2 w& A2 A, E
- SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;, {: J/ n9 F; }' p
- SPI_InitStruct.SPI_Mode = SPIx_Mode;
, u ?' R' D- g4 G* \ - SPI_InitStruct.SPI_NSS = SPIx_NSS;
) x' Q$ E3 i4 ]5 ?5 M m: V - % T: |" t% N+ Q$ S, L1 J
- /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */2 a0 s. J9 D+ D$ f7 |, L
- SPI_Init(SPIx, &SPI_InitStruct);
) {5 e- _/ I: b6 T - SPI_Cmd(SPIx, ENABLE);) ?% S% k4 f, |& |" [
- /* 拉高NSS */
% K8 u* W6 I1 H' \+ M0 K - SPI_NSS_Stop();& I. D( v8 z: v. J
- }
( f( T* Y6 ^3 ~ - & P1 W$ F0 b6 t2 l8 L* O
- }
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, ^
- /* 发送一个帧数据,同时接收一个帧数据 */
0 P! O7 a& w$ Q! A% ]6 n - uint8_t SPI_SendData( uint8_t data)6 a0 P; M, z. l8 x& i" c; K. u; ]
- { 3 e, ~, u5 ^( O$ \5 i# }7 {/ F" C
- uint16_t timeout=0x2710; //10,000
: S" s1 D+ z+ J# e l - while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
$ e i9 F7 O8 i9 d* n6 n" g - if((timeout--)==0) return printf("发送等待失败!\n");( T9 \/ v, |0 E3 F' Z; ]
) E" y7 }* l8 V4 Z+ k/ Z- SPI_I2S_SendData(SPIx, data);/ y3 r. l, S; J
- timeout=0x2710; //10,000次循环无果后为失败/ s1 A3 y1 B. V" |: B) p) l
- while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET)
7 h' M Z( K! n - if((timeout--)==0) return printf("接收等待失败!\n");" j/ C- {* s1 c' G9 z
-
% Q A" V6 X# W; j - return SPI_I2S_ReceiveData(SPIx);2 i) H q, H, I2 Z1 t* ~
- }
% @$ }5 [' {. {; `' h: A - 9 \, O9 P0 X# x2 L) V) _% X5 T
- 6 N! f/ {3 X) S$ T/ p
- /* 读取一个帧数据 */
+ J* K3 X; _: z" | - uint16_t SPI_ReadData(void)2 b9 Z \3 {5 c; @5 K) l2 {4 r
- {# H7 z" B! u) V9 z: C2 N- T
- return SPI_SendData( 1);//此时发送的值可以为任意值5 i" A! E: `1 ~ n! |# ^4 @
- }9 d, @' @, a; D8 p# H: z* p
- 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
- #ifndef __SPI_H" T" ^) l$ ~2 x, d! H
- #define __SPI_H
$ P, a* r6 s+ r9 ] - 4 x2 r: @9 H! A2 u
- #include "stm32f10x.h"3 y8 d7 b! f) W6 o. X+ N
- + X3 g* Q9 {# t& |2 i
- #define SPIx SPI1
& h7 _+ \3 A) Y) \& E9 M - #define SPI_Clock RCC_APB2Periph_SPI1
1 B {% i' H6 H0 x' v9 q/ S+ @' l - #define SPI_GPIO_Clock RCC_APB2Periph_GPIOA
2 q( |+ w p0 P& e: E - 9 F3 _1 U9 a8 U" }1 @
0 l# ]. a% r2 G- /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */
- ?7 H/ k9 r/ v6 B9 T - #define SPI_SCK_GPIO_PORT GPIOA
8 e+ W! q9 l) w - #define SPI_SCK_GPIO_MODE GPIO_Mode_AF_PP
, O# y( C3 ~9 E' ? T1 h- M - #define SPI_SCK_GPIO_SPEED GPIO_Speed_50MHz8 z3 v' |5 Y# I* \4 x' W
- #define SPI_SCK_GPIO_PIN GPIO_Pin_5
# S0 \: \9 h5 G1 \
3 k9 @' M6 q8 w+ R6 R5 [$ J2 q/ k- #define SPI_MOSI_GPIO_PORT GPIOA
# z( e; W$ u" s - #define SPI_MOSI_GPIO_MODE GPIO_Mode_AF_PP
9 j& Q2 Q9 A: s9 b4 L - #define SPI_MOSI_GPIO_SPEED GPIO_Speed_50MHz
& i3 u2 [0 E0 E - #define SPI_MOSI_GPIO_PIN GPIO_Pin_7
$ R- m& S1 x0 b3 ~( ~
^# H ?3 E8 d9 N7 M- j- #define SPI_MISO_GPIO_PORT GPIOA3 w8 {! v5 w0 k& C" r3 Z
- #define SPI_MISO_GPIO_MODE GPIO_Mode_AF_PP
( s" i$ W! N+ [4 ] - #define SPI_MISO_GPIO_SPEED GPIO_Speed_50MHz
" k, L) r i3 o- o6 h. ? - #define SPI_MISO_GPIO_PIN GPIO_Pin_66 Y5 M4 h" B- e2 D4 u+ m* w4 x
- 0 U0 p5 e" j$ e* T; J/ V
- #define SPI_NSS_GPIO_PORT GPIOA
+ d+ o+ p9 u$ r9 Z9 R' r - #define SPI_NSS_GPIO_MODE GPIO_Mode_Out_PP; ~9 d2 l: H3 W: z
- #define SPI_NSS_GPIO_SPEED GPIO_Speed_50MHz- E& s- k! b; w& R! n% H
- #define SPI_NSS_GPIO_PIN GPIO_Pin_4 //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
6 s2 L: n5 P. v& \" h4 V Y& x - # s7 A. n! |) B* j. O J
- /* 配置SPI信息 */
/ u; e5 Q- U* l0 c2 p& O8 O - #define SPIx_BaudRatePrescaler SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz+ D; }* C# [3 v
- #define SPIx_CPHA SPI_CPHA_2Edge//偶数边沿采样1 z7 i& u' e4 \% c
- #define SPIx_CPOL SPI_CPOL_High//空闲时SCK高电平/ f8 K% l8 n8 j0 F# I5 H
- #define SPIx_CRCPolynomial 7//不使用CRC功能,所以无所谓
) O. L0 q9 d4 c, V4 A1 d- \ - #define SPIx_DataSize SPI_DataSize_8b//数据帧格式为8位
$ i, O0 F" w/ r* m$ [" Q - #define SPIx_Direction SPI_Direction_2Lines_FullDuplex2 N; J7 V+ c; v0 m1 y, {/ s( L
- #define SPIx_FirstBit SPI_FirstBit_MSB//高位先行2 |* M( P+ j; K& C. r
- #define SPIx_Mode SPI_Mode_Master//主机模式: [9 M/ \; f% F3 U" D
- #define SPIx_NSS SPI_NSS_Soft//软件模拟
2 S' E1 D2 R# o3 }
8 M3 L8 Y7 z# {" Q. A% ^3 h# J# }- /***************************************************************************************/) w& R: S- O+ v# Y" S/ Y. R, d
- #define SPI_NSS_Begin() GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)" R7 {* n% m+ q7 D$ b1 n
- #define SPI_NSS_Stop() GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)* b4 Y/ m/ o/ l3 P
- #endif /*__SPI_H*/9 Q3 c" \" r, X' R: y {
- + `1 n4 ~0 T. o: u3 M, \ G
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
|