找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2130

积分

0

好友

299

主题
发表于 昨天 16:19 | 查看: 7| 回复: 0

部分终端产品在出厂前,需要在 MCU Flash 的特定地址处写入特定的数据,例如产品序列号、版本号或关键的配置参数。假设我们需要在 Flash 地址 0x08030000 处写入四个字节的数据 0x110x220x330x44,该如何实现呢?

方法一、直接写 Flash

这种方法遵循 MCU 的标准 Flash 操作流程:在程序中先擦除目标地址所在的页,然后按字写入指定的内容。虽然技术上可行,但需要额外编写 Flash 驱动代码,集成到应用中稍显繁琐,因此不推荐作为首选方案。

方法二、直接改写 Hex 或 Bin 文件

通过手动修改最终的 Hex 或 Bin 文件也可以达到目的。但这要求开发者非常熟悉 Hex 文件格式,或者需要精确计算 Bin 文件的填充位置。过程较为复杂且容易出错,同样不推荐。

方法三、离线烧录器配置

如果生产环节使用离线烧录器,操作会简便许多。例如,使用创芯工坊的 Power Writer 工具,可以在上位机的“序列号配置”栏中直接设置需要烧录的数据与地址,注意需将“序列号增量”设置为 0。

Power Writer 烧录器软件配置界面

这种方式相比前两种要简单直接,但它通常只能配置一个连续的参数块。如果需要写入多个分散的、非连续的数据,就显得力不从心了。

方法四、分散加载文件实现

通过修改链接器的分散加载文件(Scatter-Loading File),可以更灵活地实现数据定位。这里以常用的 KEIL MDK 开发环境为例进行说明。

假设默认生成的 .sct 文件内容如下:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00040000 { ; load region size_region
  ER_IROM1 0x08000000 0x00040000 { ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00007000 { ; RW data
   .ANY (+RW +ZI)
  }
}

为了实现我们的目标,可以将其修改为:

LR_IROM1 0x08000000 0x00030000 { ; load region size_region
  ER_IROM1 0x08000000 0x00030000 { ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00007000 { ; RW data
   .ANY (+RW +ZI)
  }
}

LR_IROM2 0x08030000 0x00010000 { ; load region size_region
  ER_IROM2 0x08030000 0x00010000 { ; user data
   *(myflash)
  }
}

然后,在 main.c 文件中增加以下语句:

const uint8_t my_flash_table[] __attribute__((used, section(“myflash”))) = { 0x11, 0x22, 0x33, 0x44 };

这样编译后,生成的 Hex 文件就会在指定地址 0x08030000 处包含我们预设的数据。

修改sct文件后Hex文件的数据内容

上图清晰地显示了数据 11 22 33 44 被准确地放置在 0B03 0000(即 0x08030000)地址处。作为对比,下图是未修改前的原始 Hex 文件内容,该地址区域并无特定数据。

原始Hex文件内容

关键点:定义数组时必须加上 used 属性,以防止链接器因变量未被显式使用而将其优化剔除。如果只写 section(“myflash”) 而省略 used,编译时会收到类似 L6329W: Pattern *(myflash) only matches removed unused sections 的警告,导致数据并未实际生效。

链接器警告提示

方法五、直接绝对定位实现

这是最直观的方法,直接在代码中指定变量的绝对地址。如果你使用的是 Arm Compiler 5 (AC5),可以使用 __attribute__((at(address))) 语法。如果项目已迁移到 Arm Compiler 6 (AC6),则必须使用 __attribute__((section(“.ARM.__at_address”))),因为 AC6 不再支持原始的 at 属性。

根据编译器版本,在 main.c 中添加以下语句之一:

// 适用于 AC5
const uint32_t gflashdata __attribute__((at(0x08030000))) = 0x44332211;

// 适用于 AC5 和 AC6
const uint32_t gflashdata __attribute__((section(“.ARM.__AT_0x8030000”))) = 0x44332211;

编译后,Hex 文件会在 0x08030000 地址处写入数据 0x44332211(注意字节序)。

使用绝对定位后Hex文件的数据内容

但与方法四不同的是,你会发现 0x08030000 之前未使用的 Flash 地址被大量 0x00 填充。这是因为链接器在生成最终映像时,会将从最低地址到最高已定义地址之间的所有“空洞”填充为 0x00

链接器填充未使用地址为0x00

细心的你可能会发现,地址 0x080300040x08030007 的内容(0x00, 0x24, 0xF4, 0x00)也被改变了。查看 Map 文件可以发现,链接器将 .data 段的初始内容放置在了这个加载地址(Load base)。

Map文件中RW_IRAM1的加载地址被改变

作为对比,下图是未加入绝对定位变量时的原始 Map 文件内容。可以明显看到,引入绝对定位变量后,RW_IRAM1 的加载基地址(Load base)发生了变化。

未修改前的Map文件内容

在 Keil MDK 的 Map 文件中,RW_IRAM1Load base 指的是存储在 Flash 中的、需要被复制到 RAM 的初始化数据的起始地址。这里出现的 0x00, 0x24, 0xF4, 0x00 实际上对应了代码中全局变量 SystemCoreClock 的初始值。

总结

以上详细介绍了五种在 MCU Flash 指定地址存入特定数据的方法,各有其适用场景:

  • 方法一和方法二较为底层和繁琐。
  • 方法三适合生产环节使用特定烧录器的场景,但功能可能受限。
  • 方法四(分散加载)方法五(绝对定位) 是开发者最常用的两种方式,通过在编译链接阶段就确定数据的最终位置,高效且灵活。其中,方法四更擅长管理多个、可能非连续的数据块;方法五则最为直接,但需注意其对链接器布局带来的间接影响,例如改变其他初始化数据的加载位置。

掌握这些方法,能帮助你在 C/C++ 嵌入式开发中,灵活处理产品序列号、校准参数等数据的固化需求。理解链接器如何工作,是掌握这些高级技巧的关键,这涉及到 计算机基础 中关于程序编译、链接与装载的知识。希望这些内容对您的项目开发有所帮助。您也可以在 云栈社区 与其他开发者交流更多嵌入式实战经验。




上一篇:王老师逻辑漏洞挖掘与SRC实战:从入门到精通 Web安全核心技能与SRC漏洞挖掘实战精讲
下一篇:ext4文件系统Journaling机制解析:从崩溃恢复看Linux一致性保障
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 16:09 , Processed in 0.252072 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表