STM32电机培训online,大佬带你玩电机# l: Q8 i( m7 h+ A
# G' F& V) e' Q- Q, [1 `, f' SFMC和QSPI引脚冲突的解决# }& f$ Y! I: a2 C% R) D
' | U7 x. I+ N; y2 D* }; |# L
分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式4 v5 r- H+ a1 |' }5 W3 W' D9 _
1 L+ c' t3 h7 T o) U
9 o' v$ r) L" f" l$ _2 Z* |
1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高( _% S* m3 O: P: C, \
STM32F7的quad-spi接口有三种模式
0 a" V5 Z6 Z- x& j) T N6 k(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式3 L- w& |2 `) h7 `' N
(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号
9 U+ \! x8 H$ x6 W G- s; t(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式0 q) N2 S; j7 N3 e1 f
$ ~0 k; N4 e4 a! w4 M: d
STM32F7的quad-spi接口主要特点:1 |& t/ N# z1 Q- H
(1)三种工作模式+ x/ ]7 K) m- R
(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4); k o$ g3 A9 |) v/ C
(3)支持SDR和DDR
" g- v5 e$ y' d+ i' j+ |(4)间接模式的DMA通道
; B3 u& M6 K S/ [( b: T7 P9 C(5)内嵌接收和发送FIFO# P; Q( i% F$ _
(6)支持FIFO threshold, timeout, operation complete, access error四种中断6 h9 r) E* t4 u
+ K! J5 j I8 X+ U& d; K( k
Quad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示' Q# F5 u" r8 F" F# V9 N- \
' B+ K+ h+ l. p0 h2 ]
总结一下特点:5 s P: h4 K0 N8 J9 A1 h4 }
(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输, g$ M; O! P2 W" F+ a
(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle" M* j; b$ b7 e
(3)这5个阶段都不是必须的,可以没有! }6 K5 _$ y0 N9 ?9 R: @ A' F/ y
(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA
( w# A2 \. Y% H4 ^! U3 z3 z(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输
3 r6 V [; S0 O0 l, f: I1 p$ R# i5 G(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=1
5 X f3 {3 E: b$ V
7 j% W2 @0 m4 p8 c) C, J$ ?; }' b L A5 g( ~3 l0 S3 f
9 k4 e4 w. r, l/ ?: K3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示) W- ?- H& x- B) V4 P
0 ^' r# I4 M; k5 X9 w+ Y
) i2 |4 o' D) a, ?( L( t: q这里主要说明quad-spi flash代码流程分析
; O3 r ^% I3 m- W论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。& J$ v* {$ x7 k7 |2 }. R
选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程
- C! j; l/ i! ]1 t(1)Flash配置寄存器初始化
" f( t/ l" @, g! t- /* Initialize QuadSPI ------------------------------------------------------ */
' v! }; X2 I2 T I9 K2 w; Q: D" ] - QSPIHandle.Instance = QUADSPI; 3 q% G' G9 G% o
- HAL_QSPI_DeInit(&QSPIHandle);5 e( x W4 Q6 B
- . q* M+ W# L: K9 I+ L
- /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */" R3 W; C3 Q |- U% q! k
- QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span>* p" ?. i8 x" D( U) [) a' W7 J5 Z) e- v
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,9 z% g$ V' w1 @+ d" _
- QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; ! {/ ?# X" |& ^+ t5 a* I2 ^0 y
- QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB, * {( v* N2 u5 y: O, L4 g
- QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands
' A9 h+ I& u @8 F6 q( }4 x9 t) p! ] - QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released
5 \8 R; X& ^1 P - QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash
" D1 ?: t( r! E: w4 V2 \ - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
, k" e" O7 _+ R4 b, y. s -
, m% D3 w0 ~7 m# }) J4 ~' d) S - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)" v( T* \9 r" O
- {
/ \( ?% M0 `- F7 ^, `* D/ [0 u - Error_Handler();
9 e8 p( X+ K2 V. P - }
复制代码 (2)使能写操作0 b, F5 ?4 ?+ ]- ~( \' Y
- /* Enable write operations ------------------------------------------ */' r, I# T) h# F
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; 9 K( ^) P: {! q8 L D+ n# } B
- sCommand.Instruction = WRITE_ENABLE_CMD;
) E- C: o: k1 x5 R) h. \ - sCommand.AddressMode = QSPI_ADDRESS_NONE;8 s/ D4 ?( G% R# k
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
" W2 [/ j) v( c1 S) m2 Q1 [6 |5 |2 j - sCommand.DataMode = QSPI_DATA_NONE;
, f$ q" ^( c) j2 ~7 B$ w - sCommand.DummyCycles = 0;
0 b- `" A9 c+ ^6 h" G - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;1 x6 O1 F6 Z+ ^. W+ D' X
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
. L4 }& d+ S( c# E; @3 z0 s/ N9 L" M7 g - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
! n- p# X2 `' z/ D1 w
" F/ w& Q& x1 O( y' F. T- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)! \/ W2 ?7 \/ N( \- _: B; `6 u6 |* M
- {1 ?/ \( [& K# ~1 W( |! {- H
- Error_Handler();
; F2 F u* s: y9 l8 ^3 [+ B# N - }
Q9 a x r7 r( H - / n0 s# m6 _% n3 c
- /* Configure automatic polling mode to wait for write enabling ---- */
; }1 u! o9 o1 o. o. ]+ B - sConfig.Match = 0x02;% W+ s, I0 v0 \8 D4 Z! x
- sConfig.Mask = 0x02;
" l5 w6 T8 Z9 f3 G3 T - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
( ~: I% \& k1 m - sConfig.StatusBytesSize = 1;0 X8 y7 ^! U$ I1 Z/ ]# w. F
- sConfig.Interval = 0x10;
3 E5 b0 K K2 [: Q) o7 i3 { - sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE; f/ R ~+ @* a" D& S
- ' H* G# i; G4 S/ l* h
- sCommand.Instruction = READ_STATUS_REG_CMD;1 I: ?* i/ V7 ]" a w4 m
- sCommand.DataMode = QSPI_DATA_1_LINE;
8 W! u1 A$ v) ~; H, G# g! l3 c
/ V7 ~/ z* }4 ^' v W# M- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)/ Q3 i: p4 I2 X" E3 R
- {
8 J4 b3 B7 m- D2 v0 o8 n - Error_Handler();+ W0 O* _5 G2 z8 A2 m/ @
- }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段
( V4 _' j/ P f+ i源码中定义 #define WRITE_ENABLE_CMD 0x06;
2 j7 g- L6 E J! G" S这里要查阅芯片手册( s) U3 q$ D+ d( a- h7 z
' d n( G7 H# W确实是一致的。
3 K$ v0 p/ D, R* s接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register5 ^( X/ ^! B' D' S
automatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式,
$ A* l a2 a& k& W1 Q9 }( m0 @查阅手册:
% w# ~5 y$ q% f% }7 L* }' r8 F
- s' M; G. [5 y& @6 \' q2 s! _
" R( s( j, B+ t! q1 }6 [确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;' J+ V8 @" E! G! I4 q
(3)擦除flash的第一个sector
3 }2 L$ X$ [! U: Z- /* Erasing Sequence -------------------------------------------------- */6 f& L7 q6 Z) F. x
- sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector: N; K* u5 R6 }
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
- m o7 }: \8 A1 \! M$ c0 d5 _ - sCommand.Address = qspi_addr; //qspi_addr=00 b7 |, z. }& T, D
- sCommand.DataMode = QSPI_DATA_NONE;0 |& T9 C( G: G
- sCommand.DummyCycles = 0;) f6 n V! X* ^# p
- * T! I: d1 L) D+ L% q# `8 a2 S. f
- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)
3 V# B: x$ ?5 j' |5 r - {; o9 S$ i$ Y9 D" Q1 h6 C
- Error_Handler();
T8 g% X( @. X - }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册2 O' x M* t" q% J+ A
: U i+ D7 d; N0 ` D/ i5 ]- /* Configure automatic polling mode to wait for memory ready ------ */
. ^, p) s" w5 Z. }# Z7 h - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;2 t6 U# y" C/ S& T6 P
- sCommand.Instruction = READ_STATUS_REG_CMD;
3 x( g( w- [- {9 { W( t) M) _ - sCommand.AddressMode = QSPI_ADDRESS_NONE;& H+ c- c- h- o8 b
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;6 `) E5 i l) L
- sCommand.DataMode = QSPI_DATA_1_LINE;
6 L5 M2 L2 N/ T. W! Z( | - sCommand.DummyCycles = 0;
$ U+ k& h% l4 n4 r8 w. u% m - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
) K& D+ F3 a. Y" u1 d - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
' |! {" h) Z; h7 J- u; h, w - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;( v, g3 `4 i+ L# W- z
( T+ b6 i; c M/ m4 O* S- //bit0:write in progress 0:ready 1:busy
! T4 F" ]- n8 ~8 T/ u1 r8 P1 ] - sConfig.Match = 0x00;, y7 u; |& q' ^! O: {
- sConfig.Mask = 0x01;) q' r3 R) t* u9 P4 L$ f* P
- sConfig.MatchMode = QSPI_MATCH_MODE_AND;
/ v" \7 d- P6 x3 Y4 k - sConfig.StatusBytesSize = 1;
) i2 u* J6 T1 u; A" h3 W - sConfig.Interval = 0x10;3 `$ o( f1 R1 L, A0 |- E5 g8 Z
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;- Z0 J$ K) i- F1 S7 O6 F
- $ o* ?- X& |1 `' f( M. L
- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)7 O7 w! |6 {, u
- {
; B) O& B# r2 o6 b2 f3 M! ?9 a - Error_Handler();( Q9 K C* X' ^; ]
- }
复制代码 (5)写使能并且写数据
6 _6 j8 {- u- {/ P- /* Enable write operations ----------------------------------------- */" m: u) p. o; P8 ]) G- L
- QSPI_WriteEnable(&QSPIHandle);
. _3 X/ D& n5 t
* H# F0 x2 S& a% ~ T3 J- Y% \- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ0
6 y$ C8 I* L' t7 b3 _ k+ V6 [( r - /* Writing Sequence ------------------------------------------------ */' T8 x& ?- }% M
- sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;
" U# j3 V1 u8 @/ D. S) U1 i4 { - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
% J2 Y! i- [8 F, _) G4 J! t - sCommand.Address = qspi_addr; //qspi_addr=06 M* x/ N- U: W2 S5 _0 k/ o
- sCommand.DataMode = QSPI_DATA_4_LINES;6 } H6 g0 U) u& r' ^7 W9 w
- sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256
% Y; R9 j6 O! n! b
7 D c4 A; J& P5 H- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
5 n5 Q: m2 _; z( E1 s! _% t+ n - {9 M9 M; s: L4 m; Z: h1 u+ j& l
- Error_Handler();
/ ^% n9 L: C7 a7 ?& S, ?# J - }$ i" }2 u+ z3 y8 C- s
- 0 K2 p% {8 ^& r9 R% L
- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);
/ m* Q4 J7 `* {; b9 m - {
- m6 |" P" K6 \! ~* Q' v1 x: W - Error_Handler();6 H& \5 b' W6 Z+ v5 o2 ^; n
- }
复制代码 看到这里就不得不提一下scatter file文件- I2 R: U7 m( N( F
, G& L1 |& \. ]打开这个文件,STM32746G-DISCOVERY.sct
- F, k2 k: e! E% Z- k- ; *************************************************************( e0 Y. z( U+ L' C+ M8 y
- ; *** Scatter-Loading Description File generated by uVision ***( h- z [1 }3 a
- ; *************************************************************# C8 M8 F& ^2 a4 O
- ) m* {# m1 C1 n3 B) Y
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region/ k) R o5 K; l8 |9 r7 y
- ER_IROM1 0x08000000 0x00100000 { ; load address = execution address6 {0 t# ^7 ]( F+ F4 l) W( T% Y
- *.o (RESET, +First)0 {1 H' W- U' L t
- *(InRoot$Sections)1 \* ^' }7 l# t3 c" [+ E' v. F1 R w
- .ANY (+RO)
& V: }* r6 ^6 A3 Z4 U - }
: g4 o/ p/ E. d0 u( J4 r" R - RW_IRAM1 0x20000000 0x00050000 { ; RW data
) m, u: V( Z, C$ {; m- J - .ANY (+RW +ZI)' O$ e2 ~$ B1 {- P1 m
- }
: T5 d6 G( @+ \( r/ m }# y - QSPI 0x90000000 0x00100000 {. v, _! d) a% |" F8 k
- *.o (.qspi)
; h: _! N3 C/ V9 t5 Z6 s% Q - }9 j0 q6 K9 ?! ]" K }0 @# n+ B
- }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,, h4 k3 Z% b7 B% W' f1 L! j
学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,3 j; A3 t6 k4 X, M
再看一下F7的内存映射" M7 g/ B9 D/ J( _! d( P7 b% t" \
/ T" m0 _8 D7 x6 |& O在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。. d" J7 z. Y m: O( K1 s
因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode
, |+ K6 K) l! G3 W4 A(6)判断传输是否完成,判断的方法同步骤4; N5 g1 f2 c7 F, T" {- v" z! S
(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000
! h4 c2 ^1 k( |1 D(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash
* V6 t( R& i! w! A+ T- /* Configure Volatile Configuration register (with new dummy cycles) */
, o1 ? W H. V$ h9 V7 H - QSPI_DummyCyclesCfg(&QSPIHandle);) h) B6 r) p' P5 f; t- p- q
- N8 J- i- f1 p5 ?" Z0 X
- /* Reading Sequence ------------------------------------------------ */
: l1 N7 u8 H8 N& N2 f - sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;/ z8 y1 X) C' r
- sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;6 p- n# m: D( x
- 8 \. G. A8 _- B5 o E; N/ G
- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
+ v. ~- I% K. M$ |2 R
1 Y$ {% L! ?$ b$ v0 D5 e ~2 t# }4 U- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)3 X R0 A: N% y( G
- {9 U% u) r6 G1 W( @
- Error_Handler();0 v$ V& d7 \8 c5 ] [9 p
- }
复制代码 (9)执行.qspi段的代码
* I$ t0 F& A7 m- ^7 Z1 {+ u1 ]- /* Execute the code from QSPI memory ------------------------------- */
) }. e( z8 Y/ _/ {% {! F5 N - GpioToggle();
复制代码 注意% }* G% A6 c( b# o7 }) B$ K1 H
- #if defined(__CC_ARM)
* a9 Y# c5 Y% S9 q1 R) W - #pragma arm section code = ".qspi" E1 t2 t$ M; b" L
- #pragma no_inline1 N) U- K/ g" R0 w$ X% A+ {+ a$ B6 S
- static void GpioToggle(void)/ G7 ^/ m0 w% Y; D' v: D' {, J; m- @
- #elif defined(__ICCARM__): K0 V+ x k# s, E3 j- y/ q
- static void GpioToggle(void) @ ".qspi"
; b o1 ?: m- I! i - #elif defined(__GNUC__)
1 B( T8 q1 e6 w& B0 s - static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)3 ?3 i! H1 b0 w8 U
- #endif
) D* B$ m. ]' D8 }
/ l3 S7 Q" r0 G. X- {
0 \' z/ t5 ^2 u' r - BSP_LED_Toggle(LED1);
5 x2 }+ H; G+ D$ l% u( K - /* Insert delay 200 ms */; i" Y5 i8 e1 q1 ~9 E' ?* z9 |
- HAL_Delay(200);1 P M7 F7 R" F7 \$ x
- }
复制代码 # p1 n3 g* H C: q0 e
我的问题:
3 b* @8 L4 U: L学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下! D- \& Q% ?7 z6 M
|
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!