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

【经验分享】HAL库 STM32CubeMX教程十四---SPI

[复制链接]
STMCU小助手 发布时间:2022-3-23 14:00
在我们的HAL库中,对硬件SPI函数做了很好的集成,使得之前SPI几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处,这些优异的函数,也正是HAL库的优点所在( l7 u# v* Q3 d" g# j) c. M
2 ~! {$ [0 ~% i' l
所用工具:
' t1 m( j* i& r
& g, j1 x$ H0 G1、芯片: STM32F103ZET6. s& x+ L+ s$ Y6 y$ r# I# \1 B1 }

& }* i* o1 G5 ^) }2、STM32CubeMx软件- I! n# _: M1 |( d

! X% I0 E5 p. g- j/ G3、IDE: MDK-Keil软件  ~4 ?. W: Z5 W4 u( a. G2 s; `. A

( P0 j+ x* u% n+ j8 }/ b+ ^  R0 n4、STM32F1xx/STM32F4xxHAL库8 `) ~$ ~  f' {* a

9 z5 f( E6 x" N- p5、SPI: 使用硬件SPI1
7 K# I0 Y. v' g! S5 z' u
8 E' @" h5 H' w  G知识概括:
' X1 Q' K8 o( g9 S
2 g  w# u* g; p) K通过本篇博客您将学到:* T' k6 Q+ u# e6 ?; l; m& D  Z8 i

! [$ R2 V  ^! ~2 M4 @3 SSPI的基本原理9 B- @) T1 L' E3 v
6 l2 ^) D" Z$ ?1 g2 b* ]  M
STM32CubeMX创建SPI例程
/ @! g$ E8 M0 L2 V7 m& [5 Z* T# h' @- O& e) d9 [/ V7 r
HAL库SPI函数库
5 m7 c7 p* W" m0 ~4 T$ d0 y7 z# Q! B! K; `& I" Z5 ^: h
什么是SPI0 R" q6 f  `9 }% `* d
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。
8 H& D! ~  B+ c; d
# U: K6 j" Y# d  I  A+ x+ J# W8 `) A  q, HSPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。" k" Y  I0 h: p3 @$ J+ L5 T9 i

2 [" l+ b' D3 Y- a* }' c3 i& YSPI主从模式# q7 r6 [" @( l4 o; J& I5 m
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。, s  U, W7 k& `& w7 F5 L
2 V/ E3 n2 |4 G6 ~+ f# C* O
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps
& h, f. F$ |- c% }1 s1 A3 z4 ^6 a( v
SPI信号线
* u+ f8 ~( W; z  k
SPI接口一般使用四条信号线通信:
- x5 s4 g+ G& ^SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)
; l$ [+ g4 @; ^+ S  H
' ~( b- n/ W) ^7 S0 `MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。/ R: D. t" w# ]
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
3 Q, ]. O; b" N- DSCLK:串行时钟信号,由主设备产生。7 l# C9 P  l' r, N4 v# Y
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
, x8 U, g& I$ N3 H+ M& A硬件上为4根线。; j7 R- h4 j# Q9 f, q! r9 o
0 `7 G  ]* G9 R8 Z- Y: F
SPI一对一8 S) v' _' A" v! @6 w" z' v) c6 u

1 j% e8 t8 B' [4 h; |: K6 L
U4AND{]Q35]1A2B[VX~{)$P.png
; K' H1 o2 G6 A8 X1 ]+ b0 H' a9 P$ c8 @2 y8 t
SPI一对多8 H6 s+ N5 n1 t$ o; Y% [; Y5 `0 [

9 P, u  j2 K3 x8 Z4 l- o ~EU(]W4UD{ZK@3[0U}6[WT5.png & \& t  Q7 l+ [1 c4 d
- F5 @" m8 ]8 ]6 C/ R* b4 a
SPI数据发送接收
2 e( f) x3 Z' k  rSPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
/ X; l  x0 n4 n+ t1 S; Z) J! Z* @4 L$ }9 s4 ^  \7 x2 S- S- c5 s
1.首先拉低对应SS信号线,表示与该设备进行通信
7 p* m& t' z+ ]' p3 `7 w+ l, ?& N2.主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
1 J# c4 g  `, y0 m3 [' G4 ?5 }. {  T这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍2 q" M( S0 q8 O' G5 \
3.主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。3 |3 X7 J' ]4 E5 I  ^* W
4.从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。
2 V$ u7 g$ G1 S( A! S
; [* O; s# Z: t+ s  f }9IPL%WBI(SXK~POT{1{A.png " z5 _( }- [& a5 d
8 y6 M7 ?8 ~" K2 T+ `
SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。
' q4 Y+ S) @0 w8 c& p" u
, a' H* y1 D5 M: J8 x' h- kSPI工作模式
" l; r2 M+ ^" z& j根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。2 h2 d4 Y8 K) H. G, j4 J& S
时钟极性(CPOL)定义了时钟空闲状态电平:
0 d. A; e/ k2 G" iCPOL=0为时钟空闲时为低电平
: I5 \8 ~: M+ M* l0 B, ACPOL=1为时钟空闲时为高电平( a: k% g5 {) i; A! c
8 R& b* z. |2 d+ D/ W
时钟相位(CPHA)定义数据的采集时间。3 F7 O4 }% I) {
CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。9 K& F, N4 l- ]! D9 G) f
CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。
' R6 B. I! b5 r% R" u, y
- v/ Z6 O: h7 |* b9 o WSKLH~CQVK{U~ENH5BE{QEP.png
) s; ~- p4 Z' W: U
  D. T# }& I, w' Y- nW25Q128 FLASH芯片介绍

* V! U  V0 w! a# M# M* T! DW25Q128是一款SPI通信的FLASH芯片,可以通过标准/两线/四线SPI控制,其FLASH的大小为16M,分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为 16个扇区(Sector),每个扇区 4K 个字节。通过SPI通信协议即可实现MCU(STM32)和 W25Q128 之间的通信。实现W25Q128的控制需要通过SPI协议发送相应的控制指令,并满足一定的时序。* m) r1 `8 D" a8 v4 M+ l( k* l* t- H

- M) ~% {) e" S7 Q+ O. L& v原理图连接

& K! c  L# u: U+ L. Q( ~) `  r5 R2 l$ x' ?! g1 a/ R7 O0 P! J
S65UQWR]EZ(@1G2M{E3~IV2.png
) ~0 z) o! N  j9 r
9 R* `0 s- Y0 _# N2 g+ P" w2 A0 D' [常用指令:
. V7 T' e, a" @' O$ y( m% ]1 }6 ]7 p, z/ u
写使能(Write Enable) (06h)
! e. d6 A$ o" p. P7 D
) N" t7 R: r) H7 B5 ?
XPQX%GRHXNY))9_Z864R%SE.png
0 R1 ?* Y2 l( z& D: G' R( I2 f5 g# P# A" x' f& F6 N
向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。
' Y. ]2 ~5 e/ l- c8 _% z1 C! E6 u- a1 }" B; p' Q7 w$ h1 l
扇区擦除指令(Sector Erase) (0x20h)4 I6 p$ A" `; j' R  n7 j0 R

# B$ [) N! {1 L! d2 Y _%`E7{PK0@02IY3PO3~E1.png 6 ~+ ]  m) Z4 Y+ J, y. w- i5 y
扇区擦除指令,数据写入前必须擦除对应的存储单元,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。
8 Q1 ^/ K9 C) C4 G4 G) o% y
' B, Q# v7 _4 ]! E9 Z. E- v读命令(Read Data) (03h). K* J6 G# i& f' d2 n' E- F2 z
1 a4 F: W$ c: r3 W/ ]1 k% p& B% A
读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。3 H  x  }6 s1 H* f" D% w3 f+ m
, k, l! R( V* @" S* F( i' _
, M' O& U' _, s; Y
- p. k/ S5 g! j" i$ f; h0 i+ c
状态读取命令(Read Status Register)

7 y3 _! g9 @: U6 w8 |3 M" ~0 O
6 G, r/ a) c# \9 D' q读状态寄存器1(05H),状态寄存器2(35H),状态寄存器3(15H)* Z  K. P. l& L  z4 d7 |
写入命令0x05,即可读取状态寄存器的值。
0 o9 s& C4 _% D- b2 c, s: }
7 z4 p6 U; O; O, N' E* V5 H `~_0F1_G[R2)T(M80VV1S8J.png # O1 p5 O8 X3 \- u- J( O5 L
, b# q  u; y6 O
写入命令(Page Program) (02h)
2 H: Z# o0 N1 `& A0 f5 j
( u+ |8 N) v  v+ z3 ^2 |2 Q1 x
REU_LMITYA3M}MQZPO8G2T3.png
8 \4 K' R: p) l- ]- k" C/ y
; u$ p9 R+ l% |" n/ j在对W25Q128 FLASH的写入数据的操作中一定要先擦出扇区,在进行写入,否则将会发生数据错误。
* ]9 k$ Y2 b' s2 X  n( i8 i% V6 _W25Q128 FLASH一次性最大写入只有256个字节。
- e: d8 P6 N5 t在进行写操作之前,一定要开启写使能(Write Enable)。
, O$ c& K+ n: x1 [当只接收数据时不但能只检测RXNE状态 ,必须同时向发送缓冲区发送数据才能驱动SCK时钟跳变。# T- Y6 N. X" f! Y* O& k  n7 }- H2 v
3 R: n/ e, P1 a7 @# X
基于CubeMx的讲解3 g# h9 W( l0 Z5 Y! N
1设置RCC时钟
7 Y1 ]" d; z6 I" Z% {& e
: D4 |1 d% q+ h5 F3 ?
A9XVC[L){CQYZLTRY6A]3~B.png
& S% k$ I9 Y3 ^' d! v  Y
8 d4 p9 L0 S' ]$ P7 l设置高速外部时钟HSE 选择外部时钟源. L; U/ r6 E3 b  Q
# D5 o1 |8 G' X/ a
2 SPI设置
, F, v6 ?1 c6 X' D5 y
6 r% X2 s% s6 C# f0 q5 T) qSPI2设置为全双工主模式,硬件NSS关闭,如下图:

, U( g1 Y& J) b5 V
5 H  Q) D6 o7 n& ? _OJ{EZE)BGBQ5P@`)0tLU.png
4 _. a0 I1 z- J. X4 I
  N! G$ }; T: f 08)B_EEMT$II8GC[19Y_K9K.png , A% u9 a% ^- k8 X4 n5 `1 T/ K, q
- L$ T" C. `; A8 d
模式设置:5 F. d- }0 P' f1 a0 {

# b$ a  m9 F' e& I, I/ s. W3 J有主机模式全双工/半双工$ `% p! c- s! l7 I& T8 i
从机模式全双工/半双工) V' I, i$ H2 w; c
只接收主机模式/只接收从机模式6 |* X* i# J. o' C& I! V( L
只发送主机模式$ o& S! n3 n$ q; g5 K# |
因为我们是和W25Q128V芯片闪存芯片进行通信,所以设置为主机全双工) B: h% K. I3 M& q

$ v- h8 W5 a" w7 q) E5 x不使能硬件NSS

) i& Q! R; X, W
2 r; i* _/ I8 J0 b+ D 7OP}KVCLW_WDG@T$N9}Y`BJ.png
! m1 T' g! x5 u4 I; \5 y. M( z2 D0 K* i; Y
STM32有硬件NSS(片选信号),可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替) t1 h5 D$ M9 i7 u( A
, m! L. w& C1 t& x% q$ o! W
其中SIP1的片选NSS : SPI1_NSS(PA4)! y$ |, \: X- i' n& L' x
其中SIP2的片选NSS : SPI2_NSS(PB12)

+ K5 D2 p; \3 A( D; b
9 X& O' o) T& X5 M1 D) v: p& F+ D如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选( h' h4 A) D  k/ U( q6 ^! p/ H

' J4 J% {8 ~& @5 y! cNSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。
5 O, N) y# Y( D# t/ m* \( V# A对于NSS的输入,又分为软件输入和硬件输入。
9 {7 K+ V& X% v! o: M$ j/ J软件输入:* C# Z2 d5 R1 A+ p0 Y8 S/ V! I, j; a# g
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
" x: W1 G6 B" u6 w$ [- |硬件输入:
) y* s+ z8 ?6 I9 x; m- w6 p' Y主机接高电平,从机接低电平。
$ a) L+ N" W8 Z; m8 ~
& Z: H  j3 g* X" U- t左键对应的软件片选引脚,选择GPIO_Output(输出模式),然后点击GPIO,设置一下备注。
( Q2 R! @" s# ]! e) b' ]! R
9 e1 a  E4 \  a; ]1 B$ _6 L  r2 l我这里虽然PB12是SPI2的硬件片选NSS,但是我想用软件片选,所以关闭了硬件NSS
+ `4 H* U. m5 B
' u5 X( z! d9 }" t8 D ~%UZ[)HI6C((0BNCD{K(H]7.png
: x  E' Y9 `! q6 p# c) D$ t0 S9 [, E0 l7 v  y
S%%O[%JBTU~PH@~2S~J9@.png 0 t  r/ I2 S( ^  g7 f, ~

5 q  c1 A) A. Z, G) |! z+ `3 x" q4 ]3 |# H
SPI配置默认如下:
9 R) Z5 ]! w+ \; H, W: y" K
# K  `* u/ A, ~1 @1 A  \: f- G/ s. u0 w
SPI配置中设置数据长度为8bit,MSB先输出分频为64分频,则波特率为125KBits/s。其他为默认设置。
0 D& l1 b* P5 N, ^) L, b6 XMotorla格式,CPOL设置为Low,CPHA设置为第一个边沿。不开启CRC检验,NSS为软件控制。
" \" ~! M5 d' B# Y- M/ a
5 n. s: S( v1 @; _0 h) \: i" W5 ` MNT@YEQZ`1}LDM0BBI05MGK.png ; q5 v4 O: }$ z

) E7 `* l. t% P4 v" D最后记得初始化一下串口,因为需要测试例程,发送数据到上位机。& ]- k1 V  N& Z1 U( h

3 f2 a' J0 J" z. P  Z6 x& T ))_{$KKQG4]2X{X`R(XLEPT.png & n3 x) m3 Y, H" n; O) ^

# N. y, l" Z$ q6 f+ j. l# d3 ]3时钟源设置( y# B6 D2 T0 Y6 a: c0 Y4 E
% O0 d- Z) F! ^/ V3 i+ j+ }- }
8Y(B0VG8[GH)O[VF06P0JCU.png
( Z* B; K" m# U我的是 外部晶振为8MHz" t4 ]8 x5 I" A' Z/ O, `
2 c2 l. o( y9 x# {' h
1选择外部时钟HSE 8MHz
% Z4 I2 S) R" \% \2PLL锁相环倍频9倍
1 q+ B/ Y# v" J0 v: ?! e0 x3系统时钟来源选择为PLL# M( U) j& T( z4 f% t
4设置APB1分频器为 /2
  ^) z! f: m- g! @5 使能CSS监视时钟$ d0 N6 y5 P/ [4 n
* _0 C; f& G: o' Y
32的时钟树框图 如果不懂的话请看《【STM32】系统时钟RCC详解(超详细,超全面)》2 g4 k+ r! q/ ], r0 r: j' M

9 O6 a0 V' O; H) y. T5 ?2 E+ A! d4项目文件设置
# L$ K( h4 d/ k( f9 S: Q
$ Z# m- w6 m" X2 z* P/ r7 E: t5 _' ]; p" }
K[I448YQ}X]MOK]ZO1[6C.png 0 E6 t: [2 X- V

' b, U% i( j+ R, C1 设置项目名称
) U1 }/ Y, U5 i( ?" D3 M) e1 ~! s2 设置存储路径: X' N  F; C# v+ V% S% ~
3 选择所用IDE$ g+ j5 }0 ?9 K$ ?: V
2 ?" ?6 g) T9 m; `* D; J+ \
GPR)1~0V)H[YO(A)U2I]`LF.png   I) H! o  `2 B% o) p* _

, l5 Q& F+ M! _5创建工程文件
; F" {* y6 v  T3 X+ o! q

2 b' v' L( P  U; j0 `, H然后点击GENERATE CODE 创建工程  G0 M. e8 V( @6 H4 M

6 P# p! j+ R  D- a& Q* _. X配置下载工具

! Q6 o8 B% P6 U! {' s5 e新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行. ]# i9 z$ H! c- P

4 L; K; R/ t# H9 U7 g$ x5 }* ]* X. } K)5%$]7K[06O]YRL~9QE)Y4.png
% N( R( ]# G. V% ?4 e0 c9 y; h! B  [9 T6 B
SPI函数详解
0 S; F) a$ \- O: K1 q: |& }( |, B% J3 j. ^; a* K
在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。
9 @5 [, b; D" K
  n& \( B/ y3 { KEO@HHK%AOM{_HUQ0H4FNWN.png
  I, m: Q9 N1 C5 {) p- }2 K. Z5 V
轮询: 最基本的发送接收函数,就是正常的发送数据和接收数据# F" t& c6 {5 L( f
中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能
0 Y- k# r. ]% C% `1 Z2 p( X* M. b( gDMA: DMA传输SPI数据! I% e: M" K9 S- r* u& a
$ h5 ]! O" o8 C' k( L2 g2 x( T
利用SPI接口发送和接收数据主要调用以下两个函数:( z6 H9 S! `! l' t: T  @
  1. HAL_StatusTypeDef  HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
    1 l% \5 Z" g/ r! {. \4 H
  2. HAL_StatusTypeDef  HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码
; F/ A) P( m" H  c' X5 t7 d
SPI发送数据函数:
( j8 T! `) }: ^: c) Q, l% Z
  1. HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
复制代码

4 J; ^5 X  d: G1 }$ G" K参数:
, W- x& o0 ^' v  h3 J, h, M3 h4 J  \1 U# M1 b. p: ?- V$ {2 M
*hspi: 选择SPI1/2,比如&hspi1,&hspi2! Z  F4 C& w( @9 J# A
*pData : 需要发送的数据,可以为数组
7 }8 F$ p* }% w9 I) Q$ |Size: 发送数据的字节数,1 就是发送一个字节数据- [7 R" e% }: K- Q: D! }/ F5 G
Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
4 K, o1 m3 ^, M5 T- e
' v! q3 |, B: y! kSPI接收数据函数:- ]+ x7 x- s* S8 [
  1. HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码
& Q1 x6 K; f- g% ?0 X' y. |2 U: E. Z8 I
参数:
* w9 Y! a+ z: G3 {. k# m9 p*hspi: 选择SPI1/2,比如&hspi1,&hspi2
) e% z) T5 H' g. y*pData : 接收发送过来的数据的数组5 g& u4 I' K# t- u* l
Size: 接收数据的字节数,1 就是接收一个字节数据
2 |: U- B8 n$ Y* X9 A5 B- P! jTimeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数4 e' t+ i4 W/ H8 X
; Y, F1 `6 Q; n8 }
, z0 Z9 {8 t. \# y& S8 P) y# u/ u
SPI接收回调函数:
5 b5 m$ c; a9 ^1 D0 R  }
  1.  HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);
复制代码
! e; }4 `, f/ p4 s- N
当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数:
  1.    HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
复制代码

& R' [8 b, W  `# W/ y用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数
. g/ v8 z6 \; d
# a1 X0 i3 ?; {# M片选引脚:& D/ S# U" h" V7 d" ?& {- ^4 v2 i

) x7 K. g2 x: A  `  r# `因为我们是软件使能片选,定义片选引脚,CS片选低电平为有效使能, CS片选高电平不使能# ?) e0 u) Z$ r0 R
7 V# H& u5 U! ~* H  I/ p
这里用两个宏定义来代替
6 T% I0 T: |) H5 C- E6 O" k2 J) \
% i* }* L' }. g2 _$ h在main.h中有宏定义命名,SPI2_CS_Pin 就是PB12
  1. //以W25Q128为例
    5 u+ Z  }& x/ i! s7 e) j
  2. #define SPI_CS_Enable()                         HAL_GPIO_WritePin(GPIOB, SPI2_CS_Pin, GPIO_PIN_RESET)
    4 X5 Z& t4 P* @0 h
  3. #define SPI_CS_Disable()                 HAL_GPIO_WritePin(GPIOB, SPI2_CS_Pin, GPIO_PIN_SET)
复制代码
6 R: B; K$ ~" @2 Y7 E
SPI例程详解

; |3 S. f4 T8 s9 i; L" X( `' e因为不同的flash芯片通信协议以及方式都是不同的,所以这里介绍下具体的SPI的发送和接收应该怎么写,具体的请看芯片手册修改下即可,这里提供下W25QXX的驱动文件,以及测试例程,测试是正常没问题* o! A# A2 ]5 N4 V6 B2 G

& \! T! a$ q5 v& W7 ^挑几个函数讲解一下:" B* @' @0 ]: i+ z$ y# I
: O0 Y9 ~, z( G) r- @
在w25Qxx.h钟可以修改CS片选引脚,W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平
  1. #define W25Qx_Enable()                         HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_RESET). l% F0 w: k1 s6 [" j
  2. #define W25Qx_Disable()                 HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_SET)
复制代码
3 r2 Z7 n- `+ x0 |
w25Qxx复位函数:
8 ~6 ?; A  r2 E# l: I, h& I- u
5 h# z9 m1 T8 c, w( n
函数开始先将要发送的数据(命令(0x66)和地址(0x99))存储在cmd数组中," A/ Z4 @& C7 ^
拉低片选信号,开始SPI通信; Y0 N& J  q" `
然后后通过HAL_SPI_Transmit()函数发送出去
2 Y/ A' U) {# A6 B拉高片选信号,关闭SPI通信9 Z* w4 o5 P0 K
2 O  N% n3 w8 q1 f4 r
W25Qx_TIMEOUT_VALUE是最大超时时间,在w25Qxx.h中定义为1000,单位为us* j" d2 q- ~9 A
8 j9 q$ p8 T9 Z3 H# X+ b) F
FA)X9VQ]S3IY97FX]V8X1FW.png
) g6 R2 a7 z) q* u$ \( g/ h
  1. /**
    0 a, r4 D9 t( Z1 b3 @
  2.   * @brief  This function reset the W25Qx., h" a0 S+ w6 u) d$ |& L! f
  3.   * @retval None$ `  g( K  g( x! b4 a6 q2 `
  4.   */
    , h+ r$ N; W1 ?% @8 i& B
  5. static void        BSP_W25Qx_Reset(void)0 K/ T  ~: f5 G: e
  6. {! M. D' a* W! a2 T# |- D
  7.         uint8_t cmd[2] = {RESET_ENABLE_CMD,RESET_MEMORY_CMD};2 S# T2 |' F, @) f% l& ^8 h+ M
  8.         * y2 n, n% K9 k* S. A
  9.         W25Qx_Enable();
    ' M7 _% r8 e9 z& `& k
  10.         /* Send the reset command */
    6 }( _# T) n; g
  11.         HAL_SPI_Transmit(&hspi2, cmd, 2, W25Qx_TIMEOUT_VALUE);        
    % S" T" o8 ~. v8 D: W5 r7 a8 s, ], z
  12.         W25Qx_Disable();+ O$ D1 }" I. N2 ]$ {$ T

  13.   e9 b( {$ Y' h/ }! ]9 ^2 y; k
  14. }
复制代码

" Q$ ?2 y' V$ F2 \. N  UW25QXX读函数:
& [% D. T; l: K6 f9 ?" g& B0 o三个参数:- _! k3 L" [$ j5 d* p% N
pData 存放读取到的数据的数组
0 {8 \& ~+ |! v0 ~- LReadAddr 读取数据的地址1 A- ^$ l) o8 `+ p" }$ h
Size 读取数据的大小
7 Y* a" I" E8 T1 ]: C7 c, j5 N3 N( v2 L- ^
+ q( {! e( d( p: q5 e: `0 Z& V# ^
函数开始先将要发送的数据(命令和地址)存储在cmd数组中,; x& y! P7 u3 y: r+ Y! S% u
拉低片选信号,开始SPI通信( ~8 s7 o# y4 Q+ |
然后后通过HAL_SPI_Transmit()函数发送出去,首先发送写命令(0X03),上方有讲解,然后发送三个字节(24 Bit)的地址
+ i7 T' b& N' U" o( D接着通过HAL_SPI_Receive()接收读取的数据。- N  N' n( ~. z% D: B' }( W4 _
拉高片选信号,关闭SPI通信
8 o1 ~$ i( b2 F; ~- N) t4 K7 ?6 b+ k# x2 `% f
ESCEO7$I`2621{[NT1@4MFY.png
0 [: x; r2 i3 r0 P3 ^
( D* C  M+ _* A/ M0 b
  1. uint8_t BSP_W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
    " l- v3 ]4 n' ?, x8 B
  2. {
    : H+ d. A" L/ b8 }
  3.         uint8_t cmd[4];% k; ~0 m3 K. L; k. }" C6 w! P1 |' h3 Q5 `

  4. 6 r) ^" Z5 t' \& G; N2 [
  5.         /* Configure the command *// R% |: f: F6 I' T
  6.         cmd[0] = READ_CMD;/ R' F: n0 e! _- M
  7.         cmd[1] = (uint8_t)(ReadAddr >> 16);8 C4 @2 q$ Q! _- c: \4 }4 c; ^
  8.         cmd[2] = (uint8_t)(ReadAddr >> 8);/ _  o* B/ v* v' |8 Y7 p9 a# j
  9.         cmd[3] = (uint8_t)(ReadAddr);" t6 T) r+ l! [7 e+ K6 T2 {' G0 h
  10.         8 Z* C5 ^" e6 ?- b
  11.         W25Qx_Enable();
    5 X: T& s) v! ^9 ^8 q
  12.         /* Send the read ID command */
    / D- t9 }" e2 N# H; o; s# U
  13.         HAL_SPI_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE);        
    2 N# q  w' J/ A1 }& c
  14.         /* Reception of the data *// r; n2 n1 v, k, D. R- o8 \# n2 |
  15.         if (HAL_SPI_Receive(&hspi2, pData,Size,W25Qx_TIMEOUT_VALUE) != HAL_OK)
    - z. K, y) _  P7 X- I: c" d7 h
  16.   {5 a3 z3 h4 N$ t5 y2 J
  17.     return W25Qx_ERROR;# `$ E" ?% q, ], X9 f
  18.   }
    : S; P( A* G  h& I. C- ?( j+ X6 s! ]; k) ?
  19.         W25Qx_Disable();
    # y1 u( f. c( X; z  L& Z. Y8 j9 y
  20.         return W25Qx_OK;$ z, L  a' e" q$ Q
  21. }
    & a: ]2 ^; Q" G" b1 S
复制代码

. \) f3 A1 I1 d- _* o1 j写使能(Write Enable) (06h)
" n  y( O; h+ s, {

3 c: m  j- }) {, N! v6 g& X* { _H${X10%GJ3V4NONTH0B1}7.png
, Y: Y; E5 l1 y, G
  C- X% r: j" H4 J- k$ m向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。/ P! R3 z4 {7 r' D2 Y8 X

: _1 q5 u2 u: |( a% n3 x
  1. uint8_t BSP_W25Qx_WriteEnable(void)8 U+ O  @) y/ E0 J, @/ B' g
  2. {9 w' j- v3 [; P) F% E0 X* [
  3.         uint8_t cmd[] = {WRITE_ENABLE_CMD};! J) h$ u. t8 E& r2 J6 l
  4.         uint32_t tickstart = HAL_GetTick();1 |6 s- ?- O. A: {

  5. ( W  f" q2 _2 }
  6.         /*Select the FLASH: Chip Select low */2 M; O- F* t& r# r
  7.         W25Qx_Enable();! q/ C8 }( W: j
  8.         /* Send the read ID command */
    8 m4 D9 v: C7 N3 C  x
  9.         HAL_SPI_Transmit(&hspi2, cmd, 1, W25Qx_TIMEOUT_VALUE);        1 u' `+ Z5 J& M6 {$ @) M  ~
  10.         /*Deselect the FLASH: Chip Select high */
    7 K7 P0 ^! W0 i7 j! K, }
  11.         W25Qx_Disable();. x  x  X' }; F$ o
  12.         5 k9 f; ~2 K/ Z9 S# l: }$ E& p" v
  13.         /* Wait the end of Flash writing */
    5 E$ w8 c% P6 G4 Y# A  S& h
  14.         while(BSP_W25Qx_GetStatus() == W25Qx_BUSY);
    " r$ U  ]7 _! I& |
  15.         {
    : f4 ~7 H9 U/ A( z6 K
  16.                 /* Check for the Timeout */( C" S8 Q6 H' K' M4 _. J
  17.     if((HAL_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE)
    3 x5 s  r. {" e  |" k/ C5 a: R$ S9 [3 m
  18.     {        + X% G( O! H1 k
  19.                         return W25Qx_TIMEOUT;
    6 a- A7 `& a9 Q* r
  20.     }  z1 q4 W3 Q; n$ a( B2 I$ x7 R2 N
  21.         }
    ; Q) \  z# N  L! F- H/ D" h
  22.         
    1 P9 H/ {3 \" T* A' T
  23.         return W25Qx_OK;
    1 u4 m1 B0 M% H% S$ c3 m$ E: w/ o
  24. }
复制代码

! N  ?) Z6 T4 {扇区擦除函数:6 g/ ^8 u" _3 b  a/ @0 l4 l2 A7 O3 k

! H3 e, D- S  M/ i! B" Z扇区擦除指令(Sector Erase) (0x20h)
: B  ^, A, B/ g: |: N% ]# a
5 i) [) V' s/ T) K% Q$ l) V
EY@MZDOQ(DHAJFP0UNAWDTQ.png , ~- U! Q' ]) U+ c% o" F2 n

6 }, P6 d4 {8 G5 t扇区擦除指令,数据写入前必须擦除对应的存储单元,并且使能写操作,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。判断flash是否为忙状态,如果不为忙则擦除操作完成。
/ c8 g2 x/ i: y8 y6 t5 b% N9 z
+ i8 ^. `6 _3 o+ w+ x2 m2 g- [$ d3 b. v
  1. uint8_t BSP_W25Qx_Erase_Block(uint32_t Address)
    5 C# f  n; W: I- K0 T  l( ~# |
  2. {/ f" n+ c; R0 n
  3.         uint8_t cmd[4];6 s7 F* s5 i% a* Z8 X- t2 q
  4.         uint32_t tickstart = HAL_GetTick();
    ' t# t' e; P2 X) ?
  5.         cmd[0] = SECTOR_ERASE_CMD;. y6 @3 v8 [! K
  6.         cmd[1] = (uint8_t)(Address >> 16);& x6 f/ m" x) ]# Q3 y; Z
  7.         cmd[2] = (uint8_t)(Address >> 8);! `2 V7 V% h! {! J; {3 I
  8.         cmd[3] = (uint8_t)(Address);
    # }3 _% h2 @3 `; D5 g. k' l+ e7 d( G
  9.         5 n# r/ ^+ y3 i" I; |! M8 a3 R
  10.         /* Enable write operations */
    2 P: y5 K* z- l, m$ ^1 g
  11.         BSP_W25Qx_WriteEnable();
    8 m# S, T+ `7 C$ d3 X8 a; m7 |2 ]
  12.         " T( J  n6 v1 @; x2 t( x
  13.         /*Select the FLASH: Chip Select low */; Q, A. I8 E$ U! g. |4 k7 r
  14.         W25Qx_Enable();) W$ m/ F/ f0 W' l
  15.         /* Send the read ID command */
    ; T- _5 @+ @; @7 A
  16.         HAL_SPI_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE);        
    9 b( W* ]8 r/ @* w* V, P7 z" ^/ t
  17.         /*Deselect the FLASH: Chip Select high */2 j' J1 r2 S  C2 e: n0 B; b7 c
  18.         W25Qx_Disable();
    ! l2 e$ y3 J- x6 e
  19.         
    # @, D$ F* @1 C- v6 {8 J8 ~
  20.         /* Wait the end of Flash writing */+ ~" |' Z; l* r  l/ V" B- \
  21.         while(BSP_W25Qx_GetStatus() == W25Qx_BUSY);% o( P' j1 }% L
  22.         {% Z! y# W( u. J7 t
  23.                 /* Check for the Timeout */4 _4 T+ G0 p3 M; s5 Y  G) _( ~" w
  24.     if((HAL_GetTick() - tickstart) > W25Q128FV_SECTOR_ERASE_MAX_TIME)/ W' D; {4 P: Z
  25.     {        4 u/ o' q) J# B# T: `
  26.                         return W25Qx_TIMEOUT;0 W+ X) ?: c, P- V$ m1 O
  27.     }+ r# o- `3 ^4 G* V- v3 Q' r
  28.         }
    9 O% H, M7 C; B- |
  29.         return W25Qx_OK;) x% @$ m  o' I. o/ F0 t( B- |
  30. }
复制代码
# c) x0 v3 y4 ^( P& M; e
例程测试
) h; W1 I/ D2 k& h2 V重新定义printf函数
: T8 f( B$ `3 ?  z
在 stm32f1xx_hal.c中包含#include <stdio.h>1 V4 K2 ^" o! G, g( c3 }- ^# \/ A
  |- y' `2 }5 D( c9 c! X  j
  1. #include "stm32f4xx_hal.h") i: H( w! V2 ~: C( g  k  ?! @6 S! u
  2. #include <stdio.h>
    + I) i% z0 p0 c4 m- J4 ^
  3. extern UART_HandleTypeDef huart1;   //声明串口
复制代码

2 _! D6 V- H5 @0 A# A' @5 J4 i/ E在 stm32f1xx_hal.c 中重写fget和fput函数3 C# B. w" n; k7 U
" V& g1 i( m8 ^/ `' s
  1. /**; {3 S7 \7 _" M9 v& q+ C- M
  2.   * 函数功能: 重定向c库函数printf到DEBUG_USARTx1 x2 L# M- b; u* ~
  3.   * 输入参数: 无  N3 c! n. H, p0 B2 x% M
  4.   * 返 回 值: 无
      G, y$ h, G1 f+ M( T# V
  5.   * 说    明:无( Y1 i; T7 e* Q4 L0 E& L0 h
  6.   */
    % Y0 ^, H0 n9 D! S4 F9 ^  P1 v! H
  7. int fputc(int ch, FILE *f)
    - c: E0 r: W9 J9 K/ P5 k' j
  8. {
    ' m7 I: d7 K) Y1 `$ u: o/ v
  9.   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);* N# e6 R9 L$ O+ N% w* K: m: u
  10.   return ch;
    * m; T6 S2 i( V; `% r
  11. }9 s7 r* L: b! E; p% D, @* f  q
  12. & h1 }. @6 u6 ^+ O  v
  13. /**
    7 L6 T8 |" Y8 b1 q: N5 \5 _7 r
  14.   * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx, |+ z% r: D0 Z6 U
  15.   * 输入参数: 无5 ]9 c3 I2 M$ i  S- T
  16.   * 返 回 值: 无
    * o4 d! v" _9 ~' d4 F
  17.   * 说    明:无% D) ?" X  X  z: |  T
  18.   */6 J1 J, j6 @4 }, O) P2 M! l
  19. int fgetc(FILE *f)
    # k# Z, x9 O" I5 c7 v( I0 Z3 Z
  20. {5 P8 X. N! h4 I/ |4 r& D* }4 ?/ k
  21.   uint8_t ch = 0;
    / V+ R: C& K( E  X/ a
  22.   HAL_UART_Receive(&huart1, &ch, 1, 0xffff);0 M# d8 a" N- \5 T
  23.   return ch;
    ' A' u) q8 x8 K, k1 |* T' v
  24. }
复制代码
7 u& K5 x# v" b5 x  g# A1 F7 e
main.c
' q1 f. S* F) l3 F* z  \/ A# S在main.c里添加以下代码:
% C1 e* C& G% Q* c: k. L0 \) Z/ j' K7 Y& R3 k; H, m
  1. #include <string.h>
    7 D6 m+ \- A: c/ l3 F# V4 ^& m3 I
  2. #include "W25QXX.h"/ b4 d; d& R0 L9 h- O& N
  3. 0 j8 k/ }, Y1 O9 ~
  4. 5 S7 l9 q! Q4 L+ B6 }8 W/ D+ {

  5. " R' C3 p( E* z, m9 z
  6. uint8_t wData[0x100];
    ; g1 y% \9 a# L9 O
  7. uint8_t rData[0x100];
    6 N7 y1 e8 s/ p/ g1 Y3 W" f: q5 `
  8. uint32_t i;
    8 J2 e5 u' E0 W, ]  h6 `! j
  9. uint8_t ID[2];
    " [$ B+ l, T9 M7 W  z1 i
复制代码
  1. printf("\r\n SPI-W25Qxxx Example \r\n\r\n");7 C2 S% b* [8 ?0 y5 Y) R

  2. , Z8 ^' k" w8 t/ F5 l* j
  3.   /*##-1- Read the device ID  ########################*/
    . f/ ^9 J6 |; n9 u5 P

  4. + q; `# j! [6 \; m) X( }: n% N/ f4 Y
  5.   BSP_W25Qx_Init();; C& a) N7 Q; k" H! |  }, H
  6.   BSP_W25Qx_Read_ID(ID);7 Q. [9 T9 x  Y& E% [$ Q

  7. 2 [* _; ]9 A0 \& F4 V0 z( K; q
  8.   printf(" W25Qxxx ID is : 0x%02X 0x%02X \r\n\r\n",ID[0],ID[1]);
    9 s% @6 F/ g0 M0 m! x
  9.   /*##-2- Erase Block ##################################*/
    8 w: A( |) e1 Y8 D- Q7 W
  10.   if(BSP_W25Qx_Erase_Block(0) == W25Qx_OK)
    $ A+ t  B! q9 u5 }3 }' }
  11.       printf(" SPI Erase Block ok\r\n");
    ( h- N5 f4 C/ M
  12.   else
    % d* A% N6 ^# b
  13.       Error_Handler();. l3 T4 s+ K# q8 Y0 `9 e8 e7 ~/ ]
  14.   /*##-3- Written to the flash ########################*/9 h$ ~2 o$ J0 M/ P
  15.   /* fill buffer */
    ( T' h+ B9 |  B6 O
  16.   for(i =0;i<0x100;i ++)7 y7 D6 C+ j/ I; [9 j
  17.   {! _6 I1 q$ q2 l6 I: c- h0 H0 T
  18.           wData<i> = i;% J* |4 e" z5 {- `; W0 s
  19.         rData<i> = 0;' h6 `) _7 ?3 r& k
  20.   }
    + A! T! @5 i" y8 L' m3 `. d1 \& t: k
  21. if(BSP_W25Qx_Write(wData,0x00,0x100)== W25Qx_OK)  U4 Y6 i( O* [
  22.       printf(" SPI Write ok\r\n");
    ' a/ s9 i  @/ x9 ?( _# G
  23.   else; `- |9 E' \( T: c  _
  24.       Error_Handler();
    ' i7 k5 [0 [( e) }; f: l
  25.   /*##-4- Read the flash     ########################*/
    2 Z! Y! b* l0 H& @% V

  26. % d6 c) k+ p  V1 F6 f' @
  27.   if(BSP_W25Qx_Read(rData,0x00,0x100)== W25Qx_OK)  ?, {0 D0 ^! u6 A4 p& P- z
  28.       printf(" SPI Read ok\r\n\r\n");9 G$ Z' c/ Q/ W; G
  29.   else. Z0 ^) E, t; u
  30.       Error_Handler();6 c0 U: ^$ z+ u. A, d' K
  31.   printf("SPI Read Data : \r\n");
    ! Z# s& I5 L' c5 a/ d$ F$ p
  32.   for(i =0;i<0x100;i++)& x* h, \7 `+ |8 g
  33.       printf("0x%02X  ",rData<i>);5 i. v9 x  P9 |
  34.   printf("\r\n\r\n");
    0 @$ P$ c9 t1 y' r' }* ^+ H
  35.   /*##-5- check date          ########################*/   , J  f% Z9 t* m  L
  36.   if(memcmp(wData,rData,0x100) == 0 )
      X/ k. L9 |$ k0 I9 T1 F4 \/ Q
  37.       printf(" W25Q128FV SPI Test OK\r\n");& U* Z  o3 ~& T) A4 e2 P# M8 w
  38.   else1 d9 o% y$ ]( K6 l
  39.       printf(" W25Q128FV SPI Test False\r\n");' O# I( m2 z4 V' p8 g
  40. </i></i></i>
复制代码

$ Z% y% a' ]. R3 G) C/ ?STM32F103测试正常:
1 A* N, ?5 G" K) p! H( n! x! C& I% s/ r% R. ~( E
W[@WHY6O~O%7YTVLCI@9J(F.png " d4 ]7 X6 G5 `* b# J
: d: a2 y0 Z! p+ i/ ?9 y
收藏 评论0 发布时间:2022-3-23 14:00

举报

0个回答

所属标签

相似分享

官网相关资源

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