我必须在AVR程序集中编写一个程序,该程序在被C程序调用时接收指向整数数组的指针,并对其元素执行操作而不实际输出值。为了简单起见,让我们说我希望我的程序将每个元素的值加倍 - 这样给定数组{2, 4, 6, 8}
,在C中调用打印方法与我编写的单独打印将打印{4, 8, 12, 16}
。
我的问题是我不明白如何更改数组元素的值,并在执行完该函数的函数后保持这些更改。我无法通过寄存器r24
返回任何内容,因为我需要为不同的目的返回不同的数字。
我的想法是,因为寄存器r24
上的输入作为指向数组的第一个元素的指针,我会mov r26 r24
,将数组与X指针相关联,然后将ld
关联到另一个寄存器以便我可以使用X指针在数组中递增,如ld r18, X+
。
虽然我在导航阵列时遇到一些麻烦,但我不明白如果有意义的话,如何让我的变化永久化。我的印象是我希望使用st
和/或sts
解决这个问题,但我很难理解它们是如何工作的。我的尝试是保留一个像Z一样的指针与输入数组相关联,每当我有一个值准备替换数组中的旧元素时,我会写st Z+, rXX
,将值放在索引Z处,然后指向下一个指数。这不起作用,所以我想知道:我需要做什么才能将本地寄存器的内存与提供给程序的输入的内存相关联?
首先,我建议你阅读Application Note AT1886: Mixing Assembly and C with AVRGCC(pdf文档)它描述了如何在被调用的例程中传递参数和返回值。
要使汇编代码可以从C调用,您必须为汇编函数编写声明存根。你可以把它放在.h
文件中。让它成为具有一个指针类型参数且没有返回值的函数。
extern void my_function(void *);
关键字extern
将告诉链接器函数体是在其他地方,而不是在这个.c文件中
现在,您可以添加程序集文件,在项目中创建新的.s
文件。最重要的是你可以:
#define _SFR_ASM_COMPAT 1
#define __SFR_OFFSET 0
#include <avr/io.h>
这些声明将使您能够使用in
/ out
/ cbi
/ sbi
等指令访问较低的IO寄存器。
现在你应该声明一个与函数名称相同的标签,并声明它为.extern
.extern my_function
my_function:
// assembly for the function body is here
如appnote中所述,第一个参数放在r25:r24,(第二个,如果有的话,在r23:r22,第三个在r21:r20,第四个在r19:r18)。如果您甚至有1个字节的参数,它都会使用两个寄存器,r24将存储其值,而r25将保持未使用状态。第二个参数将在r23:r22等。如果你有4字节值(例如long int
),那么它将使用两个后续参数位置,即它的值将存储在r23:r22:r25:r24
如果您的代码使用寄存器r2-r17,也就是r28或r29(Y寄存器),则应在返回之前保留并恢复其先前的值。此外,建议保留r0(参见appnote中的表5-1,但考虑到印刷错误:r0
位于底线第二位,r31以上,应读作r30
)
寄存器r1总是包含0,如果你以某种方式更改它的值(例如,调用MUL
指令),那么你必须在返回之前将其清除。
因此,考虑到我们的示例,假设您有一些C代码,它会调用汇编程序例程:
uint8_t my_array[10]; // declare an array
my_function(&my_array); // call the routine, passing pointer to the array
然后调用你的函数,第一个参数(寄存器r25:r24)将包含一个指向数组的指针。因此,汇编代码可以将其带入任何指针寄存器并执行您喜欢的任何操作。例如
.extern my_function
my_function:
movw X, r24 // copy r25:r24 into X (r27:r26)
ldi r18, 10
st X+, r18 // store 10 into first element of the array
ldi r18, 20
st X+, r18 // store 20 into second element of the array
... etc
ret // return
现在,当上面的例子中调用函数时,my_array[0]
将包含10,my_array[1]
== 20等。