Сборка AVR: запись в массивы, ничего не возвращая - PullRequest
0 голосов
/ 26 апреля 2019

Мне нужно написать программу в сборке AVR, которая получает указатель на целочисленный массив при вызове программой C и выполняет операции над ее элементами без фактического вывода значений. Для простоты, скажем, я хочу, чтобы моя программа удваивала значение каждого элемента - так, чтобы, учитывая массив {2, 4, 6, 8}, вызов метода print в C отдельно от того, что я написал, напечатает {4, 8, 12, 16}.

Моя проблема в том, что я не понимаю, как изменить значения элементов массива, и чтобы эти изменения сохранялись после завершения выполнения функции. Я не могу ничего вернуть через регистр r24, потому что мне нужно вернуть другой номер для другой цели.

Моя идея заключалась в том, что поскольку вход в регистр r24 входит в качестве указателя на первый элемент массива, я бы mov r26 r24 связал (?) Массив с указателем X, а затем ld это в другой регистр, чтобы я мог использовать указатель X для увеличения массива, как в ld r18, X+.

И хотя у меня есть небольшие проблемы с навигацией по массиву, я не понимаю, как придать моим изменениям постоянство, если это имеет смысл. У меня сложилось впечатление, что я должен использовать st и / или sts для решения этой проблемы, но я изо всех сил пытаюсь понять, как они работают. Моя попытка состояла в том, чтобы зарезервировать указатель, такой как Z, для связывания с входным массивом, и каждый раз, когда у меня было готовое значение для замены старого элемента в массиве, я писал бы st Z+, rXX, помещая значение в индекс Z и впоследствии указывая к следующему указателю. Это не сработало, поэтому у меня возникает вопрос: что мне нужно сделать, чтобы связать память моих локальных регистров с памятью входов, предоставленных программе?

1 Ответ

2 голосов
/ 26 апреля 2019

Сначала я призываю вас прочитать Замечание по применению 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 и т. Д.

...