STM32电机培训online,大佬带你玩电机 s1 \% I. ]) G X
6 y* h! k# D- r t
FMC和QSPI引脚冲突的解决
9 r( I% a) U3 g( {. K" @3 n. F$ R. D k
分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式' l3 F7 V4 R- v# q9 ^
1 Y: j1 s* j( m& ^$ R5 L/ a5 V4 P
) ]$ l4 R$ A7 N6 X1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高3 t/ T( h( o2 k! s" _
STM32F7的quad-spi接口有三种模式 O) T0 H+ l! p' y
(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式( J) F, a. w' N% f* C% ?
(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号' Y7 E* ~% K) \, t, p/ K, C
(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式0 J- v+ T+ l' f* q
. f# R6 s* b7 [% j W; @STM32F7的quad-spi接口主要特点: Y3 Q# ~3 o9 p, ^+ p& @( \
(1)三种工作模式
, K( t5 z/ ?! e; W9 C" x- b: I6 S(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4)
! W* `6 k$ a( z) G(3)支持SDR和DDR
; W# Q! f) ]& a+ k4 G& L9 i(4)间接模式的DMA通道
1 o# g5 x6 Q0 {. |4 Y(5)内嵌接收和发送FIFO6 d3 m) @+ M) h9 f% z
(6)支持FIFO threshold, timeout, operation complete, access error四种中断
2 B) {8 w% X7 L) o1 |3 B* l) p, E; T. `- A" o; E1 W
Quad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示
r6 P# S4 x$ F! k/ g
! }8 P. _# a3 r m0 D
总结一下特点:+ A; D, m: O2 O6 g$ N
(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输,
! J8 Y8 B- M* X: g! V(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle) [: j6 b9 V; d! ]9 m, j
(3)这5个阶段都不是必须的,可以没有$ W' x( v- L9 _3 v2 ?/ }! d
(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA
& n# X9 G5 K. p) @(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输; }1 ~! G3 }! l% a8 m7 g
(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=12 a& ?: d0 ?% w1 m, N
) \, y u& V! p+ L( \( T. g
* R$ n# R2 K, t2 M7 P! U
" m9 }2 c1 g" M% O2 P1 Y2 F3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示
8 _) _1 s& e. M1 g$ |$ F, h8 S, E& O G$ ^8 |: w
$ |* m8 v- u8 e" c v V" q
这里主要说明quad-spi flash代码流程分析' W5 e/ V4 K' m
论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。3 j2 O w! ^7 A
选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程0 y0 o" `. G( `, k: q) S) u6 \; Z
(1)Flash配置寄存器初始化+ X$ x4 Z2 v3 E$ k7 W9 c" `2 h4 H+ i
- /* Initialize QuadSPI ------------------------------------------------------ */
; q( Q8 @ J0 |* c. S - QSPIHandle.Instance = QUADSPI;
; p9 w, V4 _5 M$ O; D3 {! ?+ @ - HAL_QSPI_DeInit(&QSPIHandle);' ^ z4 H0 c5 _ N# M% C3 c% O
-
0 D$ C7 U8 ]& [/ w3 I1 [# n - /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */0 O5 k' B5 e, k3 n7 D5 R( R/ w
- QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span>: j$ Z+ o; Z# X8 u1 e: l* i2 I
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,7 w2 | K/ G" r; `2 M( {1 [- K
- QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
8 }9 X. p! a0 o5 K7 e - QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB, : D+ m) v p- C9 ]' }0 k& I$ y
- QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands3 u3 n* ^0 C# Y% o+ W* m
- QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released; p8 h3 v, P$ L; I$ e
- QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash
; ~9 }7 S/ s. P( Q$ i - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;9 O+ t8 V( I9 C% \5 u7 n8 B
-
) v U3 J- F- [ ^- K5 u% u - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)1 U4 `9 i* i6 w7 A0 }$ N$ ~
- {
6 c% F+ _( i* P4 Z# l5 b6 H0 m - Error_Handler();2 |8 `2 O* m+ l& v
- }
复制代码 (2)使能写操作; X- O ]& ~7 S$ G! a" S. U0 ^
- /* Enable write operations ------------------------------------------ */
8 g9 [) ]4 g4 \. k9 e! F# [ - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; 1 B, t' j0 ]/ M% t7 h. n6 B
- sCommand.Instruction = WRITE_ENABLE_CMD;
7 v. q* K% [% {7 V: s3 u9 }4 y0 S - sCommand.AddressMode = QSPI_ADDRESS_NONE;6 ^# n- u% e8 S4 Z
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;2 E! m% d P3 X3 e# f& B d6 e0 Y
- sCommand.DataMode = QSPI_DATA_NONE;- H7 `* x1 s, y* c+ x
- sCommand.DummyCycles = 0;
) @; F, v- @ J9 Q' D: Z - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
0 ]8 Z+ ]* v s6 Q! b: N - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;5 b% h, ^ U. I- P) S
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
7 z" z* a5 o/ x
# S2 s/ B; G" Z+ O2 `- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
& X2 b6 |4 H; K2 }- h1 \ - {* u7 M0 G0 A9 \7 g
- Error_Handler();
; W$ s5 P1 f0 H' ? - }
! U& h, l% g0 X+ @: d0 @- Y - ; P; S3 {& ]" z" e: F
- /* Configure automatic polling mode to wait for write enabling ---- */
' T: [+ l- {- }8 {+ `4 v - sConfig.Match = 0x02;6 g; S/ _" J, x
- sConfig.Mask = 0x02;9 C: k( n" O! T- L# d* |
- sConfig.MatchMode = QSPI_MATCH_MODE_AND;6 t1 d; B8 ^/ s) y; {- a
- sConfig.StatusBytesSize = 1;7 t, h: E) Z9 l1 n: Z3 V" r
- sConfig.Interval = 0x10;
+ S& i* x) _) z* n' n8 t& q& Z - sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
K8 _% ?' W3 k) X3 A% L% I. Q
! V2 x- T1 P4 a* J4 F G+ q/ N- sCommand.Instruction = READ_STATUS_REG_CMD;
" V% @6 t4 g+ ?* ?, v- n# I% l - sCommand.DataMode = QSPI_DATA_1_LINE;
( L1 U6 }/ F* c& |/ e
$ F6 {$ M# P+ c( |, [+ {- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK); O7 Z+ O$ O: W2 S
- {
$ h$ Q& Q0 p! u6 q9 ~ - Error_Handler();- r+ `9 L0 |+ d1 S
- }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段; C/ k/ E- a: @% ?- z- C, u
源码中定义 #define WRITE_ENABLE_CMD 0x06;4 L. y5 `/ S3 u! |- ~) i
这里要查阅芯片手册8 u6 e" a- t8 L' K' b) u
3 H/ ~: X5 c: {* d6 {/ C
确实是一致的。. j( \( Y% n- z
接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register$ B4 o- X, y* [: j
automatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式,
8 M5 l* X; w5 D; t查阅手册:
" T7 ]+ R6 @" ]" P1 z! k" |, P& c8 u" d8 |% K. L$ h
8 {4 ]% i! `9 x: S确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;
" E" R& U+ P8 C2 V+ N( {(3)擦除flash的第一个sector
/ T- B* m/ {1 ~$ a# u4 m# A- /* Erasing Sequence -------------------------------------------------- */& ^3 N8 U& a# E* f7 k. |
- sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector
4 w/ Y3 Y" u1 L3 Q2 `* q, u+ ^" p0 d - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;( F; F. \: y; O. N/ y
- sCommand.Address = qspi_addr; //qspi_addr=0
$ D+ q; f' A7 W0 y - sCommand.DataMode = QSPI_DATA_NONE; g6 x7 d# m8 M
- sCommand.DummyCycles = 0;
: c) n" E0 K% X" z - 8 Y1 F3 z5 S$ o
- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)9 c( t9 j [+ {( h/ L* M& i
- {# k2 E) M+ W5 g% {1 ]5 h. ^5 z
- Error_Handler();, W, E; ^9 _4 ?, l* K5 i- Q+ V! W
- }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册0 V0 P/ h$ y) ?/ X" X
/ ~# v/ B" ^5 L; g0 c0 b- /* Configure automatic polling mode to wait for memory ready ------ */
. ~$ I2 R. j$ J7 q - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
; s) r+ E1 j" j* N - sCommand.Instruction = READ_STATUS_REG_CMD;- y, r: {- ~; S& P4 N! A
- sCommand.AddressMode = QSPI_ADDRESS_NONE;
$ P: ^5 x* I3 K- F! K3 X+ I - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;$ d' a3 P* R. b+ f+ U
- sCommand.DataMode = QSPI_DATA_1_LINE;
a6 {* b3 ~5 ]1 O/ v - sCommand.DummyCycles = 0;& i2 C, ^% X$ c$ M
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
[5 u$ e( C& j) \ B1 P - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
, o1 } y4 E: ]# u1 ? - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
, d$ ^0 i5 P4 ], C8 l; B - 5 Z; S' Z, j! ?% h3 o& `3 S
- //bit0:write in progress 0:ready 1:busy - m4 o/ @# i- i3 K0 O
- sConfig.Match = 0x00;
. g4 L! {( y9 p: h* t6 ?( Z - sConfig.Mask = 0x01;
5 i/ @3 |; [5 R7 k/ |0 p - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
- t* R4 D8 \$ T% F/ k. i" X9 v/ T - sConfig.StatusBytesSize = 1;6 u, S& K6 P; t9 Z3 V$ Y0 T
- sConfig.Interval = 0x10;1 k0 @# w! b! i' u% E
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;$ `" A5 X" v6 A/ _/ N. y
- $ D- ^) A- @, F
- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)
8 ~+ ]& B, M: G3 _0 j) F" { - {* X# r4 |' i a* Q" F
- Error_Handler();
9 ~* S, d- Z; J: H/ x/ h - }
复制代码 (5)写使能并且写数据& b( D, C& ]( O$ ?! A4 U* e6 {
- /* Enable write operations ----------------------------------------- */- S4 |$ H& o0 F: f8 F2 R
- QSPI_WriteEnable(&QSPIHandle);
3 g. }$ p2 K7 W3 _8 c4 C
2 h( N& P, ~( O3 u( [8 ~- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ08 e- k7 F$ a$ y: B1 x' w! g
- /* Writing Sequence ------------------------------------------------ */
! o. R O& h- y% q1 g - sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;! A( A7 d# d! y g* f a: Z
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
, Q$ m( u$ A0 [+ r2 a1 Y' U - sCommand.Address = qspi_addr; //qspi_addr=0+ l( z- F: i$ d p, m' O) x x! n
- sCommand.DataMode = QSPI_DATA_4_LINES;
* Y! u/ W6 C t4 z* `" [* D - sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256
! E) C: K. w* W5 k - 3 Y- F5 P% {1 `& H
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)% r9 G/ w# {2 _! i6 a! z
- {; }8 I; M- c: `3 T3 e [
- Error_Handler(); I0 d- j5 \) k
- }
* `* q3 J0 a$ @8 ~4 u% g - - w4 |9 U. x& Q' Z
- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);
7 D8 m5 R5 d! C3 C - {1 s/ b7 `' d1 t1 v
- Error_Handler();; c- a0 K/ V6 r( S9 E1 o6 L
- }
复制代码 看到这里就不得不提一下scatter file文件% T6 K5 W! M' t7 X( y y
+ e8 u5 N6 I6 {0 K
打开这个文件,STM32746G-DISCOVERY.sct' a5 f, I0 Q0 C3 N0 z6 ?( t
- ; *************************************************************
; O# g" Y/ o$ K - ; *** Scatter-Loading Description File generated by uVision ***+ J4 u* d' l6 K; H5 O9 h+ x) ^
- ; *************************************************************6 \+ F K% C6 C* r! g" G8 ^
- . o- T* E$ U! n1 K p# Z1 |7 m
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region; @/ a. z* m" G3 l# b* N6 B* S; i
- ER_IROM1 0x08000000 0x00100000 { ; load address = execution address" U& X5 G% q* B
- *.o (RESET, +First)
8 R# J& t! h' K( g/ ` - *(InRoot$Sections)
5 p) i+ J5 d5 I E' Y - .ANY (+RO)
/ |5 |8 a0 k; r; u1 L O F - }
* ?2 T4 K3 l6 q - RW_IRAM1 0x20000000 0x00050000 { ; RW data3 I1 c" `2 G. i6 o
- .ANY (+RW +ZI), O! c4 g( g; ~* r0 u
- }
3 G9 _( o4 O6 r6 X7 ~3 M; ]1 U6 n - QSPI 0x90000000 0x00100000 {
9 ^8 W# @5 M* I9 s4 z w - *.o (.qspi)$ D! Z/ R( ^7 @& Z* }
- }
# c- X$ O9 i; Z& ^0 Q& B - }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,* [1 V! z [. `* Z# ^& G
学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,
! f7 f) G) h6 i5 ]1 Z k* {) A: Z* N再看一下F7的内存映射& c4 b( K+ N' y' d) }. a! a
$ z5 ?. N/ v' E8 K+ z) \
在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。; H- R0 o0 h. z9 L7 _& r
因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode
" s! Q9 [5 e' N/ Q ^2 G(6)判断传输是否完成,判断的方法同步骤4
9 F: x) H0 ^1 P4 \/ Z0 \: K(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000
8 G* w+ A; O( U O4 Z* ~(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash) T; x2 g' n! v5 d# B
- /* Configure Volatile Configuration register (with new dummy cycles) */
8 {- w" W1 ]! { f K+ k - QSPI_DummyCyclesCfg(&QSPIHandle);. s; E1 ]# e6 m0 F6 y& V
- 2 \5 I- |; A. P3 s; e' X. N3 C
- /* Reading Sequence ------------------------------------------------ */
! _# \' J! j* m" ~ - sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;
- E% t$ g, ?; R3 Y% U6 g/ g1 x - sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;
. W: }# v! \4 @. c( Q! H - 3 a# X1 p8 I# ]# m* f
- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
4 k' t! ]5 y+ a4 E
. S- Z! N) `- l" u/ d$ p- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
: m" ?' [% w" ~/ h - {
9 K. b0 ^; w9 [" |1 d) D3 Q: k - Error_Handler();
+ ^# @; c: r0 h1 k4 F/ S* |% F9 u - }
复制代码 (9)执行.qspi段的代码
+ x9 K- Q7 v" ?, `9 Q- /* Execute the code from QSPI memory ------------------------------- */1 |& T% T, Z" q+ k* w( D' h* A
- GpioToggle();
复制代码 注意1 C# ~ z/ z1 x, ]6 P s _* `
- #if defined(__CC_ARM)
$ X) |- ]! E& [7 M5 s4 u% a - #pragma arm section code = ".qspi", f Q$ ]" |2 e5 ~9 C& }9 T) Z9 d
- #pragma no_inline, M0 d+ a& o) N9 `8 y
- static void GpioToggle(void)6 S% R7 G1 o1 v8 \. j$ e, |; r
- #elif defined(__ICCARM__)) _3 B# `& b* w- w$ {
- static void GpioToggle(void) @ ".qspi"- Z h% }7 o$ ?0 _! g* `& R; b
- #elif defined(__GNUC__)
: Z# F6 D9 U; _& X' n4 X7 E - static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)
3 p7 n. H2 q5 T/ a. J# q. O - #endif9 @; [8 N' _1 M2 s4 x
- % ]" [/ U0 i5 ~+ G- s
- {9 S5 H$ ^+ Y9 Z' x. D/ z" E6 T- x- z
- BSP_LED_Toggle(LED1);& @, L4 Y4 X# ]- P7 s1 J$ S
- /* Insert delay 200 ms */
7 [# o0 }8 N5 R3 i - HAL_Delay(200);- x8 y3 S; R) x$ F/ G( I
- }
复制代码
2 ~9 M% U% t2 I! M4 L我的问题:. f5 I* R1 i% J! l/ \/ I, ?
学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下6 d& f, U; i! d
|
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!