# STM32H7_W25QXX **Repository Path**: sunzigang/STM32H7_W25QXXX ## Basic Information - **Project Name**: STM32H7_W25QXX - **Description**: STM32H7 MDK下载算法demo - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 16 - **Created**: 2024-09-04 - **Last Updated**: 2024-09-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 前言 当我们要下载编译好的镜像到Flash时,首先要做的一步就是选择合适的Flash下载算法,而这个算法本身就是一个FLM文件: ![在这里插入图片描述](https://img-blog.csdnimg.cn/8c31fc2e4bb44858b344ee32d7d4a4ab.png) 代码既可以下载到内部flash,也可以下载到外部flash,或者一部分下载到内部,一部分下载到外部。 # 一、将代码中的图片资源下载到外部flash 在UI设计中往往需要大量的图片和字体,图片和字体资源在代码中以静态数组的形式存在,这些大数组在内部flash中一般存放不下,所以需要把这些占用资源比较大的数组放在外部flash中,然后通过QSPI地址映射的方式访问,或者通过SPI将flash中的资源分批读取到RAM缓存中使用。 ## 1. 修改分散加载文件 1. 通过MDK打开分散加载文件,配置“ExtFlashSection”段: ```c ; ************************************************************* ; *** 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 。 2. 在代码中将图片资源分配到ExtFlashSection段 ```c #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 }; ``` 3. 编译代码 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f962b49b2b644a4ebed58b3634d20e77.png) 查看map文件,image_watch_seconds这个数组已经被分配到了0X90138690这个地址了,这个地址正是LR_EROM1 所在的区间。 # 二、MDK下载算法原理 ## 1. 程序能够通过下载算法下载到芯片的原理 通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。 ## 2. 算法程序中擦除操作执行流程 ![算法程序中擦除操作执行流程](https://img-blog.csdnimg.cn/242b1ebf83924cda86817770f955e8ea.png) - 加载算法到芯片RAM。 - 执行初始化函数Init。 - 执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。 - 执行Uinit函数。 - 操作完毕。 ## 3. 制作FLM文件步骤 1. 将ARM:CMSIS Pack文件夹(通常是C:\Keil\ARM\Pack\ARM\CMSIS\ version \Device\_Template_Flash)中的工程复制到一个新文件夹中,取消文件夹的只读属性,重命名项目文件NewDevice.uvprojx以表示新的flash 设备名称,例如MyDevice.uvprojx。 2. 打开工程,从工具栏中,使用下拉选择目标来选择处理器架构。 3. 打开对话框Project - Options for Target - Output并更改Name of Executable字段的内容以表示设备,例如MyDevice。 4. 调整文件FlashPrg中的编程算法。 5. 调整文件FlashDev中的设备参数。 6. 使用Project - Build Target生成新的 Flash 编程算法。 以上步骤是利用官方的工程模板修改代码,这种方式网上已有很多教程(推荐使用这种方法),不再重复介绍,接下来介绍一种不使用模板工程制作的方法,目的是为了了解其实现原理。 # 三、使用STM32CubeMX新建工程 ## 1. 新建工程 **硬件平台:** RT-Thread官方ART-PI H750开发版 **软件:** STM32CubeMX,MDK ### 选择MCU型号(STM32H750XBH6) ![在这里插入图片描述](https://img-blog.csdnimg.cn/6ac2910b9320417ca89eb6116ac41150.png) ### 配置SPI ![在这里插入图片描述](https://img-blog.csdnimg.cn/b9028968d0b84053932e9ef54534ca49.png) ### 配置UART ![在这里插入图片描述](https://img-blog.csdnimg.cn/f80d61fe911a4f5da7126cf49b1e3aa5.png) ### 配置时钟树 ![在这里插入图片描述](https://img-blog.csdnimg.cn/b27d7686359840d2abbcf72e3029a6e1.png) ### 设置调试接口 ![在这里插入图片描述](https://img-blog.csdnimg.cn/9da3ec5ee4064f7fb0e8868f2e74afb7.png) ### 设置工程并生成工程 ![在这里插入图片描述](https://img-blog.csdnimg.cn/846e39cecf5a4361bf5161a4969a258e.png) ## 2. 移植SFUD串行 Flash 通用驱动库 ### SFUD 是什么? [SFUD](https://github.com/armink/SFUD) 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。 - 主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址 - 资源占用 - 标准占用:RAM:0.2KB ROM:5.5KB - 最小占用:RAM:0.1KB ROM:3.6KB - 设计思路: - **什么是 SFDP** :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B ([点击这里查看](https://www.jedec.org/standards-documents/docs/jesd216b))。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。 - **不支持 SFDP 怎么办** :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( [`/sfud/inc/sfud_flash_def.h`](https://github.com/armink/SFUD/blob/4bee2d0417a7ce853cc7aa3639b03fe825611fd9/sfud/inc/sfud_flash_def.h#L116-L142) ) 中提供的 **Flash 参数信息表** 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。 ### 移植SFUD **将[下载](https://github.com/armink/SFUD)到sfud源代码放置在工程目录中** ![在这里插入图片描述](https://img-blog.csdnimg.cn/ac15dd1736c940b4a9093eeae892a84f.png) **将sfud添加到工程目录:** ![在这里插入图片描述](https://img-blog.csdnimg.cn/7393a6c9437948f0b8c6b76c73fd0d00.png) **添加串口打印文件:** 为了测试方便,添加一个串口打印的文件my_printf.c ```c #include #include #include "usart.h" #define RT_PRINTF_PRECISION #define RT_CONSOLEBUF_SIZE 512 /* private function */ #define _ISDIGIT(c) ((unsigned)((c) - '0') < 10) #define ZEROPAD (1 << 0) /* pad with zero */ #define SIGN (1 << 1) /* unsigned/signed long */ #define PLUS (1 << 2) /* show plus */ #define SPACE (1 << 3) /* space if plus */ #define LEFT (1 << 4) /* left justified */ #define SPECIAL (1 << 5) /* 0x */ #define LARGE (1 << 6) /* use 'ABCDEF' instead of 'abcdef' */ /** * This function will duplicate a string. * * @param n is the string to be duplicated. * * @param base is support divide instructions value. * * @return the duplicated string pointer. */ #ifdef RT_PRINTF_LONGLONG inline int divide(long long *n, int base) #else inline int divide(long *n, int base) #endif /* RT_PRINTF_LONGLONG */ { int res; /* optimized for processor which does not support divide instructions. */ if (base == 10) { #ifdef RT_PRINTF_LONGLONG res = (int)(((unsigned long long) * n) % 10U); *n = (long long)(((unsigned long long) * n) / 10U); #else res = (int)(((unsigned long) * n) % 10U); *n = (long)(((unsigned long) * n) / 10U); #endif } else { #ifdef RT_PRINTF_LONGLONG res = (int)(((unsigned long long) * n) % 16U); *n = (long long)(((unsigned long long) * n) / 16U); #else res = (int)(((unsigned long) * n) % 16U); *n = (long)(((unsigned long) * n) / 16U); #endif } return res; } static char *print_number(char *buf, char *end, #ifdef RT_PRINTF_LONGLONG long long num, #else long num, #endif /* RT_PRINTF_LONGLONG */ int base, int s, #ifdef RT_PRINTF_PRECISION int precision, #endif /* RT_PRINTF_PRECISION */ int type) { char c, sign; #ifdef RT_PRINTF_LONGLONG char tmp[32]; #else char tmp[16]; #endif /* RT_PRINTF_LONGLONG */ int precision_bak = precision; const char *digits; static const char small_digits[] = "0123456789abcdef"; static const char large_digits[] = "0123456789ABCDEF"; register int i; register int size; size = s; digits = (type & LARGE) ? large_digits : small_digits; if (type & LEFT) type &= ~ZEROPAD; c = (type & ZEROPAD) ? '0' : ' '; /* get sign */ sign = 0; if (type & SIGN) { if (num < 0) { sign = '-'; num = -num; } else if (type & PLUS) sign = '+'; else if (type & SPACE) sign = ' '; } #ifdef RT_PRINTF_SPECIAL if (type & SPECIAL) { if (base == 16) size -= 2; else if (base == 8) size--; } #endif /* RT_PRINTF_SPECIAL */ i = 0; if (num == 0) tmp[i++] = '0'; else { while (num != 0) tmp[i++] = digits[divide(&num, base)]; } #ifdef RT_PRINTF_PRECISION if (i > precision) precision = i; size -= precision; #else size -= i; #endif /* RT_PRINTF_PRECISION */ if (!(type & (ZEROPAD | LEFT))) { if ((sign) && (size > 0)) size--; while (size-- > 0) { if (buf < end) *buf = ' '; ++ buf; } } if (sign) { if (buf < end) { *buf = sign; } -- size; ++ buf; } #ifdef RT_PRINTF_SPECIAL if (type & SPECIAL) { if (base == 8) { if (buf < end) *buf = '0'; ++ buf; } else if (base == 16) { if (buf < end) *buf = '0'; ++ buf; if (buf < end) { *buf = type & LARGE ? 'X' : 'x'; } ++ buf; } } #endif /* RT_PRINTF_SPECIAL */ /* no align to the left */ if (!(type & LEFT)) { while (size-- > 0) { if (buf < end) *buf = c; ++ buf; } } #ifdef RT_PRINTF_PRECISION while (i < precision--) { if (buf < end) *buf = '0'; ++ buf; } #endif /* RT_PRINTF_PRECISION */ /* put number in the temporary buffer */ while (i-- > 0 && (precision_bak != 0)) { if (buf < end) *buf = tmp[i]; ++ buf; } while (size-- > 0) { if (buf < end) *buf = ' '; ++ buf; } return buf; } static int skip_atoi(const char **s) { register int i = 0; while (_ISDIGIT(**s)) i = i * 10 + *((*s)++) - '0'; return i; } /** * This function will fill a formatted string to buffer. * * @param buf is the buffer to save formatted string. * * @param size is the size of buffer. * * @param fmt is the format parameters. * * @param args is a list of variable parameters. * * @return The number of characters actually written to buffer. */ int rt_vsnprintf(char *buf, int size, const char *fmt, va_list args) { #ifdef RT_PRINTF_LONGLONG unsigned long long num; #else uint32_t num; #endif /* RT_PRINTF_LONGLONG */ int i, len; char *str, *end, c; const char *s; uint8_t base; /* the base of number */ uint8_t flags; /* flags to print number */ uint8_t qualifier; /* 'h', 'l', or 'L' for integer fields */ int32_t field_width; /* width of output field */ #ifdef RT_PRINTF_PRECISION int precision; /* min. # of digits for integers and max for a string */ #endif /* RT_PRINTF_PRECISION */ str = buf; end = buf + size; /* Make sure end is always >= buf */ if (end < buf) { end = ((char *) - 1); size = end - buf; } for (; *fmt ; ++fmt) { if (*fmt != '%') { if (str < end) *str = *fmt; ++ str; continue; } /* process flags */ flags = 0; while (1) { /* skips the first '%' also */ ++ fmt; if (*fmt == '-') flags |= LEFT; else if (*fmt == '+') flags |= PLUS; else if (*fmt == ' ') flags |= SPACE; else if (*fmt == '#') flags |= SPECIAL; else if (*fmt == '0') flags |= ZEROPAD; else break; } /* get field width */ field_width = -1; if (_ISDIGIT(*fmt)) field_width = skip_atoi(&fmt); else if (*fmt == '*') { ++ fmt; /* it's the next argument */ field_width = va_arg(args, int); if (field_width < 0) { field_width = -field_width; flags |= LEFT; } } #ifdef RT_PRINTF_PRECISION /* get the precision */ precision = -1; if (*fmt == '.') { ++ fmt; if (_ISDIGIT(*fmt)) precision = skip_atoi(&fmt); else if (*fmt == '*') { ++ fmt; /* it's the next argument */ precision = va_arg(args, int); } if (precision < 0) precision = 0; } #endif /* RT_PRINTF_PRECISION */ /* get the conversion qualifier */ qualifier = 0; #ifdef RT_PRINTF_LONGLONG if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') #else if (*fmt == 'h' || *fmt == 'l') #endif /* RT_PRINTF_LONGLONG */ { qualifier = *fmt; ++ fmt; #ifdef RT_PRINTF_LONGLONG if (qualifier == 'l' && *fmt == 'l') { qualifier = 'L'; ++ fmt; } #endif /* RT_PRINTF_LONGLONG */ } /* the default base */ base = 10; switch (*fmt) { case 'c': if (!(flags & LEFT)) { while (--field_width > 0) { if (str < end) *str = ' '; ++ str; } } /* get character */ c = (uint8_t)va_arg(args, int); if (str < end) *str = c; ++ str; /* put width */ while (--field_width > 0) { if (str < end) *str = ' '; ++ str; } continue; case 's': s = va_arg(args, char *); if (!s) s = "(NULL)"; for (len = 0; (len != field_width) && (s[len] != '\0'); len++); #ifdef RT_PRINTF_PRECISION if (precision > 0 && len > precision) len = precision; #endif /* RT_PRINTF_PRECISION */ if (!(flags & LEFT)) { while (len < field_width--) { if (str < end) *str = ' '; ++ str; } } for (i = 0; i < len; ++i) { if (str < end) *str = *s; ++ str; ++ s; } while (len < field_width--) { if (str < end) *str = ' '; ++ str; } continue; case 'p': if (field_width == -1) { field_width = sizeof(void *) << 1; flags |= ZEROPAD; } #ifdef RT_PRINTF_PRECISION str = print_number(str, end, (long)va_arg(args, void *), 16, field_width, precision, flags); #else str = print_number(str, end, (long)va_arg(args, void *), 16, field_width, flags); #endif /* RT_PRINTF_PRECISION */ continue; case '%': if (str < end) *str = '%'; ++ str; continue; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; break; case 'X': flags |= LARGE; case 'x': base = 16; break; case 'd': case 'i': flags |= SIGN; case 'u': break; default: if (str < end) *str = '%'; ++ str; if (*fmt) { if (str < end) *str = *fmt; ++ str; } else { -- fmt; } continue; } #ifdef RT_PRINTF_LONGLONG if (qualifier == 'L') num = va_arg(args, long long); else if (qualifier == 'l') #else if (qualifier == 'l') #endif /* RT_PRINTF_LONGLONG */ { num = va_arg(args, uint32_t); if (flags & SIGN) num = (int32_t)num; } else if (qualifier == 'h') { num = (uint16_t)va_arg(args, int32_t); if (flags & SIGN) num = (int16_t)num; } else { num = va_arg(args, uint32_t); if (flags & SIGN) num = (int32_t)num; } #ifdef RT_PRINTF_PRECISION str = print_number(str, end, num, base, field_width, precision, flags); #else str = print_number(str, end, num, base, field_width, flags); #endif /* RT_PRINTF_PRECISION */ } if (size > 0) { if (str < end) *str = '\0'; else { end[-1] = '\0'; } } /* the trailing null byte doesn't count towards the total * ++str; */ return str - buf; } /** * This function will print a formatted string on system console. * * @param fmt is the format parameters. * * @return The number of characters actually written to buffer. */ int rt_kprintf(const char *fmt, ...) { va_list args; int length; static char rt_log_buf[RT_CONSOLEBUF_SIZE]; va_start(args, fmt); /* the return value of vsnprintf is the number of bytes that would be * written to buffer had if the size of the buffer been sufficiently * large excluding the terminating null byte. If the output string * would be larger than the rt_log_buf, we have to adjust the output * length. */ length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args); if (length > RT_CONSOLEBUF_SIZE - 1) length = RT_CONSOLEBUF_SIZE - 1; HAL_UART_Transmit(&huart4, (uint8_t *)&rt_log_buf, length, 100); va_end(args); return length; } ``` **修改sfud_port.c文件:** ```c #include #include #include #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); } ``` ### 测试SFUD 在main.c中添加测试代码: ```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 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* 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_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); } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } #endif /* USE_FULL_ASSERT */ ``` **运行如下:** ![在这里插入图片描述](https://img-blog.csdnimg.cn/b620ea2e15b34c65b089f899a387b449.png) ## 3. 生成FLM文件 ### 重新生成不带main函数的工程 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5e6a50963af44261a4859cff9aed2a77.png) ### 添加修改编程算法文件FlashPrg.c 模板工程里面提供了FlashOS.h和FlashPrg.c ,复制到此工程中,然后对FlashPrg.c 代码进行填充。 ```c /***********************************************************************/ /* This file is part of the ARM Toolchain package */ /* Copyright (c) 2020 Keil - An ARM Company. All rights reserved. */ /***********************************************************************/ /* */ /* FlashPrg.c: Flash Programming Functions adapted for */ /* ST Microelectronics STM32h747I-DISCO Flash */ /* */ /***********************************************************************/ #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宏 ![在这里插入图片描述](https://img-blog.csdnimg.cn/34796056f488430cba35ec4c8984a2f3.png) ### 添加修改配置文件FlashDev.c 模板工程里面提供了FlashDev.c ,复制到此工程中,然后对代码进行修改。 ```c /***********************************************************************/ /* This file is part of the ARM Toolchain package */ /* Copyright (c) 2020 Keil - An ARM Company. All rights reserved. */ /***********************************************************************/ /* */ /* FlashDev.c: Device Description for ST STM32H747I-DISCO Flash */ /* */ /***********************************************************************/ #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和汇编的配置都勾选上: ![在这里插入图片描述](https://img-blog.csdnimg.cn/adb13a381d9e4b93a57ddb00f13ab022.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/3dfe08ea2e0a42bfbb6789bc530e7002.png) **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驱动](https://blog.csdn.net/sinat_31039061/article/details/128350295?csdn_share_tail=%7B%22type%22:%22blog%22,%22rType%22:%22article%22,%22rId%22:%22128350295%22,%22source%22:%22sinat_31039061%22%7D)中提到的需要在编译选项中添加`-ffixed-r9`的原因。 综上所述,勾选ROPI和RWPI选项,可以实现elf文件的动态加载,还遗留的一个小问题是elf模块如何调用系统函数,与此文无关,留在以后再讲。 **特别注意:** - 由于模块中不含中断向量表,所以程序中不要开启任何中断。 - startup_stm32h750xx.s不再需要参与编译 ![在这里插入图片描述](https://img-blog.csdnimg.cn/bc1b9150afaa484da3d7709078a6c16f.png) ### 修改分散加载文件 复制一份新的分散加载文件到工程目录中,然后修改成如下代码 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3a49d9fc203b4104b22c02a50412694c.png) --diag_suppress L6305用于屏蔽没有入口地址的警告信息。 ```c ; 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 } } ``` ### 将程序可执行文件axf修改为flm格式 通过这个`cmd.exe /C copy "!L" "..\@L.FLM"`命令就可以将生成的axf可执行文件修改为flm。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8a8f491733174c1b8faf611197846fc3.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/19c678dbd9d14d118f613945b4cff34a.png) 将生成的flm文件拷贝到...\Keil_v5\ARM\Flash目录,即可被MDK识别到。