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