文章目录
stm32h743存储简况
如下:
- 2MB Flash, 分2个bank(存储区), 可在两个banks并行执行 读/编程/擦除 操作
- 1 Flash_Word = 8 Words = 32 Bytes = 256 bits, 其实1 Flash_Word 还有额外的10bits ECC. 因为有写buffer, 可以按8/16/32/64bit写, 但是为了精确地锁定一个Flash_Word, 写wrap burst不能跨越32字节对齐的地址边界, 所以牢记256bit进行Flash编程
- 1 Bank 划分成 8 Sectors(扇区), 也就是(128KB/Sector, 或者4K Flash_Word/Sector)
- Bank1默认映射到0x0~0x0x080FFFFF, Bank2默认映射到 0x0~0x081FFFFF, 可以 改动 FLASH_OPTCR 寄存器的SWAP_BANK 位实现BANK的映射交换, 比较骚操作(不再划分Boot和APP程序, 1MB可以全给应用程序, 直接Bank1运行写新程序到Bank2, 然后Switch到Bank2拷贝BANK2到BANK1, 然后还可以Switch到BANK1运行, 参考AN4767 On-the-fly firmware update for dual bank STM32 microcontrollers), 本篇不按这么骚操作, 还是传统的划分了Boot和APP
- 简记: 擦除按扇区(128KB)对齐, 写按Flash_Word(32B)对齐, 读可以按字节(B)对齐
地址分布

擦除完后, 默认读出来是全 FF, 不同于Aurix的 00
区域划分
本篇不按双BANK, 还是传统的划分了Boot和APP, 只用了BANK1, 应用程序最大256KB
//boot : sector 0 1 //boot区 //a_app: sector 2 3 //程序区 //b_app: sector 4 5 //备份区 //jump : sector 6 //控制跳转和一些中间参数 //user : sector 7 //IP端口波特率采样点等外设(can/uart/eth)参数的全局配置, 方便boot和app保持一样的端口配置
分散加载
问题引出
由于程序中用到了ADC的DMA, 原先是这么写的
ALIGN_32BYTES (uint16_t adc1_data[ADC1_BUFFER_SIZE]) __attribute__((section(".ARM.__at_0x"))); ALIGN_32BYTES (uint16_t adc3_data[ADC3_BUFFER_SIZE]) __attribute__((section(".ARM.__at_0x")));
编译出来的HEX出现了 :0C2, 是 0x, 这是内存地址, 不是Flash地址, 而且如果用这个HEX生成BIN文件, 会是两个, 或者一个连续可能达数百MB~几GB的BIN文件:

这个显然是不能用来做升级文件的, 需要改分散加载文件
默认的分散加载文件
HEX文件同目录下有个.sct文件, 这个就是分散加载(Scatter-Loading)文件, 属于链接器(Linker)的范畴, 默认对应的图形界面配置如下


.sct 文件默认为
LR_IROM1 0x0 0x00 {
; load region size_region ER_IROM1 0x0 0x00 {
; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x 0x00020000 {
; RW data .ANY (+RW +ZI) } RW_IRAM2 0x 0x00080000 {
.ANY (+RW +ZI) } }
简要解释:
- 第一行
LR_IROM1 0x0 0x00, 大括号扩住了整个文件, 表示里面涉及的内容都链接到 0x0 开始, 大小为 0x(2MB)的Flash地址里面(HEX文件的地址) - 第二段
ER_IROM1 0x0 0x00 {}的*.o表示所有.o目标文件默认都链接到 0x0 开始的地址里面, 如果想指定某个文件分配到某个地址, 可以在这里指定. RO: Read Only, XO: execute-only(允许取指, 不允许读写) 也分配到这里 - 第三段
RW_IRAM1 0x 0x00020000 { }, 表示 RW: Read Write, ZI: Zero-Initialized 变量加载到 0x 开始大小为128KB的RAM里面, 但是因为被第一行包含, 所以HEX文件中还是存到了 0x0 里面 - 第四段 同上, 只是 内存地址不同, 如果
正是因为 0x 默认没有包含在 LR_IROM1 0x0 0x00 里面, 导致链接器懵逼了, 原封不动在HEX文件里输出 0x 的地址
编译后提示和.map文件, .bin文件大小的关系
# 编译提示 Program Size: Code= RO-data=5022 RW-data=1400 ZI-data=46312 # MAP文件最下面 ============================================================================== Code (inc. data) RO Data RW Data ZI Data Debug 24902 5022 1400 46312 0 Grand Totals 24902 5022 168 46312 0 ELF Image Totals (compressed) 24902 5022 168 0 0 ROM Totals ============================================================================== Total RO Size (Code + RO Data) ( 109.09kB) Total RW Size (RW Data + ZI Data) 47712 ( 46.59kB) Total ROM Size (Code + RO Data + RW Data) ( 109.26kB) ============================================================================== # 其中 BIN文件大小 0x1B508 = = + 5022 + 168 # 注意 RW Data 是 168 不是显示的 1400 # RAM = RW Data + ZI Data
如果定义并使用了一个大数组或结构体
volatile uint8_t big_array[128*1024];
编译就会报错(并不会自动分配到RW_IRAM2的512KB里面)
stm32h7\stm32h7.axf: Error: L6406E: No space in execution regions with .ANY selector matching ...
这是因为分散加载文件的默认段 RW_IRAM1 0x 0x00020000 只有 128KB大小, 这就需要把这个数组指定到大的内存里面去, 比如 RW_IRAM2 0x 0x00080000 这个512KB的RAM里面, keil可以使用类似上面的 __attribute__((section(".ARM.__at_0x")))
volatile uint8_t big_array[100*1024] __attribute__((section(".ARM.__at_0x")));
这样固然不会报错, 但是再来一个呢
volatile uint8_t big_array[128*1024] __attribute__((section(".ARM.__at_0x"))); volatile uint8_t big_array_2[128*1024] __attribute__((section(".ARM.__at_0x"))); //手动偏移128KB
不能超过128KB的都手动一个个计算, 太麻烦了
修改分散加载文件
H743的内存分布

添加内存描述:
- 给 0x 开始的内存地址起个别名 SRAM_2
- 给 0x 开始的内存地址起个别名 SRAM_4, 并且包含在
LR_IROM1 0x0 0x00里面, 这样定义在这里的变量在HEX文件中也会存到 0x0 Flash里面, 不会突兀的直接生成让BIN文件不能用
还是在 .sct里面, 直接手动修改
LR_IROM1 0x0 0x00 {
; load region size_region ER_IROM1 0x0 0x00 {
; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x 0x00020000 {
; RW data .ANY (+RW +ZI) } RW_IRAM2 0x 0x00080000 {
; .ANY (+RW +ZI) *(.SRAM_2) } ; RW_IRAM3 0x 0x00048000 {
; *(.SRAM_3) ; } RW_IRAM4 0x 0x00010000 {
; RW data - 64KB SRAM4(0x) *(.SRAM_4) } }
其中:
- 分号是注释行
图形界面Target里面就不要改了, Linker里面去掉默认勾选, 选择分散加载文件

定义变量到 SRAM_2, 这样就不用手动计算偏移了
volatile uint8_t big_array[128*1024] __attribute__((section(".SRAM_2"))); volatile uint8_t big_array_2[128*1024] __attribute__((section(".SRAM_2")));
定义函数到 SRAM_2
__attribute__((section(".SRAM_2"))) void func() {
}
定义文件到 SRAM_2(主要是函数放到这里, 全局变量之类的RW, 默认分配到前面的0x里面了)
RW_IRAM2 0x 0x00080000 {
bsp.o (+RO) *(.SRAM_2) }
这样生成的HEX文件也就不会出现 0x之类怪异的地址了, BIN文件也不会拆分为二了
fromelf --bin -o "$L@L.bin" "#L"
CubeMX部分配置
RMII接口, IO速率Very High


开DCache, MPU配置

然后才能配LWIP, 指定了静态IP, PHY选LAN8742(其实驱动通用, 已经能自动识别 ID, 对于KSZ8041/LAN8720等百兆PHY也适用)

TFTP驱动实现部分略, Flash读写参考官方 STM32Cube_FW_H7_V1.9.1 里面的例程
boot的jump_to_app
通用的
typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) {
pFunction Jump_To_Application; uint32_t JumpAddress; /* Check if valid stack address (RAM address) then jump to user application */ if(((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x) {
/* Jump to user application */ JumpAddress = *(__IO uint32_t*) (app_addr + 4); Jump_To_Application = (pFunction)JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) app_addr); Jump_To_Application(); while(1) {
} } }
这个放在boot最开始是可以的, boot 初始化后再跳转就要考虑很多外设中断了
app中断向量表偏移
int main(void) {
/* USER CODE BEGIN 1 */ SCB->VTOR = FLASH_BASE | 0x40000; // SCB->VTOR = 0x0 /* USER CODE END 1 */ }
app分散加载文件修改
app偏移256KB(0x40000)
LR_IROM1 0x0 0x00 {
; load region size_region ER_IROM1 0x0 0x00 {
; load address = execution address ... } ... }
最简升级py脚本
需要事先安装 tftpy
python3 -m pip install tftpy
升级脚本只有5行
import tftpy bin_filename = 'release/stm32h7_app.bin' client = tftpy.TftpClient('192.168.6.233', 69) client.upload('flash.bin', bin_filename, timeout=1*4) print("Congratulation! Update Success!!!")
如果mcu事先在app里面来不及tftp ack, 会报错一次, 然后自动重传最多4次
MCU处理
策略很多, 这里只介绍最简单的一种:
- App里面收到TFTP请求, 配置user config flash为boot, 直接复位, 到boot
- Boot里面直接擦bpp flash, 收文件, 写(边写边校验), 收完后复制到app, 配置 user config flash为app, 直接复位, boot 开头直接jump_to_app
- 如果考虑boot里面预留如100ms救砖, 可以再修改 jump_to_app 来防止死机
参考
- stm32L071xx使用其双bank功能实现升级备份功能_haiyanglideshi的博客-CSDN博客_双bank升级
- AN4767 应用笔记
- https://github.com/msoulier/tftpy
- Linker User Guide: Load region descriptions (keil.com)
- MDK 分散加载文件剖析(一)_TangZhenye的博客-CSDN博客_mdk 分散加载
- MDK 分散加载文件剖析(二)_TangZhenye的博客-CSDN博客
- stm32 KEIL软件设置程序烧写起始地址_alfredseng的博客-CSDN博客_烧写程序的软件
由于项目并不方便公开, 时间较短程序也不完美, 所以请不要询问源码…
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/223535.html原文链接:https://javaforall.net
