STM32电机培训online,大佬带你玩电机6 y6 |. l1 q7 e/ X0 r8 [: G
* `* d- d. S+ Y) l. f3 KFMC和QSPI引脚冲突的解决! v( x" ~- T+ [, A: S M Y
3 y1 M$ Q3 @ z8 Z" J, p+ s" Q
分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式
( O. K9 g& C1 L
' |% W, Q9 T# ?! t$ C3 h5 r7 r3 G& c1 y4 R
1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高
% a, i9 f1 ~- |6 L9 r3 f4 MSTM32F7的quad-spi接口有三种模式/ K+ A) V4 }& y% k5 [4 _1 w
(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式
' s& ^* U1 j9 P6 j(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号
+ `1 t$ u8 w; O8 ~6 a(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式
" r8 ~9 t) }& |+ c& ~8 {% e! c8 }7 o* q' n5 n
STM32F7的quad-spi接口主要特点:
* Y0 {1 V7 z9 H+ ?(1)三种工作模式
; C) _1 S! X( q! {0 V6 a( i4 S4 j(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4)
; h- c' w w5 j# M4 b/ a2 l(3)支持SDR和DDR
' I8 y- W, Y% y3 s4 i0 y(4)间接模式的DMA通道& Y& i, b' {# G( x3 {
(5)内嵌接收和发送FIFO
, g- B, e4 ?+ j, ~, b6 K(6)支持FIFO threshold, timeout, operation complete, access error四种中断
8 c) r j# s9 Z3 S+ ?) Z' A3 G
( e2 H p$ t8 Z, n/ H# [8 X. X8 GQuad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示
2 J7 h" l2 k U/ b: _
& `% E' c. Y Y& t- x5 V t总结一下特点:1 J1 M; ~; G3 h1 V" j
(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输,
" R+ f+ P2 o) p(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle
6 N( j; v. E; y" r; E(3)这5个阶段都不是必须的,可以没有, Q6 s4 R3 b! m8 ]6 T8 `$ ^
(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA
* ~; M0 o5 x* N(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输
' j$ v! p- H! S(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=1
8 d2 J8 q5 a7 E* e/ C# F5 }, k9 a- i8 e: c) [
) ^9 t# e. X" t4 K$ y- ^0 {5 D0 d; u/ {% [7 u8 h
3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示
0 ^% R e; ]' Q5 l) |; [2 j* \* o) P# I; P
1 k, A4 V% A9 w: e% a, X j这里主要说明quad-spi flash代码流程分析
! h0 d8 }/ ], ~: E1 m论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。
+ w# N! G" |+ r: j选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程
% _% `7 t7 U) _7 l5 `(1)Flash配置寄存器初始化
7 Y) X/ @! v. S2 g% P- /* Initialize QuadSPI ------------------------------------------------------ */- Z7 ?- c3 F9 s# w
- QSPIHandle.Instance = QUADSPI;
+ d: n9 C" x' [' H - HAL_QSPI_DeInit(&QSPIHandle);
2 o$ V4 y2 G+ |. U1 | - * U6 r @5 ^; W, ?3 H% V
- /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */
' R. F1 X3 c" y9 T! Z - QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span>! B: P; F) X: w6 D9 V3 r4 U. i
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,
: m1 x/ R% s7 v) v* | - QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; ; c* }' b; l5 O: M- a Q. P) i! z
- QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB,
: _* E% r0 j4 w, _# H' }. l( X - QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands
|6 o6 R2 } }& W' X* @ - QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released
: a7 y! y" }; |4 l# | - QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash
* k i6 p$ b( W7 r: R# H0 d4 | - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
% Z7 D6 b, a' O2 W8 r3 f0 U7 @9 c -
; y7 M1 m3 D4 c# F) I - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK). E+ B$ P. L3 S' m4 T; U" v
- {
, _! \7 \6 y1 E1 {; X - Error_Handler();0 t* }$ G' T* p
- }
复制代码 (2)使能写操作1 b3 P/ n5 f$ z9 T& G. }' x
- /* Enable write operations ------------------------------------------ *// I3 Z$ ^5 h$ r6 x9 d
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
, A* I5 s; }. \9 |- O& u ` - sCommand.Instruction = WRITE_ENABLE_CMD;8 f* G$ i. y2 F- W: G
- sCommand.AddressMode = QSPI_ADDRESS_NONE;
9 I' z# A/ R4 ?; U3 t [ - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;1 P, d: T" {2 b" C# W8 q5 r
- sCommand.DataMode = QSPI_DATA_NONE;' Y2 x0 b' m- H4 t7 h5 a& Q) d8 i% v
- sCommand.DummyCycles = 0;2 z7 ?/ d9 B* b) K* [
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;8 v8 l( j3 H, {. o
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
1 w+ {& j9 _8 Z4 z" I7 x - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
+ I1 K# h2 b0 B1 p( y
# z* U; L+ Y0 T* S- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)3 \( M2 U5 M7 _6 i8 H
- {
6 @' i N. T& _3 |. C - Error_Handler();
. P1 Y& r8 w9 x7 V* Y. H - }
. D& n1 L5 T9 H: l" i& l -
# i# l: ?1 [' T G5 N; v% M - /* Configure automatic polling mode to wait for write enabling ---- */
6 B& }# v% ~' Z - sConfig.Match = 0x02;
1 b' f+ `! Q' w- V1 m5 _ - sConfig.Mask = 0x02;: m! {3 t# Z2 i+ l2 y# A6 \- m
- sConfig.MatchMode = QSPI_MATCH_MODE_AND;" L9 s/ Z( [+ H3 B2 E
- sConfig.StatusBytesSize = 1;# ?: D7 X6 E$ H: S" `
- sConfig.Interval = 0x10;
7 e3 G6 X- R9 W3 t t2 } - sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
$ v7 u3 c1 `5 e$ h8 S8 p
. P* v6 c! s2 h% m, F- sCommand.Instruction = READ_STATUS_REG_CMD;
8 B) ~7 C$ _) _' U2 c% J - sCommand.DataMode = QSPI_DATA_1_LINE;
, G3 |1 `4 v4 X. j. D9 h' o8 _ - ; x* q2 z$ N- A; L. P4 B+ w+ p2 d
- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
8 h% H1 B/ N# ^ - {5 C7 m4 i" ]- O) ?& e- }) v# r
- Error_Handler();5 f p B, @- S3 b( f- L. L
- }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段9 v& `* R: k5 v, U! V( T, G
源码中定义 #define WRITE_ENABLE_CMD 0x06;
o# K1 }+ z5 Z! e, D这里要查阅芯片手册
6 a0 w2 s6 P$ ]5 x0 W
" c, N7 t. I$ m& y2 _确实是一致的。
8 F7 Z, A7 l1 _: Q' s& C接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register4 l& D9 R! d; w" I/ Y: w3 }
automatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式,
) H: `9 G8 @; ?3 _" `; V查阅手册:' h! L7 }" o* N9 X# e1 @, T" F
* O, O4 I8 h! t3 _. ^1 w5 g6 E2 t
5 F6 I/ b- U" W( h确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;
" l# B T) z) ` t% @& ^! b(3)擦除flash的第一个sector/ m, k* p( {. M
- /* Erasing Sequence -------------------------------------------------- */. e. y1 q. {% y) ~" [3 u
- sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector
* o: w4 o5 q6 B. p/ M+ D - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;4 U; u" }* w. }- R% L
- sCommand.Address = qspi_addr; //qspi_addr=0: f5 g1 s5 y9 J) O7 S
- sCommand.DataMode = QSPI_DATA_NONE;
* l S) l1 x& Y) c/ Y' d - sCommand.DummyCycles = 0;
. a" ]/ r$ l% X
4 f& U6 }& A0 r/ }5 }- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)
( X/ _& ]2 \. c) g - {
) B3 Y5 Q* D$ e$ Z( s- ]. u/ {% C' D ~ - Error_Handler();
9 y( X7 O3 c8 z - }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册0 \ ^4 O$ U7 ?6 U, t- X- G$ ^
- `9 |2 U4 S- U% _, n1 I
- /* Configure automatic polling mode to wait for memory ready ------ */ 8 a7 ?9 e- D1 z" b7 E
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
' n$ s+ R3 q# V! ^# f8 g# p - sCommand.Instruction = READ_STATUS_REG_CMD;
3 ~2 x+ x$ S+ p6 E - sCommand.AddressMode = QSPI_ADDRESS_NONE;
& n( P$ N6 D& u+ S - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
* \, {% [# F8 H7 o( d) |0 {/ s - sCommand.DataMode = QSPI_DATA_1_LINE;( A( G. p: N4 l
- sCommand.DummyCycles = 0;
2 N0 q- `% s: L0 m7 S% o - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
& C$ i" i2 R E- _ - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
! s0 A' T5 O5 C4 s7 H( o. r - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;1 z; S" a4 u* M1 W; t
- 9 m" s2 l* s& f* a+ @6 N
- //bit0:write in progress 0:ready 1:busy
6 `7 Y1 d- Z$ o5 [ - sConfig.Match = 0x00;
' D! w1 q: V5 ?: c7 Y w - sConfig.Mask = 0x01;( |. U" F+ v; Y: [9 [) Y) b
- sConfig.MatchMode = QSPI_MATCH_MODE_AND;
- d; {5 j5 k- z2 a4 Q! w/ V - sConfig.StatusBytesSize = 1;: m/ I: Z J- G( [& }( r
- sConfig.Interval = 0x10;/ i# T4 Z2 }# u
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;6 G# Z1 k: c% c4 J! g' D9 {
9 y6 d! T/ y5 u9 q1 S% {- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)1 U3 d8 Y- g' G, i8 O
- {" B2 ~$ M; @1 ]5 x e: w& [
- Error_Handler();
2 h! B8 f5 K; w1 [ - }
复制代码 (5)写使能并且写数据) d) z2 o C0 l6 D% F8 v; I9 ^
- /* Enable write operations ----------------------------------------- */
% e- F# U' W" u - QSPI_WriteEnable(&QSPIHandle);
& x2 N% Z# g! [! Q. D - 2 @$ k& N: C4 `( k( t
- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ0
) P1 \5 P( U4 W9 i, {1 E - /* Writing Sequence ------------------------------------------------ */* o* }' [2 U! O4 _# V
- sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;
" [7 \ }( X* `6 ~' o - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;! N0 J) ]4 \. }, q
- sCommand.Address = qspi_addr; //qspi_addr=0 R& Z4 v3 X8 B
- sCommand.DataMode = QSPI_DATA_4_LINES;+ p6 z9 Y# w9 ^. V# ^
- sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256
9 ]' c) J; }2 y. `+ |5 K
( i7 H. A* F& v8 N6 u9 A+ W- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
/ L4 _7 I( P- F7 T; L# V" p4 m4 o - {
9 q0 V; g; ?) |4 M* Z - Error_Handler();
" [& S) s) c4 V' k: M" n) F3 J - }
4 g$ P6 M! i& e& t. G' t - $ P, f6 B0 ^4 L* N% Q9 O8 z
- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);
" F3 v2 u" v! V! L - {
5 q! r0 G' X1 f& z - Error_Handler();
6 I" T& K6 N S- T& [% Z - }
复制代码 看到这里就不得不提一下scatter file文件
, R* s) r3 u1 D$ }: g# k
. y2 V8 J% B4 h打开这个文件,STM32746G-DISCOVERY.sct) ?% y9 Q# h& \: A5 b& `3 s) @
- ; *************************************************************) h+ w S$ e& a. n' u' X- L4 H S3 E$ U
- ; *** Scatter-Loading Description File generated by uVision ***
2 z. U9 Z- u3 H) s - ; *************************************************************( [' h8 \- H3 c' Z
5 O1 y* h' l4 f- LR_IROM1 0x08000000 0x00100000 { ; load region size_region
$ m2 `6 U6 M+ h% h O. j" O - ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
5 b6 f) e7 t' T# o7 F( W - *.o (RESET, +First)
8 J% q d A; B* b' r& G& t5 _ - *(InRoot$Sections)1 A4 h7 i5 b, K' H8 u1 w7 m
- .ANY (+RO)" y+ e. c( i$ {6 Z
- }
0 z* p' Y5 r3 |5 `! W - RW_IRAM1 0x20000000 0x00050000 { ; RW data
$ l# G2 n2 M. ` s1 j/ h! i, G ^ - .ANY (+RW +ZI)) Q7 W& a2 m) @0 s
- }
; j) B1 M+ v7 H* [ - QSPI 0x90000000 0x00100000 {& w. `5 d; i. q$ y: Y9 Q
- *.o (.qspi)
2 w4 j: [8 A3 O) Y - }! E! V+ d8 g3 S, W7 f
- }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,. D5 z! U+ S; i1 n! ^
学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,4 c3 t8 h0 U0 Z6 T- h* r' ]
再看一下F7的内存映射. ~* O( Q2 K: m
' Y7 p- M( O8 s! M0 C5 a/ Y在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。- y$ Z3 _- O; V$ ~) I. {9 D4 |
因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode 3 n$ O% C: J# f' ]) [1 [$ E
(6)判断传输是否完成,判断的方法同步骤4 F$ p. U+ R6 o3 r& K
(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000
: `6 f3 F( \* d0 C7 }# G: t5 _(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash4 ~* @: X' T7 _- D" `& w# ~3 f% t1 s
- /* Configure Volatile Configuration register (with new dummy cycles) */
$ u8 E( v" ?8 U1 q; g) Y4 z5 s - QSPI_DummyCyclesCfg(&QSPIHandle);
9 f0 e7 c+ A5 N
2 b# A' J) D9 R# G- /* Reading Sequence ------------------------------------------------ */
1 W9 O8 W1 ~. O! r i% C - sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;
" V8 ?3 Q( ~* Z) ^( I* q4 J - sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;# _ G1 d0 _8 @% |$ G3 m% H' R
- , ]5 q/ S% |( q$ Z' K; w/ v
- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;$ {; ~; X5 z$ {3 X! r7 r
: t X5 T& o! ?( ]" w5 Z+ D y A- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
: p$ k- _1 i5 {/ L7 i, k - {
" Y8 L! g* e! h2 q. M - Error_Handler();
, O- n! c, \8 p3 i7 E - }
复制代码 (9)执行.qspi段的代码! s1 ?* r3 e9 A: k% }8 w/ O, g0 o
- /* Execute the code from QSPI memory ------------------------------- */! i( Q. O! |# Y& T, x2 D$ A8 B
- GpioToggle();
复制代码 注意0 c& e4 C4 ?* u% g
- #if defined(__CC_ARM)6 ^+ y/ c# [1 e7 G% g; d
- #pragma arm section code = ".qspi"
9 {7 e7 o9 }* M- G/ f - #pragma no_inline
; t* a& b- i# M' _3 q0 } _ - static void GpioToggle(void)
' I! {9 V3 v4 w" F* M - #elif defined(__ICCARM__)
4 P! `" N9 z2 n9 g - static void GpioToggle(void) @ ".qspi"
" T/ }. X* C- S! O' \3 Z - #elif defined(__GNUC__)' z, v3 x5 a3 d6 Y2 b8 k
- static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)
" n! }6 t) B3 H; z2 z" t. l* Q - #endif8 G. u2 o0 |2 S! q6 H
- ; z* ^7 l9 ~6 x/ E3 W6 t
- {
5 |! s* }5 Z5 u) W3 \# E - BSP_LED_Toggle(LED1);
. y7 B; U0 g% U' H' V% P - /* Insert delay 200 ms */# K+ ^6 n0 E% ~2 p" \7 ^) J
- HAL_Delay(200);5 ~" R, Z+ b# U$ }
- }
复制代码
, ?5 y S' K: S) D5 H1 Y我的问题:
, |6 A4 Q8 O/ q* z学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下
9 w4 b( `/ h7 p2 n8 t$ } |
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!