极术小能手 · 2022年03月12日

【课程实验 LAB1】“施法”让CPU动起来:搭建 Keil 环境

实验前请点击获取配套资料

在本实验 LAB1 中, 你将动手学习 Keil 开发环境的搭建, 基础汇编程序的编写, 编译和仿真调试方法, 并通过几个简单程序理解函数调用的 ATPCS 规则.
在进行本实验之前, 你应该提前掌握了一些基础的 Arm 指令和伪指令语法, 并阅读过一些简单的汇编程序.

ARM 汇编回顾

这是一条为强者节省时间的提示
如果你觉得自己已经掌握了 ARM 汇编, 你可以跳过本小节.

我需要首先掌握哪些知识呢?
如果你想顺利地完成 LAB1 实验, 那么你至少需要:

  • 了解 Cortex-M0 编程模型 (实验指导书 3.2.3)
  • 了解 Cortex-M0 存储系统 (实验指导书 3.2.4)
  • 掌握 ARM 基础指令, 如 MOV, ADD, SUB, LDR, STR, CMP 和一些转移指令等 (实验指导书 3.6)
  • 掌握 ARM 基础伪指令, 如 AREA, DCD, SPACE, EQU, LDR 等 (实验指导书 4.2)
  • 如果你对自己不够自信, 没有关系, 继续往下做吧, 你可以先去试着阅读实验中用到的程序, 遇到不懂的地方再去查阅相关资料.

理解伪指令的意义

可能有的小伙伴会有这样的问题: 既然 CPU 仅依靠机器码运行, 而 "伪指令" 不能够被转换为机器码, 它的存在有什么意义呢?
伪指令其实有很多作用方式, 这里简要跟大家分享几种.

  • 假想我们手上只有指令, 如果我们期望实现对一个变量的反复读取, 处理 和 存储, 那么我们只能这样做: 先商定一个地址作为这个变量的存储地址, 之后的读写操作都在这个地址上进行, 这种方式看起来没什么问题, 但是当变量的个数很多时, 这种 "手动分配地址" 的方式貌似显得有些笨拙. 当多个文件中这样 "手动定义" 的变量之间存在相互引用关系时, 我们就变成了一个 "人工链接器", 这显然是不合理的, 我们还是需要将这一切交给真正的链接器去执行, 因此我们定义的变量必须要 "符号化", 于是我们希望通过一种方式使这些 "符号化" 的变量被自动分配地址, 而我们只需要关注这些变量的内容即可, 这种方式便是数据定义伪指令, 如 DCD, SPACE 等.
  • 有时我们希望代码尽可能简洁, 比如, 当我们想把其他文件中的某些代码拿到当前文件中使用时, 我们希望通过一条语句把它引用过来而不是笨拙地复制粘贴; 当我们写的一大段代码存在很强的规律性时, 我们希望用一种类似于 for 循环的语句去 "生成" 它们而不是把时间浪费在敲代码上; 当我们的程序在某几种应用情境下的版本只存在几条语句上的差别时, 我们希望首先通过一个情景指示符去选择使用哪几条语句, 选择好了再进行汇编.... 于是便有了汇编控制伪指令和符号定义伪指令, 这两种伪指令和 c 语言中以 "#" 开头的 "编译预处理指令" 作用很相似, 其本质是对指令代码的 "替换" 和 "生成".
  • 还有一种伪指令专门用于 "声明", "声明" 的对象不是开发者, 而是汇编器或链接器, 比如 CODE16, ALIGN 是对汇编器的声明, 告诉汇编器应该把指令翻译成多少位的机器码, 是否需要填字以保持对齐; IMPORT, EXPORT 是对链接器的声明, 告诉链接器这个符号是否是从别的文件引用过来的, IMPORT 类似于 c 语言中的 extern.

总之, 伪指令是为了方便开发者而诞生的, 我们应该对其心怀感激地去使用, 而不是把它们当成编程的负担.

迷惑众人的 LDR 伪指令
试想如果没有LDR伪指令, 我们在程序编写中会遇到这样两种问题:

  • 使用 MOV 指令向寄存器加载立即数时, 一旦这个立即数超过了指定范围 (0-255), 就要手动定义一个数据, 然后把这个数据加载到寄存器中, 这种解决办法未免过于繁琐.
  • 当使用 LDR 指令去读取一个变量时, 我们希望先把变量的地址读到某个寄存器中, 然后用这个寄存器去寻址内存, 然而怎样将一个 32 位的地址加载到一个寄存器中呢? 这时的情况又还原回了问题 1.

很庆幸我们有这样一条伪指令, 它也叫 LDR, 只是与 LDR 指令功能不同: 它可以用于加载任意 32 位立即数, 其操作数可以是 32 位立即数或变量标号.

  • 当操作数是立即数时, LDR 伪指令能够实现向寄存器直接加载 32 位立即数的功能, 如果这个立即数没有超出范围, 汇编器就将这条伪指令转换成一条 MOV 指令, 如果立即数超出了范围, 汇编器会自动地帮我们定义和加载数据, 这就解决了问题 1.
  • 问题 1 解决后, 问题 2 其实也就迎刃而解了: 由于变量标号代表的是变量的 32 位地址, 我们可以使用 LDR 伪指令把变量地址加载到寄存器中, 然后以这个寄存器作为索引去加载对应地址处的内存数据.
  • 另外, 当使用 LDR 伪指令时, 操作数前需要加上 "=" 以区分于 LDR 指令.

下面给出了一个具体例子来展示汇编器对 LDR 伪指令做了哪些事:

;源代码
         LDR    R0,    =data1 ;通过 LDR 伪指令加载 data1 地址
         LDR    R1,    [R0] ;通过 LDR 指令加载 data1
data1    DCD    0x11223344
;汇编得到的机器码
;地址 |  机器码  |   反汇编和注释
0x48: 0x4807     ;LDR R0, [PC,#0x40] 加载 0x68 地址处的数据 (data1 的地址) 到 R0
0x4A: 0x6807     ;LDR R1, [R0,#0] 加载 data1 到 R1
  ...
0x4C: 0x11223344 ;data1, 用户分配的变量
  ...
0x68: 0x0000004C ;data1 的地址, 汇编器自动分配的变量

伪指令的不同 "风格"
伪指令是 "不确定的", 不同的汇编器支持的伪指令一般是不同的, 常见的伪指令有 ADS 和 GNU 两种风格, 这两种风格的差别特别大. 如果你以后去从事 GNU 上的 ARM 开发, 千万切记不要对今天学到的东西 "生搬硬套" 哦!

搭建 Keil 环境

Keil 是一款用于 ARM 架构处理器开发的 SDK 软件, 支持灵活丰富的配置和调试等功能. 在本课程中, 我们主要使用 Keil 完成对软件工程的基础编译, 仿真和调试.

创建 Keil 工程

打开 Keil 软件, 点击左上角 Project 菜单, 选择 new uVision project.

image.png

Keil 主界面
选择在工程目录下 "/Task1/keil/" 文件夹下新建一个名为 code 的工程 (你也可以给工程取你喜欢的名字, 但是为了方便比对, 建议按照本书的要求创建和命名), 之后会弹出一个让你选择器件型号的窗口, 因为本课程的实验都是基于 Cortex-M0 这款处理器进行的, 所以这里选择 CMSDK_CM0.

image.png

器件型号选择界面
如何获取 Keil 的器件包?
Keil 的器件包都可以从官方网站上直接下载. 如果你的弹框处没有 CMSDK_CM0 这个器件选项, 可以点击这里进入官网下载, 下载后的文件直接打开即可选择对应目录安装.

添加文件

为新建的工程添加源代码文件, 右键 Source Group 1, 选择 "Add Existing Files to Group 'Source Group 1'", 将 "/Task1/keil/" 文件夹下的汇编文件 "startup_CMSDK_CM0.s" 添加进来.

image.png

添加源文件
Target 是什么? Source Group 又是什么?

  • 一个 Keil 工程可以包含多个 Target, 多个 Target 共享相同的源文件, 可以单独进行某些文件的屏蔽等操作. 设置多个 Target 有利于同一个工程中的代码版本分离. 同一时刻只能有一个 Target 处于激活状态, 这个 Target 就代表了当前的整个工程.
  • 将源文件分到多个 Source Group 中, 方便开发者根据需要对文件进行分组管理.
  • Target 和 Source Group 都可由开发者任意创建和命名.

配置存储器参数

右键 Target 1, 选择 "Options for Target 'Target 1'", 进入选项设置界面, 也可以通过点击 Keil 界面最后一行工具栏中的 "魔术棒" 工具进入该界面. 首先配置 Target 栏. 设置一块起始地址为 0x00000000, 大小为 0x10000 字节的片上 ROM 地址空间, 并设置一块片上起始地址为 0x20000000, 大小为 0x10000 字节的 RAM 地址空间.

image.png
Target 设置

为什么要设置 ROM 和 RAM?
这一步设置是为了匹配你的 SoC 硬件, 我们已经学过了 Cortex-M0 的存储映射方式, 知道 ARM 的存储器地址空间分为只读和可读可写两部分, 比如, 在 ARM 的经典产品 STM 系列 MCU 中, 前者以片上 NAND FLASH 的形式存在, 后者以片上 SRAM 的形式存在. 在我们将要实现的 SoC 中, 对这两部分的实现方式其实不用过于区分, 但是其功能本质确是需要严格划分的:

  • 前者用来存放代码和常量, 对应于 Target 设置中的 片上 ROM, 在 Cortex-M0 存储体系中被映射至 0x00000000 - 0x1FFFFFFF.
  • 后者用来存放程序变量, 对应于 Target 设置中的 片上 RAM, 在 Cortex-M0 存储体系中被映射至 0x20000000 - 0x3FFFFFFFF.

这里的 Size 其实可以任意分配, 只要满足程序运行的需要. 这样设置以后, 当你在程序中定义一个只读数据块时, 其存储地址就会从 0x00000000 向上生长.

修改 Output 路径

每个 Keil 工程都自动包含一个名为 Objects 的文件夹, 默认用来存放输出文件, 包括每个源文件编译或汇编得到的可重定位目标文件和最终链接生成的可执行文件等. 这里为了方便后续的一些操作, 我们将输出文件目录修改为工程根目录.
同样在 Options 界面中, 切换到 Output 栏的设置, 点击 "Select Folder for Objects", 将其修改为工程根目录.

image.png
Output 设置
image.png
将输出目录修改为工程根目录

可执行文件处理

一个 Target 经过编译链接最终会生成一个唯一的可执行文件, 在 Keil 的 ADS 编译器下, 这个可执行文件的格式为 .axf, 这个文件中就蕴含着所谓的程序的机器码信息.
由于我们之后需要实现软硬件的联合仿真, 也就是必须要把这些机器码导入到硬件工程中的内存里, 在之后的实验中我们将会知道, 硬件工程中的内存其实是一个 Verilog 编写的 RAM, 当使用 Modelsim 对硬件工程进行仿真时, 将机器码作为初始化文件对该 RAM 进行初始化, 就相当于我们在仿真层面上把编写的汇编程序 "下载" 进了硬件内存里. 由于 Modelsim 要求使用 hex 文件进行 RAM 的初始化, 所以这里我们需要把 .axf 文件的机器码信息输出到一个 .hex 文件中.
另外, 为了方便阅读分析 .axf 文件的内容, 我们再将其内容转换输出到一个新的 .txt 文件中. 通过在 Options 界面的 User 栏中添加两条 Keil 命令来完成上述操作:

fromelf -cvf .\code.axf --vhx --32x1 -o code.hex
fromelf -cvf .\code.axf -o code.txt

注意, 这里 .axf 文件是在工程根目录下的, 是因为我们上一步将输出目录修改到了根目录.

image.png
在 User 栏添加命令
关于这一步如果你还想了解更多

  • .axf 文件格式非常复杂, 类似于 Linux 中的 ELF 文件格式, 包含了代码, 数据, 符号表和调试信息等等, 这里我们只对代码和数据部分感兴趣, 采用 keil 的命令 "fromelf" 可以实现对 .axf 文件的转换, 第一条命令中的参数 --vhx 指明将其转换到只包含代码(机器码)和数据(变量存储)的 hex 文件中.
  • Options 界面的 User 栏根据执行时间将预设置命令分为三类: 编译前, 生成 可执行文件 前和生成 可执行文件 后, 由于我们要对 可执行文件 进行处理, 所以只能将命令设置在生成 可执行文件 后执行.

链接设置

在 Options 界面中的 Linker 栏中勾选 "Use Memory Layout from Target Dialog" 以及 "Don’t Search Standard Librarie" 两个选项.

image.png
Linker 设置

仿真/调试设置

打开 Options 界面中的 Debug 栏, 其中分为两大块设置: 仿真和调试, 调试工作需要在物理硬件和调试器的配合下进行, 考虑到我们接下来将要编写的程序没有涉及对专用外设的操作, 只涉及一些基础的内存访问和计算, 所以这里勾选 "Use Simulator", 指明通过软件仿真进行程序的测试.

image.png
设置运行在软件仿真模式

其他设置

在 Options 界面的 Utilities 栏中取消勾选 "Update Target before Debugging".

image.png
Utilities 设置
至此, 一个新的 Keil 环境就搭建好啦.

END

文章来源:

推荐内容

更多内容请关注微处理器系统结构与嵌入式系统设计专栏
推荐阅读
关注数
117
内容数
20
电子科技大学示范性微电子学院开设的「微处理器系统结构与嵌入式系统设计」课程配套实验,原链接:[链接]
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息