STM32H750片外QSPI启动配置简要
- 📍参考信息源:《STM32H750片外Flash启动(W25Q64JVSIQ)》
- 🎈如果使用STM32CubeProgrammer或STM32CubeIDE,可以看《STM32H750片外QSPI下载算法文件(stldr)生成》
- ✨ ARM官方提供的片外下载算法制作文档介绍和例程:
https://developer.arm.com/documentation/kan333/1-0/?lang=en
- 🌟ARM官方提供的MDK Flash Download文档:
https://developer.arm.com/documentation/kan334/latest/
👉以上ARM提供的内容,在个人入手制作片外QSPI启动程序前,强烈推荐先阅读了解。其他人的介绍难免有对认知水平的偏差和不全。
- 🔖本例程基于Keil MDk开发平台。
- 🍁配置框架:
- 🔖.FLM文件是在APP应用程序下载时,需要加载的文件,在下载程序过程中,首先被单片机执行的程序,并且运行在片上RAM中,然后将APP应用程序搬运到目标地址。
- 🔖此处的Bootloader程序,非STM32片内flash中,由ST厂家固化的Bootloader程序。此处自制的Bootloader程序运行起始地址是
0x8000000
,主要执行的内容是,初始化QSPI接口,然后将程序地址跳转到0x90000000
运行。- 🔖APP应用程序的其实地址设置到:
0x90000000
,在main函数中首先运行SCB->VTOR = 0x90000000;
将中断向量表地址指向起始地址。通过Keil MDK下载程序,需要配合对应的下载算法文件下载到QSPI flash中。程序运行步骤:片内flash Bootloader程序 ->0x8000000
的自制的Bootloader程序 ->QSPI flash中的程序。
✨为什么使用要使用QSPI启动方式
不管对于STM32H7系列单片机,还是其他单片机,其自身内部自带的flash容量都是很有限的,容量越大,价格翻倍。QSPI启动方式就是,将主控程序放置到外部 flash当中,这样程序空间可以做到很大,仅需要占用一个QSPI硬件接口。同时也方便程序的更新和维护。
📗程序能够通过下载算法下载到芯片的核心思想
- 📍有关参考内容介绍:
https://developer.arm.com/documentation/kan333/1-0/?lang=en
✨认识到这点很重要:通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。
- 🔖以下内容摘自《安富莱_STM32-V7开发板_用户手册》相关内容:
📑算法程序中擦除操作执行流程
🧨擦除操作大致流程:
◆ 加载算法到芯片RAM。
◆ 执行初始化函数Init。
◆ 执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
◆ 执行Uinit函数。
◆ 操作完毕。
📑算法程序中编程操作执行流程
◆ 针对MDK生成的axf可执行文件做Init初始化,这个axf文件是指的大家自己创建应用程序生成的。
◆ 查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在,加载算法到RAM。
◆ 执行Init函数。
◆ 加载用户到RAM缓冲。
◆ 执行Program Page页编程函数。
◆ 执行Uninit函数。
◆ 操作完毕。
📑算法程序中校验操作执行流程
📜校验操作大致流程:
◆ 校验要用到MDK生成的axf可执行文件。校验就是axf文件中下载到芯片的程序和实际下载的程序,读出来做比较。
◆ 查看Flash算法是否在.FLM文件。如果没有在,操作失败。如果在,加载算法到RAM。
◆ 执行Init
函数。
◆ 查看校验算法是否存在:
-
- ◼如果有,加载应用程序到RAM并执行校验。
-
- ◼如果没有,计算CRC,将芯片中读取出来的数据和RAM中加载应用计算输出的CRC值做比较。
◆ 执行Uninit
函数。
◆ 替换BKPT
(BreakPoint断点指令)为B. 死循环指令。
◆ 执行RecoverySupportStop
,恢复支持停止。
◆ 执行DebugCoreStop
,调试内核停止。
◆ 运行应用:
-
- 执行失败。
-
- 执行成功,再执行硬件复位。
- 操作完毕,停止调试端口。
🔨QSPI Flash的MDK下载算法制作
- 🌿以上面的参考源为例。这里以我使用的华邦的
W25Q64JVSSIQ
为例,这是一颗8MB的 SPI flash。- ✨不同品牌的存储flash芯片,指令可能存在差异,不通用,在生成的华邦的
W25Q64
的下载算法文件,使用STM32H750+外部挂载GD25Q64
下载测试时,在烧录下载时,在verify
阶段会出现下面的写入地址出错误的问题:
Load "H750_APP\\H750_APP.axf"
Erase Done.
Programming Done.
Contents mismatch at: 90000000H (Flash=88H Required=D8H) !
Contents mismatch at: 90000001H (Flash=88H Required=2DH) !
Contents mismatch at: 90000002H (Flash=88H Required=00H) !
Contents mismatch at: 90000003H (Flash=88H Required=20H) !
......
Contents mismatch at: 90000060H (Flash=88H Required=C7H) !
Contents mismatch at: 90000061H (Flash=88H Required=02H) !
Contents mismatch at: 90000062H (Flash=88H Required=00H) !
Contents mismatch at: 90000063H (Flash=88H Required=90H) !
Too many errors to display !
Error: Flash Download failed - "Cortex-M7"
- 🔨导致这样的原因是,由于
GD25Q64
芯片出厂,默认状态寄存器QE位置0
,不能进行QUAD 数据传输,导致在数据写入阶段报错。winbond(华邦)W25QXXJQ/IQ系列出厂芯片QE位已经置1
。所以默认支持4线传输。
- 🌿同品牌不同容量的QSPI flash,下载算法文件可以向下兼容,但是需要注意不是兼容所有的,对于使用32地址位的下载算法文件可能不会兼容其他地址位的,对于采用24地址位的4MB - 16MB flash,片外启动,在使用Keil进行下载时,可以共用一个.flm算法文件。前提是程序容量没有超出算法文件所定义的容量范围。
- ✨算法文件生成依赖分散加载配置文件:
; Linker Control File (scatter-loading)
;
PRG 0 PI ; Programming Functions
{
PrgCode +0 ; Code
{
* (+RO)
}
PrgData +0 ; Data
{
* (+RW,+ZI)
}
}
DSCR +0 ; Device Description
{
DevDscr +0
{
FlashDev.o
}
}
- 🌿 如果使用了宏
FLASH_MEM
内容, 在FlashDev.c
文件中,找到有关宏FLASH_MEM
的定义内容,填写对应的参数:
#ifdef FLASH_MEM
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, /* 驱动版本,勿修改,这个是MDK定的 */
"DIY_STM32H7x_QSPI_W25Q64B", /* 算法名,添加算法到MDK安装目录会显示此名字 */
EXTSPI, /* 设备类型 */
0x90000000, /* Flash起始地址 */
8 * 1024 * 1024, /* Flash大小,8MB */
256, /* 编程页大小 */
0, /* 保留,必须为0 */
0xFF, /* 擦除后的数值 */
1000, /* 页编程等待时间 */
6000, /* 扇区擦除等待时间 */
4 * 1024, 0x000000, /* 扇区大小,扇区地址 */
SECTOR_END
};
#endif
- ⚡需要注意一点的是,上面的
DIY_STM32H7x_QSPI_W25Q64B
定义的这个名称不能太长,否则,在APP应用程序加载算法文件,进行下载程序的时候,下载会报错,无法打开此文件。
- 🌿如果没有使用到有关宏
FLASH_MEM
来定义QSPI信息:bsp_qspi_w25q256.h
#define QSPI_FLASH_MEM_ADDR 0x90000000
#define QSPI_FLASH_SIZE 24
#define QSPI_FLASH_SECTOR_SIZE (4*1024)
#define QSPI_FLASH_PAGE_SIZE (256)
#define QSPI_FLASH_END_ADDR (1<<QSPI_FLASH_SIZE)
#define QSPI_FLASH_BYTE_SIZE (8*1024*1024)
- 🌿主控时钟初始化,在算法工程中,
Init()
函数中,进行配置。
int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
{
int result = 0;
SystemInit(); //这里配置的是内部时钟源
SystemClock_Config();//这个时钟配置函数,根据实际硬件(可以配置使用HSE或内部HSI。
// result = SystemClock_Config();
// if (result != 0)
// {
// return 1;
// }
result = BspQspiBoot_Init();
if (result != 0)
{
return 1;
}
result = BspQspiBoot_MemMapped();
if (result != 0)
{
return 1;
}
return 0;
}
- 🌿时钟函数
SystemClock_Config()
配置函数参考:(如果使用外部晶振,可以直接在STM32CubeMX中根据实际硬件外部晶振参数,进行自动生成配置,然后拷贝进来,进行替换) -
- 🎉时钟主频推荐配置为400MHz,,QSPI挂载在AHB总线线上,STM32H7 QSPI在SDR模式(单倍数据速率信号传输模式)下最高133MHz,DDR模式(双倍数据速率信号传输模式):100MHz.
- ✨ 这一点主要针对在生成算法工程文件中,时钟配置函数需要的注意重点。
-
- 📍详见:
https://www.armbbs.cn/forum.php?mod=attachment&aid=NjEwNjJ8YjE0YTdhYmV8MTcxNDExNTA5Nnw1MDAxMnw5OTQ4OQ%3D%3D
- 📍详见:
/*
*********************************************************************************************************
* 函 数 名: SystemClock_Config
* 功能说明: 初始化系统时钟
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 400000000 (CPU Clock)
* HCLK(Hz) = 200000000 (AXI and AHBs Clock)
* AHB Prescaler = 2
* D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
* D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
* D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
* D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
* HSE Frequency(Hz) = 25000000
* PLL_M = 5
* PLL_N = 160
* PLL_P = 2
* PLL_Q = 4
* PLL_R = 2
* VDD(V) = 3.3
* Flash Latency(WS) = 4
* 形 参: 无
* 返 回 值: 1 表示失败,0 表示成功
*********************************************************************************************************
*/
int SystemClock_Config(void)
{
/* 使用外部时钟 #################################################################################*/
#if 1
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
HAL_StatusTypeDef ret = HAL_OK;
HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
/*
1、芯片内部的LDO稳压器输出的电压范围,可选VOS1,VOS2和VOS3,不同范围对应不同的Flash读速度,
详情看参考手册的Table 12的表格。
2、这里选择使用VOS1,电压范围1.15V - 1.26V。
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/* 使能HSE,并选择HSE作为PLL时钟源 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 5;
RCC_OscInitStruct.PLL.PLLN = 160;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
if(ret != HAL_OK)
{
return 1;
}
/*
选择PLL的输出作为系统时钟
配置RCC_CLOCKTYPE_SYSCLK系统时钟
配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线
配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线
配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线
配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线
*/
RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 | \
RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
/* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
if(ret != HAL_OK)
{
return 1;
}
/* 使用内部HSI时钟 #################################################################################*/
#else
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Supply configuration update enable
*/
HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
return 1;
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV1;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
return 1;
}
#endif
__HAL_RCC_D2SRAM1_CLK_ENABLE();
__HAL_RCC_D2SRAM2_CLK_ENABLE();
__HAL_RCC_D2SRAM3_CLK_ENABLE();
return 0;
}
-
🌿Keil将程序可执行文件axf修改为flm格式命令:
cmd.exe /C copy "!L" "..\@L.FLM"
-
🔧其他编译选项设置:
-
🌿将最终生成的下载算法文件,拷贝到Keil安装目录下的ARM/flash文件夹内,例如:
D:\Keil_v5\ARM\Flash
🔧QSPI信息STM32CubeMX软件配置
- 🔖也就是STM32h7主控搭载的QSPI flash硬件信息填写
- 🌿QSPI信息配置:后面的的Bootloader程序里面,也保持相同配置。
- 🔖我这里NCS引脚使用的是
PB6
引脚.其他信号引脚可以根据引脚使用,进行有针对的功能引脚复用。
- 🌿引脚相关宏定义:(引脚复用和配置,需要与实际硬件电路连接的引脚保持一致才行)
#define QSPI_CS_PIN GPIO_PIN_6 //注意修改
#define QSPI_CS_GPIO_PORT GPIOB
#define QSPI_CS_GPIO_AF GPIO_AF10_QUADSPI //注意修改 GPIO_AF10_QUADSPI GPIO_AF9_QUADSPI
#define QSPI_CLK_PIN GPIO_PIN_2
#define QSPI_CLK_GPIO_PORT GPIOB
#define QSPI_CLK_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_D0_PIN GPIO_PIN_11
#define QSPI_BK1_D0_GPIO_PORT GPIOD
#define QSPI_BK1_D0_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_D1_PIN GPIO_PIN_12
#define QSPI_BK1_D1_GPIO_PORT GPIOD
#define QSPI_BK1_D1_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_D2_PIN GPIO_PIN_2
#define QSPI_BK1_D2_GPIO_PORT GPIOE
#define QSPI_BK1_D2_GPIO_AF GPIO_AF9_QUADSPI
#define QSPI_BK1_D3_PIN GPIO_PIN_13
#define QSPI_BK1_D3_GPIO_PORT GPIOD
#define QSPI_BK1_D3_GPIO_AF GPIO_AF9_QUADSPI
- 🌿QSPI初始化内容:
void MX_QUADSPI_Init(void)
{
/* USER CODE BEGIN QUADSPI_Init 0 */
/* USER CODE END QUADSPI_Init 0 */
/* USER CODE BEGIN QUADSPI_Init 1 */
/* USER CODE END QUADSPI_Init 1 */
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 3;//针对HCLK3分频
hqspi.Init.FifoThreshold = 32;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = 24;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.FlashID = QSPI_FLASH_ID_1;
hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN QUADSPI_Init 2 */
/* USER CODE END QUADSPI_Init 2 */
}
📗Bootloader程序配置
- 🌿Bootloader程序main函数内容:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
BspQspiBoot_Init();
// BspQspiBoot_Test();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_QUADSPI_Init();
// MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// BspQspiBoot_Test();//不知道QSPI容量时启用配合串口,查看flash信息
BspQspiBoot_MemMapped();//配置QSPI地址信息
BspQspiBoot_JumpToApp();//跳转到目标地址运行
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
- 🔰
Bootloader
程序中,IROM1起始地址保持默认的0x8000000
地址位。不要改。
📘APP程序程序配置
- ⚡APP应用程序的IROM1起始地址,一定要和Bootloader程序中的程序跳转地址是一致的才行。
-
🌿IROM1起始地址配置:(注意这里是
0x90000000
)
-
👉应用(APP)程序,在main主函数中,添加指定Cortex内核的中断向量表的基地址:
SCB->VTOR = 0x90000000;
(这里配置程序开始运行的地方和Bootloader程序运行的地址是不同的,需要注意。)
int main(void)
{
/* USER CODE BEGIN 1 */
SCB->VTOR = 0x90000000; //将中断向量表的基地址映射到目标地址
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
// MX_QUADSPI_Init();//注意片外启动不能再使用相同QSPI外设作为它用
// MX_SDMMC1_SD_Init();
// MX_SPI2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin|LED2_Pin|LED3_Pin);
HAL_Delay(800);
// uint16_t ID = W25QXX_ReadID();
printf("STM32H750 QSPI FLASH Boot Start... \r\n");
}
/* USER CODE END 3 */
}
- 🌿APP程序下载和更新,算法文件配置:
- ⚡上面的应用(APP)程序下载时的RAM大小是
1000
,需要修改大一点,否则下载时会报错。
📑APP应用程序,MPU内存映射单元常规参数配置
🔖 这不是硬性规定配置指标,属于个人常规配置内容。
void MPU_RegionConfig(void)
{
/* Disable MPU */
HAL_MPU_Disable();
/* 设置QSPI FLASH空间的MPU保护 */
SCB->SHCSR &= ~(1 << 16); /* 禁止MemManage */
MPU->CTRL &= ~(1 << 0); /* 禁止MPU */
MPU->RNR = 0; /* 设置保护区域编号为0(1~7可以给其他内存用) */
MPU->RBAR = 0X90000000; /* 基地址为0X9000 000, 即QSPI的起始地址 */
MPU->RASR = 0X0303002D; /* 设置相关保护参数(禁止共用, 允许cache, 允许缓冲), 详见MPU实验的解析 */
MPU->CTRL = (1 << 2) | (1 << 0);/* 使能PRIVDEFENA, 使能MPU */
SCB->SHCSR |= 1 << 16; /* 使能MemManage */
/* Enable MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
⛳采坑注意事项
🔰参数差异
-
🔱由于每个人所使用的主控不同,时钟频率不一样,又或者配置的外置spi flash容量和引脚复用不同,上面的资源参考中,仅仅提供的是一个模板,需要根据个人所使用的硬件差异和参数进行稍微改动。
-
🌟在个人首次移植时,需要对自己手上的硬件信息有个,基本的了解,主控搭载的外部晶振频率、SPI flash容量、以及flash引脚与主控对应连接的脚位。
-
⚡APP应用程序,如果主频480MHz,HCLK3频率最快只能设置在120MHz,,如果配置主时钟频率480MHz,QSPI的主频240MHz,片外运行可能会出现异常。如果想获得最佳的片外运行速度,配置主频400MHz最合适,经过分频QSPI频率200MHz
-
- 400MHz主频,QSPI频率:200MHz:
- 400MHz主频,QSPI频率:200MHz:
-
✨在使用QSPI片外启动,APP应用程序中,不能再使用相同一个QSPI外设作为它用。
-
🎉不同品牌型号的QSPI flash操作指令可能存在差异,下载算法和Bootloader程序不通用。(最好将对应型号的QSPI调通再进行移植,主要是数据读写功能验证)
-
🧨spi flash容量参数直接根据个人具体型号直接在工程中填写。如果不知道的可以烧录Bootloader程序时启用串口和启用
BspQspiBoot_Test()
测试函数,并其中插入调试打印信息。
void BspQspiBoot_Test(void)
{
bool bTestResult = true;
uint8_t ucTestCnt;
uint32_t ulTestAddr;
uint32_t ulSectorAddr = 0x2000;
ulFlashID = BspQspiBoot_ReadID();
// if(ulFlashID != 0x00EF4018) //16MB:15679512
if (ulFlashID != 0x00EF4017) // 8MB:15679511
{
bTestResult = false;
}
else
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin | LED3_Pin, GPIO_PIN_RESET);
while (bTestResult == false)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin | LED2_Pin | LED3_Pin);
HAL_Delay(800);
// printf("FlashID:%d \r\n",ulFlashID);//查看容量,十进制的
}
BspQspiBoot_EraseSector(ulSectorAddr);
for (ucTestCnt = 0; ucTestCnt < 16; ucTestCnt++)
{
ulTestAddr = ulSectorAddr + (ucTestCnt * QSPI_FLASH_PAGE_SIZE);
{
uint16_t i;
for (i = 0; i < sizeof(ucaTestBuff); i++)
{
ucaTestBuff[i] = i;
}
}
BspQspiBoot_WritePage(ucaTestBuff, ulTestAddr, sizeof(ucaTestBuff));
memset(ucaTestBuff, 0, sizeof(ucaTestBuff));
BspQspiBoot_ReadBuff(ucaTestBuff, ulTestAddr, sizeof(ucaTestBuff));
{
uint16_t i;
for (i = 0; i < sizeof(ucaTestBuff); i++)
{
if (ucaTestBuff[i] != i)
{
bTestResult = false;
break;
}
}
}
}
while (bTestResult == false)
;
while (1)
{
}
}
-
🌿外部时钟时钟初始化函数可以通过
STM32CubeMX
配置设定好参数自动生成工程,然后拷贝对应的时钟初始化函数到工程中进行替换。来匹配个人使用的主控芯片。QSPI时钟主频设置在100MHz.这一点主要针对在生成算法工程文件中,时钟配置函数需要注意重点。 -
🌿QSPI引脚实际连接,一定要和下载算法制作工程以及Bootloader工程中,对QSPI引脚的配置保持一致才行。
- 🔖例如: QSP NCS引脚复用调整:
#define QSPI_CS_PIN GPIO_PIN_6 // 注意修改 GPIO_PIN_10
#define QSPI_CS_GPIO_PORT GPIOB
#define QSPI_CS_GPIO_AF GPIO_AF10_QUADSPI // 注意修改 GPIO_AF9_QUADSPI
- 👉由于QSPI引脚复用功能,配置的QSPI引脚存在差异,以及外部晶振频率选择差异,个人使用,需要根据具体的硬件QSPI引脚连接和配置条件进行调整才行。模板采用的是HAL库的,能通用,具体的使用,需要根据个人的硬件信息进行调整适配。
- 📍STM32H750片外Flash启动通用模板地址:
https://www.armbbs.cn/forum.php?mod=viewthread&tid=101586
- 🌟MDK算法生成时,如果编译器采用的是V5版本生成的,那么BOOT程序最好也采用V5版本的,进行烧录,否则可能会出现无法跳转到APP程序。对应APP程序对编译器无此要求。