STM32电机培训online,大佬带你玩电机
' ]0 J! k7 Y. |. W- i2 O4 p5 G$ f& I i% r0 f' R; X+ g' i" n# [( ]5 f& ^
FMC和QSPI引脚冲突的解决
! V6 d6 j7 x5 O: J9 f
$ K9 ^3 _9 ?$ P: g. w分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式' H7 z/ u8 x$ K/ |
9 n# K9 n0 C, k! z
' a+ H9 j& J/ ^9 P+ _! T1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高4 h" x7 z. j' E4 v$ Q) o2 {
STM32F7的quad-spi接口有三种模式& E3 k( i( P; z$ K; O7 ~ w" r
(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式
1 x8 e* z, X9 n V7 B# h(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号) f9 m0 H7 H6 d u! v
(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式
8 l7 W) N- o! M. M) [( p: @2 u* A( G
STM32F7的quad-spi接口主要特点:% N' o" c% ?: ~& B3 Y
(1)三种工作模式* \5 s3 _. J" G1 U: e& x
(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4) }% D z: P8 _. D
(3)支持SDR和DDR
9 J. x7 |- |, X& q4 T; h% D x(4)间接模式的DMA通道$ {( j1 m2 F' k) |" A3 N& T
(5)内嵌接收和发送FIFO. r9 K; r( b1 j; _
(6)支持FIFO threshold, timeout, operation complete, access error四种中断
4 e2 B& R1 ?5 d/ N$ U: L7 H7 P" D% e- W" l
Quad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示: n6 M/ I7 k' s# f( x$ |4 h$ l
A! l1 a' P' m4 F总结一下特点:$ }+ u% t, p- Y9 `7 w& P5 y1 d
(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输,
7 w: f5 m5 u5 x: N$ @7 G# s/ H% q(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle9 O$ m6 G6 e$ @, E$ T
(3)这5个阶段都不是必须的,可以没有
. p4 U# w1 s+ r+ R(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA
! e( D: n1 b$ ^; o$ r4 L(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输
2 K4 x8 T4 F( J9 }2 w5 v(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=1# W6 h( M+ J' T+ }. t% u: [
2 P0 L4 E2 U3 C9 Q) E& ]% j2 F
; n$ J A+ x) \, X
) V1 V ^) D6 a$ U# ?/ w3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示
3 G- |8 U7 p; f& I) f6 m6 {0 b- T& h3 r" Z
* _* Q' y; B, S5 f
这里主要说明quad-spi flash代码流程分析
9 x: H" s6 [* W( o, L; |论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。
8 z. i0 s& I2 W* T) o' G选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程
( Z# I4 v' x' A0 T5 E! v! h(1)Flash配置寄存器初始化) O$ v/ s6 l* {7 B! v8 z" {
- /* Initialize QuadSPI ------------------------------------------------------ */8 l) P O7 c$ Y0 l* L& q9 @
- QSPIHandle.Instance = QUADSPI;
5 e" X2 @0 m9 u& E8 _" x/ { - HAL_QSPI_DeInit(&QSPIHandle);
y2 B; c- }. { - , v% X$ v1 L+ ^5 r+ A* h2 a: B
- /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */. z1 t; J8 L& U
- QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span>- V9 {6 }. b, j8 E8 Y+ J
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,
+ w1 f7 `; x7 O- S - QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
2 z% ?5 e+ T) |. ?. | O' s - QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB,
3 q4 C. {" v) ^+ H1 `$ o9 H4 I" S - QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands
* q. s) n- p8 P# p5 s - QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released
. Z% ]8 l8 V" ?2 l - QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash7 U+ L9 f W/ S& w. p. D' \
- QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
0 Y3 u1 q8 I5 @. y5 R+ g - ) `0 A: z {8 t& p, }/ J- a
- if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
6 f+ v7 Q8 Z* |# V1 ? - {
; {( z- k6 S/ i* M) A8 [ - Error_Handler();
4 {4 e$ u7 L8 l0 r5 N1 S - }
复制代码 (2)使能写操作3 q7 M, z9 ~' a
- /* Enable write operations ------------------------------------------ */
: ]& {8 T" M% c+ A$ r: @" M5 W5 { - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
" v. U" B! }8 L4 v - sCommand.Instruction = WRITE_ENABLE_CMD;
! F- Q/ i; h) t0 e) L - sCommand.AddressMode = QSPI_ADDRESS_NONE;; _3 |3 E5 F9 v1 s" e
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;, F! ] L7 T' u Y$ p
- sCommand.DataMode = QSPI_DATA_NONE;
/ u7 Y. a* \ `' ~( z - sCommand.DummyCycles = 0;/ w( C6 E1 P; H& b/ B3 D1 o6 H
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;) C- M, z7 ]. N1 f8 ]
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
% ]% y1 |4 T G1 @( f4 h. N w - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
! f! G; a' E9 A
* [: c- i0 M/ C0 c- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)) c: W$ Z/ m& M( j
- {
( T6 ]% y+ V* j {0 b( x0 w$ a - Error_Handler();
5 d, M6 H% Y$ |# I1 m& l4 w - }
% i7 {& f# C$ a - 3 D. m' P8 `( a. R$ y1 c
- /* Configure automatic polling mode to wait for write enabling ---- */ + \2 T- m1 y8 L, r7 m" p
- sConfig.Match = 0x02;2 D( ` m3 [' D5 ~7 Q, F) z8 I4 z
- sConfig.Mask = 0x02;
* y! m0 Y5 }5 O" h* i) I- ]& Y; ? - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
7 X! O* }, q6 {2 ?0 [( V$ w" N. d - sConfig.StatusBytesSize = 1;
4 s) l' S5 q1 v+ J: [+ }. H$ e& R - sConfig.Interval = 0x10;) \1 J' H" I/ _" ]; W" T
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;+ y2 k0 j# [! S/ z: [
- , k: L3 x4 b- I5 w
- sCommand.Instruction = READ_STATUS_REG_CMD;
+ i/ R; k/ {4 k) a - sCommand.DataMode = QSPI_DATA_1_LINE;
* M! V0 U& S5 | y5 W" ~
3 f v* Y5 l% K$ X6 k& E( Z; k, Y- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)2 ~9 E- X1 X; u3 M* h+ \; m2 l0 _/ H
- {. K2 L0 f* ^! W. P
- Error_Handler();
% D! j' t0 w" {% B* d - }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段
# c% G$ d4 L6 D7 U* k' o* B2 O源码中定义 #define WRITE_ENABLE_CMD 0x06;
8 l* R, ~' O$ A5 U% {7 y这里要查阅芯片手册
' l9 n& A: G$ \( X
1 j9 o! J# w+ d: B7 c: F确实是一致的。' A% n% z2 x* u/ O1 m2 q) r9 a
接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register6 `* z/ R7 y. V/ L
automatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式,
$ r' R+ f3 P+ ]+ u查阅手册:
. d8 c9 S& E4 x$ a4 V/ ?7 @6 R D6 L! l5 o8 [
4 z& u9 i) V. @9 v" O- T
确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;$ L8 D( R g, T, ], o4 Y1 E
(3)擦除flash的第一个sector
9 y, Y b, H5 u1 U- /* Erasing Sequence -------------------------------------------------- */
0 ^" N1 x4 }( c - sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector( G9 Q! F) m; H
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
: F. M2 |( j: u2 ~ l D1 Q& t - sCommand.Address = qspi_addr; //qspi_addr=0; F* j, y$ u: K' q3 |+ Y# W: E
- sCommand.DataMode = QSPI_DATA_NONE;6 I( }. A; c! q# ~5 b$ G
- sCommand.DummyCycles = 0;2 g& S1 O6 ?& D* }
, s+ S9 w# C* O8 K. x- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)
. x9 L5 d9 Z3 {! ?* t) T - {
) ]/ j K2 X( X; s4 P8 C* j1 b) B - Error_Handler();
( L0 |6 v/ @1 Y1 [' b4 U0 h - }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册
% ^6 P) Q# f2 T, a0 W f
. J) a" y+ D* s" z6 b
- /* Configure automatic polling mode to wait for memory ready ------ */
( O1 i0 h' v% M; a( m: | - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;/ h1 Q$ x9 r4 f. F: b& ^# U- q
- sCommand.Instruction = READ_STATUS_REG_CMD;: `( J, H) F% e# r% r; o" H
- sCommand.AddressMode = QSPI_ADDRESS_NONE;
9 V& A4 t; F9 z' S( I! i* u - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;1 H, s1 l" s0 ]" N& I2 P
- sCommand.DataMode = QSPI_DATA_1_LINE;& G7 ]& f, H, l
- sCommand.DummyCycles = 0;# N) ~" Z- Y6 C3 F
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
- z! t0 D" A# I7 f" K# i - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
# W5 [% E- D) v$ i/ j( I - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
. u5 u. { u% F2 ~5 V
4 o5 B h# Z9 y* |" O% o- //bit0:write in progress 0:ready 1:busy # ~( j* W' m" d/ H( {
- sConfig.Match = 0x00;
! _- r6 }9 o+ W' Y) w: z - sConfig.Mask = 0x01;
& e( C! ]9 w+ Z0 Y6 I, G& v8 p - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
; V/ Q6 q) J7 A9 r: z- Y8 p. { - sConfig.StatusBytesSize = 1;
l) \# e G% P+ R5 c$ j6 W! t - sConfig.Interval = 0x10;
$ o, r, i( n# M( w0 S3 }2 u - sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
5 h; C) Y; ~4 z2 N3 E' Z
& b! f/ X: ]4 V& `6 ^$ C0 ]- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)
8 A1 E h8 h3 ~! ^' v0 S - {7 y. ]( Y. P' z2 m0 U- C Q
- Error_Handler();' G/ I' b& a: @2 Y& e+ N, i
- }
复制代码 (5)写使能并且写数据( |+ B6 e7 j8 P1 i { ]. w" m' Z
- /* Enable write operations ----------------------------------------- */
* z8 w- v% ?) g2 [1 q: n* A - QSPI_WriteEnable(&QSPIHandle);+ Q/ S, K$ Q0 f9 R2 ^4 i
1 H& I% S# I A0 U N/ @- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ0% A+ x8 a+ |/ w0 u
- /* Writing Sequence ------------------------------------------------ *// C3 h6 ?: Y9 D( z- ?
- sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;
% {: b, V/ p. P5 h u - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
* g$ Y5 y6 j3 X+ ~ - sCommand.Address = qspi_addr; //qspi_addr=0
! s8 R) R+ P' @" O - sCommand.DataMode = QSPI_DATA_4_LINES;/ f6 F9 Q7 |8 n7 [+ p& _) G
- sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256
) S1 O. Q/ }& w1 ~" y+ g
" K8 u: C) g! U) H( [7 a D( A- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)+ e. T, `4 _! |2 _$ e, |1 _
- {; \) L8 E- ^3 I6 W. G' C( }
- Error_Handler();. i$ T% o4 U! s7 Q- M; Y
- }
6 Q$ }* @6 y# v2 ]2 _! K
" C. ~; g( G# @& Z4 T, J1 u) g/ d5 k2 g- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);) {) T& W0 H/ x8 Q0 z3 i
- {
! H( h7 O& r# T z - Error_Handler();1 }0 j) j0 l5 T3 d4 Y
- }
复制代码 看到这里就不得不提一下scatter file文件8 ~7 R( H2 g( s V7 T) n
' n1 g9 M# a3 b. w. g1 k
打开这个文件,STM32746G-DISCOVERY.sct0 H/ J, F6 k, N' c
- ; *************************************************************, i! B3 F( [4 b4 A
- ; *** Scatter-Loading Description File generated by uVision ***5 |9 [3 _3 ~0 G
- ; *************************************************************# j4 ^- \: d$ C6 g0 t
- ) U9 q% W ~1 H9 z5 r' S
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region
4 W5 O9 d7 W& H! c& [ - ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
1 w8 o l2 F& ~1 ] - *.o (RESET, +First)6 w9 u2 ?+ w: w' O. c7 h3 c4 K" M% Y
- *(InRoot$Sections) ?# W# v2 Y; _7 Y. v$ D; w2 a3 B# ?
- .ANY (+RO)
! T' g8 i! e$ J8 C, _& `) I7 G+ c; G - }( q5 j- ?- R; |
- RW_IRAM1 0x20000000 0x00050000 { ; RW data) L* {$ e0 j* H6 U9 Z: e/ f
- .ANY (+RW +ZI)" b0 F$ ^! S* @- y
- }
# h% M0 J& V7 A2 w - QSPI 0x90000000 0x00100000 {
% j! r$ c* b) V) N8 ? - *.o (.qspi)" w( J3 R' } T) r
- }4 x- M2 i; G l
- }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,
5 L( Z: u- F% Z0 K8 j7 ^, _学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,
$ f( Q5 W% l7 g3 v1 l: q再看一下F7的内存映射
# ^+ r7 W' R0 |. W0 D
8 M4 P; U4 H: @/ k/ E+ s: [在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。
9 D# |" l) K4 @" v% z* i1 y# m因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode
A# @* H/ q* @! z( I# J- ](6)判断传输是否完成,判断的方法同步骤4' x% v+ M$ s9 X, F
(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000 ) @5 B# V& L9 d* c
(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash
# D' ~8 z$ Y' c9 `- /* Configure Volatile Configuration register (with new dummy cycles) */
- a% ?, P- l; A# I1 a7 f - QSPI_DummyCyclesCfg(&QSPIHandle);8 m; E# b x& l7 V0 C* ~( g
- & G9 H! t' L% g. c/ `& d. X: H
- /* Reading Sequence ------------------------------------------------ */; }( V, |; L% r% o
- sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;
: `" l3 X/ d |3 g& p% @) E- q - sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;
0 w X8 ]( {* t0 ~: y
: B5 I# A3 O2 d- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;- c$ p* F K4 V1 ]" d0 ]
/ \8 h! z1 x! A! l* ^2 I, Y- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
9 ?' K0 A" K+ P - {- } o3 |6 @" p9 T$ q
- Error_Handler();
3 t2 e3 f* v+ K( i1 z - }
复制代码 (9)执行.qspi段的代码1 Z, T) G/ [8 A8 q5 }% G$ I$ z
- /* Execute the code from QSPI memory ------------------------------- */
) k+ m6 x0 X! V1 C" n9 P - GpioToggle();
复制代码 注意) q2 _8 k+ Z4 g* k$ N% }
- #if defined(__CC_ARM)
, {2 {4 i b8 Z% m7 @ - #pragma arm section code = ".qspi"
" F& s2 |3 B. a8 X - #pragma no_inline
; W( \, i% z0 x4 U7 K - static void GpioToggle(void)& }! J1 f; u0 P$ g: ]9 J
- #elif defined(__ICCARM__)
- f& p; }+ H' ~( t' s! _1 G - static void GpioToggle(void) @ ".qspi"+ K4 Z% ^9 C! |/ @" a2 |6 |; F
- #elif defined(__GNUC__)
/ G; j+ @/ p( x. `: ] - static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)
9 ]4 `" E4 w4 h - #endif
+ u( e) v- u1 ] l9 g
5 X* m3 r/ c; ^- {
1 G$ U+ _# ~- o" `# u# e2 Q8 d - BSP_LED_Toggle(LED1);1 S" \- T/ Z. u' W1 z/ v) o
- /* Insert delay 200 ms */& a0 f# a- ?" ^, f5 z
- HAL_Delay(200);
, H& Q/ M5 _6 J% l9 @ - }
复制代码 " i9 a. Q. @; ?6 K* S
我的问题:5 y8 b6 H' i/ W1 `
学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下
; k' U) U1 q- t' c |
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!