STM32H743 TFTP Boot 简录

STM32H743 TFTP Boot 简录文章目录 stm32h743 存储简况区域划分分散加载问题引出默认的分散加载文件修改分散加载文件 CubeMX 部分配置 boot 的 jump to appapp 中断向量表偏移 app 分散加载文件修改最简升级 py 脚本 MCU 处理参考 stm32h743 存储简况如下 2MBFlash 分 2 个 bank 存储区 可在两个 banks 并行执行读 编程 擦除操作 1Flash Word 8Words 32Bytes 256bits 其实 1Flash Word 还有额外的 10bitsECC

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

(0)
上一篇 2026年3月17日 下午1:58
下一篇 2026年3月17日 下午1:58


相关推荐

  • OpenClaw的5个替代方案

    OpenClaw的5个替代方案

    2026年3月13日
    2
  • centos7 安装gitea使用

    centos7 安装gitea使用参考官网 https gitea iohttps docs gitea iohttps docs gitea io en us install from binary 关于 GiteaGitea 是一个自己托管的 Git 服务程序 他和 GitHub BitbucketorG 等比较类似 他是从 Gogs 发展而来 不过我们已经 Fork 并且命名为 Gitea 对

    2025年9月25日
    8
  • phpstorm激活码2021[免费获取]

    (phpstorm激活码2021)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/ide…

    2022年3月21日
    58
  • 电力-101/104规约基础1

    电力-101/104规约基础1内容包括IEC101/104规约术语及释义(嵌入实际工程C代码),IEC101/104规约标准与遥测量类型转换介绍,IEC101规约遥信、遥测与遥控及加密。

    2022年6月20日
    66
  • 设置grid高度

    设置grid高度jqxSalaryCal jqxGrid height jqxTree height 73 nbsp

    2026年2月27日
    3
  • Java字符串匹配_获取字符串

    Java字符串匹配_获取字符串文章目录一、示例二、解释1.replace()方法2.replaceAll()方法3.replaceFirst()方法4.常用的字符列表一、示例如图,都是为了替换字符串s中的”(“符号,但三种匹配方法,有三种不同的效果及写法。二、解释1.replace()方法replace()方法没有用到正则表达式,但会匹配所有的参数并进行替换2.replaceAll()方法replaceAll()方法使用的是正则表达式来匹配,而括号在正则表达式中是特殊字符,所以需要用双斜杠来进行转义,同时会匹配所..

    2022年8月21日
    8

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号