很多人第一次碰到 Keil 的 SCT 文件,其实不是因为想学,而是被项目逼出来的。
可能是你要做 Boot + App,或者 OTA,又或者只是单纯内存不够了。这个时候你会突然发现一件事:你一直在写代码,但你其实不知道程序“最后是怎么放到芯片里的”。
这事一旦意识到,就有点不太对劲了。
因为你平时写代码,是站在“逻辑”的角度看问题的,比如一个变量、一个函数,写在哪就是在哪。
但 MCU 不是这么回事。你写的这些东西,编译完之后其实变成了一堆零散的段,有的在 Flash,有的要搬到 RAM,有的甚至只是运行时才存在。真正决定它们“最终落在哪”的,是链接阶段,而不是你写代码的时候。
SCT 就卡在这个位置上,它不是在描述代码,而是在定义“这些东西最后怎么被摆到内存里”。

SCT文件的基本语法
; 注释以分号开头
加载域名称 起始地址 [属性] [最大尺寸] {
执行域名称 起始地址 [属性] [最大尺寸] {
输入段描述
}
}
先来看个栗子,对照着看更熟悉,我们知道STM32 的 Flash 地址从 0x08000000 开始,而 RAM 是从 0x20000000 开始。
LR_IROM1 0x08000000 0x00100000 { ; 加载域:起始0x08000000,最大1MB
ER_IROM1 0x08000000 0x00100000 { ; 执行域(在Flash中执行)
*.o (RESET, +First) ; 异常向量表放在开头
*(InRoot$$Sections) ; 包含__main等
.ANY (+RO) ; 所有只读段
}
RW_IRAM1 0x20000000 0x00020000 { ; 执行域(RAM)
.ANY (+RW +ZI) ; 所有可读写和零初始化数据
}
}
LR_开头的加载域,是Load Region的缩写。
ER_开头的执行域, 是Execution Region的缩写。
- 加载域名称:标识加载区域的符号,通常使用
LR_前缀,如LR_IROM1。 - 起始地址:加载域的基地址,可以是数字,也可以是符号。
- 属性:可选,如
ABSOLUTE(绝对地址,默认)、PI(位置无关)、RELOC(可重定位)等。 - 最大尺寸:可选,指定该区域的最大字节数,链接器会检查是否超限。
以上是加载域的解释,上面sct 的代码中说明,加载域只有一个,那就是从 0x08000000 开始的 0x00100000长度的 Flash,这是存放所有代码的地方,包括运行的代码和执行时的 RAM 变量。
加载域的内部会分别配置对应的执行域,大部分可执行的代码会放在 Flash 里面,程序运行时的全局变量则会放在 RAM 里面,也就是 0x20000000 这个地址后面。当然,还有一些需要快速执行的算法代码,也可以被指定在 RAM 里面。
执行域属性:
ABSOLUTE:执行域地址固定(默认)。PI:位置无关执行域。OVERLAY:允许重叠,用于覆盖技术。FIXED:固定地址,与加载域地址相同(常用于Vectors表)。EMPTY:不包含任何输出段,仅保留地址空间。PAD:填充区域到指定大小。
以上可选的属性用的不是很多,更重要的是输入段的描述。
输入段描述
输入段描述: 用于指定哪些模块(目标文件、库)的哪些段被放入该执行域。格式为:
模块名(段名, +属性)
模块名:可以是目标文件名、库名,或*(匹配所有模块)。段名:可以是输入段名称(如RO、RW、ZI、CODE、DATA等)或具体节名(如.text、.data)。+属性:如+FIRST(放在区域开头)、+LAST(放在区域末尾)。
再来看一个复杂的栗子
LR_IROM1 0x08000000 0x00100000 {
ER_IROM1 0x08000000 0x00100000 { ;这里是第一个加载域
startup.o (RESET, +First) ;复位向量,这里开始是中断向量表
*(InRoot$$Sections)
.ANY (+RO)
}
; 将关键函数放在Flash的特定地址
ER_FUNC 0x08020000 FIXED 0x1000 {
critical.o (+RO) ;整个文件的函数放在特定 flash 地址
}
}
LR_IROM2 0x08010000 0x00010000 { ; 另一个加载域(例如存放常量数据)
ER_CONST 0x08010000 FIXED {
*.o (.rodata) ;所有常量数据放在这一个加载域
}
RW_IRAM1 0x20000000 0x00010000 {
.ANY (+RW +ZI)
}
RW_IRAM2 0x20010000 0x00010000 {
.ANY (+RW +ZI) ; 更多RAM区域
}
}
这里要注意,对于 +RW 属性的一些变量一定要写在加载域里面,因为这部分初始化的全局变量是有初始值的,他们要在执行的时候被拷贝到 RAM 中,从哪里拷贝呢?肯定是从 Flash 中的某个地址拷贝,这里把他们写到了 LRIROM2加载域中,也就是告诉了链接器,他们的地址被安排在了LRIROM2 0x08010000 0x00010000中,拷贝的时候就知道地址在哪里了。
为什么初学者不知道 sct 的存在

你一开始可能感觉不到它的存在,是因为 Keil 默认帮你做了一套分配:代码进 Flash,变量进 RAM,一切都还能跑。但这个前提是,你的工程还没复杂到需要“控制”。一旦你开始做 Boot + App,这个默认策略马上就失效了。
很多人第一次翻车就是这里。你以为把 App 烧到 0x08010000,它就会从那里运行,但实际上如果 SCT 没改,它还是按 0x08000000 去链接的。程序运行的时候,是按照“它认为的地址”来的,而不是你烧录的地址。这时候你再去看问题,就会发现完全对不上——代码没问题,烧录也成功,但就是跑飞了。

这其实是个很典型的认知错位:你以为你在控制地址,其实你没控制,SCT 才是那个真正控制的人。
等你再往后走一步,内存问题也会变得不那么直观了。很多人遇到 RAM 不够,第一反应是变量太多,但你真去拆,会发现问题不只是“多少”,而是“怎么放”。带初始值的变量在 Flash 里有一份,在 RAM 里还要再拷贝一份,是双占;而没初始化的变量只是在启动时清零,只占 RAM。你如果没有这个视角,优化的时候就很容易走偏,删了很多东西,效果却不明显。
再往深一点,比如你开始做一些对实时性要求高的东西,像电机控制或者 DSP,你会发现 Flash 跑代码有瓶颈,这时候就得把一部分函数搬到 RAM 里执行。到了这里,其实已经不是“写代码”的问题了,而是你要明确知道,这段代码存在哪、启动时怎么搬、运行时在哪执行。这一整套行为,本质上都是通过 SCT 在定义的。

没图了
走到这一步,你再回头看 SCT,就不会再把它当成一个“语法奇怪的配置文件”了。它其实是在回答一个更底层的问题:你的程序,作为一堆资源,是怎么在这块芯片上被组织起来的。
所以很多人会觉得 SCT 难,其实不是难在写,而是难在没这个视角。你如果脑子里还是“代码=程序”,那它就很拧巴;但一旦你开始把程序理解成“内存里的布局结果”,SCT 反而变得很自然,因为它只是把你脑子里的那套布局规则写出来而已。
说白了,一个很简单的分界线:当你还在让工具帮你决定放哪的时候,你是在写代码;当你开始自己决定每一块资源该放哪的时候,你其实已经在做系统设计了。而 SCT,就是你手里那把最直接的控制工具。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
没有相关内容!
暂无评论...