文章目录

  • 前言
  • 一、将代码中的图片资源下载到外部flash
    • 1. 修改分散加载文件
  • 二、MDK下载算法原理
    • 1. 程序能够通过下载算法下载到芯片的原理
    • 2. 算法程序中擦除操作执行流程
    • 3. 制作FLM文件步骤
  • 三、使用STM32CubeMX新建工程
    • 1. 新建工程
      • 选择MCU型号(STM32H750XBH6)
      • 配置SPI
      • 配置UART
      • 配置时钟树
      • 设置调试接口
      • 设置工程并生成工程
    • 2. 移植SFUD串行 Flash 通用驱动库
      • SFUD 是什么?
      • 移植SFUD
      • 测试SFUD
    • 3. 生成FLM文件
      • 重新生成不带main函数的工程
      • 添加修改编程算法文件FlashPrg.c
      • 添加修改配置文件FlashDev.c
      • 地址无关代码实现
      • 修改分散加载文件
      • 将程序可执行文件axf修改为flm格式

前言

当我们要下载编译好的镜像到Flash时,首先要做的一步就是选择合适的Flash下载算法,而这个算法本身就是一个FLM文件:
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
代码既可以下载到内部flash,也可以下载到外部flash,或者一部分下载到内部,一部分下载到外部。

一、将代码中的图片资源下载到外部flash

在UI设计中往往需要大量的图片和字体,图片和字体资源在代码中以静态数组的形式存在,这些大数组在内部flash中一般存放不下,所以需要把这些占用资源比较大的数组放在外部flash中,然后通过QSPI地址映射的方式访问,或者通过SPI将flash中的资源分批读取到RAM缓存中使用。

1. 修改分散加载文件

  1. 通过MDK打开分散加载文件,配置“ExtFlashSection”段:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************LR_IROM1 0x08000000 0x00020000  {    ; load region size_regionER_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_regionER_EROM1 0x90000000 0x01000000  {  ; load address = execution address*.o (ExtFlashSection)*.o (FontFlashSection)*.o (TextFlashSection)}
}

添加LR_EROM1 段,起始地址为0x90000000 ,大小为0x01000000 。

  1. 在代码中将图片资源分配到ExtFlashSection段
#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
};
  1. 编译代码
    【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
    查看map文件,image_watch_seconds这个数组已经被分配到了0X90138690这个地址了,这个地址正是LR_EROM1 所在的区间。

二、MDK下载算法原理

1. 程序能够通过下载算法下载到芯片的原理

通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。

2. 算法程序中擦除操作执行流程

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

  • 加载算法到芯片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)

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

配置SPI

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

配置UART

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

配置时钟树

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

设置调试接口

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

设置工程并生成工程

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

2. 移植SFUD串行 Flash 通用驱动库

SFUD 是什么?

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 (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
    • 不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。

移植SFUD

将下载到sfud源代码放置在工程目录中
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
将sfud添加到工程目录:
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
添加串口打印文件:
为了测试方便,添加一个串口打印的文件my_printf.c

#include <stdarg.h>
#include <stdint.h>
#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_LONGLONGinline int divide(long long *n, int base)
#elseinline 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_LONGLONGres = (int)(((unsigned long long) * n) % 10U);*n = (long long)(((unsigned long long) * n) / 10U);#elseres = (int)(((unsigned long) * n) % 10U);*n = (long)(((unsigned long) * n) / 10U);#endif} else {#ifdef RT_PRINTF_LONGLONGres = (int)(((unsigned long long) * n) % 16U);*n = (long long)(((unsigned long long) * n) / 16U);#elseres = (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_LONGLONGlong long  num,
#elselong  num,
#endif /* RT_PRINTF_LONGLONG */int   base,int   s,
#ifdef RT_PRINTF_PRECISIONint   precision,
#endif /* RT_PRINTF_PRECISION */int   type)
{char c, sign;#ifdef RT_PRINTF_LONGLONGchar tmp[32];#elsechar 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_SPECIALif (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_PRECISIONif (i > precision)precision = i;size -= precision;#elsesize -= 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_SPECIALif (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_PRECISIONwhile (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_LONGLONGunsigned long long num;#elseuint32_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_PRECISIONint 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_LONGLONGif (*fmt == 'h' || *fmt == 'l' || *fmt == 'L')#elseif (*fmt == 'h' || *fmt == 'l')#endif /* RT_PRINTF_LONGLONG */{qualifier = *fmt;++ fmt;#ifdef RT_PRINTF_LONGLONGif (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_PRECISIONif (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_PRECISIONstr = print_number(str, end,(long)va_arg(args, void *),16, field_width, precision, flags);#elsestr = 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_LONGLONGif (qualifier == 'L') num = va_arg(args, long long);else if (qualifier == 'l')#elseif (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_PRECISIONstr = print_number(str, end, num, base, field_width, precision, flags);#elsestr = 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文件:

#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);
}

测试SFUD

在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                     1024static 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 */

运行如下:
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

3. 生成FLM文件

重新生成不带main函数的工程

【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

添加修改编程算法文件FlashPrg.c

模板工程里面提供了FlashOS.h和FlashPrg.c ,复制到此工程中,然后对FlashPrg.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;elsereturn 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;elsereturn 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;elsereturn 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宏
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

添加修改配置文件FlashDev.c

模板工程里面提供了FlashDev.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 Type0x90000000,                 // Device Start Address0x08000000,                 // Device Size in Bytes (128MB)0x00001000,                 // Programming Page Size 4096 Bytes0x00,                       // Reserved, must be 00xFF,                       // Initial Content of Erased Memory10000,                      // Program Page Timeout 100 mSec6000,                       // Erase Sector Timeout 6000 mSec// Specify Size and Address of Sectors0x1000, 0x000000,            // Sector Size  4kBSECTOR_END
};
#endif // FLASH_MEM

特别注意:"STM32H750-ARTPI"就是MDK的Option选项里面会识别出这个名字。0x90000000是MDK分散加载文件中定义的外部flash起始地址。

地址无关代码实现

C和汇编的配置都勾选上:
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

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模块如何调用系统函数,与此文无关,留在以后再讲。

特别注意:

  • 由于模块中不含中断向量表,所以程序中不要开启任何中断。
  • startup_stm32h750xx.s不再需要参与编译
    【STM32H750】从零编写MDK的FLM烧录算法-编程知识网

修改分散加载文件

复制一份新的分散加载文件到工程目录中,然后修改成如下代码
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
–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}
}

将程序可执行文件axf修改为flm格式

通过这个cmd.exe /C copy "!L" "..\@L.FLM"命令就可以将生成的axf可执行文件修改为flm。
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
【STM32H750】从零编写MDK的FLM烧录算法-编程知识网
将生成的flm文件拷贝到…\Keil_v5\ARM\Flash目录,即可被MDK识别到。

DEMO下载地址:https://gitee.com/Aladdin-Wang/STM32H7_W25QXXX