AVR 汇编:将一个 16 位数字加载到两个 8 位寄存器中

AVR assembly: load a 16 bit number into two 8 bit registers

我对 AVR 汇编完全陌生,我有一个 16 位的十六进制数 0x20C

我想在 AVR 程序集的两个 8 位寄存器中加载这个常量十六进制数

是否可以这样做:

LDI R17:R18 0x20C

编辑:如果无法通过这种方式在两个 8 位寄存器中加载 16 位数字,有人可以给我一个替代方案吗?

有一些指令可以对寄存器对进行 16 位加法或递增,但不能从内存加载,当然也不能立即执行。 你需要单独加载每个字节,使用一个ld/ldi/ldd/lds/每个字节的任何指令/每个目标寄存器。

(有一条指令可以将一对寄存器复制到另一对,在许多现代 AVR CPU 上都支持,(参见 ),但不是加载或立即加载。您可以为2 条指令,如 ReAl 所示,但它不会提高性能或代码大小,只会提高人类的可读性。


AVR 是一个 RISC 指令集,其中大部分(几乎所有?)整个指令都是 16 位的。没有空间容纳 16 位立即数,只有 8 个。您始终可以将一个立即数分成两个 8 位的一半,例如 ldi r17, 0x0c 用于低半,而 ldi r18, 0x2对于高半部分。

我检查了 AVR 指令集,没有看到任何多字节加载或立即数(https://onlinedocs.microchip.com/pr/GUID-0B644D8F-67E7-49E6-82C9-1B2B9ABE6A0D-en-US-1/index.html - 更新;在线 ISA ref 的格式远不如以前)。

然后我用 AVR gcc on the Godbolt compiler explorer 编译了这个 C,看看是否有我遗漏的东西。

int return_immediate(void) { return 0x20c; }

    ldi r24,lo8(524)    # ldi r24, 0x0c
    ldi r25,hi8(524)    # ldi r25, 0x02
    ret


int glob;
int return_global(void) {
    return glob;
}

    lds r24,glob
    lds r25,glob+1
    ret


int add(int *a, int *b) {
    return *a + *b;
}

    mov r30,r24
    mov r31,r25   Z = a
    ld r24,Z
    ldd r25,Z+1   retval = *a
    mov r30,r22
    mov r31,r23   Z = b
    ld r18,Z
    ldd r19,Z+1   tmp = *b
    add r24,r18
    adc r25,r19   retval += tmp
    ret

因此,除非 AVR gcc 有遗漏的优化,或者出于某种原因它避免了单词加载,否则您不能这样做。

更新:gcc 以基线 AVR 为目标,因此它不能使用 movw 复制一对寄存器。

使用 -mmcu=avr6 编译使 add(int*, int*) 更高效:

add:
    movw r30,r24   # Z = a
    ld r24,Z
    ldd r25,Z+1
    movw r30,r22   # Z = b
    ld r18,Z
    ldd r19,Z+1
    add r24,r18
    adc r25,r19
    ret

但正如我们所见,仍然没有对一对寄存器执行任何其他操作的指令,因此所有这些都必须单独完成。尽管如此,用一条机器指令复制一对寄存器还是很不错的,因为需要这样做的情况并不少见。

正如Peter Cordes answer中提到的,AVR指令集没有将16位值加载到寄存器对中的指令。但是……

if it is not possible to load a 16 bit number in two 8 bit registers this way, may someone could give me an alternative option?

为方便起见,汇编器通常具有创建宏的能力(如果您打算在汇编中进行大量编程,请注意这个机会)。

宏语法依赖于工具链。对于 gnu as 我使用以下宏将 16 位立即值加载到给定和下一个寄存器

.macro ldi_w reg:req, val:req
    ldi \reg, lo8(\val)
    ldi \reg + 1, hi8(\val)
.endm

所以

ldi_w   r16, 0xBEEF

0xEF 加载到 r16 并将 0xBE 加载到 r17

重要说明: gnu as 允许使用寄存器编号 (ldi 16, 0xC3) 以及寄存器名称 (ldi r16, 0xC3)。上面的宏需要寄存器 numbers,这样我所有的汇编程序源代码都包含 avr_reg_numbers.h,其中包含

之类的定义
#ifdef r0
#undef r0
#endif
#define r0 0

使用此样式宏,Peter Cordes answr 的示例可以重写为

mov_w r30, r24 ; r30 ← r24, r31 ← r25
ld_w  r24, Z
mov_w r30, r22
ld_w  r18, Z
add_w r24, r18

p.s.0 我的 avr_as_macro.h 包含很多宏,例如 lds_wsts_wpush_w、…、ror_w 和很快。它们中的大多数由其他宏汇编程序命令生成。例如,由

生成的asr_wlsr_wror_w
//----------------------------    right word shifts
// asr_w, lsr_w, ror_w
.irp cmd, asr, lsr, ror
    .macro \cmd\()_w reg:req
        \cmd \reg+1  $  ror \reg
    .endm
.endr

p.s.1 一些 AVR 芯片有汇编指令 movw 在一个 cpu 周期内移动寄存器对,其中一些(旧的或非常简单的)没有这样的命令.因此,我使用调用 movw 的宏 mov_w 或生成一对命令,具体取决于当前的 AVR arch 级别。

//----------------------------------    word move
// movw exist in all new AVRs except __AVR_TINY__ (tiny4..10/20/40)
// Not exist in tiny26
// odd register numbers and overlapping are not supported
#ifdef __AVR_HAVE_MOVW__
.macro  mov_w   dst:req, src:req
    movw    \dst, \src
.endm
#else
.macro  mov_w   dst:req, src:req
    mov \dst, \src  $  mov \dst+1, \src+1
.endm
#endif