从零编写STM32H7的MDK SPI FLASH下载算法 - 知乎
当我们要下载编译好的镜像到Flash时,首先要做的一步就是选择合适的Flash下载算法,而这个算法本身就是一个FLM文件:

代码既可以下载到内部flash,也可以下载到外部flash,或者一部分下载到内部,一部分下载到外部。
在UI设计中往往需要大量的图片和字体,图片和字体资源在代码中以静态数组的形式存在,这些大数组在内部flash中一般存放不下,所以需要把这些占用资源比较大的数组放在外部flash中,然后通过QSPI地址映射的方式访问,或者通过SPI将flash中的资源分批读取到RAM缓存中使用。
- ; *************************************************************
- ; *** Scatter-Loading Description File generated by uVision ***
- ; *************************************************************
-
- LR_IROM1 0x08000000 0x00020000 { ; load region size_region
- ER_IROM1 0x08000000 0x00020000 { ; load address = execution address
- *.o (RESET, +First)
- *(InRoot$$Sections)
- .ANY (+RO)
- .ANY (+XO)
- }
- RW_IRAM1 0x20000000 0x00020000 { ; RW data
- .ANY (+RW +ZI)
- }
- RW_IRAM2 0x24000000 0x00080000 {
- .ANY (+RW +ZI)
- }
- }
-
- LR_EROM1 0x90000000 0x01000000 { ; load region size_region
- ER_EROM1 0x90000000 0x01000000 { ; load address = execution address
- *.o (ExtFlashSection)
- *.o (FontFlashSection)
- *.o (TextFlashSection)
- }
- }
-
添加LR_EROM1 段,起始地址为0x90000000 ,大小为0x01000000 。
- #define LOCATION_ATTRIBUTE(name) __attribute__((section(name))) __attribute__((aligned(4)))
-
- KEEP extern const unsigned char image_watch_seconds[] LOCATION_ATTRIBUTE("ExtFlashSection") = // 4x202 ARGB8888 pixels.
- {
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0xff,
- 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0xff, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00, 0xf8, 0xfc, 0xf8, 0x00,
- 0xf8, 0xfc, 0xf8, 0x00
- };
-

查看map文件,image_watch_seconds这个数组已经被分配到了0X90138690这个地址了,这个地址正是LR_EROM1 所在的区间。
通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。

算法程序中擦除操作执行流程
以上步骤是利用官方的工程模板修改代码,这种方式网上已有很多教程(推荐使用这种方法),不再重复介绍,接下来介绍一种不使用模板工程制作的方法,目的是为了了解其实现原理。
硬件平台: RT-Thread官方ART-PI H750开发版
软件: STM32CubeMX,MDK

选择MCU型号

配置SPI

配置UART

配置时钟树

设置调试接口

生成工程
广告
嵌入式/物联网开发学习资料
SFUD(https://github.com/armink/SFUD) 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。
/sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。将下载到sfud源代码放置在工程目录中

将sfud添加到工程目录:

修改sfud_port.c文件:
- #include <string.h>
- #include <sfud.h>
- #include <stdarg.h>
- #include "gpio.h"
- #include "spi.h"
-
- typedef struct {
- SPI_HandleTypeDef *spix;
- GPIO_TypeDef *cs_gpiox;
- uint16_t cs_gpio_pin;
- } spi_user_data, *spi_user_data_t;
-
- static spi_user_data spi1;
- static char log_buf[256];
- void sfud_log_debug(const char *file, const long line, const char *format, ...);
- extern int rt_vsnprintf(char *buf, int size, const char *fmt, va_list args);
- extern int rt_kprintf(const char *fmt, ...);
- static void spi_lock(const sfud_spi *spi)
- {
-
- }
-
- static void spi_unlock(const sfud_spi *spi)
- {
-
- }
- /* about 100 microsecond delay */
- static void delay_100us(void) {
- uint32_t delay = 2000;
- while(delay--);
- }
- /**
- * SPI write data then read data
- */
- static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
- size_t read_size)
- {
- sfud_err result = SFUD_SUCCESS;
- /**
- * add your spi write and read code
- */
- spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
-
- HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin,GPIO_PIN_RESET);
- if (write_size) {
- HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf,write_size,1);
- }
- if (read_size) {
- HAL_SPI_Receive(spi_dev->spix, read_buf,read_size,1);
- }
- exit:
- HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin,GPIO_PIN_SET);
- return result;
- }
-
- sfud_err sfud_spi_port_init(sfud_flash *flash)
- {
- sfud_err result = SFUD_SUCCESS;
-
- switch (flash->index) {
- case SFUD_W25Q128_DEVICE_INDEX: {
- spi1.spix = &hspi1;
- spi1.cs_gpiox = GPIOA;
- spi1.cs_gpio_pin = GPIO_PIN_4;
- /* 同步 Flash 移植所需的接口及数据 */
- flash->spi.wr = spi_write_read;
- flash->spi.lock = spi_lock;
- flash->spi.unlock = spi_unlock;
- flash->spi.user_data = &spi1;
- /* about 100 microsecond delay */
- flash->retry.delay = delay_100us;
- /* adout 60 seconds timeout */
- flash->retry.times = 60 * 10000;
-
- break;
- }
- }
-
- return result;
- }
-
- void sfud_log_debug(const char *file, const long line, const char *format, ...) {
- va_list args;
-
- /* args point to the first variable parameter */
- va_start(args, format);
- rt_kprintf("[SFUD](%s:%ld) ", file, line);
- /* must use vprintf to print */
- rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
- rt_kprintf("%s\r\n", log_buf);
- va_end(args);
- }
-
- void sfud_log_info(const char *format, ...) {
- va_list args;
-
- /* args point to the first variable parameter */
- va_start(args, format);
- rt_kprintf("[SFUD]");
- /* must use vprintf to print */
- rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
- rt_kprintf("%s\r\n", log_buf);
- va_end(args);
- }
-
在main.c中添加测试代码:
- /* USER CODE END Header */
- /* Includes ------------------------------------------------------------------*/
- #include "main.h"
- #include "spi.h"
- #include "usart.h"
- #include "gpio.h"
-
- /* Private function prototypes -----------------------------------------------*/
- void SystemClock_Config(void);
- static void MPU_Config(void);
- /* USER CODE BEGIN PFP */
- extern int rt_kprintf(const char *fmt, ...);
- #include "sfud.h"
- /* USER CODE END PFP */
-
- /* Private user code ---------------------------------------------------------*/
- /* USER CODE BEGIN 0 */
- #define SFUD_DEMO_TEST_BUFFER_SIZE 1024
-
- static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
- /**
- * SFUD demo for the first flash device test.
- *
- * @param addr flash start address
- * @param size test flash size
- * @param size test flash data buffer
- */
- static void sfud_demo(uint32_t addr, size_t size, uint8_t *data) {
- sfud_err result = SFUD_SUCCESS;
- const sfud_flash *flash = sfud_get_device_table() + 0;
- size_t i;
- /* prepare write data */
- for (i = 0; i < size; i++) {
- data[i] = i;
- }
- /* erase test */
- result = sfud_erase(flash, addr, size);
- if (result == SFUD_SUCCESS) {
- rt_kprintf("Erase the %s flash data finish. Start from 0x%08X, size is %d.\r\n", flash->name, addr,
- size);
- } else {
- rt_kprintf("Erase the %s flash data failed.\r\n", flash->name);
- return;
- }
- /* write test */
- result = sfud_write(flash, addr, size, data);
- if (result == SFUD_SUCCESS) {
- rt_kprintf("Write the %s flash data finish. Start from 0x%08X, size is %d.\r\n", flash->name, addr,
- size);
- } else {
- rt_kprintf("Write the %s flash data failed.\r\n", flash->name);
- return;
- }
- /* read test */
- result = sfud_read(flash, addr, size, data);
- if (result == SFUD_SUCCESS) {
- rt_kprintf("Read the %s flash data success. Start from 0x%08X, size is %d. The data is:\r\n", flash->name, addr,
- size);
- rt_kprintf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
- for (i = 0; i < size; i++) {
- if (i % 16 == 0) {
- rt_kprintf("[%08X] ", addr + i);
- }
- rt_kprintf("%02X ", data[i]);
- if (((i + 1) % 16 == 0) || i == size - 1) {
- rt_kprintf("\r\n");
- }
- }
- rt_kprintf("\r\n");
- } else {
- rt_kprintf("Read the %s flash data failed.\r\n", flash->name);
- }
- /* data check */
- for (i = 0; i < size; i++) {
- if (data[i] != i % 256) {
- rt_kprintf("Read and check write data has an error. Write the %s flash data failed.\r\n", flash->name);
- break;
- }
- }
- if (i == size) {
- rt_kprintf("The %s flash test is success.\r\n", flash->name);
- }
- }
-
- /* USER CODE END 0 */
-
- int main(void)
- {
-
- /* MPU Configuration--------------------------------------------------------*/
- MPU_Config();
-
- /* MCU Configuration--------------------------------------------------------*/
-
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
- SystemClock_Config();
-
- MX_GPIO_Init();
- MX_SPI1_Init();
- MX_UART4_Init();
- /* USER CODE BEGIN 2 */
- if (sfud_init() == SFUD_SUCCESS) {
- sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
- }
-
- while (1)
- {
-
- }
-
- }
-
- #endif /* USE_FULL_ASSERT */
-
运行如下:


模板工程里面提供了FlashOS.h和FlashPrg.c ,复制到此工程中,然后对FlashPrg.c 代码进行填充。
- #include "FlashOS.H"
- #include "sfud.h"
- #include "gpio.h"
- #include "usart.h"
- #include "spi.h"
-
- static uint32_t base_adr;
-
- /*
- * Initialize Flash Programming Functions
- * Parameter: adr: Device Base Address
- * clk: Clock Frequency (Hz)
- * fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
- * Return Value: 0 - OK, 1 - Failed
- */
- #if defined FLASH_MEM || defined FLASH_OTP
- int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
- {
- MX_GPIO_Init();
- MX_UART4_Init();
- MX_SPI1_Init();
- base_adr = adr;
- if(sfud_init() == SFUD_SUCCESS) {
- return 0;
- } else {
- return 1;
- }
- }
- #endif
-
-
- /*
- * De-Initialize Flash Programming Functions
- * Parameter: fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
- * Return Value: 0 - OK, 1 - Failed
- */
- #if defined FLASH_MEM || defined FLASH_OTP
- int UnInit (unsigned long fnc)
- {
-
- return (0);
- }
- #endif
-
- /*
- * Erase complete Flash Memory
- * Return Value: 0 - OK, 1 - Failed
- */
- int EraseChip (void)
- {
- int result = 0;
- const sfud_flash *flash = sfud_get_device_table();
- /* Add your Code */
- result = sfud_erase (flash, 0, flash->chip.capacity);
-
- if (result == SFUD_SUCCESS)
- return 0;
- else
- return result; // Finished without Errors
- }
-
- /*
- * Erase Sector in Flash Memory
- * Parameter: adr: Sector Address
- * Return Value: 0 - OK, 1 - Failed
- */
- #ifdef FLASH_MEM
- int EraseSector (unsigned long adr)
- {
- int result = 0;
- uint32_t block_start;
- const sfud_flash *flash;
- flash = sfud_get_device_table();
- block_start = adr - base_adr;
- result = sfud_erase (flash, block_start, 4096);
-
- if (result == SFUD_SUCCESS)
- return 0;
- else
- return result;
- }
- #endif
-
-
- /*
- * Program Page in Flash Memory
- * Parameter: adr: Page Start Address
- * sz: Page Size
- * buf: Page Data
- * Return Value: 0 - OK, 1 - Failed
- */
- #if defined FLASH_MEM || defined FLASH_OTP
- int ProgramPage (unsigned long block_start, unsigned long size, unsigned char *buffer)
- {
- const sfud_flash *flash = sfud_get_device_table() + 0;
- uint32_t start_addr = block_start - base_adr;
-
- if(sfud_write(flash, start_addr, size, buffer) == SFUD_SUCCESS)
- return 0;
- else
- return 1;
- }
-
-
- #define PAGE_SIZE 4096
- uint8_t aux_buf[PAGE_SIZE];
- unsigned long Verify (unsigned long adr, unsigned long sz, unsigned char *buf)
- {
- int i;
- const sfud_flash *flash = sfud_get_device_table();
- sfud_read(flash, adr - base_adr, sz, aux_buf);
-
- for (i = 0; i < PAGE_SIZE; i++) {
- if (aux_buf[i] != buf[i])
- return (adr + i); // Verification Failed (return address)
- }
-
- return (adr + sz); // Done successfully
- }
-
- #endif
-
在工程中定义FLASH_MEM宏

模板工程里面提供了FlashDev.c ,复制到此工程中,然后对代码进行修改。
- #include "FlashOS.H"
-
- #ifdef FLASH_MEM
- struct FlashDevice const FlashDevice = {
- FLASH_DRV_VERS, // Driver Version, do not modify!
- "STM32H750-ARTPI", // Device Name
- EXTSPI, // Device Type
- 0x90000000, // Device Start Address
- 0x08000000, // Device Size in Bytes (128MB)
- 0x00001000, // Programming Page Size 4096 Bytes
- 0x00, // Reserved, must be 0
- 0xFF, // Initial Content of Erased Memory
- 10000, // Program Page Timeout 100 mSec
- 6000, // Erase Sector Timeout 6000 mSec
-
- // Specify Size and Address of Sectors
- 0x1000, 0x000000, // Sector Size 4kB
- SECTOR_END
- };
- #endif // FLASH_MEM
-
特别注意:"STM32H750-ARTPI"就是MDK的Option选项里面会识别出这个名字。0x90000000是MDK分散加载文件中定义的外部flash起始地址。
C和汇编的配置勾选上:


ROPI地址无关实现
如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:
(1)加载以响应运行事件。
(2)在不同情况下使用其他例程的不同组合加载到内存中。
(3)在执行期间映射到不同的地址。
RWPI数据无关实现使用Read-Write position independence同理,表示的可读可写数据段。使用RWPI编译代码,解决RW段即全局变量的加载。首先编译的时候会为每一个全局变量生成一个相对于r9寄存器的偏移量,这个偏移量会在.text段中。
在加载elf阶段,将RW段加载到RAM当中之后,需要将r9寄存器指向此片内存的基地址,然后接下来就可以跳转到加载的elf的代码中去执行,就可以实现全局变量的加载了。这也就是利用MDK的FLM文件生成通用flash驱动中提到的需要在编译选项中添加-ffixed-r9的原因。
综上所述,勾选ROPI和RWPI选项,可以实现elf文件的动态加载,还遗留的一个小问题是elf模块如何调用系统函数,这与此文无关,留在以后再讲。
特别注意:

复制一份新的分散加载文件到工程目录中,然后修改成如下代码

--diag_suppress L6305用于屏蔽没有入口地址的警告信息。
- ; 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
- }
- }
-
通过这个cmd.exe /C copy "!L" "..\@L.FLM"命令可以将生成的axf可执行文件修改为flm。


将生成的flm文件拷贝到...\Keil_v5\ARM\Flash目录,即可被MDK识别到。