Keil MDK 纯汇编「你好,世界」:从 7 个报错到仿真跑通的全记录
目标:不写一行 C,用纯 ARM 汇编在 Keil uVision 的软件仿真"控制台"(Debug (printf) Viewer 窗口)里输出一行"你好,世界"。 结果:能跑,但一路踩了 7 个错误。这篇把每个报错的原文、根因、解法都记下来,给同样要交汇编作业、手头又没开发板的朋友排雷。
0. 环境与程序形态
Keil MDK 5.36(ARM Compiler 5.06 update 7,armasm 汇编器)
器件:STM32F103ZE(Cortex-M3,只要装了对应器件包,换任何 Cortex-M 芯片同理)
纯软件仿真,全程不需要开发板
输出通道:程序把字节逐个写入 ITM 激励端口 0(0xE0000000),仿真器把它转发到 View → Debug (printf) Viewer 窗口——这就是"控制台"
程序非常短,但五脏俱全:
一个 16 项的向量表:第 0 项是初始栈指针(0x20010000,SRAM 顶),第 1 项是复位向量
Reset_HandlerReset_Handler先使能 ITM(写 TER 0xE0000E00 和 TCR 0xE0000E80),然后循环LDRB取字节、STR到 ITM 端口,遇到 0 结束,最后WFI休眠中文字符串不直接写在源码里,而是用
DCB写死编码字节(源文件保持纯 ASCII,不受编辑器编码影响),留一个USE_GBK开关在 GBK / UTF-8 字节间切换。中文 Windows 的 uVision 输出窗口按 ANSI(GBK) 显示,默认 GBK 即可正常显示中文
下面是错误实录。前 3 个错误在"打不开/进不去"阶段,中间 2 个在"跑飞"阶段,最后 2 个是仿真脚本的问题。
错误 1:Project 'HelloWorld' requires 'C51' Toolchain which is not installed
现象:工程一打开,Build Output 直接报错,连编译都不让。
原因:.uvprojx 本质是 XML,可以手写。但里面 <ToolsetNumber> 这个字段是工具链编号:0x0 = C51(8051),0x4 = ARM。手写时想当然填了 0x0,uVision 便认定这是 51 单片机工程,去找没安装的 C51 编译器。更糟的是它还会"好心"把整个工程重写成 MCS-51 格式(生成一整套 Target51 选项块)。
解法:改回 ARM 工程三件套(对照 ST 官方工程的写法):
| |
经验:手写工程文件,先抄一份官方生成的一对比对字段,别凭感觉填。
错误2:No CPU DLL specified under 'Options for Target - Debug' !
现象:编译通过了,Ctrl+F5 进仿真时弹这个窗。
原因:工程曾被误认成 C51 工程又转回来,<DllOption> 块里的仿真器 DLL 配置被清空了。ARM 工程的软件仿真靠两个 DLL:CPU 模拟器和器件对话框。
解法:补全 <DllOption>(同样是标准 ARM 工程的固定写法):
| |
经验:工具链字段错误会引发连锁污染;修完标识字段还要检查仿真配置是否被清空。
错误3:No ULINK2/ME Device found
现象:DLL 配好后再进仿真,弹出硬件调试器错误。
原因:uVision 首次保存工程时,Debug 页默认选中了 Use: ULINK2/ME(硬件调试器),而仿真作业根本没插硬件。
解法:Options for Target(魔法棒/Alt+F7)→ Debug 选项卡 → 左侧改选 Use Simulator。这个选择存在 .uvoptx(界面状态文件)里,uVision 每次退出都会重写该文件,所以在界面里选一次是最保险的改法,手改文件会被覆盖。
经验:.uvprojx(工程配置)和 .uvoptx(界面/调试状态)分工不同;后者会被 uVision 反复重写,改它不如在 GUI 里点。
错误 4:error 65: access violation at 0xFFFFFFF4 : no 'write' permission(本篇最核心)
现象:仿真进去了,F5 一跑就停,Command 窗口报访问违例。中途报错地址还变过:0x0000000C no 'read'、0x00000000 no 'execute/read'——错误在变,说明每次修复都前进了一步。
原因(两层):
程序没有被链接到仿真器期望的位置。打开
Listings\HelloXIT.map一看,铁证如山:RESET 0x00008000——链接器把程序放在了 armlink 的默认地址 0x8000。而 Cortex-M 复位后硬件从 0x00000000 取初始 MSP 和复位向量,那里是空的,于是栈指针=0、入口=0,CPU 起飞即坠毁。为什么会链到默认地址?工程文件里
<umfTarg>(“使用 Target 对话框的存储器布局"开关)是 0,Target 对话框里的 IROM1 设置根本没传给链接器。在界面里改 IROM1 起始地址属于"改了个寂寞”。
解法:不再依赖对话框状态,改用显式分散加载文件。.uvprojx 链接器段改为:
| |
HelloXIT.sct 内容(向量表段强制放最前):
| |
验收方法:编译后看 map 文件,RESET 应从 0x00008000 变成 0x00000000;进仿真后反汇编窗口 0x0 处应能看到向量表数据(开头是 20010000)而不是全零的 MOVS r0,r0。
经验:map 文件是链接结果的唯一真相。程序"跑飞"先看 map,再猜。
错误5:Scatter Error: no default 'Read/Write' range selected + L6031U: Could not open scatter description file ...HelloWorld.sct
现象:改用对话框布局开关(umfTarg=1)尝试修复坑 4 后,链接直接失败:scatter 文件生成不出来。
原因:让链接器从 Target 对话框自动生成 scatter 时,对话框里的存储器区域状态已经被前几轮反复编辑搞残——它认为"没有任何可用的 RAM 区域",自动 scatter 半途而废,链接器连 .sct 文件都找不到。
解法:就是坑 4 的方案——放弃对话框布局,手写 .sct。一个显式文件胜过十个不可靠的开关状态。
经验:对话框存储器页出过事故的工程,别试图"再点对它",直接上显式 scatter 文件一步到位。
错误 6:error 10: Syntax error(debug.ini 里的注释)
现象:编译、加载全绿,进仿真时 Command 窗口却对初始化脚本报语法错误,^ 指在某一行中间。
原因:仿真器初始化文件(Debug 选项卡里的 Initialization File,每次进调试自动执行的命令脚本)走的是 uVision 命令行解析器,不支持 ; 分号注释。我顺手写的说明注释成了语法错误。
解法:debug.ini 删掉所有注释,只留命令。
经验:ini 脚本是"命令流"不是配置文件,一个字都别多写。
错误 7:error 128: invalid map command
现象:注释删掉后,第一条命令过了,第二条 MAP 0x20000000, 0x00010000 READ WRITE 报错。
原因:0x20000000 起的 SRAM 仿真器本来就已经自动映射好了,MAP 一条已被映射的区域就是错误。
解法:只补缺的、不补已有的。最终 debug.ini 只剩一行——把 0 地址的 Flash 区映射成可读可写可执行(这一段恰恰是仿真器默认不映射的):
| |
经验:报错读全一点——invalid map command 不等于"MAP 不能用",只等于"这一条不能用"。
最终能跑的全部配置(检查清单)
文件(4 个):
工程选项(GUI):Debug 页选 Use Simulator,Initialization File 填 .\debug.ini。
操作顺序:F7 编译(0 Error)→ Ctrl+F5 进仿真(Command 窗口应无任何 error)→ View → Debug (printf) Viewer → F5 → 窗口出现"你好,世界"。
经验总结
手写
.uvprojx可行但字段有雷:ToolsetNumber这类"枚举魔数"必须抄官方工程,别猜。出问题先和一份官方生成的文件逐字段对比。改工程文件要"先退出、别保存":uVision 退出时会用内存里的状态重写
.uvprojx/.uvoptx,文件改完若不退出就重开,改动会被静默覆盖(本文踩了两次)。排障看证据链,别猜:
error 65报访问违例时,真正定位靠的是 map 文件里RESET 0x00008000这一行和反汇编窗口 0 地址全零——报错文本本身从不说根因。仿真 ≠ 真机:真机上 Flash 在 0x08000000,芯片硬件自动映射到 0;软件仿真不做这个映射,要么把程序链到 0 地址(本文做法),要么自己用 MAP 命令补映射。作业是仿真,就把一切为 0 地址服务。
附:编译期那条
A1581W: Added 2 bytes of padding警告无害,是汇编器在字符串数据前补对齐字节。


