聲明:此博文所述我未實踐,目的是知識整理。
1. 常說的 "單片機的norflash上可以執行代碼 “ 這句話該如何理解?
CPU做取指、譯碼、執行。
常說的哪些介質可以執行程序,實際上指的是CPU可以從那里取指,以供后續譯碼和執行。
2. 單片機一般在內部norfalsh上執行代碼
Soc讀取內部NorFlash時可以總線式訪問,這對SoC來說更方便,由SoC直接以地址方式來訪問,而且,NorFlash不需要初始化,時序簡單。
PS:nandflash接口管理復雜,需要專門的控制器。
3. 單片機內部norflash上執行代碼面臨的困局
用ST做GUI界面,面臨最大的問題就是芯片內部flash最大才2M。在現在這個時代,2M其實也放不了幾張圖片。
如果嫌棄你單片機的norflash不夠大呢?你換再大的單片機norflash也大不了多少了,所以:
方案1. 使用ST在M7內核芯片上增加的QSPI控制器,讓用戶把代碼放在外部存儲就地執行。
方案2. 使用sdram,將代碼搬運到sdram內執行。
方案1 對應的方式稱為XIP 。
方案2 對應的方式稱為 BootROM,顧名思義,從ROM搬運代碼到RAM去執行,所以叫BootROM。
4. norflash不夠的解決方案1 -- XIP
(1). 在外部閃存(外部QSPI或FMC-NOR閃存)“就地執行”。
(2). 用戶應用程序代碼應鏈接到目標執行存儲器地址(外部QSPI或FMC-NOR閃存)。
(3). 需要內存映射支持
上面 (1)意思是:XiP是可以在外部閃存直接執行代碼的,就像芯片在內部flash 的地址0x0800 0000直接執行一樣,稱為“就地執行”。
上面(2) 意思是:用戶應用程序代碼編譯的時候鏈接地址要改成外部閃存的地址,如STM32 H7系統給QSPI Flash在系統總線分配的地址是0x9000 0000,那么代碼的地址就要改成0x9000 0000。
XIP模型操作流程
XiP啟動方式實際是借助一個bootloader,這個bootloader做了一件很與眾不同的事情,就是把QSPI FLASH映射到了系統總線0x9000 0000 這個地址上。
映射上去以后,只要我們訪問0x9000 0000這個地址,系統總線就會自動去讀QSPI FLASH 0地址的數據。此時是自動去讀!
比如:uint32_t temp = *((uint32_t *)0x90000000)就能直接拿到QSPI FLASH第0~3地址數據。
當映射完成后,先關閉全部中斷和cache,然后跳轉到用戶程序執行。
借助STM32H7的QSPI、XIP的方式,我們就可以實現讓程序在W25QXX系列芯片內跑起來了。
5 . norflash不夠的解決方案2 -- BootROM
(1). 從閃存啟動,配置外部RAM存儲器(SDRAM或SRAM),
先從閃存復制用戶應用程序二進制文件(SDCARD或SPI-Flash存儲器)到外部SDRAM或外部SRAM,然后跳轉執行用戶應用程序。
(2). 用戶應用程序代碼應鏈接到目標執行存儲器地址(外部SDRAM或SRAM)。
這種模型就很通用了,適用於凡是有FMC能驅動外部RAM的任意一款ST芯片。當然鏈接地址肯定是外部RAM存儲器的地址了。至於閃存,隨意都可以,什么SPI FLASH、TF卡、I2C存儲器都可以。
比如:STM32F407ZG + SRAM(32MB) + TF卡,把程序編譯成二進制放到TF卡里面,每當我把新的程序更新到TF卡,運行的程序就更新了,就有點像玩Linux TF卡啟動的感覺了。
6. XiP模型bootloader編寫
#include <stdio.h> #include "w25qx_qspi.h" typedef void (*pFunction)(void); #define APPLICATION_ADDRESS QSPI_BASE
static uint32_t QSPI_EnableMemoryMappedMode(QSPI_HandleTypeDef *QSPIHandle); pFunction JumpToApplication; int main(void) { /* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* 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(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ printf("Sudaroot XiP BootLoader\r\n"); /* 1.W25Qx Init */ W25Qx_QSPI_Init(); /* 2.Enable MemoryMapped mode */ QSPI_EnableMemoryMappedMode(&hqspi); /* 3.Disable CPU L1 cache before jumping to the QSPI code execution */
/* Disable I-Cache */ SCB_DisableICache(); /* Disable D-Cache */ SCB_DisableDCache(); /* 4.Disable Systick interrupt */ HAL_SuspendTick(); /* 5.Initialize user application's Stack Pointer & Jump to user application */ JumpToApplication = (pFunction) (*(__IO uint32_t*) (APPLICATION_ADDRESS + 4)); __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); JumpToApplication(); /* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) { /* USER CODE END WHILE */
/* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief Configure the QSPI in memory-mapped mode * @retval QSPI memory status */
static uint32_t QSPI_EnableMemoryMappedMode(QSPI_HandleTypeDef *QSPIHandle) { QSPI_CommandTypeDef s_command; QSPI_MemoryMappedTypeDef s_mem_mapped_cfg; /* Configure the command for the read instruction */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = QUAD_INOUT_FAST_READ_CMD; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = 6; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_HALF_CLK_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Configure the memory mapped mode */ s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE; s_mem_mapped_cfg.TimeOutPeriod = 0; return HAL_QSPI_MemoryMapped(QSPIHandle, &s_command, &s_mem_mapped_cfg); } int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000); return ch; }
參考資料:
安富萊_STM32-V7開發板_用戶手冊,含BSP驅動包設計(V3.1).pdf
STM32CubeIDE QSPI間接模式和內存映射模式 讀寫W25Q64
https://blog.csdn.net/sudaroot/article/details/109097135
.