1. C禁止程序优化(链接器)
-
使用链接器命令:–keep=section_id 此选项指定删除未使用节时不能删除的输入节
-
其中 section_id 是以下项之一:
-
symbol 指定在删除未使用节时保留定义 symbol 的输入节。 如果 symbol 存在多个定义,则 armlink 将生成一则错误消息。
例如,您可能使用 --keep=int_handler。若要保留定义以 _handler 结尾的符号的所有节,请使用–keep=*_handler。
object(section)指定在删除未使用节时保留 object 中的 section
例如,若要保留vectors.o 对象的 vect 节,请使用: --keep=vectors.o(vect)若要保留 vectors.o 对象中节名称的前三个字母是 vec 的所有节,请使用:–keep=vectors.o(vec*)z
由于我的程序test()函数是单独放在一个文件里(entry.c)的,所以我在linker标签栏下的Misc controls编辑框中输入–keep=entry.o


2. Keil simulatar 使用

3. 运行时,插入仿真器

- ini文件内容: LOAD %L INCREMENTAL

4. 告警信息处理

–diag_suppress=L6329,1
| 代码 | 解释 |
|---|---|
| 177 | variable “XXXXX” was declared but never referenced |
| 550 | variable “XXXXX” was set but never used |
| 1295 | Check_Save_Flag( ) 错误; Check_Save_Flag( void) 正确 |
| 1 | last line of file ends without a newline |
5. 函数优化
- 优化运行时间
#pragma push //必须增加上 #pragma push 和 #pragma pop 代表优化这个他们之间的所有函数
//若没有 push 代表优化 #pragma Otime之后的所有函数
#pragma Otime
void function2(void){
... // Optimized for execution time
}
#pragma pop
- 优化代码空间
#pragma push
#pragma Ospace
void function2(void){
... // Optimized for code size
}
#pragma pop
- 优化等级
#pragma push
#pragma O3
void function2(void){
... // Optimized at O3
}
#pragma pop
6. 匿名函数、结构体
keil中默认是不支持匿名结构体的,需要编译指令#pragma anon_unions指名
#pragma anon_unions
typedef union {
unsigned int num;
struct {
unsigned int nLow :8;
unsigned int nHigh :8;
};
} Test;
void func(void)
{
Test test;
test.num=99;
test.nLow=10;
}
7. C语言嵌套汇编
-
keil C嵌入汇编1
__asm void SysClkDelay(uint16_t clkcount) { subs r0,#1 bne.n SysClkDelay bx lr //跳到调用出 } -
IAR中有如下写法,在keil中报错
void SysClkDelay(uint32_t clkcount)
{
__asm("subs r0,#1\n"
"bne.n SysClkDelay\n"
" bx lr");
}
8. 系统相关函数
-
关总中断
keil:__disable_irq() //关 总中断
__enable_irq() //开总中断 __asm(“NOP”) 没有找到,使用的是内嵌汇编
IAR:__disable_interrupt( )
__enable_interrupt( )
__NOP()
9. 运行中获得链接脚本变量的方法
- kinker文件
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
}
- 测试代码
void EXTI4_IRQHandler(void)
{
/* USER CODE BEGIN EXTI4_IRQn 0 */
extern unsigned int Image$$ER_IROM1$$Base;
extern unsigned int Image$$ER_IROM1$$Limit;
extern unsigned int Image$$ER_IROM1$$Length;
extern unsigned int Image$$RW_IRAM1$$Base;
extern unsigned int Image$$RW_IRAM1$$Limit;
extern unsigned int Image$$RW_IRAM1$$Length;
extern unsigned int Image$$RW_IRAM1$$ZI$$Base;
extern unsigned int Image$$RW_IRAM1$$ZI$$Limit;
extern unsigned int Image$$RW_IRAM1$$ZI$$Length;
unsigned int base,limit,length;
base = (unsigned int )&Image$$ER_IROM1$$Base;
limit = (unsigned int )&Image$$ER_IROM1$$Limit;
length = (unsigned int )&Image$$ER_IROM1$$Length;
printf("\nER_IROM1 Base = 0x%x\nER_IROM1 Limit = 0x%x\nER_IROM1 Length = %d\n",base,limit,length);
base = (unsigned int )&Image$$RW_IRAM1$$Base;
limit = (unsigned int )&Image$$RW_IRAM1$$Limit;
length = (unsigned int )&Image$$RW_IRAM1$$Length;
printf("\nRW_IRAM1 Base = 0x%x\nRW_IRAM1 Limit = 0x%x\nRW_IRAM1 Length = %d\n",base,limit,length);
base = (unsigned int )&Image$$RW_IRAM1$$ZI$$Base;
limit = (unsigned int )&Image$$RW_IRAM1$$ZI$$Limit;
length = (unsigned int )&Image$$RW_IRAM1$$ZI$$Length;
printf("\nRW_IRAM1_ZI Base = 0x%x\nRW_IRAM1_ZI Limit = 0x%x\nRW_IRAM1_ZI Length = %d\n",base,limit,length);
/* USER CODE END EXTI4_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
/* USER CODE BEGIN EXTI4_IRQn 1 */
Reset_test();
/* USER CODE END EXTI4_IRQn 1 */
}
- 打印结果

10. no_init 变量
-
linker文件
LR_IROM1 0x00000000 0x00040000 { ; load region size_region ER_IROM1 0x00000000 0x00040000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00005000 { ; RW data ;UNINIT 代表不初始化 RW_IRAM1 0x20000000 UNINIT 0x00005000 代表不初始化+ZI,否则初始胡为0 .ANY (+RW +ZI) } RW_IRAM2 0x20005000 UNINIT 0x00001000 { ;UNINIT 代表不初始化 .ANY (NO_INIT) } } -
使用方式一
uint32_t uLP0SendCount __attribute__((section("NO_INIT"),zero_init)); //把变量放在NO_INIT段
- 关键字定义
#defnie __no_init __attribute__((zero_init)) //注意,使用此关键字定义的变量,不指定的话,默认放在BSS段,




11. 变量固定位置/区间
- 方法一:使用SETADR宏固定变量位置
#define ADR_TO_STR(adr) #adr
#define SETADR(adr) __attribute__((section(".ARM.__at_"ADR_TO_STR(adr))))
- 方法二:使用宏固定变量
#define SETADR(adr) __attribute__((at(adr)));
- 方法三:变量固定区间
#pragma arm section zidata = "SRAM" // 在 C 文件中定义新的段
unsigned char GucTest1[20 * 1024]; // 定义第一个 20KB 数组
#pragma arm section // 恢复原有的段
- 特殊用法:自动产生一个数据段
RT_USED const char tt1 __attribute__((section(".rti_fn4."))); // 把tt1固定在.rti_fn4.数据段
12. C堆栈和寄存器的使用
- 其规定了C函数的形参通过R0-R3四个寄存器传递,其余通过栈传递,所以函数的参数一般不要超过四个,如果参数过多,可以通过结构指针的方式传入
- 调用函数时,父函数需要保存R0-R3中自己需要用到的寄存器,子函数刚进入时需要保存R4-R12,SP,LR,PC及XPSR需要使用的寄存器
按照以上过程,就可以实现C和汇编的嵌入
__asm void SysClkDelay(uint32_t clkcount)
{
subs r0,#1
bne.n SysClkDelay
bx lr
}
- 使用C的局部域,并且只在变量需要时才声明,这样可以做到栈内存复用
int test(void)
{
int local;
{
int a;
// do something
}
{
int b;
// do something
}
return local;
}
13. 生成bin文件
13.1. fromelf
fromelf –bin –output=xxxx.bin xxxx\xxxx.axf
其中–bin是制定输出文件格式是bin格式,
–output=xxxx.bin指定输出文件名,
xxxx\xxxx.axf 是指定路径下的指定axf文件,此文件作为输入文件,是keil编译后产生的
13.2. keil中使用fromelf
- *方式一

或改为 fromelf --bin -o “$Loutput@L.bin” “#L”
-
方式二

-
方式三
- 创建bat
bat内容包含:bin文件生成、hex复制、hex合并
:: 接收从Keil传递过来的参数
set "OUTPUT_DIR=%~1" :: $L
set "LINKER_OUTPUT=%~2" :: #L
set "TARGET_BASE=%~3" :: @L
set "LISTING_DIR=%~4" :: %L
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o %OUTPUT_DIR%output\DL250_%TARGET_BASE%.bin %LINKER_OUTPUT%
copy %OUTPUT_DIR%%TARGET_BASE%.hex %OUTPUT_DIR%output\DL250_APP_%TARGET_BASE%.hex
copy /B "%OUTPUT_DIR%output\RadarSingleBootUart2-251011.hex" + "%OUTPUT_DIR%output\DL250_APP_%TARGET_BASE%.hex" "%OUTPUT_DIR%output\DL250_CB_%TARGET_BASE%.hex"
- 调用bat
![![[../图片库/Pasted image 20251015181714.png]]](https://i-blog.csdnimg.cn/direct/fce737d529684bf894742a5c39432c57.png)
14. 命令语法基础
基本命令: copy, del, xcopy, mkdir, rename 等
常用 Keil 宏变量:
| 宏变量 | 描述 | 示例 |
|---|---|---|
@L | 链接器输出文件的基名(不含路径和扩展名) | MyProject |
#L | 链接器输出的完整路径和文件名 | .\Objects\MyProject.axf |
$L | 目标文件的目录路径(不含文件名) | .\Objects |
%L | 列表文件的目录路径 | .\Listing |
实战示例
假设你的项目结构如下:
MyProject/
├── UV/
│ └── MyProject.uvprojx (工程文件)
├── Objects/ (输出文件目录)
├── Listing/
└── Tools/
└── bootloader.bin
示例 1:复制生成的 BIN 文件到指定目录
copy "$L@L.bin" ".\Release\$L@L.bin"
- 解释:
"$L@L.bin": 源文件,即刚刚在Objects目录下生成的.bin文件。".\Release\": 目标目录。$L@L.bin: 目标文件名,这里保持原名。
示例 2:在构建前清理旧文件(删除)
del /Q "$L@L.*" "%L\*.*"
- 解释:
del: 删除命令。/Q: 静默模式,删除时不提示确认。"$L@L.*": 删除Objects目录下所有与工程同名的文件(如.axf,.bin,.htm等)。"%L\*.*": 删除Listing目录下的所有文件
示例 3:合并固件(复制并合并二进制文件)
copy /B ".\Tools\bootloader.bin" + "$L@L.bin" ".\Release\full_image.bin"
- 解释:
copy /B: 以二进制模式进行复制,用于文件合并。".\Tools\bootloader.bin": Bootloader 文件。+: 连接符,表示将后面的文件追加到前一个文件。"$L@L.bin": 你的应用程序 BIN 文件。".\Release\full_image.bin": 最终输出的合并后的完整固件。
示例 4:创建目录并复制
- 创建一个
post_build.bat文件,放在项目目录下
@echo off
set TIMESTAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%
set TARGET_DIR=.\Archive\Build_%TIMESTAMP%
mkdir "%TARGET_DIR%"
copy ".\Objects\*.bin" "%TARGET_DIR%\"
copy ".\Objects\*.axf" "%TARGET_DIR%\"
echo Files archived to %TARGET_DIR%
- 在 Keil 的 “After Build” 区域调用这个批处理文件:
call ".\post_build.bat"
15. 生成反汇编文件
fromelf –text -a -c –output=xxxx.txt xxxx\xxxx.axf
其中–text是指定输出文件格式为text格式,
-a Prints the global and static data addresses (including addresses for structure and union contents). This option can only be used on files containing debug information. Use the -select option to output a subset of the data addresses.
-c Disassembles code
–output=xxxx.txt 指定输出文件名,
xxxx\xxxx.axf 是指定路径下的指定axf文件,此文件作为输入文件,是keil编译后产生的

16. armcc工具使用
armar.exe armasm.exe armcc.exe armlink.exe fromelf.exe
以及动态链接库
armcompiler_libFNP.dll
-
armar.exe
可以在windows下使用命令行切换到该程序所在文件夹(keil5\ARM\ARMCC\bin),执行armar.exe -h进行命令查看。若有gitbash的话直接在该文件夹下右键选择gitbash here,之后运行./armar.exe -h。获得以下结果(下面是已经翻译过的,省略部分不重要的)
产品: MDK Professional 5.14
组件: ARM Compiler 5.05 update 1 (build 106)
工具: armar [4d0efa]
文件创建以及维护工具。
库文件:.lib或者.a
命令格式: armar options archive [ file_list ]
选项:
-r 插入在file_list中的文件, 替换掉已经存在的同名成员.
-d 删除在file_list中的成员.
-x 在archive中提取file_list中同名的成员.
-m 在file_list中移动文件.
-p 打印文件到标准输出设备.
-a pos 插入/删除pos后面的文件.
-b pos 插入/删除pos前面的文件.
-u 只更新旧的文件, 与 -r 一起使用.
-n 不要向object文件中添加符号表.
-s 强制重新生成文档符号表.
-t 打印文档的内容表.
--zs 显示符号表.
--zt 汇总文档内容 (大小和输入).
-c 当一个新文档被创建的时候不显示警告.
-C 提取的时候不要覆盖一个已经存在的文件.
-T 截取系统最大长度文件名.
-v 提供详细输出.
--create 强制创建一个新的文档.
--via file 从 via 文件中获取额外参数.
--sizes 列出所有成员大小与库的总大小.
--entries 列出包括入口点的部分.
--vsn 打印最新的armar版本.
--help 打印帮助信息.
例子:
D:\Keil\ARM\ARMCC\bin>armar.exe -r testlib __main.o _rcc.o _it.o //把__main.o _rcc.o _it.o 合并到testlib中
Creating archive 'testlib'
D:\Keil\ARM\ARMCC\bin>armar.exe -tv testlib //打印文档的内容表
rw-rw-rw- 0/ 0 1072 Sep 18 18:09 2016 __main.o (offset 934)
rw-rw-rw- 0/ 0 338632 Sep 08 20:23 2016 _rcc.o (offset 2066)
rw-rw-rw- 0/ 0 328392 Sep 08 20:23 2016 _it.o (offset 340758)
D:\Keil\ARM\ARMCC\bin>armar.exe -d testlib _it.o
D:\Keil\ARM\ARMCC\bin>armar.exe -tv testlib
rw-rw-rw- 0/ 0 1072 Sep 18 18:09 2016 __main.o (offset 774)
rw-rw-rw- 0/ 0 338632 Sep 08 20:23 2016 _rcc.o (offset 1906)
ARM 映像转换工具
fromelf [options] input_file
选项:
--help 显示帮助信息
--vsn 显示版本信息
--output file 输出文件名. (默认输出 -text 格式)
--nodebug 不要输出调试信息到映像文件中
--nolinkview 不要输出段信息到映像文件中
二进制输出格式:
--bin 普通二进制
--m32 摩托罗拉32位Hex码
--i32 英特尔32位Hex码
--vhx 定向字节的 Hex 格式
--base addr 为 m32,i32设置基地址(可选的)
输出格式要求的调试信息
--fieldoffsets Structures/Classes的汇编描述
--expandarrays Arrays inside and outside structures are expanded
其他输出格式:
--elf ELF格式
--text 文本信息
文本信息的标志
-v 详细信息
-a 打印数据的地址信息 (得到的.axf映像文件)
-c 汇编码
-d 打印数据的段内容
-e 打印例表
-g 打印调试表
-r 打印重定位信息
-s 打印符号表
-t 打印字符表
-y 打印段内容分析
-z 打印代码与数据的大小信息
例子
fromelf.exe --i32 --output=./test_prj.hex --base=0x08000000 ./test_prj.axf
Product: MDK Standard 5.12
Component: ARM Compiler 5.05 (build 41)
Tool: armcc [4d0eb9]
For support see http://www.arm.com/support
Software supplied by: ARM Limited
Usage: armcc [options] file1 file2 ... filen
主要选项:
--arm 创建 ARM 代码
--thumb 创建 Thumb 代码
--c90 切换到C模式 (默认是 .c 文件)
--cpp 切换到C++模式 (默认 .cpp 文件)
-O0 最小优化级别
-O1 受限的调试级别优化
-O2 高优化
-O3 最大优化
-Ospace 对代码大小进行优化
-Otime 优化最大优化级别的运行时间
--cpu <cpu> 选择CPU
--cpu list 输出所有可以选择的CPU列表
--device 设置目标设备类型
--device list 输出所有可以选择的目标设备列表
-o file 最终输出文件的名字
-c 只进行编译,不链接
--asm 输出汇编以及obj文件
-S 只输出汇编文件
--interleave 交叉反汇编 (use with --asm or -S)
-E 仅仅预处理C代码
-D <symbol> 定义<symbol>并且传入编译过程
-g 为高级别调试创建表
-I <directory> 在编译的时候包含<directory>作为头文件搜索目录
keil中经过配置后的选项:
-c --cpu Cortex-M4.fp -g -O0 --apcs=interwork --split_sections -I../system/usart -I../system/sys -I../system/delay -I../hardware/head
-I E:\ProjectFiles\keil5\STM32\test_prj\user\RTE
-I D:\msprograms\keil5\ARM\PACK\Keil\STM32F4xx_DFP\1.0.8\Device\Include
-I D:\msprograms\keil5\ARM\CMSIS\Include
-D__UVISION_VERSION="514" -DSTM32F40_41xxx -o "..\obj\*.o" --omf_browse "..\obj\*.crf" --depend "..\obj\*.d"
这种方式编译就不在赘述,比较简单,介绍下如何在自制makefile并且进行编译
\1. 首先下载make.exe,链接在此:http://www.equation.com/servlet/equation.cmd?fa=make
\2. 拷贝到keil的armcc的bin目录下,在工程文件中编写makefile
\3. 执行make即可实现自制make
Usage: armlink option-list input-file-list
where
option-list是不区分大小写的选项列表.
input-file-list是输入对象和库文件列表.
General options (abbreviations shown capitalised):
--output file 指定输出文件名.
Options for specifying memory map information: (指定map的信息)
--partial 创建一个被分散链接的对象文件.
--scatter file 按照分散加载文件的描述创建map文件. (Create the memory map as described in file.)
--ro-base n 设置执行地址空间域,包含RO段(只读数据段).
--rw-base n 设置执行地址空间域,包含RW/ZI段.
Options for controlling image contents: (控制image的内容)
--bestdebug 添加调试信息为image提供调试视图.
--datacompressor off 不要压缩RW数据段.
--no_debug 不添加调试信息.
--entry 指定输入段与输入点.
--libpath 指定系统库文件路径.
--userlibpath 指定用户库文件路径.
--no_locals 不要添加局部标号到image的标号列表.
--no_remove 不要移除image的未使用段.
Options for controlling image related information:
--callgraph 创建一个函数静态调用图.
--info topic 列出image信息.
Available topics: (separate multiple topics with comma) common List common sections eliminated from the image. debug List eliminated input debug sections. sizes List code and data sizes for objects in image. totals List total sizes of all objects in image. veneers List veneers that have been generated. unused List sections eliminated from the image.
--map 显示image内存映射.
--symbols 列出image中的符号.
--xref 列出输入的段之间所有的交叉引用.最终输出会放在.map文件里面
keil里面的配置
--cpu Cortex-M4.fp *.o
--strict --scatter "..\obj\test_prj.sct"
--summary_stderr --info summarysizes --map --xref --callgraph --symbols
--info sizes --info totals --info unused --info veneers
--list "..\obj\test_prj.map"
-o ..\obj\test_prj.axf
Usage: armasm [options] sourcefile
Options:
--list listingfile 生成列表文件
-o outputfile 最终输出文件名
--depend dependfile 保留 'make' 源文件依赖
--errors errorsfile 把标准错误判断放入errorsfile
-I dir[,dir] 添加源文件的搜索目录
--pd
--predefine directive 预执行 SET{L,A,S} 指令
--maxcache <n>最大闪存空间 (default 8MB)
--no_esc 忽略C文件
--no_warn 关闭警告信息
-g 输出调试表
--apcs / //比较复杂,暂不关心
--li ARM小端模式
--bi ARM大端模式
--cpu 设置目标ARMcpu类型
--device 设置目标设备类型
--fpu 设置目标 FP 体系结构版本
--thumb 以 Thumb 指令集编译
--arm 以 ARM 指令集编译
keil里面的配置
--cpu Cortex-M4.fp -g --apcs=interwork
-I E:\ProjectFiles\keil5\STM32\test_prj\user\RTE
-I D:\msprograms\keil5\ARM\PACK\Keil\STM32F4xx_DFP\1.0.8\Device\Include
-I D:\msprograms\keil5\ARM\CMSIS\Include
--pd "__UVISION_VERSION SETA 514" --pd "STM32F40_41xxx SETA 1" --list "..\obj\*.lst" --xref -o "*.o" --depend "*.d"
#armasm.exe程序的路径
ASMCOMPILE_PATH = /d/msprograms/keil5/ARM/ARMCC/bin/armasm.exe
#汇编编译选项
#--cpu Cortex-M4.fp cpu型号是Cortex-M4.fp
#--pd "__UVISION_VERSION SETA 514" 编译之前将__UVISION_VERSION赋值为514,后者同理
ASMCOMPILE_FLAG = --cpu Cortex-M4.fp -g --apcs=interwork --pd "__UVISION_VERSION SETA 514" --pd "STM32F40_41xxx SETA 1" --xref
#armcc.exe程序的路径
CCOMPILE_PATH = /d/msprograms/keil5/ARM/ARMCC/bin/armcc.exe
#汇编编译选项
#--cpu Cortex-M4.fp cpu型号是Cortex-M4.fp
#后面的解释看上面的相关指令选项注释
CCOMPILE_FLAG = --cpu Cortex-M4.fp -g -O0 --apcs=interwork --split_sections -D__UVISION_VERSION="514" -DSTM32F40_41xxx
#fromelf.exe程序的路径
FROM_ELF_PATH = /d/msprograms/keil5/ARM/ARMCC/bin/fromelf.exe
#intel 32位hex格式
#输出文件名为test_prj.hex
#基地址为0x08000000
FROM_ELF_FLAG = --i32 --output=./test_prj.hex --base=0x08000000
#头文件查找目录,如果要添加直接添加CINCLUDE_FILE +=样式即可
CINCLUDE_FILE += -I../system/sys
CINCLUDE_FILE += -I../system/delay
CINCLUDE_FILE += -I../hardware/head
CINCLUDE_FILE += -I../system/usart
CINCLUDE_FILE += -I /e/ProjectFiles/keil5/STM32/test_prj/user/RTE
CINCLUDE_FILE += -I /d/msprograms/keil5/ARM/PACK/Keil/STM32F4xx_DFP/1.0.8/Device/Include
CINCLUDE_FILE += -I /d/msprograms/keil5/ARM/CMSIS/Include
#目标文件列表
OBJS += ../user/startup_stm32f40_41xxx.o
OBJS += ../system/usart/usart.o
OBJS += ../system/sys/sys.o
OBJS += ../system/delay/delay.o
OBJS += ../hardware/src/gpio.o
OBJS += ../user/test.o
LINK_PATH = /d/msprograms/keil5/ARM/ARMCC/bin/armlink.exe
LINK_FLAG = --cpu Cortex-M4.fp --strict --scatter "test_prj.sct" --summary_stderr --info summarysizes --map --xref --callgraph --symbols --info sizes --info totals --info unused --info veneers --list "..\obj\test_prj.map"
test_prj.axf : $(OBJS)
echo "hellow"
$(LINK_PATH) $(LINK_FLAG) -o $@ $^
$(FROM_ELF_PATH) $(FROM_ELF_FLAG) ./test_prj.axf
%.o : %.c
$(CCOMPILE_PATH) $(CINCLUDE_FILE) $(CCOMPILE_FLAG) -o $@ -c $< --omf_browse $(subst .c,.crf,$<) --depend $(subst .c,.d,$<)
%.o : %.s
$(ASMCOMPILE_PATH) $(CINCLUDE_FILE) $(ASMCOMPILE_FLAG) -o $@ $< --list $(subst .s,.lst,$<) --depend $(subst .s,.d,$<)
clean :
rm -rf $(OBJS)
#字符串查找替换
#$(subst <from>,<to>,<text>)
#把text中的from字符串替换为to字符串
#例:$(subst he,HE,hellow)
#把hellow中的he替换为HE</text></to></from></code>
- scatter文件(分散加载文件)
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; 加载域,也就是代码文件下载到0x08000000 代码段最大尺寸为0x00100000(1M)
ER_IROM1 0x08000000 0x00100000 { ; 执行域,也就是从0x08000000开始执行,执行空间为0x00100000大小
*.o (RESET, +First) ;所有的.o文件存放位置,First标示该段位于该执行域的最开始,+ 表示连续放置,以RESET段作为开始,RESET在启动文件里面
*(InRoot$$Sections) ;一些库文件的加载,启动文件STM32F4xx.s里面跳转到__main而不是main函数就有这句话的作用。 而__main函数里面会执行RW,ZI的"解压缩",也就是把这些数据初始化并且放在它的执行域当中去。 __main是一个库函数,执行完解压缩之后才会跳转到真正的main函数处执行代码
.ANY (+RO) ;所有的RO数据存放位置,RO数据为常量数据,运行过程不会被改变。 .ANY则是编译器根据情况可将该段放在该执行域任意位置,+ 表示连续放置
}
RW_IRAM1 0x20000000 0x00020000 { ; 变量数据段,从0x20000000开始,最大尺寸为0x00020000,此属于RAM区
.ANY (+RW +ZI) ;所有的RW以及ZI数据
}
}
17. symdefs符号表
- 1、生成符号表

–no_remove --symdefs=symbol.txt
- 2、 使用符号表


或者

18. 调试相关
18.1. 打印Ram数据
在仿真状态下,使用“Command”窗口,输入指令 打印Ram数据指令“save C:\Users\W.L.Laptop\Desktop\临时文件\data.hex 0x20000800,0x2000097F”
“C:\Users\W.L.Laptop\Desktop\临时文件\data.hex” 为保存文件路径和名称“0x20000800,0x2000097F”为起止Ram 地址范围输入完成后,点击回车
本地创建 data.hex文件

18.2. 设置软断点
__breakpoint(0); 设置软断点
18.3. KEIL5.25 当编译有错误时,会死机(在win11上测试)
18.4. KEIL 设置断点问题
当C文件中嵌入汇编时,这个C文件不能设置断点。需要把汇编文件放入其他的C文件。
19. 链接命令
–no_scanlib 不搜索本地库文件
–no_remove 不移除不用的函数
–symdefs=symbol.txt 生成symbol表
20. 分散加载示例
20.1. 示例一:基础示例
; **************************MASTER***********************************
LR_IROM_MASTER 0x08006000 0x00016000 { ; load region size_region
ER_IROM_MASTER 0x08006000 0x00016000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM_MASTER 0x20000120 UNINIT 0x00002EE0 {
.ANY (+RW +ZI)
*.o (STACK, +LAST)
}
RW_IRAM_NO_INIT_MASTER 0x20003000 UNINIT 0x00001800 {
.ANY (NO_INIT)
}
}
; **************************MEASURE***********************************
LR_IROM1_MEASURE 0x0801C000 0x00004000 { ; load region size_region
ER_IROM_MEASURE 0x0801C000 0x00004000 { ; load address = execution address
; *.o (RESET, +First)
; *(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM_MEASURE 0x20004800 0x00000800 { ; RW data 2K
.ANY (+RW +ZI)
; *.o (STACK, +LAST)
}
}



20.2. 示例二:O文件顺序加载
LR_IROM1_DLIB 0x00005020 0x00003000 { ; load region size_region 0x00005000 位置放置结构体
ER_IROM___dczerorl2 +0 { __dczerorl2.o(+RO) }
ER_IROM__chval +0 { _chval.o(+RO) }
ER_IROM__strtoul +0 { _strtoul.o(+RO) }
ER_IROM_cdcmple +0 { cdcmple.o(+RO) }
ER_IROM_cdrcmple +0 { cdrcmple.o(+RO) }
ER_IROM_cfcmple +0 { cfcmple.o(+RO) }
ER_IROM_cfrcmple +0 { cfrcmple.o(+RO) }
}
20.3. 示例三: 函数固定位置
LR_IROM3 0x00038000 0x00008000 { ; load region size_region
ER_IROM3 0x00038000 0x00008000 { ; load address = execution address
API_SpecialProInstance.o
*(i.__ARM_common_switch8, +LAST) ;__ARM_common_switch8函数固定在最后的位置
}
RW_IRAM_SPECIALPROINSTANCE_RW 0x20004800 0x00000004 {
API_SpecialProInstance.o (+RW)
}
RW_IRAM_SPECIALPROINSTANCE 0x20004804 UNINIT 0x000017FC {
API_SpecialProInstance.o (+ZI)
}
}
20.4. 函数固定位置
函数放在CCM SRAM中
__attribute__((section ("ccmram")))
void SystemClock_Config(void) {……}
21. 常量赋予属性
- 也就是说0x00是int型,0x00u是无符号int型,0x00ul长整型
22. 批删除命令
del /S *.i
del /S *.__i
del /S *._ia
del /S *.crf
del /S *.d
del /S *.o
23. KEIL插件
23.1. KEIL 嵌入Notepade

23.2. keil嵌入vscode
条件编译
24. KEIL VS IAR
| 比较内容 | IAR | KEIL |
|---|---|---|
| 编译时长 | 优 | 差 |
| 代码自动对齐 | √ | × |
| no_init使用 | 方便 | 比较繁琐 |
| 变量固定位置 | 使用方便@就可以 | 需要增加预编译语言 |
这个问题待解决:
测量程序不优化处理机制
25. attribute
25.1. __attribute__((used))
1. 通知[编译器](https://so.youkuaiyun.com/so/search?q=编译器&spm=1001.2101.3001.7020)在目标文件中保留一个静态函数,即使它没有被引用
2. 标记为attribute__((used))的函数被标记在目标文件中,以避免链接器删除未使用的节
3. 静态变量也可以标记为used,方法是使用 \_\_attribute__((used))
```c
static int lose_this(int);
static int keep_this(int) __attribute__((used)); // retained in object file
static int keep_this_too(int) __attribute__((used)); // retained in object file
```
```c
#define __USED __attribute__((used)) //cmsis_armcc.h 定义
```
25.2. attribute((section (“section_name”)))
1. section 函数属性使您能够将代码放置在图像的不同部分中。
```c
void Function_Attributes_section_0 (void) __attribute__((section ("new_section")));
void Function_Attributes_section_0 (void)
{
static int aStatic =0;
aStatic++;
}
```
```c
#pragma arm section code="foo"
int f2()
{
return 1;
} // into the 'foo' area
__attribute__((section ("bar"))) int f3()
{
return 1;
} // into the 'bar' area
int f4()
{
return 1;
} // into the 'foo' area
#pragma arm section
```
- section关键字可以将变量定义到指定的输入段中,下面以具体的例子来讲解section的使用方法
#define SECTION(level) __attribute__((used,__section__(".fn_cmd."level)))
#define CMD_START_EXPORT(func,func_s) \
const struct CMD_LIST cmd_fn_##func SECTION("0.end") = {func,func_s}
CMD_START_EXPORT(start_fun,"start_fun");
25.3. attribute((constructor[(priority)]))
在下面的例子中,构造函数在执行进入main()之前按照指定的顺序被调用
int my_constructor(void) __attribute__((constructor));
int my_constructor2(void) __attribute__((constructor(101)));
int my_constructor3(void) __attribute__((constructor(102)));
int my_constructor(void) /* This is the 3rd constructor */
{ /* function to be called */
...
return 0;
}
int my_constructor2(void) /* This is the 1st constructor */
{ /* function to be called */
...
return 0;
}
int my_constructor3(void) /* This is the 2nd constructor */
{ /* function to be called */
...
return 0;
}
25.4. attribute((aligned(4)))
__attribute__((aligned(4))) uint8_t[4] Buff; //4字节对齐
#pragma pack(2)
typedef struct
{
char a;
int b;
} SP;
#pragma pack () // 取消结构体对齐
26. keil代码折叠

27. 提前与main运行
为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 SubSubSub$ 和SuperSuperSuper$。可以给 main 添加 SubSubSub$ 的前缀符号作为一个新功能函数 SubSubSub$main,这个 SubSubSub$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统启动,进行系统一系列初始化),再调用 SuperSuperSuper$main 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。
关于 SubSubSub$ 和 SuperSuperSuper$ 扩展功能的使用,详见 ARM® Compiler v5.06 for µVision®armlink User
Guide。
$Super$$main()
28. 批量拷贝调试数据
- 方法1:使用 Function Editor 和日志功能
这种方法适用于将变量值输出到文件中,适合数组或大量数据的导出。
- 进入调试模式:启动调试会话,运行程序到需要查看变量值的地方。
- 打开 Function Editor :• 菜单栏选择 Debug -> Function Editor 。
- 编写日志输出函数:
• 在 Function Editor 中输入以下代码:
FUNC void SaveVariableValues() {
int idx;
exec("log > VariableValues.log"); // 指定日志文件名
for (idx = 0; idx < ARRAY_SIZE; idx++) {
printf("Variable[%d] = %d\n", idx, Variable[idx]); // 根据需要修改变量名和格式
}
exec("log off");
}
• 其中 ARRAY_SIZE 是数组大小, Variable 是目标数组名。
-
编译并运行函数:
• 在 Function Editor 中点击 Compile 。
• 在 Command Window 中输入 SaveVariableValues() 并回车。 -
查看输出文件:
• 在指定路径下找到 VariableValues.log 文件,打开后即可看到变量值。
29. 代码运行在RAM
- 将函数放在CCM SRAM中

- 将整个C文件放在CCM SRAM中

2万+

被折叠的 条评论
为什么被折叠?



