Day6 — 组合语言浅谈

对於组合语言,最大的迷思在於:「有了编译器,为什麽我还要学组合语言?」,关於学习组合语言的好处我认为可以分成几点讨论。

1. 知道处理器(MCU)是如何运作的

由於编译器会产生一些 bootstrap 程序码,所以光看编译器编译後的程序码,可能会比想像中的数量还多。为了深入底层,你可能还需要知道编译器对於特定片段的程序码是如何编译的。

2. 简单直觉

一旦了解组合语言的概念,其实它并没有想像中那麽难理解。因为我们是直接撰写组合语言,所以也不会有编译器转换时的隔阂。你也不需要再去学习各种语言特性,组合语言运作的方式不会因为 chip 不同而有变化。

学习组合语言不代表你必须要精通它,或是从此以後只能用组合语言撰写,回头看看人类发展高阶语言的原因,正是因为组合语言的表达能力有限,繁琐的操作也会造成开发人员的心智负担,不过知道组合语言是如何运作的,能够帮助你在使用高阶语言撰写程序时,对实际上电脑会如何运作有更深的了解。

因此,如果你真的想更了解 AVR 的架构,认识组合语言就是必经的一段过程。


语法

组合语言通常会由三个部分组成:操作、运算数 1(Operand)、运算数 2。举例来说,在 AVR 当中若要将数字载入至暂存器当中可以这样写:

ldi r16, 00000001

在这段程序码中,ldi 代表操作,在这里指得是 load immediately,r16 为暂存器,00000001 则为数字。我们通常会将操作称为指令(instruction),在 AVR 当中大部分的 instruction 都是一个 cycle。

在这边要特别注意的是,并不是每个指令都能够像这样,後面直接接一个常数,而是要先将数字载入到暂存器之後,再继续接下来的操作。

例如我希望计算 1 + 1 後将结果放入暂存器中,这样写是不对的:

add r16, 1, 1

而是需要先将 1 载入到暂存器中,再进行加法:

ldi r14, 1
ldi r15, 1
add r15, r14

定址模式(addressing mode)

在 AVR 当中有许多定址模式,最主要的目的在於简化在记忆体(RAM)与暂存器之间的资料交换与操作。

1. Register Direct:single register

一个指令搭配一个暂存器,例如 inc 指令

inc r16

将暂存器 r16 增加 1。在 AVR 组合语言当中,通用暂存器会以 r 当作前缀加上数字表示,assembler 会对照 AVR 的架构将这些暂存器映射到对应的位址(Register File)。在 AVR 当中有 32 个暂存器,所以 r0 ~ r31 都是可使用的暂存器。

2. Register Direct: 2 registers

可使用两个暂存器,如 add 指令

add r16, r17
mov r0, r1

3. I/O Direct

可以直接在暂存器与 I/O Port 间传输资料,最常见的就是 inout 指令:

in r16, PINB ; 将 PINB 的资料传给暂存器 r16 
out PORTC, r16 ; 将 r16 的资料传给 PORTC

在这边可以发现 PINB 以及 PORTC 之类看起来很像变数的东西,这是由 assembler 事先定义的变数。在 AVR 当中除了通用暂存器(general purpose register)这类可以让我们直接操作的暂存器之外,还有控制各种功能、参数的暂存器可以使用。

https://ithelp.ithome.com.tw/upload/images/20211002/20103565tMNFcLnjw7.png

在 AVR 当中暂存器可以透过特定的位址存取,举例来说 PORTB 这个暂存器就存在於 0x18 这个记忆体位址当中。不过直接撰写记忆体位址撰写时比较麻烦,因此 assembler 通常会事先定义好这些暂存器的位址与名称,在撰写时就不需要查表写记忆体位址了。

4. Data Direct

将资料写到 data space 当中。在 AVR 当中 data space 包含 Register file、I/O memory、SRAM。例如 sts 指令

sts 0x1000, r16

将 r16 的资料写入到 data space 的位址 0x1000

AVR 采用的是哈佛架构(Harvard Architecture),在哈佛架构当中,程序指令和资料储存会分别存放在不同的记忆体空间。目前使用哈佛架构的微控制器与中央处理器的晶片有 AVR、ARM9、ARM10、ARM11。

因此我们将资料储存的地方称为 data space,储存指令的地方称为 program memory space。将程序指令与资料储存空间分开存放最大的好处在於,当我们在执行指令时,就可以预先读取下一条指令,进而提高效能。

5. Data Indirect

间接定址。在 AVR 当中有三个比较特别的暂存器称为 X, Y, Z 暂存器。我们在前面有讲到,AVR 具有 32 个通用暂存器,分别从 r0 ~ r31,其中 r26 ~ r31 在特定指令中会具有定址功能,而当这些暂存器当作定址功能使用时就称为 X、Y、Z 暂存器。

LD r16, Y

这个指令的意思是以 Y 的值当作记忆体位址,找出此记忆体位址储存的值,再将值放入 r16 暂存器当中。因为不是直接将值放入暂存器中,而是先找记忆体位址再去找值,因此才有 indriect 之称。从这个指令多少就能感受出指标在组合语言的表达是什麽,其实就是 indirect 的对应。

除此之外 AVR 还有提供执行後增、减的功能。

LD r16, Y+

当执行完这条指令後,将 Y 暂存器的值 +1,这样在存取连续的记忆体空间时很方便。

ST Y+, r28

这条指令则是将暂存器 r28 的内容存入到以 Y 暂存器的值为位址的记忆体。

6. Data Indriect with displacement

可以在 X、Y、Z 当中自行加入常数的 offset

LDD r16, Y+0x10

将 Y 的值加上 0x10 後当作记忆体位址去寻找对应的值。

7. Program memory addressing

可以使用 Z 暂存器当作记忆体位址存取到 program memory。

LPM

8. Indriect program addressing

使用 ijump 或是 icall 时可根据 z-register 的值改写 PC 的位址。

PC 指得是 program counter,在 MCU 当中会不断从 flash memory 当中获取下一条指令解码後执行,为了得知目前执行到哪一条指令,通常会使用 program counter 来储存,每次执行一条指令时就将 PC+1。PC 可透过 ijump、icall 修改,让程序跳到指定的位置後执行,进而实现像是回圈、条件式判断、函数呼叫等功能。

9. Relative Program Addressing

以 ijump 与 icall 都是直接以记忆体位址改动 PC 值,也可以透过 offset 的方式来改变。

rjmp r16
rcall r16

总结

AVR 的指令集虽然不多,但算下来也有 100 多条,文章里无法一一介绍,不过我会在接下来介绍到特定功能时,一并介绍对应的指令。


<<:  [Day-23] 小练习二进制转十进制

>>:  设定档格式INI + Service的管理工具Systemd简介

[Day 14] Sass - Lists

在Day10的文章中有提到,Lists是指一个变数的值由多个值组成,这些值可以用底下的几种方式隔开 ...

威胁建模-DREAD

-Stride、VAST、Trike 等:哪种威胁建模方法适合您的组织? 风险敞口是根据可能性、後...

[Day02 - 规划与设计] 从生活中发想需求

发想自己的需求,看看生活中有什麽是想要改进的或是解决的,再来立定一个主题吧! 目前比较烦恼的大概是:...

[day16]机器人对话纪录

以前遇到一个情况阿,使用者输入,我要ㄧ个汉堡,二杯奶茶,到後台却变成,我要\xe3\x84\xa7个...

[Day28]约束规则、更改结构实作

前几篇的实作都是从HR或OE帐户中查询,这篇的实作帐户使用自己新建的Hotel帐户命名、新增并查询。...