STM32H750如何在外部QPI接口的FLASH上运行程序?
2020.10.24 屋脊雀工作室
本次实验硬件是Albatross H750小板
STM32
H750
QPI
xip
相关代码在百度云,目录W107-H750VB-Albatross\5 Mdk_Demo\H750_QSPI_外部FLASH执行代码\
链接: https://pan.baidu.com/s/1H4sD9XpXz83YTTXoSIyFXQ 提取码:52f3
概述
STM32H750VB官方号称内部FLASH只有128K(实际有2M)。
128K空间太小,随随便便一个模块就能占满了。H750是M7架构,有内部Cache。所以,能够在QSPI上直接取指令执行程序,速度并不慢。
那么如何在QSPI上运行程序呢?有以下几步:
将程序下载到外部QSPI Flash。
有两种手段:
1是通过MDK等工具直接将某些指定代码下载到外部Flash。
2是拆分功能,将在外部运行的代码单独编译为一个BIN,做一个BOOT放到内部FLASH,用BOOT将这个单独的BIN下载到QSPI FLASH上。量产产品通常是这样做。
程序下载后,将QSPI Flash映射到程序地址空间(要芯片支持才可以)。映射前,QSPI上的数据只能通过SPI接口读写;映射后,可直接读(不能直接写)。有点绕口。
比如,要读一个字节数据,用SPI操作,则需要通过SPI发送地址、发送直接、读取数据等操作。
如将QSPI映射到0x90000000,那么程序中可以直接读,比如定义一个指针P,赋值0x90000000给P,然后 *p就能读出数据。执行代码同理,只不过代码是内核自动取指令。
我们本次只是验证STM32H750VB在QSPI上执行代码的功能,因此使用MDK直接将部分代码下载到QSPI FLASH上。
预备
- 用普通的SPI接口控制FLASH的知识要先了解。
- 在做本次验证之前,先要验证硬件是否正常,也就是用QSPI读写例程验证。
关键知识
- 我们常说QSPI falsh,QSPI通信,我认为是有误解的。
- SPI、DSPI、QSPI,其实都是SPI,那么,也就都是串行总线。
- 真正4线总线的,应该叫 QPI,QPI是并行总线。
请看W25Q64的规格书,
Flash支持SPI和QPI两种模式,其中我们常说的SPI/DSPI/QSPI都是SPI。
芯片上电后,默认都是SPI模式,注意,是上电,也就是掉电再上电。
先置位状态寄存器2的QE位,然后发送0x38命令,就可以从SPI模式切换到QPI模式。
在QPI模式发送0XFF指令,就可以返回SPI模式。
这几种模式到底有什么区别?
- 不同的模式有不同的指令,请查规格书。
- 指令的时序不一样。
比如0x06指令,在SPI模式和QPI模式都支持,时序如下,左边是SPI的时序,右边是QPI的时序。不同点就是,发送0x06这个数字时,SPI只用了DI一根信号,需要8个时钟周期,QPI用了4根信号,只需要2个时钟周期。
看一个DSPI的命令,3BH。这时一个快速读指令。命令和地址,只用DI,也就是IO0通信。返回数据却用IO0和IO2。
那么在QSPI的读指令时序会是怎么样嗯?和DSPI的区别就是,返回数据用4根线。
更多指令请自行查看文档。
总结:
不同模式有不同的指令。
不同的指令时序不一样:指令用几个IO发送,地址用几根IO发送,地址多少位,数据用几根IO发送。
其中SPI/DSPI/QSPI 由使用的指令决定。
但是QPI就需要配置FLASH进入QPI模式。所以呢,QPI有些指令的值和SPI是一样的,但是时序不一样。
调试步骤
本次目标是在FLASH上执行代码。调试步骤如下。
- 用SPI接口读ID,能读到说明硬件基本没问题。
- 用QPI接口读写FLASH,数据正常说明QPI模式可以。
- 在程序中定义一个数组,指定放在FLASH上,通过MDK下载。程序映射后访问数组正常。
- 将一段代码放在FLASH上,执行正常。
跟多细节见代码,下面只说关键处。
读ID
- 调用MX_QUADSPI_Init函数初始化STM32的SPI接口。
- 调用函数BSP_QSPI_Init初始化FLASH。在初始化中设置QE位。
- 读ID
现在说的都是SPI下的指令,有种,分别是SPI的0x90,DSPI的0x92,QSPI的0x94。
那要怎么读呢?如下,是一个完整的SPI通信过程。设置一个指令结构体,用HAL_QSPI_Command发送指令,在用HAL_QSPI_Receive接收数据。
不同的接口不同的指令区别就在命令结构体。
InstructionMode: 用几根IO发送命令
Instruction:命令
AddressMode:用几根IO发送地址
AddressSize:地址有多少位
Address:地址
DataMode:数据用几根IO
DummyCycles:等待周期
。。。
上面几个设置,都可以从指令的时序图看出来。
/* Read Manufacture/Device ID */
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();
}
90指令读ID,就是命令1根IO,地址1根IO,数据1根IO。
92指令读ID,就是命令1根IO,地址2根IO,数据2根IO。
92指令读ID,就是命令1根IO,地址4根IO,数据4根IO。
读写测试
见代码
流程是擦除,写,读,比较内容,其中读使用QPI模式。
地址映射
- 添加一个test_qspi.c
- 在文件中添加一个数组。
- 修改分散加载文件,将test_qspi.c放到外部FLASH,地址是0x90000000。
上面只说了流程,至于很多原理,请大家自行学习。
比如外什么外部地址是0X90000000,分散加载文件是什么,,,
4 添加下载算法到MDK。
先将
STM32H750_W25Q64_WJQ.FLM
文件拷贝到keil安装目录,比如D:\Keil_v5\ARM\Flash\修改工程调试配置,将外部FLASH算法添加到工程。
修改前面读写测试的流程,不擦除不写,仅仅读。
编译将工程下载后,应该能读到我们定义的数据的内容。
注意要重新断电上电,此时我们的程序还不够健壮,在切换SPI和QPI模式还不够完善
5 添加地址映射
所谓的地址映射,就是,我们只提供一个地址,CPU帮我们完成读写流程,所以呢,命令设置和前面读操作是一样的。然后调用HAL_QSPI_MemoryMapped函数,将命令配置读到芯片中。
x/* Initialize the read command */
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();
}
当然,我们需要新让FLASH芯片进入QPI模式。
然后定义一个指针指向0x90000000,就可以访问到FLASH上的数据了。
或者,我们直接访问定义的test_qspi_tab数组也是可以的。
xxxxxxxxxx
__IO uint8_t *qspi_addr = (__IO uint8_t *)(0x90000000);
for (i = 0; i < 0x100; i++){
uart_printf("0x%02X ", *qspi_addr);
qspi_addr++;
}
PrintFormat(test_qspi_tab, 32);
执行程序
很简单,我们在test_qspi.c中添加一个小程序,看下是否能正常运行。
定义函数, 很简单,对传入的num进行+1操作。
xxxxxxxxxx
u8 test_qpi_run_fun(u8 num)
{
num++;
return num;
}
测试
xxxxxxxxxx
u8 cnt = 0;
while(1) {
cnt = test_qpi_run_fun(cnt);
uart_printf("%d ", cnt);
}
测试有一个现象要注意, 现在0x90000000地址不再是我们定义的数组了,因为我们多加了一个函数,这个函数在数组的前面。
测试肯定成功。
再确认
如何确定数组和程序就真的放在外部FLASH了呢?
- 看map文件,查数组和函数的地址。
- 把FLASH管脚撬开,看还能不能读到。
其他
本次测试仅仅说明过了如何在外部执行程序。
对性能并没有测试,比如,需要认真配置MPU中的CACHE设置。
完