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