H750_QSPI_å¤é¨FLASHæ§è¡ä»£ç .rar (1.63 MB, 下载次数: 58) 2020-10-24 14:33 上传 点击文件名下载附件 3 F& f9 A) w1 a, z: b: |- r. _" V, k 排版格式有点乱,可到http://www.wujique.com/2020/10/24/elementor-795/查看 ( v! _* B& S* P4 H" d 3 _1 `1 O# N" J: g; x9 v& x ! Y5 H' w7 j, J3 c' K5 h 5 T0 l/ n8 b! ~' m& `6 mSTM32H750如何在外部QPI接口的FLASH上运行程序? 4 v G; m$ t( J6 l, v" g+ L5 f2020.10.24 屋脊雀工作室 6 Q! s9 c+ g4 f' g3 P* e/ i本次实验硬件是Albatross H750小板 2 B6 `0 N3 l! X 8 D, U) |4 l+ m: oSTM32 H750 QPI xip7 L ^/ d! Z8 ^( q2 ]1 Z- s 概述STM32H750VB官方号称内部FLASH只有128K(实际有2M)。' h7 Q, I: [2 k; K6 O$ w 128K空间太小,随随便便一个模块就能占满了。H750是M7架构,有内部Cache。所以,能够在QSPI上直接取指令执行程序,速度并不慢。0 L2 Z1 U( V$ Q* M 那么如何在QSPI上运行程序呢?有以下几步:2 W( h" V5 a t9 t( C3 f 将程序下载到外部QSPI Flash。% T" R4 I/ e$ ?! h- X 有两种手段: f; e! J% k: x/ H7 k! ^& s1 q1是通过MDK等工具直接将某些指定代码下载到外部Flash。! Q- _! \- r1 g) h2 A" ^ 2是拆分功能,将在外部运行的代码单独编译为一个BIN,做一个BOOT放到内部FLASH,用BOOT将这个单独的BIN下载到QSPI FLASH上。量产产品通常是这样做。程序下载后,将QSPI Flash映射到程序地址空间(要芯片支持才可以)。映射前,QSPI上的数据只能通过SPI接口读写;映射后,可直接读(不能直接写)。有点绕口。 . D j% e r8 C( @2 g" c* N7 U5 x比如,要读一个字节数据,用SPI操作,则需要通过SPI发送地址、发送直接、读取数据等操作。; e* O5 ] k, F7 ?- m 如将QSPI映射到0x90000000,那么程序中可以直接读,比如定义一个指针P,赋值0x90000000给P,然后 *p就能读出数据。执行代码同理,只不过代码是内核自动取指令。7 b7 \' `$ W! O $ Q5 E% C/ Y% A6 t9 v, y, R7 U$ }我们本次只是验证STM32H750VB在QSPI上执行代码的功能,因此使用MDK直接将部分代码下载到QSPI FLASH上。 . }, v( _: e# J' l0 j2 H预备用普通的SPI接口控制FLASH的知识要先了解。在做本次验证之前,先要验证硬件是否正常,也就是用QSPI读写例程验证。 . Q" U* ^2 }+ y% o; D3 e关键知识我们常说QSPI falsh,QSPI通信,我认为是有误解的。SPI、DSPI、QSPI,其实都是SPI,那么,也就都是串行总线。真正4线总线的,应该叫 QPI,QPI是并行总线。 Y3 M8 r7 J3 p% M 请看W25Q64的规格书,3 r2 |; I( c7 h! X1 f" A L # D4 {) Y: _3 M% R2 a4 p) W Flash支持SPI和QPI两种模式,其中我们常说的SPI/DSPI/QSPI都是SPI。' t. `9 w; h6 f& y) K 芯片上电后,默认都是SPI模式,注意,是上电,也就是掉电再上电。9 T; E: f9 L6 o5 a% ]8 ^' h 先置位状态寄存器2的QE位,然后发送0x38命令,就可以从SPI模式切换到QPI模式。2 v! e# A3 M {5 Z8 W. T 在QPI模式发送0XFF指令,就可以返回SPI模式。/ s; x* |! q& o4 i9 h! j& @ 这几种模式到底有什么区别?5 |3 [5 C8 V# k9 L- U5 r 不同的模式有不同的指令,请查规格书。指令的时序不一样。 9 T$ @* D9 }1 f, d9 ~% P" f% k比如0x06指令,在SPI模式和QPI模式都支持,时序如下,左边是SPI的时序,右边是QPI的时序。不同点就是,发送0x06这个数字时,SPI只用了DI一根信号,需要8个时钟周期,QPI用了4根信号,只需要2个时钟周期。9 K1 n! t( {, q. x5 G4 X! O: q5 e 1 `; ~" J m" q8 K C8 C看一个DSPI的命令,3BH。这时一个快速读指令。命令和地址,只用DI,也就是IO0通信。返回数据却用IO0和IO2。6 O! H/ h9 G' S+ Z 9 h* Z1 p' t1 l8 v. h! p$ r 那么在QSPI的读指令时序会是怎么样嗯?和DSPI的区别就是,返回数据用4根线。! J3 I+ l7 R( f. C3 R $ u5 m( t4 |1 D3 _0 r% l+ h! P更多指令请自行查看文档。2 M* U' N5 D6 C. b 总结: $ f0 M$ v4 @2 p% [不同模式有不同的指令。. }. Z' v6 y. S( h7 |3 [ 不同的指令时序不一样:指令用几个IO发送,地址用几根IO发送,地址多少位,数据用几根IO发送。! b0 F' R- j9 [' b2 u 其中SPI/DSPI/QSPI 由使用的指令决定。- l% \' r8 l, y& @ 但是QPI就需要配置FLASH进入QPI模式。所以呢,QPI有些指令的值和SPI是一样的,但是时序不一样。% Q" f6 I6 f) C8 g7 g* S$ v: G; } 调试步骤本次目标是在FLASH上执行代码。调试步骤如下。 G: Z" c. R( _& W/ l 用SPI接口读ID,能读到说明硬件基本没问题。用QPI接口读写FLASH,数据正常说明QPI模式可以。在程序中定义一个数组,指定放在FLASH上,通过MDK下载。程序映射后访问数组正常。将一段代码放在FLASH上,执行正常。" e5 X4 t' ]6 r0 h 跟多细节见代码,下面只说关键处。 2 I: [/ R1 ], c( i7 X) y读ID调用MX_QUADSPI_Init函数初始化STM32的SPI接口。调用函数BSP_QSPI_Init初始化FLASH。在初始化中设置QE位。读ID ]+ O9 ^3 B6 Q6 w8 Y7 G% ^7 l ] 现在说的都是SPI下的指令,有种,分别是SPI的0x90,DSPI的0x92,QSPI的0x94。 * Z7 d* [( \( O那要怎么读呢?如下,是一个完整的SPI通信过程。设置一个指令结构体,用HAL_QSPI_Command发送指令,在用HAL_QSPI_Receive接收数据。' C2 Q+ T6 k0 V7 l 不同的接口不同的指令区别就在命令结构体。5 S1 z' m1 o }$ h. K InstructionMode: 用几根IO发送命令# r, G9 h1 T& N Instruction:命令& |8 D% q, p1 R+ y: c1 m- { AddressMode:用几根IO发送地址 7 L; Z; H5 |7 @$ D6 y# U! |3 yAddressSize:地址有多少位+ v& P' k5 e% V- a' } Address:地址7 @7 ], |+ B" t( R, |, I" l | DataMode:数据用几根IO' T; u1 _1 Y8 A DummyCycles:等待周期" f/ D5 a- [. @) o0 T 。。。 ( V2 L3 t6 Q! K3 O上面几个设置,都可以从指令的时序图看出来。 ?; K& B) x) d4 n# E 0 b6 m4 Z; }9 R. v' @( l8 E2 B2 v& y1 L% c" o /* Read Manufacture/Device ID */ + z5 N) a! N7 q/ B% @4 L s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = READ_ID_CMD; s_command.AddressMode = QSPI_ADDRESS_1_LINE; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.Address = 0; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.NbData = 2; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { Error_Handler(); } if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { Error_Handler(); } `' y# i1 }8 L + G& B4 k3 O K% }9 Y( w/ ~( a# `+ ~' A $ i% m4 d: W) I' R6 c7 Z6 k3 v. Y3 a b3 C- t2 B* _" g2 ? f, t , B* {, d0 F0 i6 B: x 90指令读ID,就是命令1根IO,地址1根IO,数据1根IO。9 y, E) F8 Q. k4 v" ?5 o 92指令读ID,就是命令1根IO,地址2根IO,数据2根IO。7 F& Y/ @2 Q; N( o( y7 x# F 92指令读ID,就是命令1根IO,地址4根IO,数据4根IO。 # h& m3 |% X: i读写测试见代码 I* i7 K) M5 @& D* u' |" Y$ M( v流程是擦除,写,读,比较内容,其中读使用QPI模式。 1 q/ @! U2 G8 Q: ?3 F地址映射添加一个test_qspi.c在文件中添加一个数组。修改分散加载文件,将test_qspi.c放到外部FLASH,地址是0x90000000。 ' Q5 i! [5 j z- W6 p( ^: q5 w. h0 Z8 @5 x* O5 u: O 上面只说了流程,至于很多原理,请大家自行学习。) I1 {+ C0 f% w 比如外什么外部地址是0X90000000,分散加载文件是什么,,,# \6 _" |* T1 u% N- d5 V: }7 P 4 添加下载算法到MDK。 $ l% M6 X$ Q- X4 p6 b/ f先将STM32H750_W25Q64_WJQ.FLM文件拷贝到keil安装目录,比如D:\Keil_v5\ARM\Flash\( P6 L$ x/ o5 E8 t) u+ x( q A 修改工程调试配置,将外部FLASH算法添加到工程。 * @+ [' g6 D! M9 T$ s: e! T; w1 ^! n; s: p+ ` 修改前面读写测试的流程,不擦除不写,仅仅读。' ?! Z9 i$ h7 b0 E3 P5 Z q) h) t 编译将工程下载后,应该能读到我们定义的数据的内容。: B+ O4 u/ W: `# ^! R) g: J$ X$ \ 注意要重新断电上电,此时我们的程序还不够健壮,在切换SPI和QPI模式还不够完善; I' s% G3 w% O* { 5 添加地址映射 e1 y. o z9 g2 V" Y# R+ s8 l 所谓的地址映射,就是,我们只提供一个地址,CPU帮我们完成读写流程,所以呢,命令设置和前面读操作是一样的。然后调用HAL_QSPI_MemoryMapped函数,将命令配置读到芯片中。 : O, X/ P2 p2 \" |8 Y! [9 T, `0 n" L6 T1 Y + o6 L+ W- Q4 p+ R2 R2 O /* Initialize the read command */+ {# B w; H. v s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES; s_command.Instruction = FAST_READ_CMD; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.Address = 0; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = 2; s_command.NbData = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; sMemMappedCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE; if (HAL_QSPI_MemoryMapped(&hqspi, &s_command, &sMemMappedCfg) != HAL_OK){ Error_Handler(); } 7 m% h9 _& u1 R& i+ }" P* P2 }( Q( J) I% c$ H& c ! p. L) s* k" o, W0 _ 8 T W4 t2 C# _5 H; L& O( H : T+ F' }3 N8 I! @% ^2 Z# O5 K" W8 x) \; ^+ f% K2 p) R 3 Y. J& x. ]4 c5 G2 d当然,我们需要新让FLASH芯片进入QPI模式。 , @9 ]& x4 y& u! P( Z' O然后定义一个指针指向0x90000000,就可以访问到FLASH上的数据了。 Y- g2 c" h5 `$ I或者,我们直接访问定义的test_qspi_tab数组也是可以的。 * g( N Y# t1 T; W" a " \8 W* X5 j4 [$ F6 _ " s) Q- Z( U, _' J8 y4 a. s__IO uint8_t *qspi_addr = (__IO uint8_t *)(0x90000000);" m. N7 ^+ M) {- r. H, |( F for (i = 0; i < 0x100; i++){ uart_printf("0x%02X ", *qspi_addr); qspi_addr++; } PrintFormat(test_qspi_tab, 32);" \5 b; ?; S4 P* o0 s! G2 N 9 x5 `( j/ |! e6 I . q* ]. ]4 T2 c0 Y; B7 c5 E5 _+ n$ c* B4 a$ @ D! i( l) v7 p( p4 [' _0 E1 s # q- h7 f7 u' i* m( s4 H7 Q2 F3 q& l9 z* D5 U/ u3 x: _ 执行程序很简单,我们在test_qspi.c中添加一个小程序,看下是否能正常运行。8 }- A$ m6 K% N9 \$ }# x; \ 定义函数, 很简单,对传入的num进行+1操作。) }) o" X7 W: p8 B) s: _! h ( ?/ T' X; x: j8 ]. I3 C$ d$ N3 l | D% x) h! E9 W5 s E u8 test_qpi_run_fun(u8 num) - d3 l7 ^+ b S. a0 \8 }$ @& v* y/ Q{ num++; return num;} # O0 \9 p7 w$ n' d# c5 ~& D2 p. {' O: |1 Y9 U ! |% [: d/ v3 f% ^) s 7 C$ E2 V" o! A+ F& \+ ~8 O4 R5 _9 ~9 ~. Z6 b/ w3 d+ |* ?% X 1 V3 V+ T% I$ ~% A0 c % v/ i% W; d: J# j" }测试 # K/ T N/ i8 K; g 2 t. G. Z) ?% N' l8 h5 ?* I " v9 J2 t" |. d+ E) Ju8 cnt = 0; 2 X+ d3 P3 g' F1 c7 e$ ~6 A! r while(1) { cnt = test_qpi_run_fun(cnt); uart_printf("%d ", cnt); } & m! O- k) |4 z7 x+ X" c" G( ] |* s" k) f# }3 s$ W2 O0 J& K 0 G- \. Z# M' t& Y; |! _! ?6 b$ ?! Q v: x N" \5 F . e6 s. z1 b) P y: {0 y4 v' n/ a+ J/ A8 [1 F5 y7 i. g & K& {3 I! `5 T测试有一个现象要注意, 现在0x90000000地址不再是我们定义的数组了,因为我们多加了一个函数,这个函数在数组的前面。 8 c, B* l, C7 \0 Y* j8 m8 M% b7 L" f测试肯定成功。7 d2 K# Q! Y" q4 P. o2 U- u0 T 再确认如何确定数组和程序就真的放在外部FLASH了呢? I) s/ N. y8 c! I# F 看map文件,查数组和函数的地址。把FLASH管脚撬开,看还能不能读到。8 l% `8 O! }" n! x3 D 其他本次测试仅仅说明过了如何在外部执行程序。 v3 O/ G1 j) l: E 对性能并没有测试,比如,需要认真配置MPU中的CACHE设置。 8 X- j% U5 n( h, K* [完- Z* m, O6 \; k+ X