文章目录
- 前言
- 一、将代码中的图片资源下载到外部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文件:
代码既可以下载到内部flash,也可以下载到外部flash,或者一部分下载到内部,一部分下载到外部。
一、将代码中的图片资源下载到外部flash
在UI设计中往往需要大量的图片和字体,图片和字体资源在代码中以静态数组的形式存在,这些大数组在内部flash中一般存放不下,所以需要把这些占用资源比较大的数组放在外部flash中,然后通过QSPI地址映射的方式访问,或者通过SPI将flash中的资源分批读取到RAM缓存中使用。
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 。
- 在代码中将图片资源分配到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
};
二、MDK下载算法原理
1. 程序能够通过下载算法下载到芯片的原理
通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。
2. 算法程序中擦除操作执行流程
- 加载算法到芯片RAM。
- 执行初始化函数Init。
- 执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
- 执行Uinit函数。
- 操作完毕。
3. 制作FLM文件步骤
- 将ARM:CMSIS Pack文件夹(通常是C:\Keil\ARM\Pack\ARM\CMSIS\ version \Device_Template_Flash)中的工程复制到一个新文件夹中,取消文件夹的只读属性,重命名项目文件NewDevice.uvprojx以表示新的flash 设备名称,例如MyDevice.uvprojx。
- 打开工程,从工具栏中,使用下拉选择目标来选择处理器架构。
- 打开对话框Project – Options for Target – Output并更改Name of Executable字段的内容以表示设备,例如MyDevice。
- 调整文件FlashPrg中的编程算法。
- 调整文件FlashDev中的设备参数。
- 使用Project – Build Target生成新的 Flash 编程算法。
以上步骤是利用官方的工程模板修改代码,这种方式网上已有很多教程(推荐使用这种方法),不再重复介绍,接下来介绍一种不使用模板工程制作的方法,目的是为了了解其实现原理。
三、使用STM32CubeMX新建工程
1. 新建工程
硬件平台: RT-Thread官方ART-PI H750开发版
软件: STM32CubeMX,MDK
选择MCU型号(STM32H750XBH6)
配置SPI
配置UART
配置时钟树
设置调试接口
设置工程并生成工程
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源代码放置在工程目录中
将sfud添加到工程目录:
添加串口打印文件:
为了测试方便,添加一个串口打印的文件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 */
3. 生成FLM文件
重新生成不带main函数的工程
添加修改编程算法文件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
添加修改配置文件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起始地址。
地址无关代码实现
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}
}
将程序可执行文件axf修改为flm格式
通过这个cmd.exe /C copy "!L" "..\@L.FLM"
命令就可以将生成的axf可执行文件修改为flm。
将生成的flm文件拷贝到…\Keil_v5\ARM\Flash目录,即可被MDK识别到。
DEMO下载地址:https://gitee.com/Aladdin-Wang/STM32H7_W25QXXX