一.什么是SPI
& l. P3 E$ g; t+ j9 e' \) q( jSPI是串行外设接口(Serial Peripheral Interface)的缩写,SPI是一种高速、全双工、同步通信的通信总线,被广泛应用在ADC、LCD等与MCU的通信过程中,特点就是快。
3 g8 I$ O9 e; c S8 {7 t/ T' z U+ z. [) x/ K8 ?/ S
二.SPI协议% U% c G' @' s4 a
就像IIC、串口一样,SPI也有其通信协议,我们一般按照分层的思想来学习SPI的协议,主要分为物理层和协议层。
w2 k/ E/ U9 `& Q$ P6 `! j) C. I
" S: y$ e' K7 P$ g2 J! B物理层
+ ~1 F$ @ f2 N; v% |首先看一下SPI通信设备之间的常用连接方式,主机和从机之间通过三条总线和片选线组成:& j2 }0 j' q" p. s
3 J) t) X( D" [; M
6 Q/ F( C* ~, G2 E* {7 U6 c0 v' |
8 ~; @; d9 Q/ _) B4 V, w G6 c" lNSS:片选设备线,每个从机都有自己的一条单独的总线与主机连接,此总线的作用就是为主机选择对应的从机进行传输数据,每个从机与主机之间的NSS总线互不相干。SPI中规定通信以NSS信号线拉低为开始,拉高为结束。6 L9 G' x4 Y1 q; z0 r
SCK:时钟信号线,因为SPI是同步通信,所以需要一根时钟信号线来统一主机和从机之间的数据传输,只有在有效的时钟信号下才能正常传输数据,不同设备支持的最高传输频率可能不一样,在传输过程中传输频率受限于低速的一方。1 A1 l% D, u; x2 r; t
MOSI:(Master Output, Slave Input),顾名思义,MOSI就是主机输出/从机输入,因为SPI是全双工的通信总线,即主机和从机可以同时收发数据,这样的话就需要俩条线同时分别负责:主->从和从->主这俩条传输线路。而MOSI就专门负责主机向从机传输数据。
: b4 k, Z3 z! aMISO:(Master Input,, Slave Output),与MOSI恰恰相反,MISO专门负责从机向主机传输数据。1 }5 g- W7 w* C& }) a, u
. M4 F% `, Q# b6 c4 V) Y4 `! ~8 {
; J! x6 V. @4 R( @- q$ U7 J$ g协议层
+ F: H; c- U, s6 L. S* q和IIC一样,SPI协议层规定了传输过程中的起始信号和停止信号、数据有效性、时钟同步、通讯模式,接下来依据通讯时序图来剖析协议层的内容。
+ o# r5 m. `5 C
+ i1 Q5 m% Q" a M' |: G7 R* b( a# q, y# Y" p
1.通讯时序图1 T( ^' ?& m& \$ }% ~
如图所示是SPI的一种通信模式下的时序图: V, D1 _' V8 {& E5 a/ K
& M/ Z- ~; S6 ^# B$ @# q+ H* {! n3 D2 ]+ t o3 z. j. z# L* u
所有的运作都是基于SCK时钟线的,SCK对于SPI的作用就像心脏对于人体的作用,SCK为低电平就代表心脏停止跳动。
: i1 V9 L% Q) P! X
" w1 i: o6 y2 J& O
- q; T: m7 V) ~# x$ L2.起始和停止信号
* w$ S2 d% k1 r/ S前面物理层说过,SPI通讯的起始和停止由NSS信号线控制,当NSS为低电平时代表起始信号,当NSS为高电平时代表停止信号。时序图中1和6部分代表起始信号和停止信号。: k2 w! x0 h' y! |; M) J
% C8 [4 o! K4 R! @+ S
3.数据有效性
$ K! V5 d3 ~3 d# u4 eSPI中使用MOSI和MISO来进行全双工传输数据,SCK来同步数据传输,即MOSI和MISO同时工作,在时钟信号线SCK为有效时对MOSI、MISO数据线进行采样,采到的信息即为传输的信息。IIC中通讯中的数据是在SCL总线为高电平时对数据采样,SPI中数据的采样是在SCK的上升沿或下降沿时进行的。图示模式中3和5部分就是对数据进行采样的时刻,可以看出图示中数据是在SCK的下降沿进行采样的。MOSI和MISO的高低电平代表了1和0。5 ]% I0 W$ ?+ f, \
- @$ l7 l) _: f4 O, i4.通讯模式% e& _" e1 _5 M: \
SPI有四种通讯模式,他们的主要依靠总线空闲时SCK的时钟状态和数据采样时刻来区别。这里就涉及到时钟极性CPOL和时钟相位CPHA的知识。
3 g. _: [5 `) `4 A' D. B时钟极性CPOL:CPOL是指NSS总线空闲时SCK的电平信号,如果SCK为高电平,CPOL=1;SCK为低电平,CPOL=0。下面的这种情况CPOL=0.
- X" u( G" u8 c! j( @
$ H" _8 a' j/ @( {, C" F5 L
& X$ d. ]2 [% ~+ g; {9 N; N; b: e. E! {# T$ K$ O, ^) v1 }
时钟相位CPHA:CPHA是指数据的采样时刻,SCK的信号可以看作方波,CPHA=0时会在SCK的奇数边沿采样;CPHA=1时会在SCK的偶数边沿采样。
. T7 A1 B0 O2 i) M如图:NSS空闲时SCK为低电平,而且在SCk的下降沿(也就是第二个边沿)采样,所以这种通讯模式下CPOL=0,CPHA=1.
( Y. b# b. ]* |: M$ F* P
0 U+ G0 T. I8 D$ y: S- t
3 j( e }2 r; W' P! I' q3 _5 U" n6 T
3 @5 t: @1 [# w6 e四种通讯模式:所以,根据CPOL和CPHA的搭配可以得出四种不同的通讯模式,如下:
" P, A% D+ ~8 r9 J. r/ k: Q# G, w% P: D! `3 i
0 n5 |8 Y9 U- ^
; R/ K2 D1 x" D
0 g. \" A: i4 A" D/ [+ O三.STM32中的SPI
2 J& l1 x- {& [简介4 O; `6 |1 R9 v, f
STM32中集成了专门用于SPI通讯的外设。支持最高的 SCK 时钟频率为 fpclk/2
7 x* l; \4 V% B9 ^( f5 |0 U9 w! f+ o(STM32F103 型号的芯片默认 fpclk1为 72MHz, fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。
$ J0 N9 t9 F$ x
& S1 C5 l+ g, |9 a1 R9 O
0 q8 T# m) S5 s; @/ U功能框图
. C" m; J, K7 t7 m+ v4 U- I" F$ V6 w7 s9 j/ [1 _
/ v# t) x$ q8 d$ j( o" u
5 B% R* v% Y- | z5 `5 bSTM32中SPI外设的功能框图可以大体分为四部分,对应的1、2、3、4分别是:通讯引脚、时钟控制逻辑、数据控制逻辑、整体控制逻辑,下面进行一一分析。
/ @5 [6 d& u6 X' A: d" |6 f5 x/ Q( S5 ]( \- D
1.通讯引脚1 y5 \, _4 g h* ?0 e; S
STM32中有多个SPI外设,这些SPI的MOSI、MISO、SCK、NSS都有对应的引脚,在使用相应的SPI时必须配置这些对应的引脚,STM32中的三个SPI外设的引脚分布情况如下:5 i' g( a9 E+ s1 [4 J2 {
. z$ X3 t E% [) y" O* a
( H5 }, E$ y2 k
( w3 U9 B A9 B8 r4 j! e
根据他们的引脚分布知道SPI1是挂载在APB2总线上的,SPI2和SPI3挂载在APB1总线上,这挂载在不同的总线上的主要区别就是,APB1和APB2总线的时钟频率不同,导致三个SPI的通讯速率收到总线时钟频率的影响。而且SPI3的引脚的默认功能是下载,如果要使用SPI3,必须禁用这几个口的下载功能。
$ x: \( g1 z- z1 D {6 j, n, D5 j. C" _' p! n! Q
0 E. }$ e: X }8 A: [2.时钟控制逻辑
: E# U3 P. Z& m这一块的内容主要是配置SCK的时钟频率和SPI的通讯模式(CPOL和CPHA)。
4 d/ h6 T' R6 k1 L& N时钟频率的配置:5 u, W4 I" p& Z+ v
波特率发生器通过控制“控制寄存器CR1”中的BR[2:0]三个位来配置fpclk的分频因子,对fpclk分频后的频率就是SCK的时钟频率,具体配置如下图所示:! Z( ~, Y" l. V' s
_$ `8 [5 a" D: y2 B5 d6 K
9 g1 c/ x* N- `. I8 u% f' U+ q" u
2 @ d$ Y D1 v( O# s% V2 C, X$ x4 d9 u& T' X, z
(PS:fpclk为对应SPI挂载总线的时钟频率)- f1 `' k$ J6 N& d, _' M
通讯模式的配置:- N: l$ A" ?2 d0 S1 j4 E$ O
通过配置“控制寄存器CR”中的CPOL位和CPHA位将通讯模式配置为上文所说的四种模式之一。
`* q0 l* J" q. | f' l% Z( W7 j+ S. }6 [* D% N
3.数据控制逻辑
1 g3 p9 Q( {# ]; {8 V( b这部分主要控制数据的接收和发送以及数据帧格式和MSB/LSB先行,和串口通讯类似,SPI的收发数据也是通过缓冲区和移位寄存器来实现的。MOSI和MISO都与移位寄存器相连以便传输数据,下面具体说明一下主机发送数据和接收数据的流程。当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表+ {+ A5 v" B6 Y& @
示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“ RXNE
6 J8 Y A) h+ l% ~. O6 g$ v标志位”会被置 1,表示传输完一帧,接收缓冲区非空;" m& r' ]* S* C+ G# Q/ }
发送数据:
# }3 c1 q4 P5 q' `地址和数据总线会在相应的地址上取到要发送的数据,将数据放入发送缓冲区,当向外发送数据时,移位寄存器会以发送缓冲区为数据源,一位一位的将数据发送出去。; e( ?4 t; `! i, \: k
接收数据:
/ _: l5 _ U2 z! d5 M1 z1 |0 B+ J接收数据时,移位寄存器把数据线采样到的数据一位一位的传到接收缓冲区,再由总线读取接收缓冲区中的数据。! ~ ]* j# _; \$ w' k4 a) Y
数据帧格式:
' j _# ~3 U" H$ ^5 D; k通过配置“控制寄存器CR1”的“DFF为”可以控制数据帧格式为8位还是16位,即一次接收或发送数据的大小。2 G- `" G' q! L8 O: ~9 a" Y w
先行位:6 S; k" h4 l5 S
通过配置“控制寄存器CR1”的“LSBFIRST 位”可选择 MSB(最高有效位) 先行还是 LSB(最低有效位) 先行。
+ } A) c+ ?6 F% [: M/ t: {% e' s& G4 ^3 K$ Q [
4.整体逻辑控制
2 p7 T4 U" ~, S$ ~- p3 M在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、DMA 请求及控制NSS 信号线,不过NSS信号线我们时一般是连接GPIO口,通过软件来控制电平输出,从而产生起始信号和停止信号。
: {/ p; J: H, b# V; s7 y3 r
8 Z3 T6 o% s2 n, c) f7 f初始化结构体
`" N* ?+ g& f) t: S" X库函数编程中几乎每一个外设的灵魂部分就是其初始化结构体了,初始化结构体中包含了外设运作的状态、工作模式、对象等重要信息,配置好初始化结构体后,通过初始化函数将初始化结构体中的信息写入相应的寄存器中。SPI外设的初始化结构体如下:
" T. p& F: k/ y, R- typedef struct4 A2 Y1 i1 O% b" X
- {
, M" g$ |3 ~$ i+ Z( P - uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */% L/ ?5 Z3 l( b% q2 {+ N
- uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */; f; t9 C+ O5 _1 [5 h- {( N" J; f% k9 ?
- uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */' w0 b& ?: x: n
- uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*// d+ Z- ?+ A0 V$ W7 V, G
- uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
% G: @& G+ X: i( V - uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/4 C4 }" [$ Y, O1 z0 U
- uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子, fpclk/分频数=fSCK */; h' S8 q1 V9 a0 Y6 X v6 C
- uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */
% r ?* V1 b3 j" h- V. q: a/ W - uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */ L- v7 t1 n$ p, m' `, O
- } SPI_InitTypeDef;
. h) Y: k0 o$ J5 D& z4 E! o
复制代码
$ J5 R3 c) x! y7 V; F% z这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:6 q7 O( M5 K% R. U: Z
(1) SPI_Direction$ U+ ]' x% V; X( ~
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模(SPI_Direction_1Line_Tx)。
5 ~5 U' t4 r1 y- H" f2 F/ i(2) SPI_Mode
# I5 X! }9 ]% c3 M. M, e. N本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。8 ~' _5 ^0 m9 O* ~% a) n5 c
(3) SPI_DataSize
) L2 T, q$ `5 n+ d* h% |1 S本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)还是 16 位; T( Z+ W. T. m1 f2 i( L
(SPI_DataSize_16b)。
$ a: {# p) G! V; v9 D: g" I: u7 y(4) SPI_CPOL 和 SPI_CPHA6 W; v$ N$ }" g0 S" N4 s* i
这两个成员配置 SPI 的时钟极性 CPOL 和时钟相位 CPHA,这两个配置影响到 SPI 的通讯模式,时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
# @; ?% x; _2 r) C, y/ F(5) SPI_NSS
4 N: }' k' w9 z+ f! }1 ? |本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。/ v6 Q( i( d6 J% j! H6 L
(6) SPI_BaudRatePrescaler
8 _# }. ^! T2 }1 }# f本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
; g8 y$ {* p. O( B2 A* h3 t(7) SPI_FirstBit
; b( n+ W3 P! ~- j所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
: C0 f, D, M9 U( P3 Y. W2 P! R- a7 E(8) SPI_CRCPolynomial
$ Y, g% B, m7 `" e这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。% D+ ~7 c! B& E& q
- J0 U: b+ V/ Q7 P- ]
$ U$ A2 k( C; |4 `- i: B' f2 I; r9 o初始配置函数3 `' _8 ^# V+ {2 z8 ]" o$ J9 w( J
- void SPI_Config(void)
% H7 ~. d+ H6 y4 d4 ?5 J4 W! O - {
) g/ V- p ]) Z; d8 b, i5 U7 v - /* 初始化SPI和相对应的GPIO口 */
! k& c% J" r& I. d" x0 | G - SPI_InitTypeDef SPI_InitStruct;
7 g7 T. V/ q: i0 N. d5 E4 k$ ? - GPIO_InitTypeDef GPIO_InitStruct;
# z2 t) M6 j1 M) C - /* 打开SPI1和GPIOA的时钟 */0 b) W8 s6 n/ v2 |& d' J
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
+ T$ `/ g! r& ?3 G1 N - /* 将NSS配置为普通推挽模式 SCK、MISO、MOSI配置为复用推挽模式 */9 s# w0 E6 n% X, o* d# \" z$ `
- GPIO_InitStruct.GPIO_Mode = SPI_SCK_GPIO_MODE;
, q4 J0 u" ^1 R& I4 v - GPIO_InitStruct.GPIO_Pin = SPI_SCK_GPIO_PIN;
, b1 ~- o; F" f, A8 L* R! a - GPIO_InitStruct.GPIO_Speed = SPI_SCK_GPIO_SPEED;6 V. {; Y7 `* j7 I" I# z
- GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);& ]5 t8 b6 Z% o: u+ A" J
- % |+ S2 w2 @9 h0 Z" Y! Q/ \
- GPIO_InitStruct.GPIO_Mode = SPI_MOSI_GPIO_MODE;7 ]) A) \- q1 `/ _( l8 B
- GPIO_InitStruct.GPIO_Pin = SPI_MOSI_GPIO_PIN;
# I6 [9 T: }1 h/ Q/ N - GPIO_InitStruct.GPIO_Speed = SPI_MOSI_GPIO_SPEED;
) G- l) {7 Z0 r) F: K" Y - GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
$ E; X; B7 |/ Q6 B% ]0 t/ l% b - 5 P. N9 N9 k, T! h
- GPIO_InitStruct.GPIO_Mode = SPI_MISO_GPIO_MODE;
9 x% k' M3 T; k& O; [ P - GPIO_InitStruct.GPIO_Pin = SPI_MISO_GPIO_PIN;) X5 G! R$ I- T/ {' x2 Y
- GPIO_InitStruct.GPIO_Speed = SPI_MISO_GPIO_SPEED;
1 \! w9 `" Z) e2 H - GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct); H: ]* d1 k0 L4 p0 M' D" O# _
-
! |; l. q; b. i' ?* m, o; R1 h" | - GPIO_InitStruct.GPIO_Mode = SPI_NSS_GPIO_MODE;* ]% B2 e8 v/ W. L
- GPIO_InitStruct.GPIO_Pin = SPI_NSS_GPIO_PIN;
, I- P1 Q0 ^3 n: @! N: b - GPIO_InitStruct.GPIO_Speed = SPI_NSS_GPIO_SPEED;
$ {( a8 o) J5 F) V+ D1 s0 n - GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
' D1 @1 a i( ^0 n: Z+ v X- q* A4 u -
4 {! c6 @$ E4 m! ?; \* f - /* 配置SPI为四分频、SCK空闲高电平、偶数边沿采样、8位数据帧、MSB先行、软件NSS、双线全双工模式,SPI外设为主机端 */
& z9 _% {( @+ Q - SPI_InitStruct.SPI_BaudRatePrescaler = SPIx_BaudRatePrescaler;
/ H5 x6 ^( X" Q/ `$ H - SPI_InitStruct.SPI_CPHA = SPIx_CPHA;! S6 q3 F' z6 N! d
- SPI_InitStruct.SPI_CPOL = SPIx_CPOL;
+ p3 S& O/ I) F7 g+ p- F - SPI_InitStruct.SPI_CRCPolynomial = SPIx_CRCPolynomial;! H; t7 z. f/ t2 n
- SPI_InitStruct.SPI_DataSize = SPIx_DataSize;
' r1 C5 K! [. s8 a9 \/ G" d) c; \ - SPI_InitStruct.SPI_Direction = SPIx_Direction;
% t1 E/ ?9 _( ? - SPI_InitStruct.SPI_FirstBit = SPIx_FirstBit;/ _: \. _# d0 }- `1 j5 ]" X- j
- SPI_InitStruct.SPI_Mode = SPIx_Mode;7 p% ]( n3 d: o u9 m% {: C
- SPI_InitStruct.SPI_NSS = SPIx_NSS;
/ y; P/ n y: H# n% V - + U0 U8 A1 x% e6 ?. @
- /* 将SPI外设配置信息写入寄存器,并且使能SPI外设 */
% O4 {7 z: Z! u4 K - SPI_Init(SPIx, &SPI_InitStruct);& Q+ E. [, [9 ]5 Z# D: |6 i
- SPI_Cmd(SPIx, ENABLE);
- K; h/ u+ L9 b - /* 拉高NSS */
: v* v& N3 P: q. B2 r) \ - SPI_NSS_Stop();
( M7 `. B5 X( x# f) h0 B- j3 o' M5 M - } y a2 l! [- P! }/ [( \; T
3 N" ]0 l0 p3 H0 `" w5 G) h- }
) z% L* A$ L" q0 q5 c% c# Z& V
复制代码
( s+ D2 n7 v& y这段代码中,把 STM32 的== SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。 代码中把 SPI 的时钟频率配置成了 4 分频==。 最后一个成员为 CRC 计算式,由于我们不需要 CRC 校验,并没有使能 SPI 的 CRC 功能,这时 CRC 计算式的成员值是无效的。赋值结束后调用库函数 SPI_Init 把这些配置写入寄存器,并调用 SPI_Cmd 数使能外设。
' \0 U* ~5 i: ^0 S) y5 c* F$ q" [$ M9 G! z! J7 f+ \+ @
$ ~) u: q# I" U
发送、接收一个字节
6 P, g1 R$ t- @5 U- /* 发送一个帧数据,同时接收一个帧数据 */
7 G, h1 i: B6 s% m4 {8 I/ X - uint8_t SPI_SendData( uint8_t data)* P N3 ], M1 Q8 H+ ?6 v* A
- {
7 F6 R# D; m8 \. h( { - uint16_t timeout=0x2710; //10,000+ E9 f! b9 o' B0 _: i, u
- while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE)==RESET) //寄存器的状态读取可以随时就行,这个不受SPI是否在传输数据的影响
5 o2 n% c) U* d) S- L- M - if((timeout--)==0) return printf("发送等待失败!\n");1 C Z9 @ ~* D
0 \, k: V% T8 S4 j- SPI_I2S_SendData(SPIx, data);. j6 K& B$ S H D' Z# t( N- R
- timeout=0x2710; //10,000次循环无果后为失败1 |% U9 o- e: m! ?) i
- while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE)==RESET): E4 J+ o) J# W3 }$ Q8 M; ]
- if((timeout--)==0) return printf("接收等待失败!\n");
9 i! O1 J3 V; h: z9 O6 p% [+ C Q5 j -
; N" b' F8 `; ?+ i) e' }6 t - return SPI_I2S_ReceiveData(SPIx);, ]2 P @0 G7 r3 n
- }0 {5 Y5 G2 _/ l
1 d0 @' j- E, S2 ~- 5 D. F* V6 `4 l+ O( P
- /* 读取一个帧数据 */
: {; t5 Z7 S2 \0 i - uint16_t SPI_ReadData(void)! x' d& W) e% }5 A7 y8 r- _
- {# m; ?! p8 x$ h
- return SPI_SendData( 1);//此时发送的值可以为任意值
0 |+ ]3 x! Z! w: j g L - }
7 R% ^5 ~/ W# k( e9 j; n
: R/ [2 ^. c# N! E0 ^( c
复制代码 , B- L. F# ^ G' @) r9 P+ ?7 `. }
发送数据前要等待发送缓冲区为空,靠TXE标志判断,所以开始的while循环是等待发送缓冲区为空,同时,等待接收缓冲区是否有数据,靠RXNE标志来判断,把接收缓冲区的数据作为返回值返回。由于发送和接收是同时进行的,而且要接收一个数据时必须在有效的SCK下,而只有发送数据才能产生有效的SCK,所以接收数据的函数时在发送数据的函数的基础上,将发送的数据设置为Dummy_Byte假数据来骗取有效的SCK。
% Q+ P. e( r7 Z; M: _# W/ y V2 ]* c, ]; t
4 P8 R, @! r+ s0 W" Q7 C
头文件/ q6 _, `+ ~% E: ~. f& n' Y7 K
- #ifndef __SPI_H
) u) |# b( J# c& `. d# a% k7 D - #define __SPI_H
7 y; E* x7 H1 ?. D
. c: e9 g2 e" y- #include "stm32f10x.h"' b& c/ n" i+ y' l- u2 `
$ q1 s! W9 v+ q+ s: a! Y1 l0 n- #define SPIx SPI1
) k- u P0 }9 n - #define SPI_Clock RCC_APB2Periph_SPI1
* O8 T/ m M$ k( o- c - #define SPI_GPIO_Clock RCC_APB2Periph_GPIOA
: g b1 _" p$ |
2 `. U, l/ ~! z/ h- 7 [) x* n+ @( X. D4 L; C
- /* SCK/MOSI/MISO都配置为复用推挽输出,NSS由软件控制配置为普通的推挽输出 */: R1 A8 ~1 c/ j: K' e' ^
- #define SPI_SCK_GPIO_PORT GPIOA
- W8 g0 {7 h. R- N% o - #define SPI_SCK_GPIO_MODE GPIO_Mode_AF_PP
( ~6 f& v8 T! C2 S8 k# b - #define SPI_SCK_GPIO_SPEED GPIO_Speed_50MHz9 x% |1 @2 X" w& t% w
- #define SPI_SCK_GPIO_PIN GPIO_Pin_5% K9 E/ g! T6 N
0 }1 C* ]# U2 J9 a, F# T- #define SPI_MOSI_GPIO_PORT GPIOA* T2 \. b5 A& C7 w h
- #define SPI_MOSI_GPIO_MODE GPIO_Mode_AF_PP
: i! `. V& U) c( V, K; C( I V - #define SPI_MOSI_GPIO_SPEED GPIO_Speed_50MHz
- _3 D4 F; s4 ~0 Y9 V - #define SPI_MOSI_GPIO_PIN GPIO_Pin_7
5 \( S6 U% O* I& l: h" z" z0 m
3 m; E- M5 |: g/ w% k" f' Z- #define SPI_MISO_GPIO_PORT GPIOA
2 L# X) Y( V a! ^ - #define SPI_MISO_GPIO_MODE GPIO_Mode_AF_PP, O4 r( | w6 O0 Y' d; i
- #define SPI_MISO_GPIO_SPEED GPIO_Speed_50MHz! g* f5 v4 T2 u
- #define SPI_MISO_GPIO_PIN GPIO_Pin_63 e# v) w+ F7 C
7 n( E% {7 D% @0 e# T+ S" s; _- #define SPI_NSS_GPIO_PORT GPIOA+ d; }9 F. }0 ], A
- #define SPI_NSS_GPIO_MODE GPIO_Mode_Out_PP# c3 C% @" v5 j- L1 k& K( z% y
- #define SPI_NSS_GPIO_SPEED GPIO_Speed_50MHz
$ B5 A) C# [' [0 o' J - #define SPI_NSS_GPIO_PIN GPIO_Pin_4 //因为串行FLASH的CS引脚是PA4,SPI的NSS要和串行的一致
. ~ k% X6 v3 v% Q
' Q k, a9 h8 [9 l7 A4 t- /* 配置SPI信息 */# z+ I" m/ h3 x9 q4 D! b H
- #define SPIx_BaudRatePrescaler SPI_BaudRatePrescaler_4//四分频,SPI1挂载在APB2上,四分频后波特率为18MHz5 M. e: r4 D ^& ^ l! n4 i
- #define SPIx_CPHA SPI_CPHA_2Edge//偶数边沿采样9 D6 o$ @7 h7 g6 Z: C4 {
- #define SPIx_CPOL SPI_CPOL_High//空闲时SCK高电平
4 ?1 o) ]/ n* d/ `* y1 I - #define SPIx_CRCPolynomial 7//不使用CRC功能,所以无所谓9 {/ N$ f, N6 a- d( T
- #define SPIx_DataSize SPI_DataSize_8b//数据帧格式为8位
( M) ?: c, w4 I7 s1 T; D1 y - #define SPIx_Direction SPI_Direction_2Lines_FullDuplex
' i* n% o8 Z3 W8 ~5 p- U - #define SPIx_FirstBit SPI_FirstBit_MSB//高位先行& j( r2 `- M! i; P' \1 f. F' Z
- #define SPIx_Mode SPI_Mode_Master//主机模式
" E+ o6 J8 H" x- H2 K3 `: ` - #define SPIx_NSS SPI_NSS_Soft//软件模拟0 a3 `3 t* e0 C" o+ p/ h8 w. }
. l& O+ s- S2 _) S% P! E! K2 W- /***************************************************************************************/
# ?1 C; n) U: P - #define SPI_NSS_Begin() GPIO_ResetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)/ H- W, ?; q' X# [5 t
- #define SPI_NSS_Stop() GPIO_SetBits(SPI_NSS_GPIO_PORT, SPI_NSS_GPIO_PIN)$ {) M' i3 W1 r; ]6 D# {0 a* @
- #endif /*__SPI_H*/" B. q9 M9 P6 t. X9 n7 r
" m1 b2 O+ J: b4 T" P( O
9 p7 w, g$ S- O: w9 \& S
复制代码 $ M+ v! b/ r. {$ y
————————————————5 d$ M7 Z/ j @0 h( s2 D P
版权声明:Aspirant-GQ2 @: U# d1 I7 A: G: Z
如有侵权请联系删除+ n% u5 j* _. j% n @
! Y w. m! A$ R3 w: f a& W3 Y" L5 P4 L5 J+ H1 h; i5 c* a4 |+ G
( W! p$ k" |) T* G+ S! Y |