在我们的HAL库中,对硬件SPI函数做了很好的集成,使得之前SPI几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处,这些优异的函数,也正是HAL库的优点所在) y' Q$ n& ^' U4 a
4 o3 g0 J8 Q0 j4 I8 O8 j所用工具:4 D1 s% A. n$ N& O( L
4 I7 R/ ]9 I* D- f1、芯片: STM32F103ZET68 j5 Y: c2 e: E0 @2 P" E9 f
9 o Q- M# C: c2、STM32CubeMx软件
! A: n3 [2 i* O" T; F. h; O& W& \8 `4 s9 Q* p* a0 ^ A. ?, v7 ?
3、IDE: MDK-Keil软件
3 v% n+ t5 D( r. J5 L. U F4 z! L; }
4、STM32F1xx/STM32F4xxHAL库+ `# y+ V* L8 m4 Y* ?! u7 o: z
1 G: p( B4 q% N$ |! ^! K0 m, R5、SPI: 使用硬件SPI1) e# B& ^8 e9 `* i* T6 U
$ `) k% k2 m- e; S知识概括:( ?; E: {: ~4 z" ^
. H8 O8 ?3 d* a/ I# X6 j0 B, s' o* u( e通过本篇博客您将学到:
% M# c+ {: _9 L4 \
; [" z* t1 u4 [; gSPI的基本原理6 m% O2 i8 A+ M5 w. ~
7 z* c# x* X# ASTM32CubeMX创建SPI例程
2 l% _; P7 B3 X2 n! ]# J y7 |, G& n2 ]
HAL库SPI函数库3 v! B; Q6 p n8 P9 X- Q
/ [% u0 b" j; @% J% l0 @4 h$ c什么是SPI7 K; {! g: N6 v$ V1 Z
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。) m) }+ @2 V1 t& v- t1 g# T% `
; j$ T6 ?* E/ A/ g% c4 N
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。. L& [ R% O2 v) Y8 U8 U6 G
2 d4 R6 L( j1 R# U
SPI主从模式0 N$ k# T# k7 B% s2 q! d& [# y
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。
2 }/ j8 x0 u9 ?) G( D
& t& [# \8 B5 @; bSPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps: I0 {1 G* p& ^3 G1 r
. U5 @' R- \/ s$ [- p1 |# q) n4 m
SPI信号线
, A0 J5 m6 R6 r& d4 S& z. N3 V3 h: OSPI接口一般使用四条信号线通信:
: _% J: R( _; W3 T. cSDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)9 z/ I) L: S* X5 }. L# @
; A* r$ M$ E& N& eMISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
( D; q9 M% U) \* @, [- TMOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。8 W" l# G* C: y5 d( R/ i
SCLK:串行时钟信号,由主设备产生。* r" e# u$ T3 d1 ^3 u
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
6 D9 c/ N" z; ?# E9 {% l" ^硬件上为4根线。
( H/ Y8 E% w1 y' ~2 O/ L: n3 w- x$ d- ~* X6 v3 C7 f
SPI一对一
3 R6 h8 I* E2 h d) Q0 ?
" d/ p' T( J7 T7 \, I, t
1 t0 E/ a4 v' N5 j% e6 Y1 x c4 g
# w9 d* `/ B: T8 B B7 F
SPI一对多' W* T% ^& S$ P* `6 |
7 L0 c" Z5 A! O( P$ x& [
5 r6 s) L; a' _: l; }9 p
. _( O7 @1 L" Q5 T* [SPI数据发送接收
* b# q* W* {& R }" _1 R' f% _0 ]& FSPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
0 l& ?5 ?1 C/ l" b( F
. j/ r& j1 P% t- S1 m7 H1.首先拉低对应SS信号线,表示与该设备进行通信
% _" w6 H2 p2 ?9 @7 A! n% x2.主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
. [* G( u0 z4 c8 u1 c. ^这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍- f, X+ N2 n1 u, S' t* I6 }* J
3.主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。6 j: C5 F+ d5 Y( q; W4 f# b5 |
4.从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。; W( A. g8 ]! \; k
$ Y/ g; j, B$ b8 q0 r
) F5 L, @2 p/ M6 |8 u- ?1 {; b" r2 z3 E$ n( ^( a
SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。
# ]4 M# Z$ {0 ~1 B: P; _6 H. Z1 l4 J& D5 a$ S$ ?
SPI工作模式" O7 h p& ?7 Z Q4 Q* Z
根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。
2 g s" M& ?- r4 k时钟极性(CPOL)定义了时钟空闲状态电平:
+ A i0 Y; Y- i+ jCPOL=0为时钟空闲时为低电平
3 H1 m- c/ N5 T, m# C1 QCPOL=1为时钟空闲时为高电平" `( J' N- x2 a1 p' E
9 a: c Z; [( X0 T" f) d- W
时钟相位(CPHA)定义数据的采集时间。
! O0 Z2 C1 d: O9 Q$ WCPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。1 D. q, o1 j3 J# h& F: ~! w6 ]
CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。
8 m# A7 B2 v+ g% d/ L
- L; B, ^+ Z$ Q. m
0 z. p5 P% [( U- f7 [) T. P' |) P
% k: w* b3 k' }' r
W25Q128 FLASH芯片介绍
- ^9 g ^0 o# jW25Q128是一款SPI通信的FLASH芯片,可以通过标准/两线/四线SPI控制,其FLASH的大小为16M,分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为 16个扇区(Sector),每个扇区 4K 个字节。通过SPI通信协议即可实现MCU(STM32)和 W25Q128 之间的通信。实现W25Q128的控制需要通过SPI协议发送相应的控制指令,并满足一定的时序。
; H; V9 I: |7 ^5 G* x# n4 ]
) j# s/ y8 Y9 Z0 A, t原理图连接
. ?" Z0 i' K8 \* ?/ w! d2 j0 v
5 H5 k) g& a4 H# s9 W9 R
) p2 K6 u) @0 e! P P
7 }: T# ? j4 I6 M) \- Q) ~: v. X常用指令:
4 R1 L' g8 c2 p( Q' \+ ^8 J9 k8 \! n3 c" h: m9 W
写使能(Write Enable) (06h): T8 f1 n- n1 d0 }* p* q
% G7 D4 t, T, u# S, q# j: [& `
, t6 C o$ r/ U/ w
+ z7 v+ M8 M- P* a0 w! ^4 B
向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。
; p% b* H3 d! e
# A5 t7 \4 E1 n& ]% l# M0 T5 ^扇区擦除指令(Sector Erase) (0x20h)6 r, ^9 b c, E+ D0 i$ F$ ?7 o: i
p. V! Y! V2 M% G3 c0 A
2 h: u% W2 q7 o, b扇区擦除指令,数据写入前必须擦除对应的存储单元,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。% u) J; d8 V# w$ K/ L' E
! Y% g/ ^+ p5 U' s
读命令(Read Data) (03h)
5 Y4 m/ O5 _6 z- k6 _: |+ H H4 }+ ^( |1 Q4 _. R
读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。
' B) T# F# z+ C+ U. p8 B* a/ w0 `5 S" r6 i4 K8 h
* u( M9 L5 P4 d$ \" q/ V }# }
/ U% _3 W- r8 n; r5 y, A
状态读取命令(Read Status Register) j" k: d$ i# Y0 u6 m6 \
1 M$ f1 ]( z3 C3 d- O- j* @7 }/ S
读状态寄存器1(05H),状态寄存器2(35H),状态寄存器3(15H)7 L8 r3 @% k+ m9 Q
写入命令0x05,即可读取状态寄存器的值。- t- r& M+ V$ K) c3 l
/ I: z6 _( h9 w) Q" y2 ^% {
+ S0 h8 g# g2 Y
4 ^2 o8 t( r2 y% w( p, u, U+ E写入命令(Page Program) (02h): n$ W* _ a% a( P& h$ N8 F, j
4 ?( x' {% ~" k
* v+ @, c' M" L) \& Q5 |, l% l: ]# E3 h# j. |/ w
在对W25Q128 FLASH的写入数据的操作中一定要先擦出扇区,在进行写入,否则将会发生数据错误。
0 p& p3 |" V5 n+ z) xW25Q128 FLASH一次性最大写入只有256个字节。
$ a, U3 y! h4 G6 W在进行写操作之前,一定要开启写使能(Write Enable)。- }. g# L6 u' p
当只接收数据时不但能只检测RXNE状态 ,必须同时向发送缓冲区发送数据才能驱动SCK时钟跳变。
- m+ [7 c" K6 m% w0 i8 |$ M# I4 i$ }' S$ f7 U. t
基于CubeMx的讲解3 \2 D; m2 U5 q: [8 n
1设置RCC时钟
; B; A1 P6 M9 I) _) A& s$ l/ h+ i8 T1 E- n1 @
6 h+ }7 c7 V+ X$ ?( n+ J
; Q2 `4 W* f6 k' ^* x
设置高速外部时钟HSE 选择外部时钟源
$ q0 ^2 _8 x9 t3 g
) G/ {1 J6 s2 q6 j4 l0 R2 SPI设置
1 Q b1 R; d2 W t l( o( @. H' Q" h. C/ a) Z! L1 b
SPI2设置为全双工主模式,硬件NSS关闭,如下图:
6 N1 K& N( M# o. d) u! ~" \
0 M" h$ p( R( U, L, y/ `# B+ g
V* E( p9 G* @" @* B ~# [! w6 Y$ l
* v, P2 ?- \3 W9 m6 A" Z3 J
1 S( Z8 C2 J( B
/ P* Y! d; `0 L& k& Z+ Y模式设置:
! B1 y; k2 m4 G k
3 b- r- v9 H$ k k3 l$ R有主机模式全双工/半双工
) ^2 \% J; m7 i9 t$ Y' T) ~从机模式全双工/半双工, a9 W# ?% I4 j. C( `; ~. z
只接收主机模式/只接收从机模式
7 T" B8 L$ [0 r8 g5 a0 F" C只发送主机模式
/ S5 l' Z5 D* H- F因为我们是和W25Q128V芯片闪存芯片进行通信,所以设置为主机全双工" @5 E' o0 t9 i/ r9 T" I0 f
7 m/ Z5 v' U; e不使能硬件NSS
. e: ?0 A- Z; w8 g8 {
. |. `- R5 _3 R- k
& ~" ]+ D1 L) t* y: H' l6 C
7 d+ X1 w; o7 t5 V8 \
STM32有硬件NSS(片选信号),可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替3 B# ^' u$ |; I$ w5 L! ~- W
/ n. s; d# u, w1 Q
其中SIP1的片选NSS : SPI1_NSS(PA4)
7 O7 r- ?: }! ^5 v% w其中SIP2的片选NSS : SPI2_NSS(PB12)/ P' ~% ^7 `: V! p5 W4 e
( |% q( a* b6 i t& `3 Y如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选. [4 m$ a! j$ V* m
9 k* r9 j, f- T8 @, b
NSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。: Q2 B5 W3 p, A, H" F4 M4 M$ |, c7 b
对于NSS的输入,又分为软件输入和硬件输入。
* h$ z& p# s) c: [+ S软件输入:4 _* y/ i( Z; [0 k- H
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
; z! Q& G; S. g6 `: x硬件输入:
. H& Q" x6 {4 m7 A主机接高电平,从机接低电平。
- d X; O- d8 g2 W) c; z6 Z- `! m O4 W1 ~
左键对应的软件片选引脚,选择GPIO_Output(输出模式),然后点击GPIO,设置一下备注。
' v7 i" a) f; g3 e6 Y. M- i% P1 ^3 q
我这里虽然PB12是SPI2的硬件片选NSS,但是我想用软件片选,所以关闭了硬件NSS
4 [. c9 s1 S# |; K/ P- U+ {, G( t: @2 u1 }, s
( C( f- l+ `' Y* t! q* U- V7 {" u; ~2 V
3 ]2 K! c4 Q% j- H: ?
4 @+ x8 }& b/ |
$ q+ ]( ? h2 m- W+ s( m; @
" F6 p" v$ d: l% Q+ C" u/ FSPI配置默认如下:
- Z: {+ X- H! y( X7 J; F1 ~0 c5 _5 [' M& Y( J* n- Z
SPI配置中设置数据长度为8bit,MSB先输出分频为64分频,则波特率为125KBits/s。其他为默认设置。
4 }% O& y( W. r! D; f5 ]0 aMotorla格式,CPOL设置为Low,CPHA设置为第一个边沿。不开启CRC检验,NSS为软件控制。
/ {7 f Y1 _( L6 N+ k- o0 C
0 ?; `- ^" [/ \2 r3 T! L
6 s) R( e3 {2 y& f7 x1 ^: ?
3 X L- C1 Z2 E. w, I
最后记得初始化一下串口,因为需要测试例程,发送数据到上位机。6 s4 O$ s9 ], v+ @) I+ r- |: C
3 A; n& J3 K2 @$ {: {' j5 Y% U
' E* u) U' }& U
% ]5 E z" d8 S3时钟源设置
1 o# Z! X$ @# h; a+ E& J4 X/ ]/ w) e% J$ R
6 N3 U* l6 ^) ?* @我的是 外部晶振为8MHz
1 ^( R2 q# \/ Z% e& E
9 i, ` D5 W6 i' W5 E1选择外部时钟HSE 8MHz
4 ~3 }! u; r: A, a2PLL锁相环倍频9倍# c! J& x) K# K q( ^. a
3系统时钟来源选择为PLL
; F- D$ Z/ L2 T$ Q( z; u* g4设置APB1分频器为 /2) I- r! i' \$ }, U9 ^
5 使能CSS监视时钟
5 b/ N" E# b- D* Y& S
) x# C' w# c6 ^# O( n% \32的时钟树框图 如果不懂的话请看《【STM32】系统时钟RCC详解(超详细,超全面)》# ]- D R1 g7 G; F7 E
* p% O J+ j. z* f: f' y/ X. W& N4项目文件设置, T. y& ^& h* [0 Y1 L
( D- Y+ E# M: f% g. Y5 E0 |( M
* Z& s& K/ m; D1 J. W {) h2 U
3 J. B! }" q) ~. i* b! B# z' R4 ?1 设置项目名称6 i1 N1 U% C! F1 w3 W# x w9 T- q
2 设置存储路径% u1 H2 E# I& d" k0 {
3 选择所用IDE
, w! o# \+ U4 [
/ I9 E3 Q8 V- L) M
3 u# R5 s& {5 F* b# o% x' S. K
- G5 l$ y% O; F7 d& m( R. V5 i f5创建工程文件
* D `6 L( V4 a w6 [9 y. o x& V2 B" d/ k& i$ f
然后点击GENERATE CODE 创建工程- N6 W% U2 j5 q$ B4 i
: l% f3 r8 g' V+ l3 u% I/ ]
配置下载工具
5 L; z }- u9 J( m新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行
: B. I, v, j1 D' b8 p+ w: B3 x
9 j9 a- x4 U8 ~, G
7 H+ Y1 L* Q: N, z( ?
5 v8 E: ^, v% R, I- MSPI函数详解2 k/ v, Z: n, D# Y( I8 X3 j( k
$ P, e2 B/ T7 @# g. B5 X$ F在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。
' `" O9 h& |2 ~
0 H) u; H% |+ V& {: e+ D
& X4 \4 O; J; z/ |1 V% D G
( y p1 E3 i; C- I+ E# S, m! [轮询: 最基本的发送接收函数,就是正常的发送数据和接收数据
- U$ Q* r$ F0 p# }; m中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能
, t2 U% Q2 Z* k* j1 D% tDMA: DMA传输SPI数据
' e7 O- C# ]8 e' e4 ^
% j; ?) E! C8 B% u利用SPI接口发送和接收数据主要调用以下两个函数:- \$ h( i( J! p* B, d. F
- HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
: }; ^/ C, [$ A7 v - HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码 # E& P0 y% r6 J1 P: m6 a+ `& \
SPI发送数据函数:3 P6 E: H6 E* h
- HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
复制代码
1 I v; y3 d2 u4 u$ s. G参数:
- `/ t; ~5 p' I! {+ P( k6 H7 d) o) z9 y4 W7 \) Z- k( [
*hspi: 选择SPI1/2,比如&hspi1,&hspi26 V0 O$ x% R8 v# b6 c$ N
*pData : 需要发送的数据,可以为数组
+ ?2 l( a% @2 s- k# I9 gSize: 发送数据的字节数,1 就是发送一个字节数据9 f% _' |0 P E7 V& \
Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
% E3 x+ O5 ^& L, q8 y$ P) X" V$ O+ { s* J9 k
SPI接收数据函数:7 F/ A+ W* v! @$ r" F
- HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码 9 k: S$ i! Y/ M9 f2 a
参数:- n$ D& b, o( z5 A
*hspi: 选择SPI1/2,比如&hspi1,&hspi27 u" s1 _8 o2 W
*pData : 接收发送过来的数据的数组! \2 @, `, M' L& ^1 H
Size: 接收数据的字节数,1 就是接收一个字节数据
! _, h% J+ Q/ J% o v, E) L0 |Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数
" r5 n$ D( \% t( k5 W6 U) n1 A
4 `" k$ b4 B/ a6 D6 N
, ^1 A% W0 e8 I5 H5 x; d8 ~SPI接收回调函数: e: t, T! w/ ]9 f1 H
- HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);
复制代码
( J5 P) ]$ E" |" Y当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数:- HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
复制代码 $ A& i& s! l: W9 v
用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数+ E3 N q$ v# t3 E+ |( Y$ s7 s W
: ]$ F# k1 ]5 M% n; |* P$ a3 U% l/ T' M片选引脚:
& Y1 w' c" H, y4 n9 P- v/ d1 Q6 I6 J) V- q
因为我们是软件使能片选,定义片选引脚,CS片选低电平为有效使能, CS片选高电平不使能0 j w: y$ L' `6 B# K- |
4 O t+ B: ~9 {( q% Y- E: W
这里用两个宏定义来代替
& J Q1 T, H$ Y( H9 @& |1 N# x9 G# N: @% g" \/ y
在main.h中有宏定义命名,SPI2_CS_Pin 就是PB12- //以W25Q128为例
' X# \1 i6 ~+ U, z$ | - #define SPI_CS_Enable() HAL_GPIO_WritePin(GPIOB, SPI2_CS_Pin, GPIO_PIN_RESET); W, J$ H$ g% |
- #define SPI_CS_Disable() HAL_GPIO_WritePin(GPIOB, SPI2_CS_Pin, GPIO_PIN_SET)
复制代码 # V k0 }9 y4 H, z# C4 n6 O
SPI例程详解 g4 _1 K" r: N8 D; F/ N
因为不同的flash芯片通信协议以及方式都是不同的,所以这里介绍下具体的SPI的发送和接收应该怎么写,具体的请看芯片手册修改下即可,这里提供下W25QXX的驱动文件,以及测试例程,测试是正常没问题6 Z( i d `$ @6 F# l
+ M. g8 q. X3 N1 `) T挑几个函数讲解一下:
% ]! C' E# c. @6 O3 m; A! ]) S X8 t: Z! \" h D& f
在w25Qxx.h钟可以修改CS片选引脚,W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平- #define W25Qx_Enable() HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_RESET)
/ h; p4 q7 I/ c* r9 V - #define W25Qx_Disable() HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_SET)
复制代码
* V7 ] ^( d) X4 K* K) rw25Qxx复位函数:
/ w: p) |# n. ?% T' ]5 Y) n' z4 W9 R( ]
函数开始先将要发送的数据(命令(0x66)和地址(0x99))存储在cmd数组中,
5 Q( _; v2 I5 B) g7 \$ A拉低片选信号,开始SPI通信
7 s" G- E' @5 i8 r然后后通过HAL_SPI_Transmit()函数发送出去* c% |8 R) A {/ ?
拉高片选信号,关闭SPI通信 m/ B$ }4 Z" W( l6 j4 f6 `
2 K% E6 l2 n8 _
W25Qx_TIMEOUT_VALUE是最大超时时间,在w25Qxx.h中定义为1000,单位为us% S5 Z" Z* u1 ?. ^ U1 P
: x# e, A2 l* C
* }6 M% c* |& o% h- s. Z- /**% p' D1 p/ o6 u9 ?
- * @brief This function reset the W25Qx.
8 \7 j8 q3 F# j# n* b5 F: ?# @ - * @retval None
$ l/ E% }. _8 z. Y- [- L! S3 P - */
5 d; Y& J: @6 @* S* ? - static void BSP_W25Qx_Reset(void)6 l2 \ I% C. J' Q, d" h# A6 i
- {
. ]' X" F9 q( \; } - uint8_t cmd[2] = {RESET_ENABLE_CMD,RESET_MEMORY_CMD};: k4 {& {/ U& f9 Z% h
-
, t& @7 t5 _( K) _: F - W25Qx_Enable();# m, s% K2 X" e o
- /* Send the reset command */
5 C5 D: n. ]# k. ] - HAL_SPI_Transmit(&hspi2, cmd, 2, W25Qx_TIMEOUT_VALUE); 3 G3 t' w8 f" B) ~, h% y/ R( E3 c
- W25Qx_Disable();
# Z* X# a! j, m5 R- B
& q1 ]0 s3 t1 ^* z, E- }
复制代码
" \% ?3 j7 `; N2 H8 e& I# W7 m$ PW25QXX读函数:
3 y1 o, b, ~ v, j2 c6 S三个参数:
2 Y5 A9 k/ D' i0 B- [4 h TpData 存放读取到的数据的数组
' |/ S2 x7 A: h! W9 W* j5 l0 zReadAddr 读取数据的地址+ N' o" G- r( g8 W) C
Size 读取数据的大小4 ~: ]/ @ l7 L8 o8 }+ Q. X
…- `0 ]( H; R+ t9 W: ^
0 V6 c9 t5 x) E
函数开始先将要发送的数据(命令和地址)存储在cmd数组中,
# M6 p, e1 D. [% x/ B8 F+ E拉低片选信号,开始SPI通信
8 f+ c# P8 n! T$ T& a8 {然后后通过HAL_SPI_Transmit()函数发送出去,首先发送写命令(0X03),上方有讲解,然后发送三个字节(24 Bit)的地址
2 p1 n4 {" k! I) C# C4 h7 A% ~* G h接着通过HAL_SPI_Receive()接收读取的数据。6 i3 h n4 ]5 t+ Y" j3 |5 g
拉高片选信号,关闭SPI通信
3 D) I8 D, P8 T
4 I+ v3 F: {* m9 f0 H& h9 f$ E
, m) z5 w: k* r
+ x0 I( [: m& c6 l) R- uint8_t BSP_W25Qx_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)+ f) j+ f3 u* @) A
- { C. o2 e! E7 P! E: H a
- uint8_t cmd[4];, k; o3 U, k9 d4 `; z" d
- 0 Q* X% t* m* @, t% F
- /* Configure the command */
2 {* B+ t S; ~3 s - cmd[0] = READ_CMD;1 b# |) F! f* R5 r5 `
- cmd[1] = (uint8_t)(ReadAddr >> 16);
0 q4 p( T3 K1 g3 [; B; G: c - cmd[2] = (uint8_t)(ReadAddr >> 8);) O2 a' S6 T7 |* O# M" H/ W
- cmd[3] = (uint8_t)(ReadAddr);
; i! X7 U9 L0 I3 m7 X2 v R/ F - : z. |. V. K4 I
- W25Qx_Enable();" n. y& X: P. Z$ D; m
- /* Send the read ID command */9 T( ?/ Z( ]+ c; m. H4 `1 c
- HAL_SPI_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE);
L' ?- T4 B- A: j5 b' e- f - /* Reception of the data */' B' \4 d, ?! ~, Q2 ~
- if (HAL_SPI_Receive(&hspi2, pData,Size,W25Qx_TIMEOUT_VALUE) != HAL_OK), J6 S3 p( @9 g2 }
- {
# s4 M* X' `; `6 _4 e; d1 Z - return W25Qx_ERROR;7 _+ U% H5 b1 Y! o a0 t
- }3 k0 m$ S: M- g) y# d1 J1 A( N. T$ [
- W25Qx_Disable();
- Q9 A. p [) g) x: O - return W25Qx_OK;9 n$ A n' j$ u" V+ O- | @
- }
. H; M" r) |# _) o$ g' A
复制代码 3 B; l3 a% w7 Q
写使能(Write Enable) (06h)" r8 [! q. F* L5 f' j, G9 T! L5 r2 r
{# a, E5 l! e* m) P
8 K: k) ^2 Y; Z0 h0 K1 x
5 m8 q2 }/ f% t" X" w) V8 p向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。1 D/ ^# M' Y, e' G$ @
& T: I: d) s5 w8 v; B& B- uint8_t BSP_W25Qx_WriteEnable(void)- \( Y& N6 X$ J$ q/ n
- {
5 c3 E1 `8 [" [$ Y. R6 F - uint8_t cmd[] = {WRITE_ENABLE_CMD}; C6 f7 b- S9 S' o
- uint32_t tickstart = HAL_GetTick();
5 |- f# {. L8 ]: b
/ z1 g: V* N) S. H3 y/ ^- /*Select the FLASH: Chip Select low */( O6 J" t6 W5 @8 n# e1 T
- W25Qx_Enable();3 i- U, i, R$ ?9 f
- /* Send the read ID command */
% V* S6 I9 ?7 N% y1 b; J m* w* n - HAL_SPI_Transmit(&hspi2, cmd, 1, W25Qx_TIMEOUT_VALUE);
! Y% b. U* _- _+ M$ l, ^5 J& p) } - /*Deselect the FLASH: Chip Select high */, I1 q* x+ Q1 b8 [6 N$ ?$ h
- W25Qx_Disable();* k5 h: j5 A$ l. t6 R' ^/ L, E% N
-
2 Z1 N4 b$ n1 Z4 s& p* t+ w - /* Wait the end of Flash writing */
$ _) F3 A% l" r X) g$ c' }) e - while(BSP_W25Qx_GetStatus() == W25Qx_BUSY);7 n( P) l9 Q7 O- R) p! u' _
- {
9 J/ ^, j( W% Q' G A' C3 ~$ l) u - /* Check for the Timeout */8 c- `; l; ^; R' o. T; i" ]8 s
- if((HAL_GetTick() - tickstart) > W25Qx_TIMEOUT_VALUE)8 C3 |$ p+ V( e! q& c+ Z
- {
$ l9 k* L7 ]9 k; b - return W25Qx_TIMEOUT;- }, ?& g6 m2 a j8 j4 ~
- }8 M- o) X9 C8 M% C, E, N3 X! L4 P' H
- }6 c+ c" {6 @. X* I+ W
- , o; |% L" M' ^- i7 ~/ [" }7 d1 W
- return W25Qx_OK;) s1 v- j# M6 E7 B4 I( L1 Z
- }
复制代码
2 \! x2 P, b% A1 |/ q9 W. X扇区擦除函数:8 D \ C% c! S7 T, Q5 m/ t
. b7 `$ f. ]! V& m7 G扇区擦除指令(Sector Erase) (0x20h)& m3 H+ \7 |) u& e& \
6 `* Z n: {" w" O6 E' w5 \- U
4 H0 c3 B$ W. a" }# ~3 K+ s
V$ j3 U6 d9 T, P扇区擦除指令,数据写入前必须擦除对应的存储单元,并且使能写操作,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。判断flash是否为忙状态,如果不为忙则擦除操作完成。7 c! q9 o& Q6 Y( k/ I8 e$ G& P
. G% f- @' j+ k7 J/ ^% b* q
- uint8_t BSP_W25Qx_Erase_Block(uint32_t Address)" Z6 }8 w1 z9 s; h8 ~& [5 j# g
- {/ P% o. H: `2 p, V: H, C5 F' m3 M1 m
- uint8_t cmd[4];
2 }2 t+ B1 w& ?6 I5 P - uint32_t tickstart = HAL_GetTick();
9 ~7 A" Q2 }0 A- w2 i- O - cmd[0] = SECTOR_ERASE_CMD;
0 ?" w9 \, ^! |( B# v- ~ - cmd[1] = (uint8_t)(Address >> 16);
% k5 e3 {- e. Y& n4 h, H - cmd[2] = (uint8_t)(Address >> 8);
# A E% w- o9 n. ?8 s - cmd[3] = (uint8_t)(Address);
0 L) }9 l7 U9 _- c -
* u! E1 b1 c0 v9 j& m5 z% C' K' q - /* Enable write operations */
9 e1 `$ @: a' O6 U) f" j! O& S9 | - BSP_W25Qx_WriteEnable();# m1 u5 }" W& c0 {3 M, [
-
, N: u, B9 F+ v1 ~9 \ - /*Select the FLASH: Chip Select low */* R& ~0 f! Y1 ~3 l8 _% e9 `
- W25Qx_Enable();
$ {9 m& B4 S# S% ?' O- J5 ?8 d - /* Send the read ID command *// I8 K! [ }* a+ o5 f% J4 z
- HAL_SPI_Transmit(&hspi2, cmd, 4, W25Qx_TIMEOUT_VALUE); $ O# p. ? E- [2 q
- /*Deselect the FLASH: Chip Select high */
1 R5 ]6 ~! Y6 B2 L - W25Qx_Disable();
8 J6 k: ^+ q9 i( W - * J- C. {4 `( O6 D$ I3 X
- /* Wait the end of Flash writing */
% N& D9 B- S9 ^+ q1 q l - while(BSP_W25Qx_GetStatus() == W25Qx_BUSY);
) e4 k! `' n5 I6 Z - {2 j9 B5 n0 x' i5 ~4 @/ u; V
- /* Check for the Timeout */4 d, p0 R; f4 A
- if((HAL_GetTick() - tickstart) > W25Q128FV_SECTOR_ERASE_MAX_TIME)! k) V4 B2 w; N( ?; G
- { # q. q' M' E6 V9 e" J- L
- return W25Qx_TIMEOUT;
; R4 J& b" h @- [ - }; R. U! k+ _: I- e* W1 b
- }& ?9 a5 f$ _5 @4 ?3 K/ L- O8 r
- return W25Qx_OK;" c( i7 p* g& j# A- G" O( `7 |' h
- }
复制代码
: Z5 V. W7 N, X# c例程测试: h5 z3 V4 Q7 T, ` G& W& [ t1 t4 e
重新定义printf函数
* X( H1 V8 d& Z% j0 C. x7 C" M在 stm32f1xx_hal.c中包含#include <stdio.h>
7 c0 X" a0 R8 {$ z0 M
/ Z/ Q* l2 ^, [" g- #include "stm32f4xx_hal.h"8 {. h3 O. U }. r {+ }2 Y
- #include <stdio.h>
3 E) w. f. i0 |! _0 \6 ^& g; D m7 v - extern UART_HandleTypeDef huart1; //声明串口
复制代码
7 q4 d* e, n( E. H8 ` ]; E在 stm32f1xx_hal.c 中重写fget和fput函数
: h A9 R* N/ p, P' v+ T- ?, v5 [
7 P. w+ X4 X j0 `- g4 n4 M' _& W- `- /**0 J3 ]" t5 g0 o- Z
- * 函数功能: 重定向c库函数printf到DEBUG_USARTx3 H8 q& K9 Z& c9 B! t+ {1 g1 d
- * 输入参数: 无' ?" K1 v" t. h( \0 t6 C
- * 返 回 值: 无
* O2 l+ L& k. C2 f - * 说 明:无
! [& d, f. W% k - */: T r. _5 Y9 ~3 _ T# M
- int fputc(int ch, FILE *f)
3 W% i+ z$ X5 F p2 ]- e! z+ o - {
3 @" a+ ^0 f" x) y# I - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);" c0 ` m, x2 P
- return ch;5 |, `& E% |- m- {1 o
- }9 V, n. K! \# d; [7 t6 x$ \4 k
$ `5 h' h; u- A- /**
' X- t9 k7 \+ q5 M/ L% C! e - * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx" l1 w9 Q5 g. K
- * 输入参数: 无% S( e2 u/ ]3 X
- * 返 回 值: 无
' u' p- {, T( q) n3 S* ]9 H - * 说 明:无
+ |+ j2 R3 X& ]. ?8 i% B - */& k" _1 K% r6 Y; C$ F
- int fgetc(FILE *f)) D Z' t+ H8 k9 C/ M" y5 z( C% t, L
- {' R* l; r. M7 I) t# f8 E! w! |& W
- uint8_t ch = 0;
1 l) O' ~3 N: n1 v" f - HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
! { N3 ~1 B. M0 J# G - return ch;
1 a. \/ N- a, s: S) @ W - }
复制代码
) @, ^$ _. V4 t7 Rmain.c
" E- J8 E0 M7 K% y3 }在main.c里添加以下代码:2 M% u4 a) M4 t1 X7 l) B
|4 V/ A9 ^# _2 P4 x! W: h- #include <string.h>
, p# P# i! F% _0 W - #include "W25QXX.h"
% L& q, S0 o7 }9 D: c" N, j - 5 h# j* a0 ^6 z% `
- & i- \, S: T5 l0 w2 _
- ; @* m$ Q, f) C% i V
- uint8_t wData[0x100];
1 S1 J) D8 m8 L3 i# `) B, @ - uint8_t rData[0x100];
P& X. V3 c! U& B: m$ O+ R( b - uint32_t i;
) l7 ?4 Y# d6 a) ^ - uint8_t ID[2];
8 q# C5 j$ _% Z8 d' V) a0 S
复制代码- printf("\r\n SPI-W25Qxxx Example \r\n\r\n");
0 v+ y8 f. {7 J
# a {9 e! P" @5 I6 F" W( c: Y- /*##-1- Read the device ID ########################*/
X7 O( h: ]6 K h9 V9 g - - \, Q- s4 X9 e
- BSP_W25Qx_Init();
( a: Y3 W, a: n6 L% _% ` - BSP_W25Qx_Read_ID(ID);1 `8 D0 c! D& u5 _, B, C2 ~, h
% N3 t- a. |, F" ~- printf(" W25Qxxx ID is : 0x%02X 0x%02X \r\n\r\n",ID[0],ID[1]);
. t7 B! Y+ _+ r' ^# {) a7 F1 r* S - /*##-2- Erase Block ##################################*/
$ }3 p9 ]7 [. S( ]* d - if(BSP_W25Qx_Erase_Block(0) == W25Qx_OK)) M: g. f" a. D! r& M5 H" ^
- printf(" SPI Erase Block ok\r\n");
1 ?0 ~4 [0 @7 d# K- D$ O - else$ G' H5 |) j( D( ]9 R; G
- Error_Handler();
! y. o! [) L) U3 G0 C' N, k& o - /*##-3- Written to the flash ########################*/
/ H( Z- z3 d- h# P - /* fill buffer */
+ A0 d9 S( r! p* G+ c2 H% e3 i - for(i =0;i<0x100;i ++)
. T0 b5 O9 Y. F - {
( i: S* _/ @, J) J5 U3 h6 j' [ - wData<i> = i;
6 f: j; R0 S& H4 `' H - rData<i> = 0;+ M% K4 E+ [" R
- }
# @8 I" O; _7 }2 h e - if(BSP_W25Qx_Write(wData,0x00,0x100)== W25Qx_OK)
# C q$ j# D; t, R - printf(" SPI Write ok\r\n");
' f% N5 n! u) N& ` - else, h% ^: J, p* p" h4 N
- Error_Handler();
1 N3 R: v$ J! C - /*##-4- Read the flash ########################*/# G0 I/ M' f+ n& L# P$ w0 v
- & ?8 \" Y6 y+ J4 K8 P/ U
- if(BSP_W25Qx_Read(rData,0x00,0x100)== W25Qx_OK)4 @' ]" S; m- {# k
- printf(" SPI Read ok\r\n\r\n");
9 _; r$ T, `9 Y% B- t& U - else2 A" y0 k8 Z y7 a( I
- Error_Handler();
' P8 r& b+ ?7 Q - printf("SPI Read Data : \r\n");8 o1 _$ i6 D0 h) X& N+ t! t% X2 F
- for(i =0;i<0x100;i++)
* ~# T, L: }% j/ T - printf("0x%02X ",rData<i>);
, z7 \1 g) i$ ~! `" d9 v - printf("\r\n\r\n");
$ w/ u0 c) Y3 [7 R/ i - /*##-5- check date ########################*/
5 @! O J2 a7 Y& ^( [0 J4 _ - if(memcmp(wData,rData,0x100) == 0 )
! n& P# j9 g* h# V5 A6 { - printf(" W25Q128FV SPI Test OK\r\n");
0 k" c( ^" L. \, \ - else
9 }( b, V9 r6 _ - printf(" W25Q128FV SPI Test False\r\n");
, k5 S- Y0 c% S9 `0 V - </i></i></i>
复制代码
2 b6 p( `- w4 G; g8 \STM32F103测试正常:) C6 x) d- O* a7 _0 N: e @: |
( C9 x) {/ x% z
! x$ h( p0 q! {7 K
1 j' S: T; T8 m* Q. s, F8 K
|