Сначала я призываю вас прочитать Замечание по применению AT1886: Смешивание ассемблера и C с 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
и т. Д.
Теперь вы должны объявить метку, которая будет совпадать с именем функции, и объявить ее .extern
.extern my_function
my_function:
// assembly for the function body is here
Как сказано в приложении, первый параметр помещается в 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 (см. Таблицу 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 и т. Д.