STMCubeMx入门教程(6) SPI 读写FLAH的应用 导语“本教程将使用CubeMX初始化SPI,使用SPI对W25Q64 FLASH进行读写操作,通过HAL库的读写应用来数据FLASH的操作细节。” 01 ------------ 系统要求----------------- •硬件 野火指南者开发板 •软件 CubeMx & MDK & 串口调试助手 •原理图 根据原理图,我们看到FLASH连接在SPI1上,我们不适用SPI1自带片选,使用PC0开进行软件片选。 02 ------第二节 CubeMx配置----------------- (1) 我们还是使用前面的USART串口(串口的配置没有变化)项目,在此基础上进行SPI1 的配置: 我们从配置的信息上看,使用了SPI1,主从全双工模式,8位数据传输,高字节在前,模式3,CRC不校验,软件控制。SPI的模式配置看下图,采样时刻是偶数边沿所以CLOCK Phase =2: (2) 完成片选信号的GPIO配置 完成上述配置后点击代码生成。 03 ------------第三节MDK 配置-------------- 将CubeMx生成的代码使用MDK打开进行应用代码编写: 在spi.h 中进行FLASH操作的指令宏定义: - //指令表
- T- F6 Z+ \/ R - #define W25X_WriteEnable 0x06
8 s' J/ |) e5 C J" L - #define W25X_WriteDisable 0x04 * D/ _$ N0 r+ C5 I) P9 c! k! o
- #define W25X_ReadStatusReg 0x05
( I: N# T, z2 N- E - #define W25X_WriteStatusReg 0x01
1 o! O: a* p8 `' e' b - #define W25X_ReadData 0x03 7 I! x( U6 ~3 x) J) H1 e
- #define W25X_FastReadData 0x0B
* s8 j: Z3 w5 @1 s9 B - #define W25X_FastReadDual 0x3B 3 Z t: Z, z/ R
- #define W25X_PageProgram 0x02 8 Q6 v4 B1 \& J s5 X9 m
- #define W25X_BlockErase 0xD8
/ P+ c# g# t3 J- C - #define W25X_SectorErase 0x20 : p5 Y0 W0 B# O9 t' G
- #define W25X_ChipErase 0xC7
% O2 g1 l0 R$ n. Q - #define W25X_PowerDown 0xB9 " i- m% n5 L! ]
- #define W25X_ReleasePowerDown 0xAB 7 E8 e' o% P. h- t/ C3 A
- #define W25X_DeviceID xAB
4 T% \' m8 @; l( e# N6 u4 w, t - #define W25X_ManufactDeviceID 0x90 : a. w9 J. k$ }
- #define W25X_JedecDeviceID 0x9F
7 E7 ~$ Q8 }3 E7 W1 B" C - // others defined
; _$ P6 A) ]! d& X" K3 W) O. \. z - #define sFLASH_ID 0XEF40178 o* l9 _* S3 W8 m
- #define Dummy_Byte 0XFF8 Z$ G o3 L3 z$ [& d/ y% `% ?
- #define SPI_FLASH_PageSize 256
/ h D% ?* S8 W Y. ~! v7 e - #define SPI_FLASH_PerWritePageSize 256
9 ]/ E: i7 G0 ?7 ?0 q9 F - #define SPI1_TIME_OUT
复制代码 2 N6 j: O( T; ?) ~! Q
并且申明应用的操作函数: - void SPI_FLASH_SectorErase(uint32_t SectorAddr);
% E! U7 T# ?1 N" x# D - uint32_t SPI_Flash_ReadID(void);3 w3 H+ f' r! Q O) c6 m6 m$ Q+ R/ i
- void SPI_Flash_Erase_Chip(void);9 b, Y* h, ?$ b* V/ I" f
- void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer);. g% n' A' s: j2 W3 X H6 E, d- _
- void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
, M$ e5 F1 i P1 ~ - void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
: k& e3 v9 S; i5 V7 V
复制代码
' j8 M# s. I$ O下面我们在spi.c 中实现读写FLASH的相关函数: (1) 对片选的信号引脚进行宏定义操作: - #define SPI_FLASH_CS_H() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET) 8 j8 o' V# U' J9 g
- #define SPI_FLASH_CS_L() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)
复制代码 ! w% P, j$ M; \) p( | N
根据原理图使用的PC0引脚 (2)SPI 读写个字节函数 - /**function: SPI 读一个数据**/
: q. c* f O+ j. f. T. Y - uint8_t SPI1_ReadByte(void)5 l5 b# a6 c4 U9 {& a: D) V
- { ( C N) W9 p6 m3 \# _
- uint8_t RxData;
r# _9 N+ `. `/ } - % x" h) V1 t- Z; H! T
- HAL_SPI_Receive(&hspi1, &RxData, 1, SPI1_TIME_OUT);" N- @5 U; a6 J7 O
- 8 f: f( W5 t. K5 D# P# E' G. b
- return RxData; //返回通过SPIx接收的数据 ) i& I i* u% i0 v
- }
复制代码
! Z4 Q- x7 [8 k2 F0 o; w
; ^' C- j$ a/ c( }5 P( D我们使用了HAL封装的HALSPIReceive(&hspi1,&RxData, 1, SPI1TIMEOUT)函数来实现读一个字节。 (3)写一个字节 - /**function: SPI 写一个数据**/
6 g) v. Z" o3 t; a Q - void SPI1_WriteByte(uint8_t TxData)! ~7 ^: u) w% D9 f& G
- { * m1 f* S1 X+ c9 j
- HAL_SPI_Transmit(&hspi1, &TxData, 1, SPI1_TIME_OUT); //通过外设SPIx发送一个数据 + P" o) n! W8 U# K/ r
- }
复制代码 (4)FLASH的写使能和非使能 - /**function: SPI_FLASH写使能,将WEL置位**/ n; T9 Z4 w6 z# B n6 i1 N
- void SPI_FLASH_Write_Enable(void) & ?% S# A* D {5 U7 f
- {
# i: Z% U1 e6 _: M" c/ ^+ ] - SPI_FLASH_CS_L(); //使能器件 2 ^' I/ n4 `- R5 B
- SPI1_WriteByte(W25X_WriteEnable); //发送写使能 7 R& `% Y, G# ]9 Y( p- w/ J5 n& V
- SPI_FLASH_CS_H(); //取消片选 / |) T. {7 c d% ^1 u
- }
& A1 F6 R3 `6 A! Z1 ]$ X
' l. p4 [" V$ K. w; l" X7 _( P- /**function: SPI_FLASH写禁止,将WEL清零**/
) ` u* B/ x7 m5 u - void SPI_FLASH_Write_Disable(void)
& w! y# M4 f) Z" x6 d' e* y - {
* b7 T: e- R# ^6 N; p - SPI_FLASH_CS_L(); //使能器件
; f6 i- h4 m" z( t! O4 T - SPI1_WriteByte(W25X_WriteDisable); //发送写禁止指令
% x+ N. {3 m% H! {1 y4 P/ u - SPI_FLASH_CS_H(); //取消片选
/ ^0 a( B8 o, b- @' m( g( T - }
复制代码 & e) a {. p, \( y! V" x2 P/ ]
(5) 读取SPI_FLASH的状态寄存器,通过这个函数我们可以判断FLASH的状态,一般用到的忙和空闲两种状态,这儿也实现了等待空闲函数。 - /**
) |9 B; G. `% M' t - function: 读取SPI_FLASH的状态寄存器
_/ z; L+ n0 v3 u3 O8 P& s7 u% B - **/
) j& h! X% M. l- e5 J" C! O - //' [+ d% N$ f7 ?- }3 P* k
- //BIT7 6 5 4 3 2 1 0* ]* A- _$ P5 c1 D5 \
- //SPR RV TB BP2 BP1 BP0 WEL BUSY
: @) i1 [& L* w$ {6 v - //SPR:默认0,状态寄存器保护位,配合WP使用
+ a0 ^$ q0 Y5 L K - //TB,BP2,BP1,BP0:FLASH区域写保护设置" F) ~7 q; s$ h( \& M
- //WEL:写使能锁定
0 I3 v# e3 L6 K7 q' e - //BUSY:忙标记位(1,忙;0,空闲)0 j. P9 a# z& ~! R
- //默认:0x001 H0 n& T& y. i
- uint8_t SPI_Flash_ReadSR(void) ; l* g# o) I( E
- {
6 a5 W# `( j; W7 D - uint8_t byte=0; " S, Y3 p: ^; _; a' |: `9 w) l
- SPI_FLASH_CS_L(); //使能器件 5 w! }0 i6 V. t0 [( ~6 S7 d
- SPI1_WriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令
H3 B' J T9 d0 O" L - byte = SPI1_ReadByte(); //读取一个字节
6 y- c: E! n+ D9 q! [* o) I - SPI_FLASH_CS_H(); //取消片选
h; y' g$ L4 [/ Z4 v - return byte;
& r' F& x) u/ n% S6 G - } 1 G! ]% Y4 t$ z: z- Y
- + }; O# C; r, y" t, G8 t3 S( D
- /**
0 }! x1 C3 u9 {. f, s; d( N+ Q - function: 等待空闲 I; Q5 T2 x9 ?+ B0 L; J6 }
- **/
5 D6 X: M2 \8 a5 }! j8 t - void SPI_Flash_Wait_Busy(void) ' n/ Y' R( }% r; M7 ^' J0 g
- {
' _' {& J' W4 S( q8 |9 }8 e - while ((SPI_Flash_ReadSR()&0x01)==0x01); // 等待BUSY位清空; g$ z+ A* \$ ?9 x8 i* W
- }
复制代码 (6)读取芯片ID W25Q64的ID函数,这个函数也是一般判断是否FLASH正常的方式。查看手册可以知道ID是0XEF4017 - /**
. N% F2 j- g1 ?1 w5 m - function: 读取芯片ID W25Q64的ID
4 ~ l" l; g" S) C+ e3 @3 ~, u" a+ H - **/" T3 J& C: m" ~7 O) o" k
- uint32_t SPI_Flash_ReadID(void)! r5 w ^7 V: A- Y M$ d
- {
5 Q* }4 X/ P) q% [, c9 `$ i - uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0; - h! ]3 l5 u; B8 n7 T" n
- SPI_FLASH_Write_Enable();
% t6 X. t l& `) b6 W - SPI_FLASH_CS_L();
, _0 G, k M! I$ F - SPI1_WriteByte(W25X_JedecDeviceID);//发送读取ID命令 6 G$ z6 r+ o/ A0 k
- Temp0 = SPI1_ReadByte(); : Z% S, z4 Y) M/ y' [& M) v. m! I
- Temp1 = SPI1_ReadByte(); : P5 X4 M" y3 s! |: p+ H
- Temp2 = SPI1_ReadByte();
" [: s! ]3 _% z - /*把数据组合起来,作为函数的返回值*/3 ^( n$ S# r) u5 @
- Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;6 d3 ^. N8 i8 q' e& J7 ^* R* [
- SPI_FLASH_CS_H();
* z/ G D6 H) `+ P m- C - return Temp;
7 v( M K7 z4 G/ q y9 c - }
复制代码 8 P" [: R' J k$ x) E2 [: _" I
(7) flash 扇区擦除函数,FLASH和EEPROM不同,在写之前一定要进行擦除,否则不能正常写入。这儿实现了扇区擦除和正片擦除两个函数: - /**- @4 V' s% V. v( K4 s, i7 D' |+ f
- function: 擦除整个芯片
1 s( {& f4 @9 M$ d - **/+ G5 K- c9 {8 q6 ~
- //整片擦除时间:3 q5 P1 j( c8 n" _" J) g% ?; y
- //W25X16:25s
a j( }7 N( v2 J4 Q# s' \4 p - //W25X32:40s
' ?( [' Y, l; N% v9 } - //W25X64:40s
+ E. q1 Z* e) c: D A! s - //等待时间超长.../ O) e9 }6 v7 @ ?( e& ^ I
- void SPI_Flash_Erase_Chip(void) % @( l+ Y7 ^7 ?3 h3 Y. W
- {
1 ]" ~4 Z% r( r' j2 U - SPI_FLASH_Write_Enable(); //SET WEL # ]& M; V) v, y& d: r# K5 B9 V
- SPI_Flash_Wait_Busy(); 7 c$ `' b0 A# l* a
- SPI_FLASH_CS_L(); //使能器件 # T( q0 \) C$ u, V& W6 j1 O) @
- SPI1_WriteByte(W25X_ChipErase); //发送片擦除命令 ( _" z/ ], N3 ]5 }. O
- SPI_FLASH_CS_H(); //取消片选
: ]/ F7 D; s# X+ n - SPI_Flash_Wait_Busy(); //等待芯片擦除结束4 m; B, P9 ~9 r9 q
- } % q: z: F, t/ K1 {; L8 q$ I$ Q5 H
- /*** [/ ~: h$ j! a- @) z
- function: SPI_FLASH_SectorErase4 ?$ `. r, ]# Y8 K( r3 j& D i
- annotation:Flash的一个扇区是4K,所以输入的地址要和4K对其。
, ~" a+ V; v$ ^( f) R, H - **/
- _: |5 N4 p8 ]& L5 \1 a% \* Z/ f0 @2 Y. u - void SPI_FLASH_SectorErase(uint32_t SectorAddr)8 W0 k* b1 ~! r8 m& Z
- {& H; ]* _6 D% \! v( V
- SPI_FLASH_Write_Enable();
+ E& P6 ^# A4 s6 {; E0 @. G! A - SPI_Flash_Wait_Busy();
7 R2 N$ [' @4 u/ y - SPI_FLASH_CS_L();
2 U; Y" [7 I3 ] - SPI1_WriteByte(W25X_SectorErase);
( L: u+ \- Y- D p - SPI1_WriteByte((SectorAddr & 0xFF0000) >> 16);
) i; T% p" ^" g6 w# P5 X9 H. @ - SPI1_WriteByte((SectorAddr & 0xFF00) >> 8);
, e; S& s7 e; L0 ^; t) G; m" D - SPI1_WriteByte(SectorAddr & 0xFF);* {; {$ R1 i3 q$ M
- SPI_FLASH_CS_H();
: y8 [6 S4 ^" v7 `! M2 C - SPI_Flash_Wait_Busy();
. T$ W2 C9 t. v" @3 C/ q; y$ f5 F4 h' U3 Z - }
复制代码 (8) 读取FLASH函数,FLASH的读操作步骤很简单 - /**
' a5 A- ~% R- c9 r: ] - function: 读取SPI FLASH # _: v! l1 ?# Q% ~1 Y
- **/6 H/ y2 f( x. M; g# a1 z0 ?
- //在指定地址开始读取指定长度的数据
# Z4 B$ B3 Z9 } r# V - //pBuffer:数据存储区
& Q8 E- z7 w- f6 n3 A! ^ - //ReadAddr:开始读取的地址(24bit)
: j1 h; Q$ S: @9 `: k - //NumByteToRead:要读取的字节数(最大65535)
P5 d2 E& j! ] - void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer)
# Y3 `7 c. W" u5 Z# F8 M: D - {
% z; t+ ~1 H% M$ K5 j7 U' Q) T - uint16_t i;
- Q2 ^- Q6 K: @% s - SPI_FLASH_CS_L(); //使能器件
/ F; N& h4 r B1 p5 ^ - SPI1_WriteByte(W25X_ReadData); //发送读取命令 9 q& \2 v) {0 `' _% C! j. N0 W
- SPI1_WriteByte((uint8_t)((ReadAddr)>>16)); //发送24bit地址 , Q' C, @1 ^2 V! n) J; X3 k$ I
- SPI1_WriteByte((uint8_t)((ReadAddr)>>8));" O2 z( S0 L. ?/ B+ J
- SPI1_WriteByte((uint8_t)ReadAddr);; n: A; N8 o/ b H
- for(i=0;i
. x1 q3 c. h; [* q5 o6 g - { , y" w; q ]7 L. N1 k$ w
- pBuffer[i] = SPI1_ReadByte(); //循环读数 2 F) L& e( i" T7 }/ k8 A1 S `% O
- }
* J, f" J" r# X8 \5 k1 m/ ` - SPI_FLASH_CS_H(); //取消片选
0 z4 O2 R0 Y- M$ \$ I6 x4 v* b - }
复制代码 + \# J! L- L" k
(9) flash 的按页写入数据,FLASH的数据的写入不是随机可以任意写入,按页写入,一页最大的数据是256个字节。 - /**2 ^/ v3 {) K0 {
- function: SPI在一页(0~65535)内写入少于256个字节的数据 9 d8 i, \& Y y
- annotation:一页最大256个字节
2 P$ Q N2 \, w7 r, p: C. k& z$ @ - **/0 q1 m. X" P. q
- //在指定地址开始写入最大256字节的数据
9 ^3 K0 W; c1 b! h' C: ~ - //pBuffer:数据存储区! B/ g! w: `9 s# F0 Q
- //WriteAddr:开始写入的地址(24bit)6 P, g( @6 m( H( \2 f( x3 R
- //NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!$ ^$ [6 e" r5 I, I+ w/ s
- void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
3 s; M. K4 [# ^7 h* e" P+ G! o! t - {
$ A$ S. V% C: U. u' D9 K - uint16_t i; @& [2 S0 W$ \; `5 @, X1 c
- SPI_FLASH_Write_Enable(); //SET WEL7 k% S- Z" C8 D" n: W& U& A
- SPI_FLASH_CS_L(); //使能器件1 D5 D) B1 o. F* G
- SPI1_WriteByte(W25X_PageProgram); //发送写页命令+ T1 ]. Z' m+ |+ h: M
- SPI1_WriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址
) ]: M/ R/ Y; d$ W# W1 m6 x" g - SPI1_WriteByte((uint8_t)((WriteAddr)>>8));1 Y# k9 w1 L4 N2 z& y/ W
- SPI1_WriteByte((uint8_t)WriteAddr);
3 b; H0 ]. ^3 a" V7 f/ Y* ^7 k9 W8 b - for(i=0;i<NumByteToWrite;i++) SPI1_WriteByte(pBuffer[i]); //循环写数. ?$ n+ d8 e* ]1 i; M& ^
- SPI_FLASH_CS_H(); //取消片选
; P2 P% P" l. D( K. i# |( g - SPI_Flash_Wait_Busy(); //等待写入结束
# ?; _" |& O0 q0 v' |* w - }
复制代码
5 @' g) \: T) W9 L6 y4 r(10)flash 的随机的多字节写入,通过按页写入实现的函数来实现 - /**
; }5 E4 z9 V8 P. v N - @function 不定量的写入数据,先确保写入前擦出扇区! o2 u$ W+ w2 {& K
- @annotation
" C2 _9 {. [8 H: [6 Y: |/ l0 n - @param* d5 {9 r- t' Q# {7 T c
- @retval
, a* i1 \4 ]; L4 ?/ q - **/
. P- v8 S8 a) e6 \( C+ A( x - void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
1 Q* }, C3 Y! g# D% a. t; \ - {0 d( N( ~ D' X" x
- uint8_t NumOfPage =0, NumOfSingle=0, Addr =0, count =0, temp =0;
" g" ^0 t+ t ^( H& p2 ~. m - /*计算Addr,写入的地址是否和PageSize对齐*/5 o. a9 X! U& e! H& W" L% B
- Addr = WriteAddr % SPI_FLASH_PageSize;9 d+ ^" K" G) Q+ D: m: c
- /*count 为剩余的地址*/. M7 U) V- Q7 G8 l& ]2 d2 o& C, u
- count = SPI_FLASH_PageSize - Addr;
?$ R. p( I% n" l4 W3 V - /*计算能写入多少整数页*/
0 ]9 D7 P8 f# L3 t% ^) Q - NumOfPage = NumByteToWrite % SPI_FLASH_PageSize;9 N8 U5 W, L7 R5 L; g/ U+ Q7 O
- /*计算不满一页的数据*/
0 \& E8 Z, j4 I* \! z" y- j! h0 l - NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/ \+ t8 x: i d H! o7 ]
- /* Addr=0,则 WriteAddr 刚好按页对齐 aligned */! H4 ]8 l# ]* t. B7 f
- if(Addr == 0), ? C4 s' z2 x5 t# {' S
- {: B1 P! }0 f) z0 p8 R
- /*NumByteToWrite < SPI_FLASH_PageSize,一页能写完*/7 R) @" P' S+ h/ t: s r
- if(NumOfPage == 0)
: y- D6 k" [6 X# O! ] - {
, g% G: w1 c t' d4 _ - SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
7 j& u. p8 t+ o( n: Q6 b/ }$ }$ }3 x - }
5 v: k9 X: R, E) ^0 B! m" g - else/* NumByteToWrite > SPI_FLASH_PageSize,一页写不完,先写整数页,在写剩下的 */
2 N4 }) p! a3 ?; i B d - {: W' }9 N4 M/ O/ z" h
- /*先把整数页都写了*/
' A% ]4 p) m: Z( `. t* E - while (NumOfPage--)$ v: F& Z6 x! W$ j" V
- {
# K: R# E3 T% a. `: L - SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
) ?+ [3 C) L( S/ { - WriteAddr+=SPI_FLASH_PageSize; // flash 的地址加一页的大小9 X. b9 A0 X3 f, J1 z% t3 z% w" c. k! A* `
- pBuffer+=SPI_FLASH_PageSize; // 写缓存数据的地址加一页的大小
t" o5 M- l6 q5 g. V. g - }' R( m* k& X# j$ n
- /*若有多余的不满一页的数据,把它写完*/: {# f- b* u9 _; g( u' k* t0 o
- SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
f2 `; R: r+ u5 U; i) S - }9 S/ L# l! x3 g5 |' J
- }
8 m9 O5 W# P' e3 S% X6 p x - else/* 若地址与 SPI_FLASH_PageSize 不对齐 */
( M$ ]) F# D# m- ^3 a6 n$ ~6 V( ^ - {
R$ P, h6 S9 o - /* NumByteToWrite < SPI_FLASH_PageSize */
' a2 l) s9 ~! z9 [, N! i - if (NumOfPage == 0) `3 H Y! f; p7 r0 ?. e1 |
- {
/ ^; f* |+ o- g l/ c# m- b - /*当前页剩余的 count 个位置比 NumOfSingle 小,一页写不完*/
' }2 X% O' x& w' @$ m: g - if(NumOfSingle >count)
& B' z, c& ?- n3 ^1 Z0 k6 u7 x3 ` - {
3 e2 H1 r' H \8 j - temp = NumOfSingle -count; ~ R$ `/ y! `) `4 S0 U
- /*先写满当前页*/
$ w% a1 R) n+ N1 K: b; c7 r) [8 y. b - SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
; ~9 N, M- i4 h6 c8 ?) X" D O - WriteAddr += count;7 J& }* M6 f, c1 }) e
- pBuffer += count;$ c l) R/ y( H' F9 d
- /*再写剩余的数据*/0 O; W) T( Q- h! x8 s2 p; v$ c# M# z
- SPI_Flash_Write_Page(WriteAddr,temp,pBuffer);
# t8 J. w- k* z: A0 Z( |1 G - }3 D" Y4 |* y# ^3 @3 N# n) b
- else/*当前页剩余的 count 个位置能写完 NumOfSingle 个数据*/
) X1 Z- m9 \) Q6 e - {
n1 [. @6 ]0 U. v- U0 S8 H! P( q" u - SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
, Q3 p9 v& i1 a - }3 ~6 O+ r& F1 _5 b3 t5 X
- }: _$ U: |6 e2 E- W/ j; n
- else/* NumByteToWrite > SPI_FLASH_PageSize */
" T$ d! m8 w! n. a4 R - {
$ m$ f& A7 @/ g# f6 { - /*地址不对齐多出的 count 分开处理,不加入这个运算*/
; v; U- P" _5 ~! Z - NumByteToWrite -= count;0 s$ D/ N0 x6 M% i& w
- NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;' X; F9 X+ I- O# n0 l9 s
- NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
8 [ L" O5 w/ u8 r- {* L! l - /* 先写完 count 个数据,为的是让下一次要写的地址对齐 */8 O% Y$ ~+ X$ q; G
- SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
# R# k+ c, d; ^& }- f8 ^ - /* 接下来就重复地址对齐的情况 */( t. I# L2 B: C, U) J7 ]
- WriteAddr += count;
2 |9 R* \$ G3 I2 {. Q; X2 L5 r - pBuffer += count;
, s# x6 C. R' _$ P3 U - /*把整数页都写了*/8 u- g5 f/ m/ w5 z
- while (NumOfPage--)2 n) B! }. y0 S* ^& Z! `: N& }
- {
2 M l/ x- u$ M6 q' {* ` - SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
( F2 `5 j: ^$ r - WriteAddr += SPI_FLASH_PageSize;
( b o; t/ U% E, D5 x( Z - pBuffer += SPI_FLASH_PageSize;) f8 e" q5 Q" L( G! f* \
- }
, r; G7 j& e5 ]1 T# b2 D - /*若有多余的不满一页的数据,把它写完*/: Z8 g/ h: _& k8 g
- if (NumOfSingle != 0)
. _0 A) r9 }7 H& K4 V- o - {
6 `/ l7 Q. e1 t# e6 r+ A" O# e# e - SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);0 a" }. A- @7 I7 m1 g T
- }; {# \/ e& k& ?
- }
4 N+ d7 P" r. N3 i - }
4 V/ Y) [- @# j' F/ Z - }
复制代码 : S1 T! g( R2 @' p
通过上面的函数实现,我们就可以基本的来完成FLASH的读写操作了。有些函数参考了野火标准库的操作步骤。 在main.c 的主函数中对FLASH的读写地址进行宏定义: - #define FLASH_WriteAddress 0x00000
, z; y; y( z ^. U ? \2 Z - #define FLASH_ReadAddress FLASH_WriteAddress& r% F" ?$ [2 |9 d ^! p
- #define FLASH_SectorToErase FLASH_WriteAddress
' ?+ Y8 p# p7 z9 Z - #define FLASH_SPI hspi1
复制代码 9 D' f2 w4 I; K) k0 d( n
在mian函数中定义变量: - uint32_t flash_ID = 0;1 [+ Y; G" x G* }9 q: r
- /* 获取缓冲区的长度 */$ L( |! u7 ^1 r3 L* q
- #define countof(a) (sizeof(a) / sizeof(*(a)))
! M) F# z" p) s7 H - uint8_t Tx_Buffer[] = "现在进行FLASH的读写测试\r\n";5 S! v9 G+ L0 J, b, C9 T! {: g
- #define BufferSize (countof(Tx_Buffer)-1)
6 s- ~9 I. r9 g - uint8_t Rx_Buffer[BufferSize];
复制代码 / Z S9 ]: l0 y. B- `
在main函数中进行测试代码: - printf("*************this is test for coding...**********\t\n");
* h/ K3 ]! n0 P5 _5 L - printf("this is test code for spi1 read and write flash w25Q64 \r\n"); p; E5 |4 F; N# d1 h5 q
- flash_ID = SPI_Flash_ReadID();+ o7 i% {+ N0 T8 Q8 o" I
- printf("\r\n flash ID is 0X%x\r\n",flash_ID);( Q; X5 F4 h9 q6 q) B1 c
- SPI_FLASH_SectorErase(FLASH_SectorToErase);
a7 `, p( v7 y - SPI_Flash_Write(FLASH_WriteAddress,BufferSize,Tx_Buffer);
0 i2 |" f5 I& Z( \: z7 x; C1 o; ? - printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);8 X& M& O, j' H# ]2 G
- SPI_Flash_Read(FLASH_ReadAddress,BufferSize,Rx_Buffer);1 g+ X8 R7 d0 d! i
- printf("\r\n 读的数据为:%s \r\t", Rx_Buffer);3 z) ]' x+ k9 i* e+ F
- /* 检查写入的数据与读出的数据是否相等 */
3 ]% |4 J+ R2 }# s; E6 k I - if(memcmp(Tx_Buffer, Rx_Buffer, BufferSize)==0)
r9 v4 W1 Y8 a3 t& N/ T6 z - {) Y4 X5 U0 e) U- U. a
- printf("写入的和读出的数据是正常的!\r\n");0 }5 K9 {$ F/ i1 i- _; a( ?% ~' i. P
- }
7 W0 ~1 F+ s5 g+ i - printf("SPI 试验结束......!\r\n");
) L! l1 r8 R' T. D8 c - printf("*************this is test end....**********\t\n");; ]6 j1 c8 e" m Q
复制代码 0 h/ g6 F1 u$ X! x
04 ------------第四节 效果演示-------------- 我是使用串口将写入FLASH的数据进行读出来,比较数据是否一致,同时独处FLASH的ID看是否一致: 我们可以可以看到FLASH的ID是0xef4017和产品手册中W25Q64的ID是一致的,读写测试也是正常的,我们使用了C库函数,需要包含头文件<stdio.h>。 文章出处: 小鸟的早晨 |