STM32电机培训online,大佬带你玩电机
& z, u& Q* J" g" ~! K/ `
% {8 v: n( [* K2 b G; }FMC和QSPI引脚冲突的解决
. O* _& f# E" g( r+ T: b: u
; w* V" @ _% y* h, a" P! T分享一个 QSPI N25Q256A的读写程序,支持QUAD, 4字节模式
% w6 [# h! Z$ F( K% s& c c
, `- r8 p& E' L: }# e3 m- {0 d: L1 G5 |% s- `9 r K
1.QuadSPI接口的特点。与普通的SPI Flash接口相比,quadSPI可以接四位数据线,传输速率大大提高
, r; B2 D, n& Z# nSTM32F7的quad-spi接口有三种模式$ i- U/ F" V' ]" u" U
(1)indirect mode(间接模式):所有操作都是用的QUADSPI寄存器,通常在对FLASH寄存器配置时用这种模式
. Y+ O: ~7 S4 ]$ ?/ I1 S8 p. z(2)status polling mode(状态轮询模式):外部Flash的状态寄存器查询使用的是这种模式,如果开启中断,可以产生中断信号$ X7 u: a7 A; \$ s3 m" t
(3)memory-mapped mode(内存映射模式):外部flash映射到MCU的地址空间,可以视为内部闪存,读写数据用的这种模式
4 h1 F* d6 { y; U! |1 t- c( F/ ~ [7 d) P! l
STM32F7的quad-spi接口主要特点:, Q7 J3 G% Q: w' w/ p
(1)三种工作模式
# d% _% E4 M$ z* l" h; x7 N(2)Dual-Flash模式,可以同时接两片Flash,共用CLK和CS片选线。这样可以最多同时传输8位数据(4+4)* _/ Q* ?6 t* {' U% r" z M! D
(3)支持SDR和DDR
& O$ e3 H* Z# f3 M6 x) Y2 B7 {# c(4)间接模式的DMA通道' d+ D. L5 O& `2 h K1 c0 ?/ ]
(5)内嵌接收和发送FIFO" K1 i, K+ v9 f
(6)支持FIFO threshold, timeout, operation complete, access error四种中断' H1 W' g( |: o# C5 R/ J2 J+ I5 g
' Z6 L: a6 n* ^+ sQuad-spi完整的命令格式由5部分组成,分别是Instruction,Address,Alternate-bytes,dummy-cycles和Data这5个阶段,时序图如图所示9 m& {% }- F& G) m, z2 ?
' z$ g$ C5 o: E% h- o总结一下特点:
% {* `* B) \" \ p(1)每个阶段都可以选择是 1bit(SO/SI线 single SPI mode),2bit(IO0/IO1线 dual SPI mode),和4bit(IO0/IO1/IO2/IO3线 quad SPI mode)传输,
9 m$ A; v) `% ]3 w(2)写数据时,dummy cycle可以为0;读数据时,为了保证足够的转换时间,因为之前是写数据,现在要变成读,至少要1个dummy cycle
8 C( |0 O9 m8 l2 z* Y% [$ ^) H6 U(3)这5个阶段都不是必须的,可以没有0 H# b$ ?# x% T4 r5 h( g6 O
(4)indirect mode模式,数据读取时通过QUADSPI_DR寄存器;memory-mapped mode模式,数据直接返回和输出通过AHB总线或者DMA
" y0 G+ p( ~4 Z1 Z0 w5 T5 U* p(5)SDR和DDR模式的区别:两者的instruction阶段都是CLK信号的下降沿数据传输;在DDR模式中,Address,Alternate-bytes和Data这3个阶段都是上升沿和下降沿都有数据传输) H/ J# V& c S( Y, |; q* H
(6)F7有32-byte FIFO,可以设置threshold(阈值),接收数据数目超过该值时,FTF(FIFO threshold flag)=1
: Y: _: j* n4 O; ^ @4 }; c0 K; B( z( l% j) r; n
c. T$ {/ E$ H$ w, ^9 k7 c2 Y
2 d! l- K3 ~% T# I6 q. E& `3.STM32F7-Discovery的quad-spi flash使用的是micron公司的N25Q128A系列,有128Mbit容量,后面附上数据手册,原理图如图所示8 O* R! T$ V+ }2 t8 { U
2 J* V- [; V" ~9 C* T/ R/ ]4 W
& e8 [+ k' F! I2 n这里主要说明quad-spi flash代码流程分析- k" e0 ~. z+ F3 K
论坛可以下载STM32Cube_FW_F7_V1.1.0压缩包,我也是从里面的例程中学习的。
" d8 L6 M( k" H: N/ Q9 V选择STM32Cube_FW_F7_V1.1.0/project/STM32746G-Discovery/example/QSPI/QSPI_ExecuteInPlace例程5 b; \5 C# R& n' q) [
(1)Flash配置寄存器初始化) j0 r7 h8 \7 E: K( t t- d
- /* Initialize QuadSPI ------------------------------------------------------ */0 E3 C' A3 ]$ t$ t
- QSPIHandle.Instance = QUADSPI; 7 ?& Z8 {4 K6 Q& d" x7 w
- HAL_QSPI_DeInit(&QSPIHandle);9 @- Z9 o6 }1 O/ X' r$ s! D
- ) t" v7 k) {- V z1 s1 z$ }# d
- /* ClockPrescaler set to 2, so QSPI clock = 216MHz / (2+1) = 72MHz */
: m' e2 e+ ~$ Y7 p" ^7 }, m( D0 i - QSPIHandle.Init.ClockPrescaler = 2; // <span style="background-color: rgb(255, 255, 255);">查阅手册可知,最大频率108MHz,这里为什么不用1呢??</span> h% S0 e; Q* \& E% u9 |
- QSPIHandle.Init.FifoThreshold = 4; // FIFO的阈值为4bytes,
$ v6 }3 `1 ]) o X$ x9 w, D - QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; 9 u3 e( _7 f; j3 ^. G' N( f, q
- QSPIHandle.Init.FlashSize = POSITION_VAL(0x1000000) - 1; //0x1000000=16MB, & r; R9 `$ J2 E: R$ V
- QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE; //nCS stay high for at least 2 clock cycles between commands* x+ f/ i% {% M, P1 c C
- QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0; //Clk stays low while nCS is released. H3 I' L- x) ]' _. h( Q, h
- QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1; //选择第1片flash
- P4 z, z1 ~0 X( L - QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;: o% ]$ g( ~8 E3 d0 M
- 2 z, Y, F: w) R0 K, L, q7 W
- if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
. a5 O& N8 G) _" p* L+ d- F - {
1 P1 K9 w4 G4 G Y! m - Error_Handler();
' H% H; U; Y$ Q2 E/ S - }
复制代码 (2)使能写操作
2 n9 W8 b# Q2 L. h3 K8 X4 X- /* Enable write operations ------------------------------------------ */
2 w' b+ z4 A2 M) P - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
w% k: c1 q. B( F - sCommand.Instruction = WRITE_ENABLE_CMD;
n3 {4 `2 ^1 K; U) w* c - sCommand.AddressMode = QSPI_ADDRESS_NONE;+ }+ T8 l% Z( @" v2 g
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
" G0 @$ \3 w9 v7 N; s - sCommand.DataMode = QSPI_DATA_NONE;2 M6 {, E/ S ^* A
- sCommand.DummyCycles = 0;
( L, V- v$ t, E" R - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;) @0 T2 J' S0 W B; J3 m v8 x
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
9 m2 V% N$ A4 k- t2 O - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
3 u! V% m$ g) u7 k* z& Q- |5 s# U - $ K/ c5 E; i1 U/ `' ], B
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
. G% C7 i: i B. O$ W - {
! a D9 O8 r3 R - Error_Handler();8 P* f2 ^7 b3 m& {8 T: g7 q: E+ M) e
- }$ o' B. X8 ^* }4 R8 B
- 9 D$ F5 v6 E, V: q% v
- /* Configure automatic polling mode to wait for write enabling ---- */ , m; S( G; B$ B2 u7 d
- sConfig.Match = 0x02;
9 {8 o9 ~8 M9 Q- s5 y - sConfig.Mask = 0x02;3 k2 \ Y- d. I4 b' j+ n9 f
- sConfig.MatchMode = QSPI_MATCH_MODE_AND;
! c9 d$ X3 P! S" I - sConfig.StatusBytesSize = 1;( Q- Y* A& ^+ R1 Z' t; B& L1 Q
- sConfig.Interval = 0x10;3 v9 Y* L T8 J9 g$ c3 i
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
1 d3 t$ p- d1 F( H4 S6 c - " T5 G& `3 w M7 _* t; a, ^ `0 |
- sCommand.Instruction = READ_STATUS_REG_CMD;
( l) I/ ~7 {# i+ y* w3 F - sCommand.DataMode = QSPI_DATA_1_LINE;
6 z# @( k$ e# ]0 q - 2 D8 f* d6 K/ l* T" P3 U
- if (HAL_QSPI_AutoPolling(&QSPIHandle, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)* t: {4 p; {; V
- {/ R) b6 c0 T6 ^7 q* i/ B& f, n
- Error_Handler();
% K1 c6 {. K: z - }
复制代码 首先使用indrect mode,使用1bit instruction传输,命令是 WRITE_ENABLE_CMD,无其他4个阶段& l) D4 N6 i; z9 D- L* @& A
源码中定义 #define WRITE_ENABLE_CMD 0x06;. C, W4 M0 k& V+ ^
这里要查阅芯片手册
% b3 [" t* }, k( B; j0 I4 ~
$ q+ i$ J0 o0 j* t" n确实是一致的。
) ^; q, U( {# N1 U9 W( t接着使用automatic polling mode,命令是READ_STATUS_REG_CMD,读status register E' R% j- s' v+ T
automatic polling mode有如下功能,它将查询的得到的值和设置的Match值比较,只比较Mask中bit=1的位,设置可以为AND或者OR模式,
. v1 Z& E6 {/ E( Q2 c4 h查阅手册:
3 _2 p3 A, @# l0 y
g9 q8 w! J2 H, O; _( Z
- d, b5 C5 w, @9 j. ?) T3 |3 n$ n
确实是bit1,即mask=0x02,当该位为1时,说明写使能,故match=0x02;
: E5 s% m( y6 W, L1 ~+ C5 A! V(3)擦除flash的第一个sector
9 G7 F0 i i7 M- /* Erasing Sequence -------------------------------------------------- */
) D: l9 F( n( ~" N+ g0 D - sCommand.Instruction = SECTOR_ERASE_CMD; //块擦除,64KB one sector
' B8 j, @$ \. e& p - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;6 X! A9 w# _$ l9 H. a
- sCommand.Address = qspi_addr; //qspi_addr=0
5 l" L8 K- d- F - sCommand.DataMode = QSPI_DATA_NONE;2 `1 l+ ~" _# I7 b \) b* x
- sCommand.DummyCycles = 0;- [2 m5 D( x1 j% j) Z
E* T* b, D4 j; m+ Q. Q" S5 a- if (HAL_QSPI_Command_IT(&QSPIHandle, &sCommand) != HAL_OK)
/ [, s' F& K' \* m% N - {
8 l: E7 o! z$ t3 Z - Error_Handler();1 c- Y" \& Z0 f# u: r
- }
复制代码 (4)等待擦除完毕,使用automatic polling mode查询,这里还是读status register,但读的是bit0位,而且预期值bit0=0;查阅手册% S, l) m5 V0 n- Y
; H1 ?& u: q6 m I
- /* Configure automatic polling mode to wait for memory ready ------ */
. u- t C8 q5 A/ C& J2 J: u; ~ - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;, L- v) R- n; m, T7 s' f; p [
- sCommand.Instruction = READ_STATUS_REG_CMD;- a* O( f3 K1 x
- sCommand.AddressMode = QSPI_ADDRESS_NONE;& h7 _, z3 \$ R3 H8 l" f
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
. F+ v$ Y P' C* J0 T* G - sCommand.DataMode = QSPI_DATA_1_LINE;* W' W6 Q: z7 ^, t3 f# V% X
- sCommand.DummyCycles = 0;
! K5 F: l8 `+ B8 A/ E - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;5 T' J, Q4 x' y+ }6 A
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
. s+ _$ \6 A b; u7 b! Z - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;' |9 a9 V t. ?2 a% {) b/ \
- 1 z$ j3 b/ w1 y, Y" B
- //bit0:write in progress 0:ready 1:busy
0 g% ? i% S. G - sConfig.Match = 0x00;! t( V5 b t- U% R
- sConfig.Mask = 0x01;
' R- e! ] @6 z- o: M - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
1 j4 h: g8 |* ] - sConfig.StatusBytesSize = 1;0 s- v4 ]* b* g) K: l
- sConfig.Interval = 0x10; ^7 q/ w/ J& e! T! q
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;3 T& Q/ W' u2 l, u" l& K, d
1 Z& N8 J0 f- _2 A5 L: N- if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)
2 W/ ]! }& r3 R2 ^% v7 s+ J - {
, Q3 N! v8 @- r8 z8 w$ m - Error_Handler();
9 B3 A. d, ` T% F Q - }
复制代码 (5)写使能并且写数据9 t7 v) [+ B. s" ?: O/ Z
- /* Enable write operations ----------------------------------------- */
/ Z6 t: Y( y/ c6 Y: b - QSPI_WriteEnable(&QSPIHandle);0 Q8 i ]' Z/ Z8 W0 K
0 V! T- w4 \( ^. g5 Q X3 W- //QUAD INPUT FAST PROGRAM Data In:DQ[3:0];Address In:DQ0
+ q# j$ J5 j- M# o) b3 E0 g - /* Writing Sequence ------------------------------------------------ */
3 h9 Z r- d4 i& b' m/ U" A: E - sCommand.Instruction = QUAD_IN_FAST_PROG_CMD;
* I& P x! ]' H: k7 T/ ] - sCommand.AddressMode = QSPI_ADDRESS_1_LINE;3 x- l' q4 t. U$ H+ N/ W6 u5 v. o
- sCommand.Address = qspi_addr; //qspi_addr=0
& e! k! E% e! t. U& O+ n* e. f - sCommand.DataMode = QSPI_DATA_4_LINES;
8 O6 E5 G! U, f* l( Y' w, D - sCommand.NbData = size; //size = QSPI_PAGE_SIZE = 256; r: A8 h' |- M/ u
$ b0 B8 F S& O1 F, N1 d- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
0 ]$ X+ K$ z/ L0 u) }+ V! C) b - {
2 y5 G7 p1 g4 g - Error_Handler();
% Z; ^$ h# R: D9 T, P) a$ I i - }/ I) f3 u/ Y' F! ~, z
2 _$ M, v8 E7 n- if (HAL_QSPI_Transmit_DMA(&QSPIHandle, flash_addr) != HAL_OK) //flash_addr = (uint8_t *)(&Load$QSPI$Base);8 F# H: {" `; i* C& i
- {
$ N1 k$ a$ G' J6 V3 ` - Error_Handler();, A& a" N- v% R0 s/ v
- }
复制代码 看到这里就不得不提一下scatter file文件6 [/ e0 x' W5 A( {" y' c/ }& b
/ i1 Q" d9 I: C3 M& a
打开这个文件,STM32746G-DISCOVERY.sct
( y' E/ ]( }3 A! ^- ; *************************************************************
/ Z5 n( L/ V- ?9 T8 k, z - ; *** Scatter-Loading Description File generated by uVision ***
. P: v& K! G7 Z+ r! N( D - ; *************************************************************6 ? H: b I: k
- ! Y1 w0 P: J. r3 M: d/ m0 d
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region
9 g& H. H; g3 D F' Z# a) @ - ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
* A5 u* m0 p/ [/ H: M - *.o (RESET, +First)# o) M3 ^: b* Y% R! `* o
- *(InRoot$Sections)& R+ z7 m2 p( s9 N9 v0 ]% ~5 x
- .ANY (+RO)
^: f! ?( p1 c) s( t - }
. r; g% R0 M& R) D1 t9 ]/ a& _9 y4 h - RW_IRAM1 0x20000000 0x00050000 { ; RW data }# V8 f3 Q. t& ~
- .ANY (+RW +ZI)2 C0 Q6 d6 |4 L7 s
- }
1 Z+ }" _4 U* K7 n0 i$ O - QSPI 0x90000000 0x00100000 {" y1 I8 B; i4 s
- *.o (.qspi)
: |9 B0 j& x3 Y, n- z& y - }5 S2 m# p; t5 y
- }
复制代码 可以看到和一般的scatter file(分散加载描述文件)相比,多了一个QSPI执行域,/ h M' f4 x1 y; `0 Y# t/ O
学过的都知道,这里表示链接时,将所有目标文件的.qspi段放在QSPI执行域,/ k+ ?7 N% C) D5 `: ?
再看一下F7的内存映射8 q9 S+ e2 @2 W
. Z$ ]# }1 j+ m& w; G在0x8000 0000到0x9FFF FFFF有一个Quad SPI,这就是memory-mapped mode的由来。) q6 w9 A: O; J! S, Y2 |8 r0 W) e: [
因此上面flash_addr = (uint8_t *)(&Load$$QSPI$$Base) = 0x9000 0000; DMA传输就是将 0x9000 0000处开始的size = 256bytes的数据传递到flash,模式是indrect read mode 3 T5 R( O ^1 U) N
(6)判断传输是否完成,判断的方法同步骤4
$ v+ b: F2 H( j9 m1 O(7)重复5、6步直到传输完max_size = (uint32_t)(&Load$$QSPI$$Length)= 0x00100000
1 f- S+ Q: s) h8 e% {(8)设置dummy-cycle的值,配置为memory-mapped mode,命令是 QUAD_OUT_FAST_READ_CMD=0x6B,就是说映射完后,相对于内部flash8 ]1 h, i% c# A' d! _3 m3 f* d+ {, I
- /* Configure Volatile Configuration register (with new dummy cycles) */
0 n! M1 N$ R2 B O! G( Q( Q0 c - QSPI_DummyCyclesCfg(&QSPIHandle);* b5 w$ Z D( G9 x+ ~
- + n. R& H" M2 d. z0 U, C, z
- /* Reading Sequence ------------------------------------------------ */
7 n$ T( e' o1 @5 c+ X1 F - sCommand.Instruction = QUAD_OUT_FAST_READ_CMD;( P4 \8 s+ N4 r1 \' _
- sCommand.DummyCycles = DUMMY_CLOCK_CYCLES_READ_QUAD;2 u% V! J' |7 X4 e+ }, r8 ?* K
- ' W8 Y3 p9 k3 N* M- g
- sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;: D: q4 G5 C3 R/ j5 ^
- 4 \" V9 ]6 b+ k
- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &sCommand, &sMemMappedCfg) != HAL_OK)
+ i9 ^! O% j) v0 h' ` - {, W' ^$ U# P4 e
- Error_Handler();
! w+ f( |, W; \/ p& x# }( u1 i4 P - }
复制代码 (9)执行.qspi段的代码/ E; C+ I$ ?" p
- /* Execute the code from QSPI memory ------------------------------- */
3 y# f. X) T" m4 ^ [ T- z: z) i' j - GpioToggle();
复制代码 注意5 V4 }) f- E X- L: ]
- #if defined(__CC_ARM)2 N( o; X' }3 |4 s8 _
- #pragma arm section code = ".qspi"
' u/ x* m, B* U# g4 x) A( q0 I - #pragma no_inline
1 I1 x: l; `. Z; H3 v/ F0 k - static void GpioToggle(void)2 d" T; \3 R# o" E# v& v
- #elif defined(__ICCARM__)
% H( r9 {% u% Y( Z1 ~ - static void GpioToggle(void) @ ".qspi"5 r$ g: q) R, o4 U
- #elif defined(__GNUC__)
& r7 R* I0 `$ G# t5 t - static void __attribute__((section(".qspi"), noinline)) GpioToggle(void)
6 s7 x6 Y) j# B6 ^" } b& B& L Z - #endif
9 N* B# t+ v: @ d# o; l$ \ - 4 b2 J/ T& f5 R( Z
- {
3 T0 P( C2 h6 e0 y$ L3 r! t# @ - BSP_LED_Toggle(LED1);
' l" y( V* ^7 x2 U8 Z& s - /* Insert delay 200 ms */& {% G' [' w* [6 l [' d6 G7 B
- HAL_Delay(200);7 C7 G: q; x! Z* H8 J
- }
复制代码 . ~1 o% s. U) B/ O( k/ m4 e1 x5 {* {
我的问题:4 [" s" `1 V/ O: i' X, v
学完之后,我一直对一个问题感到困惑,再用st-link烧写程序时,这个.qspi段是不是放在内部flash中,只有在执行时,MCU才会到0x9000 0000处执行,这时MCU读flash,但是对我们来说是屏蔽的,我们可以直接当内部flash使用。如果前面没有执行DMA将0x9000 0000处数据传递到flash,后面是不是就不对了。这个时候为什么0x9000 0000处会有数据呢,实际上这里并没有flash啊,希望有人能帮忙解答一下( Q* z4 ]9 K- ? N
|
请问一下,这个位置实际上并没有flash,那程序在执行步骤5,6,7,将这个位置的内容通过DMA写入到实际的flash上时,MCU是怎么怎么知道要传输数据的内容,毕竟映射的地址那里并没有flash。还有程序烧写进去时 .qspi段是放在哪里呢
支持楼主的原创分享
原来是在EEPROM的 datasheet里。。。谢谢了!