79.1 初学者重要提示
5 u0 Z) I6 A5 C! U5 l 1、 学习本章节前,务必优先学习第79章。1 Q8 h9 E& j. @) E: D. n O: x# J
8 n' w# f. C/ ? F
2、 W25Q256JV属于NOR型Flash存储芯片。
S2 n7 b, |: L
& d+ U+ R: @! N. f- ^. w/ a* r 3、 W25Q256JV手册下载地址。 L6 Y: X& h; E3 w4 g5 F
6 r* L6 Y. M& I, E7 i/ g9 v6 r
4 ~3 |- ^& {0 \2 P: ?; M$ c4 |
: p. i; u2 M, h. g G4 O 4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。6 ~' |; V; R" K5 V) J: z7 I
/ t; |* r0 O7 k 5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。% F6 |7 O) P, k% u5 T
( V& V8 M& r; _" }) u+ @
6、 W25Q256JV最高支持133MHz。
: q+ i. N2 B8 D: y5 L) ]6 W8 P+ W4 ^ l5 I2 P' K
7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。% W- {$ j- q0 Y: _2 m
) `; D/ `- D" @! Q 8、 内存映射模式下,最后一个字节无法正常读取的解决办法。, ? B6 i7 A3 Y, ~) Y [
) D7 A# G5 U% [) P* v2 C9 \
9、 本章配套例子的DMA是采用性能最强的MDMA。
# u+ T2 t! c1 M1 f l* m) F. b/ \% R# O( }7 w# h# v& t+ \* t3 f: I
79.2 W25QXX硬件设计
; `' ` Y6 Z% B8 q* ^4 xSTM32H7驱动W25Q256JV的硬件设计如下:
6 S8 V2 W) E, g, S1 j+ B! T7 V- G$ ^+ F2 Z" C( f- ~" j# ]
9 V! v5 z# t( M9 v$ i u. ]! L8 ?1 {# X' `
关于这个原理图,要了解到以下几个知识:1 f/ I, |' R% m9 T% k) }) G2 T
# Y& [/ e: ]& l V7开发板实际外接的芯片是W25Q256JV。! v8 f% c0 n2 D. G8 u
CS片选最好接上拉电阻,防止意外操作。
9 B; ^; m6 ] d- ]# a3 j! y9 i( a W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。
1 a* m: G0 x$ [* ~ HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。/ M/ J! |9 Y9 N5 `8 Q
79.3 W25QXX关键知识点整理(重要)
/ y9 q- [8 M! P2 h6 E. Y5 d4 }驱动W25QXX前要先了解下这个芯片的相关信息。
: J, f2 ^5 D- @* F, E4 Q8 Y2 {$ _5 L! o; K! @" T+ a
2 l8 Y* h, c+ t. @& i( M& X S+ u2 X' X( Q
79.3.1 W25QXX基础信息
, v5 I6 G# P) \+ |- k; K) P W25Q256JV是32MB(256Mbit)。
! r' k" M+ e. L! J W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。1 N/ x' Q0 q% L" f& A8 [
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
( R$ |9 t0 g3 U9 v3 t: r- q
: L S4 O. I) \5 O" |9 f0 u' q支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。. f( A! n- s9 V) q1 _4 H6 m
; d% Y- I4 d% t/ L4 w r* `/ m(注:这里几线的意思是几个数据线)。
. r. w: l( Y* _
5 G& t1 H, h& ] W25Q256JV支持的最高时钟是133MHz。
3 u& }- o1 w- n- ~ 每个扇区最少支持10万次擦写,可以保存20年数据。
5 x6 l+ Q: y5 N6 S 页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。+ Y/ k7 l+ m( j. d) J: [
支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。4 E6 P! h. z. C) u7 x( q) {7 d! Q# H
整体框图如下:& A6 t. _: ], g
8 P: k- b8 i; ^. h; V! Q+ b& n, d( z
( ]3 C4 \+ C! ~
! L3 d/ @5 c6 n7 q/ A( u& tW25Q256JV:
& ~, B) E$ A9 g0 b( O/ C4 R( ^
9 ?0 x- G+ B M. | 有512个Block,每个Block大小64KB。
6 Q% S! ]9 X! T1 n$ n 每个Block有16个Sector,每个Sector大小4KB。
) [# |( a- T8 L$ A S. q 每个Sector有16个Page,每个Page大小是256字节。
3 P. v8 `0 \, s7 |% [0 U79.3.2 W25QXX命令
1 |3 m" ^% i1 Z) s3 ]/ \0 a使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:
' h/ r) P, W; d. ? X8 m! n9 D" l
* m8 h; U8 Z/ c/ {* k
" B) d9 M2 W/ H4 v7 J+ a( q7 a( E; [% J1 L
/ s5 a0 F3 U% ]; \1 L# O" V8 g/ o9 w
当前主要用到如下几个命令:
+ g, c F2 ^7 k! s' O$ t' W; P2 R
* R+ R+ P A- E" X- s2 s* z- #define WRITE_ENABLE_CMD 0x06 /* 写使能指令 */ 3 V8 ^+ Z$ F+ s/ I C) @( E
- #define READ_ID_CMD2 0x9F /* 读取ID命令 */ ( g4 W; M" x- m4 [4 I$ D5 ^$ Q
- #define READ_STATUS_REG_CMD 0x05 /* 读取状态命令 */ # u; A) a8 R1 L5 V+ v
- #define BULK_ERASE_CMD 0xC7 /* 整个芯片擦除命令 */ " I* @7 P& w# e6 h; N; ?3 U
- #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit地址扇区擦除指令, 4KB */
& p* B$ N' W7 F; ?6 J' {# x - #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit地址的4线快速写入命令 */. x' K5 U/ }! G. ]- l& T; Y" F
- #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit地址的4线快速读取命令 */
复制代码
7 G" \! X- B9 f6 [79.3.3 W25QXX页编程和页回卷
! X* U4 C P Y7 `% Y! {" KSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
0 q1 r+ p- ]- |9 A5 b' p
/ T3 b, v& ~$ B8 r. k3 M+ {- bsp_spiWrite1(0x02); ----------第1步发送页编程命令
6 b. r& m; K; l6 P) i& `; v# B - bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16); ----------第2步发送地址
) U% p- e5 r% Z& y8 x - bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8); " ~& o9 G9 I0 [
- bsp_spiWrite1(_uiWriteAddr & 0xFF); & H1 ?8 G0 g) s. n9 A% t/ f/ Q4 N7 \. ^( p
6 y7 f% g* B$ P I1 Z# G0 i- R6 K- for (i = 0; i < _usSize; i++)0 f1 g, _5 e" ]( S
- {
" T9 g" G4 T1 `' E; c1 H: R+ m" w - bsp_spiWrite1(*_pBuf++); ----------第3步写数据,此时就可以连续写入数据了,
8 o9 `6 }3 I: n( h; j9 Z4 l3 e/ Y - 不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。
/ q: Q2 E5 D- f9 Z' Q) q - }
复制代码
- l- F8 H9 `& L. H( J页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。! q( X) n& }! `
: q, o ?- w) D$ B比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
1 L2 @: U% ?# x N' P1 u/ R
0 }* j+ `1 u) n9 \, n/ F7 a- uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};& f' ~1 ]& F8 t' b7 |
- uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
) i( S' I9 B% L: u' A) d 从250地址开始写入10个字节数据 PageWrite(tempbuf, 250, 10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
& q+ c# W1 r( n+ ] 向地址20写入1个字节数据:PageWrite(&temp1, 20, 1);1 q, E- F6 Z2 X6 Z2 a/ [/ ^
向地址30写入1个字节数据:PageWrite(&temp2, 30, 1);8 m( A: ~9 x. F
向地址510写入1个字节数据:PageWrite(&temp3, 510, 1) (这里已经是写到下一页了)' a3 m6 N: Q9 q4 u; i" {
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。- L: H, G P' I5 ~
8 m; N2 t, u0 g: D
) J$ @8 u4 A% p9 w7 {! G
6 r @" B9 i0 g( w' O
79.3.4 W25QXX扇区擦除9 q4 N" k! e1 Z) R# g) V& i
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。( ]2 m5 w; v$ S1 N7 B
' [% C+ x, i% y3 c0 j
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。! |! v2 Z1 _7 J
, ]6 Y+ F6 Q* ^* ^5 |3 d
79.3.5 W25QXX规格参数0 e7 z& i+ {8 w: f+ q' d0 }6 j6 I
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:3 Y. o6 T7 I- x- e
! v( U! m7 p- W: d+ Z4 W+ p; k
! @* T- d, E& p3 ]
. Q+ G+ g. s2 @1 m" n$ m# G7 Y# s. ]. Y' A! T3 D3 Q' M C# f
页编程时间:典型值0.4ms,最大值3ms。
* P( U( B U5 C9 P 扇区擦除时间(4KB):典型值50ms,最大值400ms。( r9 Y$ |7 h4 W
块擦除时间(32KB):典型值120ms,最大值1600ms。
6 ]% D& B$ }1 O8 p 块擦除时间(64KB):典型值150ms,最大值2000ms。; J* j8 V- `' `" J- o- q
整个芯片擦除时间:典型值80s,最大值400s。
2 g, @& O- N3 z9 E2 L4 q: |% H! @5 v7 k
支持的速度参数如下:
4 t/ X ?- k3 x( Q* M' m0 C6 \2 F' \4 m% ]% X* m) M
/ {0 ~2 c1 U6 s% T. \: R( u8 |
& [2 m6 u$ K) Q8 E1 B" t" V
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。& [2 ]2 i9 e) P) }
" w+ C3 N: W* r$ S+ W79.4 W25QXX驱动设计
6 f7 B- B& @5 `% D8 V- ~W25QXX的程序驱动框架设计如下:0 {/ q: A- N8 h: y/ ~; |
& V4 Z2 G1 Q" p4 X2 X- V
) D! K# i! V7 }% F+ M( @ g8 A+ f6 s. a0 |; X1 A1 H% b
有了这个框图,程序设计就比较好理解了。
0 S" X6 J- {( o4 P }6 @
2 m! t* T" V4 N/ v; ]; c- F79.4.1 第1步:QSPI总线配置8 [7 N# \( n6 n3 t3 \
QSPI总线配置如下:% p! N! W% h; ^/ R& e
" V6 }' T! [/ m& i7 _7 a
- /*/ J! Y8 c! G' g5 Y
- *********************************************************************************************************# V' {) T1 c: m( b$ u' `9 {8 H! t
- * 函 数 名: bsp_InitQSPI_W25Q256% l+ j. k9 t& H8 k
- * 功能说明: QSPI Flash硬件初始化,配置基本参数
' ^0 |2 B$ G0 x- L4 A* ? - * 形 参: 无
+ h9 e2 H0 w% h1 N, b - * 返 回 值: 无
( X& \1 I% E' C: o - *********************************************************************************************************
$ O. o! v3 V; Y - */* a1 j5 L7 F. _ V/ k4 v9 ?1 k
- void bsp_InitQSPI_W25Q256(void)
5 N H6 ~8 I2 y3 D9 F4 _ - {" W: ^* Q% ?' ]: K( `! L
- /* 复位QSPI */
8 _- j9 q6 G+ B/ B8 v+ J- r - QSPIHandle.Instance = QUADSPI;
6 [2 h( Z- a( Z8 C+ v - if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
L3 Q+ h: o% m2 U- ] } - {
4 {% i8 W2 `# a9 p% _3 B$ o - Error_Handler(__FILE__, __LINE__);
& q$ x8 L( Q5 P# l - }* j4 [$ G6 W7 @
- ! @) N0 e; r8 ]# K a
- /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */$ p a# j! \) C; K* w
- QSPIHandle.Init.ClockPrescaler = 1; 6 ^9 \% _( ?( F: q0 ` H2 V
. [* ^/ Q$ p8 S- /* 设置FIFO阀值,范围1 - 32 */
5 s- q; n8 V6 ~* k! q, O - QSPIHandle.Init.FifoThreshold = 32; , r' x/ j( z$ K- D' P, C
- # D4 c. x& P s' J7 D- f& j) B5 d
- /*
{( ~0 n; ^! n m, E6 y# l" S- Z - QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。8 A8 Z8 m% I) s2 K
- 在外部信号延迟时,这有利于推迟数据采样。
# E+ W/ ^2 E$ ^2 j' x% F - */+ C. W, Q# O+ P3 f. `% \ t
- QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
6 ?3 D4 B! T! a; `' l% _! y - 2 u# h+ N7 v$ K0 h
- /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */' a' ?( `0 a2 z+ l) j/ w$ U
- //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。. @" g4 U( I* X% p
- QSPIHandle.Init.FlashSize = QSPI_FLASH_SIZE; % M" H4 @4 s: Z9 I& a
0 N T7 V, u( a8 M- /* 命令之间的CS片选至少保持2个时钟周期的高电平 */
$ M$ Q2 q3 s* F; [' _' V) E) t - QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
X+ o: s8 Q! H4 F. x2 R. c - ]; ]7 L0 d7 m' r w
- /*) h/ I2 T( D0 Y
- MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平7 I+ e% c6 o, Q+ l# h, z0 Z
- MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平1 o0 _; ]0 x$ S% [! F# X5 l
- */
- X" A6 L2 E" I* f3 l9 D - QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;$ C9 B! F4 D+ g1 e
- , g. t6 D" {/ l: W8 {/ |* n
- /* QSPI有两个BANK,这里使用的BANK1 */
1 a. C7 w& y, z7 o - QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; r3 W% G3 ~2 d9 I
9 b9 s: A, P) n9 [( ~& G" w- /* V7开发板仅使用了BANK1,这里是禁止双BANK */
/ x0 k3 j* J0 W- i$ A# a5 e - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
5 p/ v' A5 z. j- d2 I& g0 s4 @
$ H. i' S( V7 W, c+ v* y6 y- /* 初始化配置QSPI */
$ d* P. ~% r" V - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)+ w2 j9 ]. J' P
- {6 f& d3 i6 [2 Z
- Error_Handler(__FILE__, __LINE__); J3 N' g# B* O( J+ ^% h- @
- } , B/ G* a' e9 {8 a2 r9 c
- }
复制代码 1 C* C9 }& c, u! o4 q
QSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。9 a# V- \1 c" q0 N% ?% ?
# _5 h& ^; K! ]( j& W, L
79.4.2 第2步:QSPI总线的查询和MDMA方式设置
* E9 m1 m! @9 L) x! T本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。) @1 P8 m0 B8 F. r
0 m! X6 d9 ? v& T79.4.3 第3步:W25QXX的读取实现; w5 } ?, A$ i9 S
注:这里以查询方式的API进行说明,DMA方式是一样的。0 } V$ i& Q4 M( \( e/ \4 ^/ O
( W% b: m# l z; I y. `W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。
: H1 o5 a8 d+ B8 _/ r5 w% P, Y; P: r# B( v
- /*
/ b: ~; Q4 d5 A/ V9 K7 ]- N& m- Y - *********************************************************************************************************
% a6 J% B0 X) q- w - * 函 数 名: QSPI_ReadBuffer
: i0 N6 B- {8 }7 `7 u - * 功能说明: 连续读取若干字节,字节个数不能超出芯片容量。* f5 T' l& l, c0 K
- * 形 参: _pBuf : 数据源缓冲区。# r" B% y. E( e
- * _uiReadAddr :起始地址。
5 _! A7 f. w6 d/ d - * _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。! M- i/ A/ i7 J1 i0 O
- * 返 回 值: 无
" d% u, M) ~* G% L x/ Y { - *********************************************************************************************************6 A0 d/ ]" x' M7 _& b4 f
- */! E% E' y b- K" }8 t# C
- void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
/ S8 Z# h. H( U# N - {
5 b2 N! O! [# t# ]5 v$ x- b. e - _. F4 i) C8 S) [' P+ U
- QSPI_CommandTypeDef sCommand = {0};* \8 b3 ]' c" z A
3 O& [; t& @* M3 f5 |- ( m$ O- P5 Y" f2 U( X( v
- /* 基本配置 */
, s/ }8 \! Z8 Q j2 [. ^/ _% _; N - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */
$ T3 Y, ~% i% C) ?2 Y, U - sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */3 o$ F$ F6 F/ h& M1 P# d
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
. F! r; q/ `! }& D0 Y7 Y" @; @8 O - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
( n- ~* |6 R2 p+ C6 y! r - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */( [% i1 I* {' F( \
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输要发指令 */
! k1 |! }) T, @' W6 i) i
2 o/ ?- _$ I/ o- /* 读取数据 */1 H) t1 l! z: n( h* v
- sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */
( x7 v+ z9 K0 X" W- L4 S - sCommand.DummyCycles = 6; /* 空周期 */4 d% l g9 U* C- V0 b
- sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */- Q4 w& z) A- `/ O: v8 O* ?
- sCommand.DataMode = QSPI_DATA_4_LINES; /* 4线数据 */
3 D1 W3 U' ~% A: o+ m9 Y4 } - sCommand.NbData = _uiSize; /* 读取的数据大小 */
1 L6 ]0 F5 T" L - sCommand.Address = _uiReadAddr; /* 读取数据的起始地址 */
) I+ e, s3 {) N1 U+ Q
/ T g; U+ r' g y( D K8 d- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
* O( X% W) _: P6 b" G - {
0 y. ]+ G; O5 }% ~8 L - Error_Handler(__FILE__, __LINE__);% y5 y" l6 \" }: t
- }/ {/ z. B( ~$ W" R a- R
- 9 H. w3 T V3 B5 v* L4 G4 z$ G3 ]6 I
- /* 读取 */
5 c+ m' Q2 u% c# x3 K2 y - if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
% [$ y) _6 {" y* a+ y3 h - {
0 s* Q( k( |8 F - Error_Handler(__FILE__, __LINE__);0 K' H' @" |0 J$ s/ n, Y% y
- }
( R7 u! N/ R; @) [! x4 t - }
复制代码
2 G' S! Y' [4 s- J, S此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
S$ ^4 A% n/ S$ z8 ^0 k
; f. G$ @; { Z; b% V) A: x5 R0 S2 f0 k" \, J6 j% {
/ I1 y2 u: f0 G
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
J. ]5 J$ b q' _& K
/ e% G. m2 i* g6 }( Z' r4 dsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
( j, i6 k1 U, }2 _sCommand.AddressMode = QSPI_ADDRESS_4_LINES;2 u S4 N8 s0 b2 T- d
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;: @1 j H# j {3 L) H
sCommand.DataMode = QSPI_DATA_4_LINES;
1 D# s! z( Y" F8 K" w& A: X$ H) d* t F1 W& T+ j
79.4.4 第4步:W25QXX的编程实现& h; W9 {2 l* T2 x! L
注:这里以查询方式的API进行说明,DMA方式是一样的。
F) ~8 y1 D* b% Z
" k$ T3 V" a& \0 ?: H8 k下面实现了一个页编程函数:8 [" s' H/ e: `6 ~' @% c
( ? r3 |, B0 M
- /*, D' h/ P* [* t4 Y
- *********************************************************************************************************0 d; ]7 X2 `7 u. J2 \8 h8 ?
- * 函 数 名: QSPI_WriteBuffer
! r. } }6 `( S; ` - * 功能说明: 页编程,页大小256字节,任意页都可以写入
/ N# \0 A$ K$ c' e$ l - * 形 参: _pBuf : 数据源缓冲区;
7 T+ H7 V* W" O2 u) h/ {5 i! ` - * _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。
" `( i6 o/ A( y/ r$ b& m - * _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。. F2 _/ C( S; `7 ^1 Q8 W3 m, f
- * 返 回 值: 1:成功, 0:失败4 z J" {6 K3 p, X9 |# ]3 T
- *********************************************************************************************************
* V- ~* J6 o0 v- p' F - */7 U# B% }! C0 I! |' B; G' g* m
- uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize): v p' X1 }6 ~7 |
- {& M2 r: w4 Y: G3 v3 _& n
- QSPI_CommandTypeDef sCommand={0};+ X5 S% [8 ]6 L8 F9 y. Q2 x7 r, h
- ' A$ f* a# h6 B9 K
- /* 写使能 */1 a& M9 ^" I0 r# G+ T7 p; ~8 q
- QSPI_WriteEnable(&QSPIHandle);
" \) h+ b4 S5 _$ y
9 j0 c- ^2 u) F0 R9 Y& @- \- /* 基本配置 */
/ ^$ O: y7 M) V - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */5 k5 _2 I: o% X l
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
6 u5 l8 u& V4 y. x2 x y - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */) a" m; v+ Q: O7 j3 D+ O7 a
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
9 h5 G y" I2 q/ P! O3 Y* r( V - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */6 X: w* [" c; Z+ h
- sCommand.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; /* 仅发送一次命令 */
; Q* ?' e6 L: u: l( A4 X
9 r, |5 c2 a: q* M/ E. t# A# s- /* 写序列配置 */
( j% _) c2 }! [' d. T5 I0 Z - sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */5 j0 Y9 C, O F8 W
- sCommand.DummyCycles = 0; /* 不需要空周期 */
7 R& h- O1 x$ m% E h - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 4线地址方式 */" K5 _5 q* Z# f J, z/ r- b
- sCommand.DataMode = QSPI_DATA_4_LINES; /* 4线数据方式 */
- O; ]3 i; X" \6 `) w- S - sCommand.NbData = _usWriteSize; /* 写数据大小 */
/ @ Q1 l+ p, L! T9 j - sCommand.Address = _uiWriteAddr; /* 写入地址 */0 G8 b" [- I( t, }! X8 j* K
- 5 |0 U! G' O) c% Z3 K, Y) a( [. z
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)3 L! ~: w j/ G2 A6 N+ s( [3 g
- {# K( G- S! M! Q6 A" n# `
- //return 0;
4 v: | V5 ?. M; E1 d. T* C( W. H, \ - Error_Handler(__FILE__, __LINE__);) S8 o( z+ w8 D: q! H
- }/ v: t' z, {1 X, B# K2 |" G
( I% x0 f ]# J) f: L2 s$ [- /* 启动传输 */
8 o1 _ }/ X1 P. F - if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
) e6 e( U& V+ e B# m - {7 J) h# W5 R' f
- //return 0;( M. I5 W5 v' m+ M! H) A h+ z
- Error_Handler(__FILE__, __LINE__);% H4 G6 O6 j* G8 f* n; b4 t; C
{; h! O; h% C7 H! j4 Q- }( C/ l) @' T$ D4 {! t
- 9 ]4 s2 {& b4 y9 d# H* |) X7 i9 y) ^
- QSPI_AutoPollingMemReady(&QSPIHandle);
) f/ ^. C7 l# y1 m3 y
! o6 q5 }' a6 `- return 1;
8 l, _ J1 v) V: |* } - }
复制代码 % T' i/ U! p2 m5 s
此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:
- F5 c5 h+ I B& n9 }$ M1 |' \' D& j8 u" X- {; |9 E
. G/ v2 c9 J6 J: H# N$ b$ x
. M- g5 e3 z! ]左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
" r0 W M7 z7 a4 G) H; A# R, k; H% A! W+ K& W
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
! t9 J7 Y ~2 O/ S& }- N, BsCommand.AddressMode = QSPI_ADDRESS_4_LINES;
x0 f" C" r0 A' {& H. m9 B ~2 G7 BsCommand.AddressSize = QSPI_ADDRESS_32_BITS;
# U& f) p0 f! L7 S0 _0 qsCommand.DataMode = QSPI_DATA_4_LINES;
" t& T c [: `* L: L/ ^+ z1 j% ]- K' c: ?- P0 P9 I
79.4.5 第5步:W25QXX的扇区擦除实现# b' @5 O6 N$ |1 p( _7 w
注:这里以查询方式的API进行说明,DMA方式是一样的。( y; L- n* h, `( E% H0 W
* ^& T# A1 E! V$ C# Y! [: `
通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。
$ r+ B8 L4 d M
5 h0 |; M0 w/ ?8 _- /*3 X0 ?7 z: H5 p; Y% j+ Y
- *********************************************************************************************************
& `4 y+ L t/ V5 J2 `0 y - * 函 数 名: QSPI_EraseSector
% t% y: Y% Q$ b% ` - * 功能说明: 擦除指定的扇区,扇区大小4KB% h6 }; P$ a: Y
- * 形 参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,5 m( Y& h# w. G5 K4 g% {) a
- * 返 回 值: 无
# y8 _3 A' h' E+ K5 { - *********************************************************************************************************$ w$ z) m6 g* Q3 r& H8 O4 ^
- */
# f" N: g! k. c4 C, U3 C/ E- | - void QSPI_EraseSector(uint32_t _uiSectorAddr); I" R; i4 x+ j3 r- L/ V
- {
`5 \1 m& q; G# u( e6 r3 Z - QSPI_CommandTypeDef sCommand={0};
' M/ A) R4 z0 B! ~; a - 0 ?7 o# Y: U2 J1 u3 S+ [+ f+ M
- /* 写使能 */
% e; Z% j9 ^& V2 Q - QSPI_WriteEnable(&QSPIHandle);
. O3 h# x' p& R# o& |- w - ! t$ v9 B3 N, B3 Q9 l
- /* 基本配置 */ y/ X$ T# `. _: H
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */& R9 _) H8 x. A. s1 ~* }
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
$ `: s* W" N! h1 r; D6 X - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */" A! }# ?9 J8 v7 G; w& y) z, Z
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
: J, O/ b, O& |1 n: Y - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */
( m; x; H' C9 r# Q+ _7 Z4 W - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */ ' ^& y3 o- [0 ^8 X0 ?( _
- / d/ O1 `, N* _) ]2 }9 G
- /* 擦除配置 */; [. L# a6 b0 L4 C: H
- sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD; /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/
# m3 j+ T; a3 V3 V* C- e8 E: ]/ V - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 地址发送是1线方式 */
8 D7 S0 {. y K1 q' K! ]+ t5 b - sCommand.Address = _uiSectorAddr; /* 扇区首地址,保证是4KB整数倍 */ 3 t; L4 S1 M' I. E
- sCommand.DataMode = QSPI_DATA_NONE; /* 无需发送数据 */
3 {( L, P9 B( @* j C - sCommand.DummyCycles = 0; /* 无需空周期 */
$ g* e! M D8 \ B. }3 W+ N - , Q, m. i( n2 N5 o
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
) s5 [, W+ E# m - {
6 f9 d R. S, p4 g2 O7 P. s0 d - Error_Handler(__FILE__, __LINE__);
0 R) K* L2 z! K7 M. { a/ e - }
( o" W# B8 D! [- a) _ - 0 X* g+ F V I) q- }% J
- QSPI_AutoPollingMemReady(&QSPIHandle);
- n; Y5 H& S, g7 O, E$ Y - }
复制代码
# f4 z* l" M: l G y r& }) V此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:: o, W4 U6 I* Z: _
: r# j. y% Q' P$ S& c/ d2 }
/ j% a' J w+ j# r
( v# U: f9 `. z左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:" }' O2 o% {- {/ [
# @1 y0 F: _0 n- A- J7 k
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;& d1 f6 Y6 A' M
; a# h h% L6 ?# \( i) [4 c+ J
sCommand.AddressMode = QSPI_ADDRESS_1_LINES; |, l9 `2 z) K9 z6 `
8 y3 l9 ]. w: {0 I3 W& F sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
! A6 T8 ^4 i9 y; } X0 w( f" o( Q3 W1 y9 T# I, Y0 d' i
sCommand.DataMode = QSPI_DATA_1_LINES;* C1 ?7 }7 B, ~6 t
2 f# a3 Y" j4 s
79.4.6 第6步:W25QXX的整个芯片擦除实现
! a o0 J% E' \: a2 g8 e! s注:这里以查询方式的API进行说明,DMA方式是一样的。
! E5 o6 _) k0 N4 a& P( O5 m5 n0 p' I1 w
整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:
2 L: Z% k% x- C" |" D+ Q# `- W
' t6 @" X; V- X- l( K5 [; ?* u% C- /*& @5 G3 ?! Z+ P. t/ ~2 F5 O
- *********************************************************************************************************) Y+ k" r) [9 x7 h1 A7 S; q
- * 函 数 名: QSPI_EraseChip- N: z" _% O3 D+ e- Y( D6 K$ E
- * 功能说明: 整个芯片擦除
; h3 O& H7 s* T% U' b0 X - * 形 参: 无
. H: o8 z4 O: @/ B/ b) ~+ n1 ~" U - * 返 回 值: 无
' M. \0 A) E5 F* v2 \ - *********************************************************************************************************; D; t6 d' J1 O/ e: u! N2 O* D
- */, V* y0 y" Y, c l
- void QSPI_EraseChip(void)
0 a2 O( V6 e3 n - {: Y0 q. @4 r9 D$ N$ L4 l
- QSPI_CommandTypeDef sCommand={0};( X% I; v* Y- O6 P n
5 \+ _3 z% T8 N4 q I& d& X- /* 写使能 */
1 Q) E% e, }( Z0 Q! X! M- l - QSPI_WriteEnable(&QSPIHandle); 7 ^2 K9 `( Q* B* O/ G! X
- & n# L- O! n. g$ b
- /* 基本配置 */
/ s0 s* _6 Y' d* U3 w7 ~$ h - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */, z* R. d0 m: I9 G" F
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
0 k7 N; u, e: U- C7 D - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
1 q2 b) b0 ~& t - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
+ A h$ M& P+ ] - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */$ x- w& L1 h! J
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */ ) o4 F9 y( Z# g/ L
8 K. x& \' r/ w- /* 擦除配置 */* [6 Q) m/ c+ I4 d. A P2 M; ~4 l
- sCommand.Instruction = BULK_ERASE_CMD; /* 整个芯片擦除命令*/
/ N8 `8 {& O; y. a: @7 s& f, V - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 地址发送是1线方式 */
) b9 ]: }2 i3 W: O; ?$ q } - sCommand.Address = 0; /* 地址 */ 5 s; R$ h3 V" h; t0 I4 n, k
- sCommand.DataMode = QSPI_DATA_NONE; /* 无需发送数据 */ & n& }4 g; r9 f6 y) X" V# d" Z
- sCommand.DummyCycles = 0; /* 无需空周期 */ " s9 y4 q6 p# w9 h8 | b: h4 _
- ' o+ j; `$ ^2 C0 \
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK): o" i: c$ D8 d q2 p( Q+ R/ {
- {
, b0 h8 { g h - Error_Handler(__FILE__, __LINE__);
3 x( ^9 N; N8 B% s% @* K; w" Q - }9 b) N% \; m, K7 K4 j3 f* ^
/ J2 ]$ P6 }" F4 T) ~8 Q% z( E+ p- QSPI_AutoPollingMemReady(&QSPIHandle);
. G) Q$ a& \. f7 R6 N& ~ - }
复制代码
$ s+ A+ {: A7 q- e5 S0 @此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:5 ^: D7 \& J& |' j& H: R
0 d% c, Q0 o$ o5 S e2 o
5 K. N, Q; a. o# y& z) {- t
0 h; R9 D- D7 l" |7 R9 y
左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:
; I8 ^, a1 C# V9 R! v" Z5 G9 K4 W. e+ s6 T6 Z
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
+ Q) a8 Q7 K# i0 Z6 z7 zsCommand.AddressMode = QSPI_ADDRESS_1_LINES;
. h9 Y" V3 h6 K) O# l2 M2 usCommand.AddressSize = QSPI_ADDRESS_32_BITS;
9 [/ }5 q" \" x# w8 CsCommand.DataMode = QSPI_DATA_1_LINES;
& z3 @8 Z$ }3 C, d2 S3 v( c4 F* C8 e e, h3 Y
擦除用不到数据阶段,sCommand.DataMode = QSPI_DATA_NONE即可。5 q3 V4 f8 h6 e1 b
7 N+ g% S ^0 l# H! c
79.4.7 第7步:W25QXX内存映射实现
8 ?/ x+ ]7 U: _" I! z注:这里以查询方式的API进行说明,DMA方式是一样的。: b& D( ]$ f4 D$ _1 r" i
/ l6 g' n) N) {8 l Z' q g" e. s Y
通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:
; h$ ~; U8 |' }( `' o1 S3 B, M+ ^" |0 |9 q$ m, K9 }8 x, O
- /*3 Y9 c0 i% y u" J3 ~' ]# U q
- *********************************************************************************************************
6 d2 o! L) R1 F- S9 j+ r - * 函 数 名: QSPI_MemoryMapped, s+ p/ W7 l' ~) @) j! _9 V5 R
- * 功能说明: QSPI内存映射,地址 0x90000000
. s) m- M) P/ r$ t - * 形 参: 无' q& o0 p; Y; m* H8 Z+ D
- * 返 回 值: 无+ q; c/ M& y X' X9 o- w
- *********************************************************************************************************- L l5 t4 z) Z& G- Y
- */
+ ~" Y+ K0 y( ~! f7 f6 ? - void QSPI_MemoryMapped(void)
5 w. L( A9 e" ~# T- |( c4 x$ X - {, y# X) ^& I' P0 z
- QSPI_CommandTypeDef s_command = {0};& ?! Y7 e/ D4 s& H7 T
- QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};$ k) x- v* D4 |* c# P+ D" S; X
' A5 F6 q: ]9 C6 \. p; W- /* 基本配置 */+ X" p8 [. h2 E
- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */
6 G4 n X, o/ z, o - s_command.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
% h4 _; d. H. Z - s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
6 t( e! m% e |, r$ P4 M; p - s_command.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */7 Y; o) P# `; a
- s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 *// @7 u9 T. v! x5 e
- s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */9 D5 `2 u& q0 `; E! \
- & u, y7 ~5 p c0 g+ S/ q
- /* 全部采用4线 */: S4 _2 J( v' n8 d" b
- s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */( X: ^4 v0 l/ c2 B4 ]- \# g' r
- s_command.AddressMode = QSPI_ADDRESS_4_LINES; /* 4个地址线 */
D8 Z: M% B O- u `* s+ z$ l8 F - s_command.DataMode = QSPI_DATA_4_LINES; /* 4个数据线 */ X+ o* U% B0 J5 u- @$ L
- s_command.DummyCycles = 6; /* 空周期 */
5 e8 ]+ @2 g: P
( s7 P0 S% B5 O: D4 L% F1 p- /* 关闭溢出计数 */
% U M; }9 C% W+ ]' m) t - s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
0 s& ]3 s k1 z* a- i: n- R; f3 i - s_mem_mapped_cfg.TimeOutPeriod = 0;: m/ p" _1 \6 t
- 5 P/ X K4 U/ B. v" m
- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
2 W! H( M% `( _1 l - {- F3 B* @, G0 ?. o! x
- Error_Handler(__FILE__, __LINE__);
9 T% X+ {7 k6 H+ E. z/ Y" \& j5 x - }
^; K! i* C8 Y" C0 p - }
复制代码 8 s# A2 V9 l/ B2 ]
此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
& G$ u; N4 @+ z' Q
9 X3 q( W |5 J8 P' }
7 ~9 S0 W& o' x. q% g( Q( r& z6 J# O4 u$ k6 s" `( H/ `
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:1 M! A1 t7 b( c, D
& F9 V2 C! }! `1 D% e
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;/ X% _/ W+ }+ d3 W$ V8 \
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
* o( E7 h. \, ?, ~6 f9 }' OsCommand.AddressSize = QSPI_ADDRESS_32_BITS;: K& g3 \6 u( b/ x: [3 L* I
sCommand.DataMode = QSPI_DATA_4_LINES;
/ F+ X: Y1 t# d4 F2 ~
$ g3 [' C' u3 Q, X79.4.8 第8步:使用MDMA方式要注意Cache问题
* o0 V, S; `; h* f如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。
0 h. x, g, [" }. s5 B" G, a$ a; g+ C$ d }$ j9 @1 @- w. Y) j& |/ N7 I
- /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
0 b8 G6 g" C4 \+ s Q3 X5 d - MPU_InitStruct.Enable = MPU_REGION_ENABLE;- }. @5 f/ i8 T6 ~
- MPU_InitStruct.BaseAddress = 0x24000000;0 i$ x5 G8 M+ [5 v4 I
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
" `$ n6 M6 v1 n3 M, G - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
) g% b9 b0 `3 ]! Y& x* D) { - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;# j. p! P9 @5 ~: G. ^. \, N
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; |9 `$ _1 Q1 Q/ `: p w
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
9 M$ Z0 [: f3 U$ v, j - MPU_InitStruct.Number = MPU_REGION_NUMBER0;& ^2 [! D7 @4 d8 ~7 H+ ?' a
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;3 u$ N1 z, v. S, F( d4 Y5 w% @
- MPU_InitStruct.SubRegionDisable = 0x00; ] j6 g/ r' g P+ y+ _
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
) [4 [0 y) M& g3 ]% g& \ - # o- a8 B: C8 E5 e3 b1 p
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
$ H* _: L7 N# u( L2 U# a9 k79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)1 p. ?2 x: f+ k* H
W25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:
% F' ?. @' L9 A! r" z/ b) Z
! d4 S! D+ S: f QSPI_ReadBuffer
1 n; n7 z) R: L# w1 n. i$ _7 s QSPI_WriteBuffer
/ [5 U0 }* W) i/ W QSPI_EraseSector: x$ J9 O- ?* _- I: O. F& ]
QSPI_EraseChip
# h9 C/ [8 U' f i5 ? QSPI_MemoryMapped" m; I( N9 i" C( s
79.5.1 函数QSPI_ReadBuffer
: W+ k4 _. T# k+ ]函数原型:
/ v+ t7 P2 h4 N& Q7 ?) j1 N/ c5 v
8 ~/ R2 Y$ X& Q# n1 N( u& w, uvoid QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);
. S1 S- s/ Q+ S# e+ e% `/ X; K" L- Z8 Z& Y9 U( w( F
函数描述:
& G, C% O" t" c( ^
; g1 i9 ^2 ]2 I" q' M+ m/ ~4 ~0 y此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。
& b5 |) J6 x. r/ l
, H- ?6 Y7 M% E! w, ?* e$ y函数参数:
S" }& b( [7 t" m+ W* J9 S0 ?7 v
$ a! W/ h5 n4 A$ X# N; B7 _8 b 第1个参数用于存储从QSPI Flash读取的数据。
" }2 ~4 k* T! T, r$ | 第2个参数是读取地址,不可以超过芯片容量。7 ?) N. |9 P: u% P
第3个参数是读取的数据大小,读取范围不可以超过芯片容量。8 j! g- [& W' j& [% Y3 n4 y9 d
79.5.2 函数QSPI_WriteBuffer
3 u* p+ H8 E; a. h函数原型:: J8 t: s: d* Y( `# x9 E+ j7 r7 a
$ ^3 p- ~+ K8 w1 H
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);
# z1 P# d7 V3 ^ u' \- g: l2 a6 E; y @; r" T$ h& {/ C6 ^
函数描述:$ U) Q% t: y& h$ [4 B: M9 t
, ?4 f8 Y' E9 R2 F页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。$ m0 @# k# K) U0 G; Z
, f" ?% D9 w: w* e6 }) P
函数参数:% B6 {3 S$ T8 U. s# u
; f7 H8 Q; p9 ]8 r( d, ?8 T4 W
第1个参数是源数据缓冲区。: |/ ?/ {( }# v, ^+ b* P
第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。
% z6 Y i+ N' E0 g 第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。
7 `3 F+ x/ w/ k/ {+ Q& C6 | 返回值,返回1表示成功,返回0表示失败。
! ?' o' W4 K% |79.5.3 函数QSPI_EraseSector( g$ B+ k# X/ r
函数原型:
0 e y* x0 [) B, O) {. r: w
6 r h- r3 q6 x1 x& qvoid QSPI_EraseSector(uint32_t _uiSectorAddr)
7 H& `3 m5 `9 z& O. c* k9 s; d; o
函数描述:
5 t! F0 C2 a* D! k, S
$ Y8 S1 M2 r5 p, A此函数主要用于扇区擦除,一个扇区大小是4KB。
* e( a" u3 d& d6 p/ `4 w
8 M/ V1 N% R% E0 K# s. m2 e函数参数:
. g2 r0 ^6 \0 q3 L4 B& m0 t; |# k! P4 L, q0 D% b9 T$ q: ^4 z
第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
: q; l! A% f. J1 i; @79.5.4 函数QSPI_EraseChip1 i- Y. x% J6 E' d
函数原型:3 ?# U9 o. A7 U w6 g9 Y" q. D
: N X# o0 y1 y2 }' I# I: I2 kvoid QSPI_EraseChip(void)
" L5 h. Z. \$ U: e9 v) A1 T/ s" I! Y, t& G
; r4 @( b% {2 K' t8 _8 A C函数描述:6 f/ S% O) \* n( X
% b+ C4 V$ s) g
此函数主要用于整个芯片擦除。
# h8 @; Y7 g, u5 n; N4 M/ L8 B
* T! K0 k" }9 C x/ y3 B4 E+ ?79.5.5 函数QSPI_MemoryMapped8 o6 ^0 k7 l( ^3 M* |9 l* D. y
函数原型:
" ~: k* ]- l" n V# W( z6 `8 H# n& S; Q
- p) M9 f6 w L: H9 [0 m! `( Zvoid QSPI_MemoryMapped(void)! n* ?5 F/ _0 T/ ?% p
3 c6 L) f' y9 V# }. y0 f* v函数描述:
3 c& [% w" A& [! \
* O+ u+ \' A" R3 T" o; M调用了此函数就可以像使用内部Flash一样使用外部Flash。
" @1 i2 V# q1 e/ X. Y5 ~2 e9 I! |* B: _2 _. z4 a9 j
79.6 W25QXX驱动移植和使用
7 w+ v9 p$ }0 B/ ^7 _ p/ SW25QXX移植步骤如下:/ J% a3 R8 i" M( a
* n) Y* g0 H: K# K* P+ K4 C% L 第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
% j6 f9 Y. ~7 q3 H8 f7 X1 B 第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。( R# q6 Q2 U6 c6 V: I
- /*
9 Y) @; Z( e, T2 n$ c3 m4 ^# l4 f - STM32-V7开发板接线
7 w _2 j* q Q0 }7 y. M6 @% N - 2 x! R/ M. V# j! X
- PG6/QUADSPI_BK1_NCS AF102 s2 n" g5 E: c& p" f9 j: y0 s
- PF10/QUADSPI_CLK AF9
( S5 y* I% m# ?$ R/ {* E - PF8/QUADSPI_BK1_IO0 AF10
- G- |! r% m- \( D! ? - PF9/QUADSPI_BK1_IO1 AF105 S+ j: \7 w! K8 m/ c0 {* h
- PF7/QUADSPI_BK1_IO2 AF98 a/ r0 u1 J1 p( U/ D0 f
- PF6/QUADSPI_BK1_IO3 AF9. `: U+ E, p6 _) n P
8 {3 \# }! ]0 V- G* Z5 t5 U; _- W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB6 K- z6 R! O+ A( T3 u0 z! [+ D
- */
( ~1 l# {2 C* v; W, A7 A$ |
( N% } ^3 ~5 z- /* QSPI引脚和时钟相关配置宏定义 */
+ v9 a2 K. `! D) ~8 r8 M - #define QSPI_CLK_ENABLE() __HAL_RCC_QSPI_CLK_ENABLE()
; e: O5 J4 |! g - #define QSPI_CLK_DISABLE() __HAL_RCC_QSPI_CLK_DISABLE()
0 `) ^1 K. g; b" r7 F. j6 C5 s- g( Z I - #define QSPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE()2 A2 N `) W8 V+ Q0 N) K' T8 Z) p
- #define QSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()# w; }8 A5 i& c& A* \; R
- #define QSPI_BK1_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()$ u9 c% v$ j0 G5 ]- \. m" e) v* O% U
- #define QSPI_BK1_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()3 @, ]# k \: b* w* |9 S
- #define QSPI_BK1_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
- u" l) Q9 H) i2 V: ^, g% ] - #define QSPI_BK1_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()* |; X/ q" N1 P
- + c1 W( q( y1 r6 c7 D1 U" s
- #define QSPI_MDMA_CLK_ENABLE() __HAL_RCC_MDMA_CLK_ENABLE()8 S6 y9 p9 X9 f
- #define QSPI_FORCE_RESET() __HAL_RCC_QSPI_FORCE_RESET()7 T( S4 A) r1 y
- #define QSPI_RELEASE_RESET() __HAL_RCC_QSPI_RELEASE_RESET()( ^$ Y2 ? E. ?+ k3 G/ j$ v, `
( R; x6 L% }- \+ ?3 U; i$ z- #define QSPI_CS_PIN GPIO_PIN_6- u) F5 ?* N F& x+ _
- #define QSPI_CS_GPIO_PORT GPIOG5 @1 ?( V& M; i# T
/ z9 b _, m9 e& P) c* ~- #define QSPI_CLK_PIN GPIO_PIN_10 M% I5 G( W F8 @1 m
- #define QSPI_CLK_GPIO_PORT GPIOF
7 h8 q! m2 @9 |; z- j4 o
; D2 p/ m/ H8 w, M- #define QSPI_BK1_D0_PIN GPIO_PIN_8
( b2 v7 h1 t& \% ~, I9 N - #define QSPI_BK1_D0_GPIO_PORT GPIOF
) D$ K; y9 S, J' J! Q7 D
5 Z* d o& D$ U4 \7 O: c- #define QSPI_BK1_D1_PIN GPIO_PIN_94 _2 s M+ l0 X3 H, d3 l
- #define QSPI_BK1_D1_GPIO_PORT GPIOF; j% I) J5 A0 P" l; g
- + T. W' F& h t' _
- #define QSPI_BK1_D2_PIN GPIO_PIN_7
' g6 K% o- j* \6 y* D - #define QSPI_BK1_D2_GPIO_PORT GPIOF7 e+ d- p; z1 m. a+ Q
- 9 {2 Q# n7 `6 b
- #define QSPI_BK1_D3_PIN GPIO_PIN_6- y! j" G% ` L. m2 j
- #define QSPI_BK1_D3_GPIO_PORT GPIOF6 F2 O# _, q( Y m' [, E9 e6 {! ?# C
- 根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件- o! l+ e \+ L E! L& Y- ]3 p
- /* W25Q256JV基本信息 */
$ A9 W7 f( P5 ?' S. j/ Z - #define QSPI_FLASH_SIZE 25 /* Flash大小,2^25 = 32MB*/
! F8 v4 t; V, a' i/ s - #define QSPI_SECTOR_SIZE (4 * 1024) /* 扇区大小,4KB */8 T- H2 k% r6 D0 E7 y5 D" i, K
- #define QSPI_PAGE_SIZE 256 /* 页大小,256字节 */
7 K$ N- W9 S0 v% l& D' w* |/ x2 l( @ - #define QSPI_END_ADDR (1 << QSPI_FLASH_SIZE) /* 末尾地址 */. @7 t" T9 y3 d& o- c) k# z1 C
- #define QSPI_FLASH_SIZES 32*1024*1024 /* Flash大小,2^25 = 32MB*/1 ~: F( W! ]2 w$ U. E
1 Q3 u$ Z5 r1 t- /* W25Q256JV相关命令 */( a* [6 M( v9 e) }2 d2 p) L b# @+ _! o
- #define WRITE_ENABLE_CMD 0x06 /* 写使能指令 */
- y2 y- X {# ^+ C: j - #define READ_ID_CMD2 0x9F /* 读取ID命令 */ & g2 G8 a6 T9 C! _! q
- #define READ_STATUS_REG_CMD 0x05 /* 读取状态命令 */
& ?& h% p3 k% e6 m' P1 y5 Y - #define BULK_ERASE_CMD 0xC7 /* 整个芯片擦除命令 */
W2 ?! z3 C7 G; f - #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit地址扇区擦除指令, 4KB */
1 @$ b: k! \: q - #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit地址的4线快速写入命令 */
# ]5 ~6 H6 j( A - #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit地址的4线快速读取命令 */
复制代码 9 _! @( h0 k) j' @7 j6 C) L' E# y' a
第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
t- o0 J& h3 ]+ j# h/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
$ P$ b7 L, y: @8 [MPU_InitStruct.Enable = MPU_REGION_ENABLE;' ^/ m# M; P- b4 p
MPU_InitStruct.BaseAddress = 0x24000000; E% F+ j1 n- E q7 ]/ N
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;# _8 F f z$ K# Y3 c) O
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
! Z5 k; H9 Z9 ]2 n, nMPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;, p. L1 Q0 J1 N* u, ]
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
+ N9 ~; W! A8 p) ]3 |" QMPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
0 a% b" R) D, W0 H/ }: p* v% NMPU_InitStruct.Number = MPU_REGION_NUMBER0;
6 k7 p, N1 Y4 N8 N1 d9 _MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
. a7 r3 X) N8 q+ M8 ~MPU_InitStruct.SubRegionDisable = 0x00;
, | U; ?& i: ^) [5 W/ H% m% u, t1 aMPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
! w& P% v" T4 @: j8 ]1 G& k
2 R/ U x9 e1 S/ I8 L! M7 rHAL_MPU_ConfigRegion(&MPU_InitStruct);
4 p/ O. x! u6 z0 i ^1 U. k 第5步:初始化QSPI。+ {+ a# Z' c* @# a8 W/ _
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
& J4 m& ^1 z6 u4 Ebsp_InitQSPI_W25Q256(); /* 配置SPI总线 */) H a. c: {. Q; g# O& x* @( E
第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。; S3 n$ {( [9 o2 [- W' R4 W
第7步:应用方法看本章节配套例子即可。& H. T! M# l8 X1 D1 Y" G/ {9 [
79.7 实验例程设计框架
6 S% w9 R- m. A* _通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
6 d4 R8 {9 D( @; U$ H9 P( Q, a+ B: J+ y7 h5 D
! H# h8 p# {+ R3 l$ c0 J# D
( }3 s6 h) x4 c! @
第1阶段,上电启动阶段:7 W; o- {2 b0 x7 a4 _
- n! n7 h" ?- h2 i- Q5 I
这部分在第14章进行了详细说明。 z3 z& i# F) u. @
第2阶段,进入main函数:' q( S9 E1 `0 r
6 `" ?5 |8 ~& A7 @# c% Y/ \
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
1 }! q. y/ ~7 t% Q+ S% T/ O 第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。
/ f3 w- r! X: I4 f7 E0 a) B79.8 实验例程说明(MDK)6 i* [9 \* g n& W J
配套例子:
, c. u9 ^2 q9 h- J( J4 J! d9 T* ^- M! H0 D5 n) J8 g9 n
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)
: m$ l) x0 i7 Z8 }1 }1 P1 I: H1 f2 M6 M: Q% j% X
V7-059_QSPI读写例程,查询方式
; W" g6 p) C5 u7 z9 P9 K+ x/ Y
! x7 t7 [' [" X, @4 p* U% _4 J实验目的:' T4 t( x) q4 `
/ S! X, g- a! V" B, D' X- R3 J
学习QSPI Flash的读写测试例程; D8 ]9 `* Q( U9 ]& B
实验操作:
5 |8 [% x- w) e' e
8 w" a: C9 T2 W& a2 g# t支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可/ Q8 m7 k# a, X! Q$ O) N+ c; n
printf("请选择操作命令:\r\n");
; k6 |! V+ c3 k$ mprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);; j& R& B' r }" [6 m
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
' ` c- O- G% n4 W( D: t3 Lprintf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
e; _2 D [- B) @* `: Rprintf("【4 - 读整个串行Flash, 测试读速度】\r\n"); P. k. h+ L0 L$ Y c( V ]
printf("【Z - 读取前1K,地址自动减少】\r\n");0 ^5 D8 R9 M2 s) P& ^1 ]
printf("【X - 读取后1K,地址自动增加】\r\n");
- w/ o" b) _! p: h* r& O& o8 Gprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
! Y, O/ m1 ^* u+ T2 ^printf("其他任意键 - 显示命令提示\r\n");7 ~) A' W0 u1 ?' R) y6 N
上电后串口打印的信息:
" j6 Y( M! B' m, o; P. T
" E x& _& y4 @6 W" i1 v c2 |波特率 115200,数据位 8,奇偶校验位无,停止位 1。2 P6 m( y- \% a2 l$ ?
5 |% j$ ?( O' ^% {; J$ h( t: F x/ d2 k+ e. t; i0 v1 r$ q, U
* q. `* R3 g n8 g9 B6 W% Q
程序设计:
! v5 m2 b, Z$ S" G2 r
( Z4 l* f5 N$ |) o6 b: C; J) H& H8 U 系统栈大小分配:
5 X. a, ~' a/ U0 w. {8 |( E! g& y5 n; H9 V7 h
! i( O& O! w8 e: s9 [" E# [+ m$ v3 H7 k4 k
RAM空间用的DTCM:4 \( y8 A2 e4 ~& u8 C
1 H" @- f! H( x" Q
. x. C" n5 n+ \' \) G7 O4 H( X: b. A0 N/ B$ V
硬件外设初始化7 j/ Q/ i6 _9 v) f; K- W! F% R8 R: u
, J x! @( ^. @/ R6 \4 y硬件外设的初始化是在 bsp.c 文件实现:2 R8 G- }$ R! }/ D3 j% j; `" e" T
" y1 a% ?! }8 ?/ N" t2 Q; ~" i- I
- /*. [1 m' |- O6 B2 F ?; R! _
- *********************************************************************************************************. E& q" D( J3 t) x1 @2 I
- * 函 数 名: bsp_Init
* W- t4 R! I/ K6 h a) z9 Z; M$ u - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
G7 g( o3 g9 W. d# L - * 形 参:无1 H) t4 h; ~+ s6 T* M- w1 n
- * 返 回 值: 无- d, [# X" j, y+ Z) q
- ********************************************************************************************************** {7 l' ^. r; i/ R0 m2 n% C! r
- */% l# W# g" Q7 W2 P
- void bsp_Init(void)4 [& i/ B5 j' q
- {
, a( d/ ]( c0 l* j' o( Q _) B - /* 配置MPU */
0 b: n: x% e( P - MPU_Config();
2 ]" X: o+ m. K4 B - ! L+ W: W% t& J( s$ \* w' v
- /* 使能L1 Cache */
- B1 S8 l: m& }; k - CPU_CACHE_Enable();& W$ D: ~* F" G0 f
- # Z9 L% k0 Q. V7 |1 J& ~
- /*
/ N8 h d. ` [9 s w2 K1 N8 k - STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: @; a$ D) z- {* E! g. a, s6 {+ V
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
7 L8 P' O' ?' J0 M - - 设置NVIV优先级分组为4。
6 `9 y$ w$ z8 K, x: j - */
6 T) H3 p% ?- O; z$ Z9 B; |1 R - HAL_Init();( a2 x- c* d* p
- + _ A' n( w5 x! k) Z* Z
- /*
3 z1 U4 X* x% V# O5 {" B - 配置系统时钟到400MHz6 z! Z# W' s* j: g" \
- - 切换使用HSE。
! k& M4 ~) I/ Y) r2 } - - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。$ D" W1 u9 @ a) m) z
- */
5 n5 [% u! P. g" Z# e v/ c - SystemClock_Config();/ R* |8 z9 R' @! q$ Q0 F' c
+ J6 g4 u" s7 S B4 W; M- /* 4 N' m7 x3 a! M f+ {3 h
- Event Recorder:) k9 p" p4 n' ~# @: }
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
( V8 Y2 U$ x. j3 C - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
' _5 Z9 L/ H' z - */
/ W2 ]' B; W) s! @ - #if Enable_EventRecorder == 1 / q0 j& _- f2 q6 J! k9 ]
- /* 初始化EventRecorder并开启 */1 W' Z- g% h$ G: R6 s
- EventRecorderInitialize(EventRecordAll, 1U);7 c& D( ]/ ] h6 x: w' [
- EventRecorderStart();
0 Y, F3 y3 p) _. ^2 _& r: O* w - #endif
* J7 E( I. N, A, A& ^: L2 \ - k( Q1 g- Y8 @' B: J3 ?% Y
- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ " n! h2 H$ x' |, T# x: M& g
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */1 v2 b! ]1 j- f8 u7 `: E
- bsp_InitTimer(); /* 初始化滴答定时器 *// v+ O& c* v$ P9 U: e0 R9 f
- bsp_InitLPUart(); /* 初始化串口 */5 D% d% Y- d( h" N* C$ C% g
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
* C# U7 P; ^( ?5 Z5 F& h* F- n - bsp_InitLed(); /* 初始化LED */
5 b/ c# U$ @4 b% n6 D/ z - bsp_InitExtSDRAM(); /* 初始化SDRAM */+ z- A; W4 [4 n, v) A: I
- 8 z7 D! y6 e3 T4 B4 v
- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
6 M* C o* [9 C" y - bsp_InitQSPI_W25Q256(); /* 配置SPI总线 */}
复制代码 + R, Q: @# l) t8 I
MPU配置和Cache配置:3 w r# `, Q4 E
3 @1 N8 k1 f8 ]3 v) G2 P
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
. q7 i- _4 L5 ]: U+ k7 D4 ]' `6 M! T7 x L- f
- /*8 C* s1 f; P4 `2 }; R
- *********************************************************************************************************% Z1 x4 i7 b. C- ^
- * 函 数 名: MPU_Config
o5 w5 q w. k. ] - * 功能说明: 配置MPU
5 [3 V) S7 a* N# r - * 形 参: 无
$ ~: l" }+ _2 q3 ^ - * 返 回 值: 无( z, G/ e4 X$ S" S5 u6 W* t
- *********************************************************************************************************
6 w' d* M7 H8 n; k - */
2 ?& W# ]: f6 M7 O* } - static void MPU_Config( void )
- Y2 P O, T' C6 P$ J - {
8 B/ b9 L9 [& N4 g - MPU_Region_InitTypeDef MPU_InitStruct;
3 D N/ }0 D7 W. L4 ^0 T/ K
& w* c: D+ h2 c/ E/ \% ~- /* 禁止 MPU */, X7 Z6 i* A# [1 g0 ~0 @# x" `
- HAL_MPU_Disable();
0 E" m& M: O# n7 L
+ {$ c( E8 z- T7 F) N9 \# j- #if 0
2 n* [3 ]# g0 m9 r& y' G" w - /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
1 B$ A9 M7 h0 d |) K( e - MPU_InitStruct.Enable = MPU_REGION_ENABLE;* }, M) Q$ A! r! D
- MPU_InitStruct.BaseAddress = 0x24000000;4 C$ g6 k" W0 |- k
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;- P/ i0 T" }* E
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
5 T& _7 \2 z8 v4 L) }8 Q2 n - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
, e* }; W9 c/ e- c$ A1 |, ]: Z - MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;7 K! ]& z$ ]5 j4 O
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
5 n+ N) q; {7 S' F/ |9 p/ I3 { - MPU_InitStruct.Number = MPU_REGION_NUMBER0;; M8 j9 W, o1 ~ y
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;+ h! X. `. ]- f, @, o
- MPU_InitStruct.SubRegionDisable = 0x00;
/ j% @7 q0 M* J( o. p( J5 t - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;6 i( V( V. K4 o3 f# z) r6 M" e
- " ^) R O( e8 Y, J" X; {, |
- HAL_MPU_ConfigRegion(&MPU_InitStruct);. Y; i0 x4 C* R% g
- : i, D3 g& ^( k) E" m
- #else
; `" V3 I) Z2 q' d0 X3 q - /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
/ K. `9 m- L* z8 W% i2 @ - MPU_InitStruct.Enable = MPU_REGION_ENABLE;
( n) N9 C5 U) U# J2 x; i Z: L - MPU_InitStruct.BaseAddress = 0x24000000;+ q' v5 \! u, G9 Z+ E
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
M# A& C' A& B5 L5 x5 v - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
, {1 y$ O3 e4 Z9 T/ n* t* s - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;. z+ x) |1 R; P7 a/ I0 H* l
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
) t1 o% t& r0 x. j( U$ N - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;0 o$ j' [3 u$ V. E( s* B2 p4 r! X
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;5 s7 m/ \3 s5 ~/ M& r- I9 e; n" s
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;' c/ T* \" F+ _7 H! x
- MPU_InitStruct.SubRegionDisable = 0x00;
. p4 q5 X8 s* C, @; p* ]& n - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;7 N1 |3 J! M, H8 G; X
- , V. U6 u$ Z- ?
- HAL_MPU_ConfigRegion(&MPU_InitStruct);3 O1 F7 T0 t: B" Y
- #endif + y7 H) |6 Y. E
8 K8 {, O( h" y2 f1 S- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
8 D) G8 f8 R" N: l- O y - MPU_InitStruct.Enable = MPU_REGION_ENABLE;$ F5 V- t8 \2 p% h& d8 I
- MPU_InitStruct.BaseAddress = 0x60000000;
! J1 E( }( Q+ J0 j! y - MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; + ] @5 u& ~& u& f6 l9 ?) L0 o2 j3 m
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;5 M L2 F# J/ F
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
: `" I, F. ~; \" F3 ^ - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; ( O9 P& e- F- X/ [& r. R
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;0 v5 Q! f9 R. \
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
! C4 `" P l8 u+ h9 C - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;0 q* w. h$ u {; W; f
- MPU_InitStruct.SubRegionDisable = 0x00;+ ?4 c, V# Z |* I3 `- x
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
2 A4 p7 ~! j7 ~, u% \& O
P3 B# v* D5 u, I- HAL_MPU_ConfigRegion(&MPU_InitStruct);
+ Q$ [% g. g1 W& x2 O
~7 c. @# p! I2 ~- /*使能 MPU */' C6 T* [ l& o
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);% J& c5 o* Q* q1 p" L8 A
- }2 i" |) n7 h3 v! D; f9 y
- 1 h1 K9 B @0 t8 V
- /*
& t4 Y. w2 u4 s* J4 T! K - *********************************************************************************************************- b2 y' J/ d, J+ H
- * 函 数 名: CPU_CACHE_Enable! r8 X7 Z2 @, D- o! ?: o8 _
- * 功能说明: 使能L1 Cache
. G4 b8 l+ N# c - * 形 参: 无/ _6 H, K1 v9 s% _1 z
- * 返 回 值: 无
, Q8 g9 Y- {$ [6 t6 B - *********************************************************************************************************8 H" k8 F9 z* _
- */( U4 f" a; E3 v. s, t: ^
- static void CPU_CACHE_Enable(void)
0 q) _! c. W7 d) I, D - {
: [: S! ], Y: Y - /* 使能 I-Cache */
- J( g$ T6 Y; O: S1 x# S8 E. h - SCB_EnableICache();
* P, f' c; W. Y$ o: m( O- y - # A' X) I1 {5 f8 M/ r/ O
- /* 使能 D-Cache */8 _* W$ u$ ]: e7 H, g# R6 T
- SCB_EnableDCache();
2 z& t+ {8 w% w' L: S - }
复制代码 . o8 r4 m7 R6 Q
每10ms调用一次按键处理:
& o2 ]! f7 G) [% I: b/ J6 {0 R) w, }
) N D7 f0 Z" v9 }6 m按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。8 R, ?) G1 J R: I: [" f& \
, u% f6 k5 ]/ E2 v
- /*
4 `) l. K- ] s7 {! h F/ L - *********************************************************************************************************5 r3 y) Z/ }7 F) O
- * 函 数 名: bsp_RunPer10ms
+ X" T" B6 D* g } - * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
6 J7 ?$ B* ~: O B! E - * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。8 [. `+ u, ?( c5 m- y- O3 A0 g2 `
- * 形 参: 无
]6 N/ {4 l* D; Y4 H& G - * 返 回 值: 无
! x1 L1 W1 `6 T! w1 q - *********************************************************************************************************6 ]5 j" @$ ~. [5 }" v2 A& S
- */" w# A3 s! X& r' f {+ r( T
- void bsp_RunPer10ms(void)
- Y U9 h+ H" }) C1 J- b/ Z: v1 ^" S% A - {* p' S, l6 X$ q
- bsp_KeyScan10ms();
0 f% I+ m. L$ p$ y - }
复制代码
& a. a; p. S; e 主功能:
* b% K) G5 M+ V: y
# w3 m; V$ [5 G( ?( @! G% n! h8 y1 J主程序实现如下操作:/ Z9 C9 k# Z/ S5 _+ _
; J. V) q E8 D2 R6 m
启动一个自动重装软件定时器,每100ms翻转一次LED2。
6 z5 B3 t6 U4 e 支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
( o2 C& [- j& E printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);, x2 l9 k, Q8 j9 H- E
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
. _1 W0 u$ f+ m printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");" t+ a0 `6 J- L& j
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
$ d" ?* ?8 T8 H0 g+ Z printf("【Z - 读取前1K,地址自动减少】\r\n");1 ~; K% C9 n6 \& B' d. V, G
printf("【X - 读取后1K,地址自动增加】\r\n");: H# J% B0 A9 _. d
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");' S9 Z9 U* H4 Z# |! ^# W/ ]
printf("其他任意键 - 显示命令提示\r\n");0 P! I! D% ]* U
- /*) L4 r [1 P: w* q* S* r0 L" }" m
- *********************************************************************************************************5 t* J6 ? A3 H' r; i
- * 函 数 名: DemoSpiFlash# e+ p' a) Q; ?
- * 功能说明: QSPI读写例程! U/ E; D0 e3 a2 J! ^( S% l
- * 形 参:无
4 i( s' b5 U. H - * 返 回 值: 无/ }' b1 g9 S& G9 [& }( a
- *********************************************************************************************************, L: Q8 r- W, U0 j: t
- */0 O9 _1 M# v7 O; _
- void DemoSpiFlash(void)$ Q/ l- y" V r# e
- {, b+ r6 Y: O* s K7 I$ [: h! J0 u
- uint8_t cmd;0 h* i; s- U7 N* X
- uint32_t uiReadPageNo = 0, id;2 ]! D; ]& b8 j' r$ S8 m
- k% ?% O( D6 z, y" j" Y- /* 检测串行Flash OK */
5 i+ p3 o A) K; r - id = QSPI_ReadID();
+ \7 K% U1 j* j: Q. e) e8 D - printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);6 K0 G! z8 ~ i- M( i: m
- printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");
4 W# S3 l; c8 y' S3 J: H$ ~0 _
( y" Y. l. T; ~0 h- sfDispMenu(); /* 打印命令提示 */) p2 z: N7 `& Q$ y: s
- ! x$ z2 T r. @1 \
- bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */" l3 q# O5 i! H8 e J
- while(1)
* `% c0 j! @) v" g+ P+ F( Q# j' J - {
% m+ k" e3 }2 E; _3 ^ - bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
7 K6 W3 Q( z5 ?7 M3 _4 Z- ]0 s - g3 s D8 @8 ~' i K, S! h
- /* 判断软件定时器0是否超时 */5 T+ T: B% t+ c8 j% O
- if(bsp_CheckTimer(0))7 T4 L1 `. S$ _& b. O7 i5 Q0 O6 ]
- {
; v* P% B8 ^* W$ c - /* 每隔200ms 进来一次 */ 6 D9 B* d3 k% }3 r% U; f
- bsp_LedToggle(2);3 ^ N. i" x5 z& j M
- }) v+ _1 R% s2 S$ z' F' q# u
- 0 b7 _. N1 o+ Z7 y# w9 z$ j* h6 O: G
- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */. i6 i! H( p2 Q3 [5 J
- {7 {% R% Q O0 \" `$ l; W
- switch (cmd)4 L; E' W, }1 Z4 f1 @
- {: c$ ?, K* i9 M9 N0 M9 T: d7 ~
- case '1':
8 E! ~2 c) e j! o - printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);3 m9 C) O( l7 K% I
- sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */) S- g0 Y9 y! }
- break;5 F8 P- q; `7 H
- 5 x B! y8 p$ U
- case '2':
9 z7 U7 _# j3 p, ~( l, J - printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
/ @* ^+ }9 a: O$ | - sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */
9 t( ]4 ?# m/ p4 m j+ B$ n - break;
) q. Y! R' B6 L" p - 2 w; g0 Y1 D. J
- case '3':
' T/ i8 y0 L) ^3 F. H9 y/ S$ i+ j - printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");7 t4 K; p f1 {% `
- sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */. S9 j% Q5 ^ @$ f( P
- break;& D4 i* x. f* d& u
- ( Q2 v3 ?& o( D+ V
- case '4':8 a7 D7 [, N% O$ \
- printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));4 ~* R# X, l/ d7 x* v! v8 A
- sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- a$ O, P5 @4 F" M) h- X) w
- break;
" x" f& T' G/ m7 N7 ~1 Y
1 {9 q0 o9 h. J: P. y% ^0 n- case 'y':
! c) d& W" \: G: h - case 'Y':: g' K0 P; \" o: X+ T( \3 C9 @
- printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
% o/ l7 z0 G# [ j$ D& q) [ - printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");. _7 H7 @9 }% N+ G4 t' m
- sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */+ c* s& v8 k! U0 w% F( E, P+ L
- break;+ u. Y7 f1 d, q& ~1 p4 [
- ?, |$ C1 X1 b- \" r- n
- case 'z':) J8 s* ~$ |. r# j% w! K$ n1 ?* Y
- case 'Z': /* 读取前1K */: m% U' J' K1 U* p
- if (uiReadPageNo > 0)
% E" x8 p! w+ M$ j/ s - {
0 `: E3 Z4 k Q/ a# H7 @ - uiReadPageNo--;0 m9 w# A4 @% \! u
- }# p9 R$ }) u1 G; h- [
- else, K K) N5 \- `
- {
6 V# s; h5 x+ y' l) o+ L3 z - printf("已经是最前\r\n");5 Y" \- v, W1 W0 s) w
- }. G2 `6 Q% p3 R3 Z9 X) h) }! K( \
- sfViewData(uiReadPageNo * 1024);
0 o' I7 s2 C0 {! G% H! p - break;2 e) Z: p5 j' P& |: q# ~' W
- 0 L/ F. X. E ^9 h
- case 'x':
. H+ g& B F8 F. O - case 'X': /* 读取后1K *// D; m/ H" S, g: p
- if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)! L2 d* _$ z6 u9 _/ N( r; F
- {9 ]& d( E0 U5 c3 K L, Q
- uiReadPageNo++;
5 o- C- {) y, T; ? - }
" K; @3 ]; G) B- f' J - else. U; q7 c: B2 @! s
- {5 Z7 j7 ~; V# w0 B- @: B1 T
- printf("已经是最后\r\n");
* x0 | [: G5 ~: q/ J - }
, f" \8 F% q( c7 o, Z - sfViewData(uiReadPageNo * 1024);: U) N; s4 \; Y" A" U" d9 c; r
- break;( {& t3 s9 ]3 Q( Z" T' A3 L5 V
& w5 @( T6 y3 x$ S' @- default: G& r, q) V; y7 Z5 T4 D
- sfDispMenu(); /* 无效命令,重新打印命令提示 */6 `% _$ I- T P! [) G/ Y! B: C
- break;
1 @$ Z9 J1 r% \ - }
# _# z) Y9 W N, Z - }
! T5 a0 O* q H9 J - }
; V+ P4 U; ], J% U* X9 \2 c i; | - }
复制代码
, Q4 B/ G6 R0 o& x2 }1 T79.9 实验例程说明(IAR)
8 q( r- x c( }配套例子:
, c4 S- Z* z) w6 `" c) L; Z9 h. `7 Y
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)
3 E" H* P) Z2 i8 ]& n7 ~, x" k- H4 p
; q# _ D, c9 H4 \V7-059_QSPI读写例程,查询方式
p3 f: o/ w' V4 D. Z
# Z# s0 w/ G1 v实验目的:; I( N9 a2 Y! {$ M! `
: u* B, V5 {8 R/ C( n
学习QSPI Flash的读写测试例程' a$ p' C" S8 @1 k* p* Z4 g
实验操作:9 [2 b @. B8 v# x% _
0 i2 }; U) |' E1 }
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可+ T3 C4 c( J0 R7 ` B( ~* D
printf("请选择操作命令:\r\n");
6 v) B: k t9 d5 A+ i3 Iprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
2 W0 [7 ]; g* dprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 A3 ^# `6 Q) r- o: {4 nprintf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");" ^0 @) o* k) M' G
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");0 o% p! I8 S3 @- h
printf("【Z - 读取前1K,地址自动减少】\r\n");
7 q6 ? h% A* Uprintf("【X - 读取后1K,地址自动增加】\r\n");. I% |. G" a% t5 M
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
; ]$ X3 m$ n/ w, `3 g+ F# Tprintf("其他任意键 - 显示命令提示\r\n");! v7 ?+ O7 P& z; a1 C
上电后串口打印的信息:
" Y5 j- V. L$ ?- x( |
d+ p: n/ T4 Y5 b- _1 u+ F' R波特率 115200,数据位 8,奇偶校验位无,停止位 1。
9 k. o) X2 o, ]9 f9 ~% K0 j# \/ j0 R" I% w' E6 i; ~( r
& P6 \* F7 }5 Q) {+ p
9 z+ \' e5 F" l+ {程序设计:0 h9 U7 d1 q, R' ?; C. j
: [9 }) ]! n9 G5 h4 ]3 ? 系统栈大小分配:
3 U6 F1 T% K, }1 {) S8 @/ K
( l4 ?; ^5 Q& J* y. v' b& f1 `; E# m) w
4 @8 ~" r% i3 x RAM空间用的DTCM:: t' `3 n$ p: J1 ^9 |+ P Z
4 d# i4 J! d, a9 v: C9 p. D7 [
6 ?9 i5 Z7 k- @/ l, d: f
9 w2 P5 D% P' v" K 硬件外设初始化
9 h9 F" L5 S' _- k- A
4 N p9 @8 s2 n! R+ g) b+ `硬件外设的初始化是在 bsp.c 文件实现:
& L: n5 @9 R, b# Q0 f+ V) L ]+ T* Q* [1 {& f) T6 u
- /*# d6 t7 X. o; e# E: s6 k7 X* E# ~
- *********************************************************************************************************
8 G' l" L; B" [ |% `. X/ D/ r - * 函 数 名: bsp_Init
! [9 S ~8 a+ u1 p - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次4 U/ X5 N# m: }/ @
- * 形 参:无
+ v/ p! z! v- Z( q4 h2 t/ r+ P - * 返 回 值: 无6 S# i/ X* Y/ y- J. e0 j! S; i
- *********************************************************************************************************
1 a* z W- _& [, r7 } - */) Z6 F p# S! {/ H& I& `
- void bsp_Init(void)
1 Z. l: T% w3 W/ e - {
3 F) F: i2 F; R8 N - /* 配置MPU */
/ {' O6 u1 J, ^( ^. i - MPU_Config();" r. f4 J' W' F4 o/ q
- % O, r& S. [" M+ x2 v* b L
- /* 使能L1 Cache */, Q6 M4 T& U& b% P, T; r/ q( p
- CPU_CACHE_Enable();
# g( ^$ r* m6 w! ~3 d9 r {6 V* w: a - , V* p; c9 i7 G! J& n/ X3 a
- /*
. }; A6 i* Y( o# I4 {2 L. E* n - STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:1 g: ~: U+ [: t& j$ O
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
3 _8 L+ X3 j2 @& r e, j1 h. x - - 设置NVIV优先级分组为4。/ ^8 x F5 y6 s
- */- U# H% d2 l7 v
- HAL_Init();
1 ?9 \: W4 G4 ~3 q2 j - 9 D) B% f, ~$ Q4 H8 v" Y! I3 x& c
- /*
! _; w$ \0 l: s: n' Z6 w5 q - 配置系统时钟到400MHz
# S7 X" k& D* ]$ l - - 切换使用HSE。 D' |) o( _8 y& C; A. ?" K
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。0 c3 O/ [5 |/ h
- */
6 W4 b* l% X: P" [ - SystemClock_Config();
$ C; a. u& e5 i8 A; {
3 q+ @6 G1 ?( B( z4 u4 n! o. z- /*
. `% {+ }$ ]* q% y& S, m. R - Event Recorder:
) G( ~) ]' t" z! I9 Q0 ~3 M - - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。% Y& ^% g& X. x: \ Z
- - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章9 p8 S. N! P0 A! p' `5 r
- */
& N& Q) {- }3 T - #if Enable_EventRecorder == 1
7 @+ G" B; \+ Y4 h9 F ` - /* 初始化EventRecorder并开启 */
U" ?# e2 C2 Z4 ~7 n- R - EventRecorderInitialize(EventRecordAll, 1U);4 r" b9 U" t# P9 F" h1 n0 \
- EventRecorderStart();
2 r: _) `9 Z, W& o$ @+ T4 q% G2 n - #endif
; d& w$ b& c# f0 j: f* J9 I; }$ l - 7 @3 B0 k! A. |3 M1 ^
- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ 4 M' x% v! J4 Y. v5 |; Z
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
0 ?0 ^/ v; q J - bsp_InitTimer(); /* 初始化滴答定时器 */
1 B- Z3 C7 Z j; P; l - bsp_InitLPUart(); /* 初始化串口 */
! \. l# n5 r# K& z7 k0 [ - bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ . b) i% V5 W/ O$ b! @4 F$ }$ j
- bsp_InitLed(); /* 初始化LED */
* E5 o2 R3 M: Q1 @# x - bsp_InitExtSDRAM(); /* 初始化SDRAM */. `& G: b4 d8 g$ P+ r" H
; {6 n) v* J. P4 H1 s& b7 v' a, q- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
: ?& {7 M, F7 B/ a9 S9 i - bsp_InitQSPI_W25Q256(); /* 配置SPI总线 */}
复制代码 2 ?* V/ u! u8 c+ q
MPU配置和Cache配置:
6 k; Q, k, _2 \7 Z) }5 ^ \9 C8 U2 ^9 X& ?4 F$ q
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
4 |9 B. s* _9 ?' Q/ X- U& }) N: E( u3 g+ \- l" G" y6 k, ^; g
- /*
4 Q6 c6 L7 [- t+ A8 h - *********************************************************************************************************# ?1 Q6 d; j' E8 K+ a
- * 函 数 名: MPU_Config
7 F, f; c$ p' H2 I2 \& y5 b$ ~ - * 功能说明: 配置MPU
2 d" ^4 M- t6 n7 Z& R" A# H" g - * 形 参: 无
% Q }+ D# i1 h. j- L; U - * 返 回 值: 无
! O4 y2 h! B8 X5 I% d+ S - *********************************************************************************************************) E, Z* _5 I9 F
- */
1 J0 b3 f) n: z' a4 X - static void MPU_Config( void )
& S( L% A# h) N* E) v0 H9 ` - {
$ }* }# \# Z, ^9 C& m2 b0 \ - MPU_Region_InitTypeDef MPU_InitStruct;
' A/ E- U" X/ K9 w+ P! @; H
' C% t7 b7 ]9 h; S4 H6 |: N- /* 禁止 MPU */
0 r$ ?) c1 N! O, p3 V9 S8 Y - HAL_MPU_Disable();7 Q& C1 z% d7 F+ s% V4 f; e; o J# I
- ! B2 R+ D# q; \, S) {1 A
- #if 0: p+ }( m+ f# \& w, S/ ^1 E
- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */- p9 h" e3 y/ o/ K9 F) E$ B
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
$ G2 ]( p5 j" x( G% F - MPU_InitStruct.BaseAddress = 0x24000000;
! r8 @" `' I0 V' d - MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
* b. C2 w5 ]" Q/ }: o1 h - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;$ C: ?4 S. K. B4 L+ ^' e
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
& K7 f7 r" m6 n; _2 l" ] - MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; l+ _( r* ~$ U3 u9 ^
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;' [2 @2 L9 d+ e
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
$ ~. c( Y3 e/ T% } - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
/ k! J2 J, o( s1 G: V0 z+ J6 ^ - MPU_InitStruct.SubRegionDisable = 0x00;4 n1 c0 r- ?1 S/ X6 \9 X4 W
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;+ l7 |6 n9 M' B. g
' n8 C( U o* M% A" @$ T- HAL_MPU_ConfigRegion(&MPU_InitStruct);! m: @) B" |$ ?! a9 D! {
- 2 L* @2 O4 n D! [( s( z6 Q1 m$ X
- #else
3 o! x) S: i7 a7 [ - /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */! e" k$ B8 b4 H+ _5 Y% |
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
0 B. ^, ?6 D. ?; P, ~; g+ o) d5 A - MPU_InitStruct.BaseAddress = 0x24000000;
5 s/ x% [$ \' `: e - MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
: ?* Y) l+ T6 N: E9 d: N - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;* V% V$ |- ^+ r( |: V9 {2 z5 M" C
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
_9 K, e6 l0 Z2 T) ?7 e. U - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
) m% M t; V" @2 l - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;0 ?' T+ `( H4 r5 _5 i
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
- _7 M! `, T" C/ n/ f7 x4 C0 ^ - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
6 k. S+ D6 E! H( e4 c2 h - MPU_InitStruct.SubRegionDisable = 0x00;
# @. P$ c6 L' d! y1 \ - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;" q j: x p+ q, F0 y# v" z) @% ^
- 1 L7 ]& c8 V* v& p# [: g7 T# |
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
" ^: S9 s9 w3 a - #endif
) ^; s3 Z0 m$ E6 W! S$ R) a- } - 4 [$ v. \$ {. Z
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
4 q9 I/ {. r$ d3 {; x: R" s - MPU_InitStruct.Enable = MPU_REGION_ENABLE;
( W- x* \6 O2 ~' O$ F, R, G0 {( Q - MPU_InitStruct.BaseAddress = 0x60000000; `* ?" g! o, U9 R1 V- g4 J
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; 5 a' D9 r2 N# l$ z; J+ C% u0 K2 `9 T
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
0 [* I& T( H4 i2 Y8 I - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
. }# B3 `) C0 O* N - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
- B* L4 @$ H- i" \) t, @& H - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
$ F9 u( t, e% o4 C- i# [ - MPU_InitStruct.Number = MPU_REGION_NUMBER1;
4 f: U. A. T; L - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
( W1 C9 F# y' c, ~ - MPU_InitStruct.SubRegionDisable = 0x00;
/ `0 b0 Y% G3 X8 H( @" F! A - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;+ m! `/ B1 O3 M/ O% u" Y# R: |
: t# K% x7 R1 W( a/ G8 D D- HAL_MPU_ConfigRegion(&MPU_InitStruct);
5 T, Z7 M2 e' ~/ j; H n+ M2 d" @
0 ^4 V" K! q5 V5 L& e+ T( V+ \8 ]- /*使能 MPU */5 T) E; b6 ~: q
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
6 C5 Q3 k: W u; J Z9 M7 H! |* H - }& a4 `+ `! ]) X
- 4 \ R/ @2 t) C0 G
- /*
- U% k$ R/ w5 W$ e% P7 K. { - *********************************************************************************************************
4 H& ]! Z( {1 k+ Z/ e; r - * 函 数 名: CPU_CACHE_Enable
; ^" m9 Z5 _# h4 K: K% U" k - * 功能说明: 使能L1 Cache
4 }. ?' o6 F/ X+ i0 e" E) I - * 形 参: 无( D1 N- g2 K, O, y; G. M+ u
- * 返 回 值: 无
4 f8 [5 h7 W5 c - *********************************************************************************************************' k0 C n8 x# F
- */
) }, ]! }% [$ h$ I - static void CPU_CACHE_Enable(void)3 X2 ?, I3 E: w7 s: c
- {) J* u2 F' G; F9 y: K( T$ S
- /* 使能 I-Cache */
% u' P* i: E$ l" U, o$ Z) w5 E - SCB_EnableICache();. r& w% w1 O' K5 G
) r+ n. [; M7 d( E) [- /* 使能 D-Cache */
& Z$ Y1 a* z$ [1 q4 n/ T - SCB_EnableDCache();
% ]! I5 j. K/ C, q) A( T+ t5 f - }
复制代码 8 e V7 t9 Z' z5 t; ?
每10ms调用一次按键处理:/ m0 K( z" E2 Y6 V/ O8 _! G3 B, ]
( w6 K5 _' G- G" K
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
/ D9 H* F3 J0 ^' x3 Q `5 b6 U5 H5 t8 r; J
- /*
- D$ j: K- _" i" `6 m/ W - *********************************************************************************************************
4 [/ F6 l, M- P- _* ^, I( Q - * 函 数 名: bsp_RunPer10ms6 K. p3 I q/ Z* ?" u
- * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
& c/ r; ], ]: N" W5 j) ]+ O6 P - * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
; B- ^: `0 a3 M8 x- Y* n - * 形 参: 无
. _) x! w3 [- _- l - * 返 回 值: 无
' }1 B6 c" ?3 }* }9 c8 |: s. n - *********************************************************************************************************
/ J+ }7 u% H1 `, r5 T- g' @/ m - */0 C# K9 C8 L. r! h: }/ T
- void bsp_RunPer10ms(void): P5 P3 e; ?+ l& }; P3 N& d
- {6 `' h: M6 p1 x* i' H8 x2 O
- bsp_KeyScan10ms();+ P: \7 k# G$ N' B# l
- }
复制代码
1 q% ^$ m: ]; m7 u! f7 S 主功能:; j2 E* v" B# d) x2 T' m! ^
0 L: O7 N0 m3 n# W& s0 u6 W$ ?* S主程序实现如下操作:, T) L) `! y6 {; J7 k5 ^+ P3 e
2 Y2 |2 b% p9 j( B# K 启动一个自动重装软件定时器,每100ms翻转一次LED2。4 I( ]# D$ ^2 z' a0 k2 _8 M3 \+ A
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
* A3 q# |6 Q7 S: b( H: [* V# K. C printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
7 O" A) S7 A- U% E printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);5 C2 N6 R3 u8 D e- w: C
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
9 k% Y3 J1 o1 b% { } printf("【4 - 读整个串行Flash, 测试读速度】\r\n");5 Z# w' D* \9 I. |: H; z/ U
printf("【Z - 读取前1K,地址自动减少】\r\n");
$ K* D1 o# `1 Z( h3 s printf("【X - 读取后1K,地址自动增加】\r\n");
" A) G, Y2 Z) |# d% z) T# e printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
7 `) R* i% v0 N, L printf("其他任意键 - 显示命令提示\r\n");. V) \7 S% a6 d* [8 Z: O% k' F! [
- /*9 v( B, s$ F5 Z- X! |
- *********************************************************************************************************
6 {1 p) S U$ H2 w! N1 E" O: a - * 函 数 名: DemoSpiFlash
( a; u0 N0 d' B' v% q - * 功能说明: QSPI读写例程
% t) g/ S& M( r' O - * 形 参:无
, G8 A9 @" g$ k) _& ]0 H - * 返 回 值: 无4 g" n4 K/ r$ Q% b
- *********************************************************************************************************0 |1 N2 \' ] k/ t) d/ F3 ~
- */6 ^9 Q. o( {: l# ?9 M
- void DemoSpiFlash(void)1 Y7 U5 B1 m c# e3 [0 e4 p
- {
& k6 s( N' q5 R; Y& ]5 y! l& F - uint8_t cmd;
. o" m. F, y1 u, h! R( o5 p4 s - uint32_t uiReadPageNo = 0, id;" O+ i" y4 r+ A( L, [2 s1 h! j
. D8 W% d; @2 l0 j& F! p( a- /* 检测串行Flash OK */
5 n' C; [3 K. K& A - id = QSPI_ReadID();! F% A1 [! j% y. ~
- printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);7 _+ H* A/ g: \+ e7 {2 W( Z. Y2 q
- printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");4 g1 ?1 \$ H4 _. c3 F4 R
( K% _5 w; J9 `7 }2 Q- sfDispMenu(); /* 打印命令提示 */
/ x- I( M1 y) q6 }* S8 `" i7 l - `$ j- w! b9 @9 ^
- bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */. p5 r1 l" `! @* C) z
- while(1)
* [, [7 n: c' |' [ - {" B4 b5 o" K5 n
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
( M% n2 h& z' M' |
+ [- Y' l" M: @ c" \- /* 判断软件定时器0是否超时 */
+ |, e) f3 b- r3 G, q2 a9 Q% O - if(bsp_CheckTimer(0))
$ i6 V9 R! O7 o5 t% t2 s - {
7 w; }8 j p. t- ? - /* 每隔200ms 进来一次 */
9 o8 y+ t5 b; a) ?, C - bsp_LedToggle(2);
# P+ `8 z% i2 `& K# { - }% z) \ L4 {& C+ h* U& F4 ]8 Y
/ R+ Q; h) m0 r1 u- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
/ }! t4 s2 s2 k- i) v# a - {
5 A( ^0 f4 g, T6 j+ j' p5 V - switch (cmd)5 M, {& G$ w/ Y/ _/ P$ h) N
- {
% j9 S, F% D9 b% }2 O - case '1':
: F2 ]1 w- g6 T5 a; t/ a+ } - printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
4 X# B; Q$ p$ s* e* ] - sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */
; c) _) B; [7 ]- J A9 z5 X5 [$ @ d - break;
]4 A+ c3 g: u9 ^+ T - ) ~5 \' o# ?( ^
- case '2':' j x0 s" k/ k7 _. t+ g
- printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);- S X. x+ D. @# r9 t/ B* W* R
- sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */
( }. H' W/ l" i! w) v - break;
9 G% f- K" N7 R! {- z% }
7 ?7 m+ j0 W- |) f" {2 Q; R- case '3':. P' N1 I7 w/ v8 T6 J; s2 c
- printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
5 a$ J( }! N6 u A& y - sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */0 Q- D0 V. R. J, w7 G
- break;4 d. e! m. _# o8 L
- 5 P# z$ k6 d& V; G) @
- case '4':
9 k. t/ }" r) }$ i* Y - printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
2 C7 d3 x, l, P5 a6 I0 V, X- |+ S; | - sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- S( g( l% T% j- R1 Y
- break;1 I% w# S1 |2 j
- + k& ]( i0 ~0 ^! p# n0 l; a, b
- case 'y':
- f* K! K! Z4 Q - case 'Y':
2 Q: u6 y! w+ V9 _2 ^, V- y - printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");& Q4 T* J: }0 Q ^
- printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
4 j) _- c. ~" s9 M. n( f* u$ U - sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */
+ v+ f5 e" {. u6 y( L - break;
6 U0 I0 @! M( }- M- d/ M - 9 `! ]8 l9 j0 T4 Z; J# G- @) I& T1 ^
- case 'z':
' W8 ]8 b# |/ s0 j0 Z# p. h - case 'Z': /* 读取前1K */" F- z2 d V7 Z5 }5 ~
- if (uiReadPageNo > 0)
2 Z$ ~4 z# |- I9 T - {
, @$ i8 S4 [- T( T* g0 k - uiReadPageNo--;% T% O. B% _( N) o- T
- }+ ` W! ?6 A2 {" _8 t
- else$ s) r! z# l+ `
- {
* I# U" ?9 u, a/ w8 z( e5 ^. ?& B - printf("已经是最前\r\n");
3 v3 C; h8 B3 g - }
: z* }/ D' n$ {" h* B! L - sfViewData(uiReadPageNo * 1024);- U0 E) F( g" S- s
- break;0 q0 {' u" P, Y/ S2 E- Y% B4 u
7 g$ L; C$ c) E: \( [# T- case 'x':" I2 m2 j6 `5 P# B' m
- case 'X': /* 读取后1K */
$ ]5 a9 @3 {3 w2 _; [. n1 Q( k - if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)0 R/ L/ v, q/ f! H! k; g$ \- w3 ?
- {
" }3 h' d/ o" v4 K - uiReadPageNo++;
4 {# P7 p5 ~+ m# o) Y2 l1 [1 S - }
, y. E( J& B) M5 D9 ]$ r - else
- m1 {3 C$ r K" ^ O# ? - {
6 U* T# T! {- O/ G: L/ O - printf("已经是最后\r\n");
' j6 m/ f) O- j6 Q! q - }* ?' z2 r% f2 A' [* q+ b
- sfViewData(uiReadPageNo * 1024);
# R2 z8 Y' ?9 S6 G - break;! W" t1 B' k7 [& E
7 `- |* l/ m `* v1 g- default:' s s4 X+ r Z: H/ ^' }5 U% v6 j
- sfDispMenu(); /* 无效命令,重新打印命令提示 */
' {' W, o% v; H- y) g4 ` - break;4 ~' L. f+ D, K- R$ b. O
- }( j# C: o! J2 j4 a; ~) Z3 c" m& P
- }4 H5 m+ e5 w/ G" D: G3 a0 {7 o5 V
- }
# t- N+ m. |) B8 O& v; J - }
复制代码 ( e+ D/ P; ^7 e
79.10 总结
6 M; v4 i; I) p- Y: w9 M4 k7 G n本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。
2 U. v3 U6 } i2 @+ o
# u* j( x% G% q# ]8 J# E8 \, N9 W4 T
|