STM32H7使用外部flash运行程序
在淘宝上买了一块核心板,使用的STM32H7B0VBT6。
客服很尽责,帮助了我很多。
H7系列的功能很强大,但是H7B0他有个问题,只有128k的内部flash,这么强大的芯片只有这么小的flash,想搞个RTTreadOS都不行。无奈,智能选择使用外部flash,好在核心板上有两个W25Q64,一个SPI,一个QSPI足够我们使用的。
思路
使用SPI总线的Flash模拟成U盘,然后把app的bin文件拷到模拟u盘中,通过SPI去读取SPI内保存的bin文件数据,然后写入缓存中,QSPI读取缓存数据到QSPI中,最后执行跳转程序,运行app。
细节
1,使用stm32cubemx创建一个usb模板
2,编写SPI,W25Q64驱动
void MX_SPI6_Init(void)
{
hspi6.Instance = SPI6; // 使用SPI6
hspi6.Init.Mode = SPI_MODE_MASTER; // 主机模式
hspi6.Init.Direction = SPI_DIRECTION_2LINES; // 双线全双工
hspi6.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据宽度
hspi6.Init.CLKPolarity = SPI_POLARITY_LOW; // CLK空闲时保持低电平
hspi6.Init.CLKPhase = SPI_PHASE_1EDGE; // 数据在CLK第一个边沿有效
hspi6.Init.NSS = SPI_NSS_HARD_OUTPUT; // 使用硬件片选
// 这里将 pll2_q_ck 设置为 100M 作为 SPI6 的内核时钟,然后再经过2分频得到 50M 的SCK驱动时钟
hspi6.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi6.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位在先
hspi6.Init.TIMode = SPI_TIMODE_DISABLE; // 禁止TI模式
hspi6.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁止CRC
hspi6.Init.CRCPolynomial = 0x0; // CRC校验项,这里用不到
hspi6.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; // 不使用片选脉冲模式
hspi6.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; // 片选低电平有效
hspi6.Init.FifoThreshold = SPI_FIFO_THRESHOLD_02DATA; // FIFO阈值
hspi6.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; // 发送端CRC初始化模式,这里用不到
hspi6.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; // 接收端CRC初始化模式,这里用不到
hspi6.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; // 额外延迟周期为0
hspi6.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; // 主机模式下,两个数据帧之间的延迟周期
hspi6.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; // 禁止自动接收管理
hspi6.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; // 主机模式下,SPI当前引脚状态
hspi6.Init.IOSwap = SPI_IO_SWAP_DISABLE; // 不交换MOSI和MISO
HAL_SPI_Init(&hspi6);
}
3,编写QSPI,W25Q64驱动
void MX_OCTOSPI1_Init(void)
{
OSPIM_CfgTypeDef sOspiManagerCfg = {0};
HAL_OSPI_DeInit(&hospi1); // 复位OSPI
hospi1.Instance = OCTOSPI1; // OSPI外设
hospi1.Init.ClockPrescaler = 2; // 时钟分频值,将OSPI内核时钟进行 2 分频得到OSPI通信驱动时钟
hospi1.Init.FifoThreshold = 8; // FIFO阈值
hospi1.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE; // 禁止双OSPI模式
hospi1.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON; // 存储器模式,只有8线模式才会用到
hospi1.Init.DeviceSize = 23; // flash大小,核心板采用是8M字节的W25Q64,这里设置为23,即2^23
hospi1.Init.ChipSelectHighTime = 1; // 片选保持高电平的时间
hospi1.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE; // 禁止自由时钟模式
hospi1.Init.ClockMode = HAL_OSPI_CLOCK_MODE_3; // 模式3
hospi1.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED; // 不使用 wrap-size
hospi1.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE; // 半个CLK周期之后进行采样
hospi1.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE; // 不使用数据保持功能
hospi1.Init.ChipSelectBoundary = 0; // 禁止片选边界功能
hospi1.Init.ClkChipSelectHighTime = 0; // 通信结束后 0 个CLK周期CS设置为高
hospi1.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED; // 延时模块旁路
hospi1.Init.MaxTran = 0; // 禁止通信管理功能
hospi1.Init.Refresh = 0; // 禁止刷新功能
HAL_OSPI_Init(&hospi1); // 初始化 OSPI1 设置
sOspiManagerCfg.ClkPort = 1; // OSPI引脚分配管理器设置,使用 Port1 的 CLK
sOspiManagerCfg.NCSPort = 1; // OSPI引脚分配管理器设置,使用 Port1 的 NCS
sOspiManagerCfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW; // OSPI引脚分配管理器设置,使用 Port1 的低4位引脚,IO[3:0]
HAL_OSPIM_Config(&hospi1, &sOspiManagerCfg, HAL_OSPI_TIMEOUT_DEFAULT_VALUE); // 初始化OSPI引脚分配管理器设置
}
4,主函数中初始化
int main(void)
{
SCB_EnableICache(); // 使能ICache
SCB_EnableDCache(); // 使能DCache
HAL_Init(); // 初始化HAL库
SystemClock_Config(); // 配置系统时钟,主频280MHz
LED_Init(); // 初始化LED引脚
KEY_Init(); // 初始化按键引脚
USART1_Init(); // USART1初始化
MX_USB_DEVICE_Init(); // 初始化USB设备,识别SPIU盘
SPI_W25Qxx_Init(); //初始化SPI
OSPI_W25Qxx_Init(); // 初始化OSPI和W25Q64
Main_Menu(); //程序更新引导代码
while (1)
{
LED1_Toggle;
HAL_Delay(100);
}
}
5,进入到Main_Menu(); 这个程序更新函数中后会进行选择
void Main_Menu(void)
{
uint8_t key = 0;
uint8_t quit= 0 ;
// uint8_t download_flag=0;
printf("\r\n======================================================================");
printf("\r\n= (C) COPYRIGHT 2023 STMicroelectronics =");
printf("\r\n= =");
printf("\r\n= STM32H7xx In-Application Programming Application (Version 1.0.0) =");
printf("\r\n= =");
printf("\r\n= By GXY Application Team =");
printf("\r\n======================================================================");
printf("\r\n\r\n");
while (1)
{
printf("\r\n===================================");
printf("\r\n= 输入序号选择命令 =");
printf("\r\n= (1)SPI模拟U盘方式更新 =");
printf("\r\n= (2)SD卡方式更新 =");
printf("\r\n= (3)执行跳转 =");
printf("\r\n= (4)退出更新 =");
printf("\r\n===================================");
printf("\r\n\r\n");
/* Clean the input path */
__HAL_UART_FLUSH_DRREGISTER(&huart1);
__HAL_UART_CLEAR_PEFLAG(&huart1);
__HAL_UART_CLEAR_FEFLAG(&huart1);
__HAL_UART_CLEAR_NEFLAG(&huart1);
__HAL_UART_CLEAR_OREFLAG(&huart1);
key=0;
/* Receive key */
HAL_UART_Receive(&huart1, &key, 1, RX_TIMEOUT);
switch (key)
{
case '1' :
/* Download user application in the Flash */
printf ("正在更新...\r\n");
if(SpiDownload())
{
printf ("更新完成,请执行跳转\r\n"); // 初始化成功
}
else printf ("重新上电更新\r\n");
break;
case '2' :
printf ("此功能未开发,等待后续开发......\r\n"); // 初始化成功
break;
case '3' :
OSPI_W25Qxx_MemoryMappedMode(); // 配置QSPI为内存映射模式
SCB_DisableICache(); // 关闭ICache
SCB_DisableDCache(); // 关闭Dcache
SysTick->CTRL = 0; // 关闭SysTick
SysTick->LOAD = 0; // 清零重载值
SysTick->VAL = 0; // 清零计数值
printf ("跳转地址设置\r\n");
JumpToApplication = (pFunction) (*(__IO uint32_t*) (W25Qxx_Mem_Addr + 4)); // 设置起始地址
__set_MSP(*(__IO uint32_t*) W25Qxx_Mem_Addr); // 设置主堆栈指针
printf("跳转到W25Q64运行用户程序>>>\r\n\r\n");
JumpToApplication(); // 执行跳转
break;
case '4' :
quit =1;
break;
default:
printf("选择输入 1,2,3,4控制更新方式\r");
break;
}
if(quit)
break;
}
}
6,使用串口助手输入1,进入spi模拟u盘方式更新
uint8_t SpiDownload(void)
{
int i;
if(OSPI_W25Qxx_BlockErase_1024K(W25Qxx_TestAddr)==OSPI_W25Qxx_OK)
{
// printf ("擦除FLASH完成\r\n"); // 初始化成功
for(i=0;i<32;i++)
{
SPI_W25Qxx_ReadBuffer(W25Qxx_ReadBuffer,USB_FAT_Addr+W25Qxx_NumByteToTest*i,W25Qxx_NumByteToTest); // 读取数据
// printf ("读取u盘数据进度:%d%%\r\n",i*100/31); // 初始化成功
if(OSPI_W25Qxx_OK==OSPI_W25Qxx_WriteBuffer(W25Qxx_ReadBuffer,W25Qxx_TestAddr+W25Qxx_NumByteToTest*i,W25Qxx_NumByteToTest))// 写入数据
{
printf ("更新进度:%d%%\r\n",i*100/31); // 初始化成功
}
else
{
printf ("写入失败"); // 初始化成功
return 0;
}
}
}
return 1;
}
7,串口打印数据
[17:41:36.483]发→◇1
□
[17:41:36.487]收←◆正在更新...
[17:41:40.756]收←◆更新进度:0%
[17:41:40.815]收←◆更新进度:3%
更新进度:6%
更新进度:9%
更新进度:12%
[17:41:40.908]收←◆更新进度:16%
更新进度:19%
[17:41:40.950]收←◆更新进度:22%
更新进度:25%
更新进度:29%
更新进度:32%
更新进度:35%
[17:41:41.064]收←◆更新进度:38%
更新进度:41%
更新进度:45%
更新进度:48%
[17:41:41.153]收←◆更新进度:51%
更新进度:54%
更新进度:58%
更新进度:61%
更新进度:64%
更新进度:67%
更新进度:70%
[17:41:41.311]收←◆更新进度:74%
更新进度:77%
更新进度:80%
更新进度:83%
更新进度:87%
[17:41:41.424]收←◆更新进度:90%
更新进度:93%
[17:41:41.469]收←◆更新进度:96%
更新进度:100%
更新完成,请执行跳转
8,使用串口助手发送3,进行程序跳转文章来源:https://www.toymoban.com/news/detail-417138.html
[17:42:06.050]发→◇3
□
[17:42:06.053]收←◆跳转地址设置
跳转到W25Q64运行用户程序>>>
程序运行中...
[17:42:07.059]收←◆程序运行中...
[17:42:08.060]收←◆程序运行中...
[17:42:09.062]收←◆程序运行中...
[17:42:10.063]收←◆程序运行中...
[17:42:11.066]收←◆程序运行中...
[17:42:12.067]收←◆程序运行中...
[17:42:13.070]收←◆程序运行中..
至此大功告成,整个工程都是例子程序改的,所以只是列出代表性的代码,大家可以下载工程代码参考,方便更新自己的程序。如有错误,多多指教。
核心板链接:https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-17800249301.10.75712bd3PuFnlt&id=658976139303
创建u盘模板工程参考链接:https://zhuanlan.zhihu.com/p/542651742
模拟U盘更新程序参考链接:https://blog.csdn.net/qq_44810226/article/details/127508789
整个工程代码:https://download.csdn.net/download/qq_43296570/87459589文章来源地址https://www.toymoban.com/news/detail-417138.html
到了这里,关于STM32H7使用外部flash运行程序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!