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