STM32电机培训online,大佬带你玩电机$ C! j( R7 \# X( w
/ i# Y$ G" G1 Z& e2 E9 T# i# ^FMC和QSPI引脚冲突的解决- e$ D9 [1 G0 a
) F: Y, }3 t% I! a) w
分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式
; G+ c8 p" K* |
* A x. x* p& F( f
4 e; i- J- V+ t# r" y! l1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高
1 F! ~+ ^ w/ c4 v8 [- `9 BSTM32F7的quad-spi接口有三种模式
, g4 A$ p4 V) {& ?(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式8 @/ X+ q6 @/ u8 O
(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号
! e, b# M: P0 t( a6 r) S$ X: \) L(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式: `1 N$ l" x1 g/ Z2 j
" _; i% ^7 {) V/ }, M- Z
STM32F7的quad-spi接口主要特点:$ j2 [. k' m* ^% J
(1)三种工作模式
8 M1 q/ a5 u n7 I$ z" z(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4)% K% }- H H9 D
(3)支持SDR和DDR
: n$ R. N$ u' ?2 `; Y(4)间接模式的DMA通道: m5 z% w B4 M; C% g" i; w# ^
(5)内嵌接收和发送FIFO
; a. _4 Y4 v! x; u(6)支持FIFO threshold, timeout, operation complete, access error四种中断( N+ N7 M0 a' C6 B1 X) r( ^1 D
1 y6 C; |8 w! ?1 _ Z4 \, UQuad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示
7 g& o( p- C8 `- ` \& q
8 c/ Q- E8 t# S! |3 J
总结一下特点:; j$ k* M6 M5 ~1 R+ x$ O
(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输,
6 v, E% i; b9 A7 D7 Y+ \(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle. l6 [" l' z. _5 A; J6 S
(3)这5个阶段都不是必须的,可以没有
/ x5 a0 m" `8 ?! k" v4 b# i* T(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA# e8 v4 l7 K; u( i! Z
(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输5 c- m4 K2 }: _; q( F
(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=1" `1 j4 H% |2 {# q1 g7 s
3 K. E7 J& n% h0 e3 P
- P# I" b1 x' X% r
7 g8 ~8 E/ K6 V' B/ w
3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示
9 Y, V& r C# ]7 l3 l* R6 y1 K3 c$ _0 e7 {3 j: b x" b2 d7 D) [8 y
! l2 Y) E; e0 y1 x. O' e
这里主要说明quad-spi flash代码流程分析
% Z/ U) V1 m, A1 v. J2 F论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。
v6 ]5 h1 L7 a/ y+ H选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程+ A% e+ n! T, C: U
(1)Flash配置寄存器初始化
1 W. |( r8 _( m- /* Initialize QuadSPI ------------------------------------------------------ */
* ?, K, _, O8 g7 l. ?' V - QSPIHandle.Instance = QUADSPI; - F" J6 G' Q- u
- HAL_QSPI_DeInit(&QSPIHandle);
, ?4 T5 ^% ^/ c( z - 4 H1 I) {) G2 o/ q
- /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */2 p7 R4 h$ W5 @. b& H8 Y, A
- QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span>% D) M! u1 y+ V. _5 C" |
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,, q' b3 Z* o' q7 a" k
- QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; # E" r* I$ z; j$ ]
- QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB,
! z0 w) Z. I T8 @; u+ A - QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands8 J9 M. [+ |7 l6 f8 c5 s
- QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released
7 B$ \0 f) B$ F - QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash
1 ^! |8 I4 r, V9 n5 P - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;" B n" \4 x3 Y9 J7 l" X
-
! F u j9 O% j - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)& a3 {8 N Q; G# `2 B* j
- {* S7 X* Q7 t2 H! r+ {
- Error_Handler();0 ?/ [+ c; ?# c) B* ]$ T% g- w
- }
复制代码 (2)使能写操作2 }; V4 c/ r/ C, X- k
- /* Enable write operations ------------------------------------------ */
$ q! _" ]! |5 I8 m8 H* W3 n4 L" r) p - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; + {' d" c x3 u: g0 U5 g2 Q
- sCommand.Instruction = WRITE_ENABLE_CMD;
1 q& ]9 S- p0 h8 N) D - sCommand.AddressMode = QSPI_ADDRESS_NONE;- x# Q! U. _# p5 K# g" b, c
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
. K7 E5 h: M; Z9 I - sCommand.DataMode = QSPI_DATA_NONE;
( L3 U9 v; w$ i0 n1 g' x% k3 ~" x - sCommand.DummyCycles = 0;
& O! \ t( n$ o/ [1 ?( V: x - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;* y7 M. t; ]5 t! h" Q2 c$ ?7 ^; O3 X
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
`5 M, Q; Z* |8 z) _ - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
8 H) t5 C: _# p2 U, P3 ]$ | - + {) Z' M6 w# B6 v" q; H
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
- `) {' s g4 N* ^+ ]7 M - {
, f- u* o7 [1 e2 F1 M% t# j - Error_Handler();3 {6 Y* r$ \9 w$ o' m
- }! ~4 F, F' K) Q
- 3 a; [0 k8 c# ^; f
- /* Configure automatic polling mode to wait for write enabling ---- */ - Z/ n* t& P* Q& s/ N: @! k+ T" P7 ^
- sConfig.Match = 0x02;
/ [$ d2 C: N" u; a; q# \ - sConfig.Mask = 0x02;
: d# u) q+ V, k$ D/ B0 m - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
, A" C+ o1 o6 b# Q: N) j - sConfig.StatusBytesSize = 1;
- p3 t5 K7 m- k) a' U# q) D - sConfig.Interval = 0x10;
7 n% n* C3 \5 O - sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
/ e! ]4 p2 W: q8 W! G% ~& R
8 | A4 k! v& d& c- sCommand.Instruction = READ_STATUS_REG_CMD;
' o. V2 r" q# ^( ]+ y6 l ` - sCommand.DataMode = QSPI_DATA_1_LINE;
3 Q2 J+ j; P7 o5 X& \" n - ) d) ?: j1 g- u$ o u7 u6 ^
- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
. W4 ~) i" g+ O - {
; T' }8 A w. t6 c; T% s# U u - Error_Handler();7 ~; C/ @9 L9 f+ x
- }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段
# C: R: `8 k& K; ^ u源码中定义 #define WRITE_ENABLE_CMD 0x06;" q3 R% s: E2 R, ^- H
这里要查阅芯片手册
3 Z, n0 X4 e. H' H" v3 w0 c
" Z! n j8 e1 A$ ]% W# X# }
确实是一致的。
" ]; H+ k) N# r }+ F& o接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register
) I; C$ L, Y6 A' fautomatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式, c; o: ?; O9 R2 m" X; m5 M) M
查阅手册:
) V# D" D1 W/ p' R
# K: j6 @2 E/ l7 j0 x- K
* ?6 r% J! J7 m确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;
* Z* _2 l) G+ K(3)擦除flash的第一个sector
6 \! B' J9 e' b3 [- /* Erasing Sequence -------------------------------------------------- */
: k. e5 R3 ~$ {0 N% U5 M - sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector% _: o6 e$ g' V* n
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
# }5 o0 I$ g" [3 H% V4 C9 F - sCommand.Address = qspi_addr; //qspi_addr=0
: m0 Y8 ]4 T; c' a2 m9 j3 ~5 w+ [ - sCommand.DataMode = QSPI_DATA_NONE;
" D, W8 {# ?6 `! U; @3 p( |, h - sCommand.DummyCycles = 0;
5 q+ t$ J6 Z v
$ F, c1 g% @7 ^& r! ?5 z- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)5 f2 n4 I3 m- P' c/ M2 k A
- {- X" P8 U. R) e4 X( T
- Error_Handler();1 O% I5 Y1 m0 T" b
- }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册
; v) f( y9 A" r0 E$ |* c
+ C* u* q/ n2 _# |+ x6 Q- /* Configure automatic polling mode to wait for memory ready ------ */ 2 W x0 W- h' W
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
8 @! i9 E8 w7 b! ^& |, i - sCommand.Instruction = READ_STATUS_REG_CMD;
: @; P6 \; s' `% n& p; m$ }3 X - sCommand.AddressMode = QSPI_ADDRESS_NONE;
3 z2 T2 U. z+ J. d) Q# R% p5 l0 [ - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
2 a+ n+ N7 q+ K* i( Y - sCommand.DataMode = QSPI_DATA_1_LINE;8 U% F* z2 ~. p I* I
- sCommand.DummyCycles = 0;
$ T( W# N7 [2 x5 \8 p h - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
: z, E7 E. K8 w" D( ^# u - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;6 k0 I% o, x! |5 f; }- r
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;& g' u0 A* d" g: {1 q# l; b
- ]. b2 N0 e$ k! A( v2 U# z
- //bit0:write in progress 0:ready 1:busy
# j% ]9 c$ {* ` - sConfig.Match = 0x00;' A! h' v' P: `. q' o* L8 Z
- sConfig.Mask = 0x01;
* ?8 B8 f. _( d$ m; f v, E2 F" b - sConfig.MatchMode = QSPI_MATCH_MODE_AND; i7 O4 K: u9 K4 ~3 F
- sConfig.StatusBytesSize = 1;
) {5 A1 I) k/ B* {$ U } - sConfig.Interval = 0x10;* T4 f9 w! F P G/ ]
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;: _# I, Z( u3 t/ T- k
( k5 w$ U2 m: w- N- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)6 S3 Z. q8 ^: G& }: r' I& M$ h
- {; s. V! Q% H7 N9 A. }
- Error_Handler();
4 r0 @( {- |1 u/ c; S( g0 Z/ r - }
复制代码 (5)写使能并且写数据
7 y* b7 j9 n" O; Z( {7 r. W- /* Enable write operations ----------------------------------------- */& o7 Z: M0 _3 f* ?4 E
- QSPI_WriteEnable(&QSPIHandle);
( v2 F+ t7 \9 ~
4 x6 D- f; S9 S. L# i- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ0; j" p7 h# u% P
- /* Writing Sequence ------------------------------------------------ */, g7 M! C) ~$ C2 g4 `" Z- _* M
- sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;$ H) ?3 [. Q# Z. S X
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE;- Y5 j! H8 G) c9 ~" {+ q
- sCommand.Address = qspi_addr; //qspi_addr=0# r: X+ f8 X" N' _! |
- sCommand.DataMode = QSPI_DATA_4_LINES;! e8 C; x/ l* |5 q2 N
- sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256$ Y9 x3 p! N2 d! R
9 m' J- J W/ g# ~- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
5 f( G/ h$ \. {& p( @. Q - {
, R" Z1 j4 @# \! h S - Error_Handler();3 \) d k- J- n; I' b( j( C
- }1 N) B D8 h+ G: R: w
- 9 m9 ?& K( [/ {+ o9 r3 S5 {/ N
- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);5 ]/ h& \' \/ j6 m
- {
+ J% v! i* |8 S. \) R$ f' `6 a - Error_Handler();
7 g( v* T' |# M9 u/ m - }
复制代码 看到这里就不得不提一下scatter file文件
6 W: w2 s6 I8 ?
% n7 |. P) J4 n: ]3 k
打开这个文件,STM32746G-DISCOVERY.sct
' Z$ e7 ?( J( V6 j7 u1 L- z U2 Q- ; *************************************************************
8 l1 X% S2 v# g8 p: ^ - ; *** Scatter-Loading Description File generated by uVision ***
6 J$ d4 i0 h5 A - ; *************************************************************! {8 P/ K2 T @( r0 e9 q
- + s, V; m: }1 B: P# k2 {' N
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region, s$ I, L% \) T) [5 `
- ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
h ?/ `! ]. \" u$ t( n4 J - *.o (RESET, +First)6 ?* B# r$ s3 W& @
- *(InRoot$Sections)
Z% U' T6 d% m7 k% e o) b - .ANY (+RO), m7 f2 H. X% A7 C
- }
, N5 ]3 S. v- W" e! C0 G* m - RW_IRAM1 0x20000000 0x00050000 { ; RW data
# K! ]2 i; @ C8 a" e" z5 k - .ANY (+RW +ZI)& _8 N# _& I7 _" O( q6 A) r
- }' ?) F, ]0 m. w0 h4 y% A$ k
- QSPI 0x90000000 0x00100000 {
# [+ q O& u% O' M0 q3 y - *.o (.qspi)* R) K8 h" F" b* Z+ S$ ?& J
- }( O( n& I- o9 ?/ y
- }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,
! O, O$ k k' m6 e. q. b8 R学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,; M2 C8 l- ]$ s1 \% p9 d" p
再看一下F7的内存映射
, r7 `/ r; A1 ?% G; f# d3 S$ m/ G
8 p+ [+ w/ B) w/ {4 c( P7 V
在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。9 Q+ A! i5 G3 Z _
因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode 3 Q# V# m5 o; R# L! c4 M7 J
(6)判断传输是否完成,判断的方法同步骤4
7 {5 v1 F( g( x$ B' i) O' N(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000 6 ]2 C5 c' A3 E9 {! X3 ]
(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash
& l6 p* K4 G+ v/ m c7 u- /* Configure Volatile Configuration register (with new dummy cycles) */8 d/ h$ O# D0 k& v \. ~
- QSPI_DummyCyclesCfg(&QSPIHandle);
9 z7 ~( l6 |" z; K3 w - 1 T; s _2 {0 t( W. k2 ?9 ]) {
- /* Reading Sequence ------------------------------------------------ */5 V( v- L5 F. ?5 p) z6 k
- sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;) t1 l) M- c! y* Y& v
- sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;4 v) B, l5 f9 s/ \/ T. p3 f
% \) z5 v" h H0 H! M/ m- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
) e: ]4 V( `) z& M
7 N. w) N+ }8 ]; R" J7 b- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
7 m8 C8 M* U, G9 V$ K - {
0 U( F7 f/ `0 i - Error_Handler();
1 o( u" m* x& E7 B# U! u - }
复制代码 (9)执行.qspi段的代码
3 b* [# a) F' _0 y- /* Execute the code from QSPI memory ------------------------------- */
3 F% I! K" T0 Q - GpioToggle();
复制代码 注意# g( i: F- i& ~: B+ C! _2 `
- #if defined(__CC_ARM)
, a5 ~3 P' ]9 B' Z; l& x" [7 j/ ]# f$ R - #pragma arm section code = ".qspi", R5 d1 D2 \6 V; \# a# ^
- #pragma no_inline- b+ R M4 q6 q+ o/ X- b$ Q4 h
- static void GpioToggle(void)
t# |1 I; G" [6 p - #elif defined(__ICCARM__)% X! u( v# ]- E# w
- static void GpioToggle(void) @ ".qspi"
/ M3 j# [! |4 x& s4 g - #elif defined(__GNUC__)
( c5 e) E* n. m% ], o4 I - static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)
# ]2 e. V4 E: G( \6 \- \7 p' n: J - #endif/ q$ ]- u: c6 Y0 S) m3 G
- 9 N3 u9 _ {- M+ f: y4 T
- {; ?, O, M* W- p) ~8 `
- BSP_LED_Toggle(LED1);
1 Z, v! g; S* T - /* Insert delay 200 ms */
, _* `, u Y% _9 [, j1 t - HAL_Delay(200);$ {9 R# o9 l6 W; O# l2 M! S# ?
- }
复制代码 ) A# V* ~2 M$ h0 _3 A
我的问题:
( T- v+ u9 y* j! L6 T& G学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下, i* I/ L3 F+ N% a* `
|
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!