У меня возникли проблемы с созданием и адресацией массива, созданного исключительно на сборке, с использованием набора инструкций для Atmel ATMega8535.
На данный момент я понимаю следующее:
- Массив содержит непрерывные данные одинаковой длины.
- Создание массива включает определение начального и конечного местоположений массива (так же, как и стек).
- Вы можете адресовать индекс в массиве, добавляя смещение базового адреса массива.
То, что я хочу конкретно сделать, - это создать одномерный массив 8-битных целых чисел с предопределенными значениями, заполняющими его во время инициализации, в него не нужно записывать, а адресоваться только при необходимости. В конечном итоге проблема заключалась в невозможности перевести логику в ассемблерный код.
Я попытался сделать это с небольшим успехом, используя следующие книги:
- Требуется некоторая сборка: программирование на языке ассемблера с микроконтроллером AVR Тимоти С. Маргуш
- Начните с ... микроконтроллеров AVR Питера Шарпа
Мы будем благодарны за любую помощь, совет или дополнительные ресурсы.
2 ответа
Если ваш массив доступен только для чтения, вам не нужно копировать его в ОЗУ. Вы можете сохранить его во Flash и при необходимости читать оттуда. Это сэкономит драгоценную оперативную память за счет более медленного доступа (чтение из оперативной памяти - 2 цикла, чтение из флэш-памяти - 3 цикла).
Вы можете объявить свой массив следующим образом:
.global my_array
.type my_array, @object
my_array:
.byte 12, 34, 56, 78
Затем, чтобы прочитать член массива, вы должны вычислить:
adress of member = array base address + member index
Если бы ваши члены были более одного байта, вам также пришлось бы умножить индекс по размеру, но здесь это не так. Затем вы помещаете адрес необходимого члена в регистре Z и выдать lpm
инструкция. Вот функция, реализующая эту логику:
.global read_data
; input: r24 = array index, r1 = 0
; output: r24 = array value
; clobbers: r30, r31
read_data:
ldi r30, lo8(my_array) ; load Z = address of my_array
ldi r31, hi8(my_array) ; ...high byte also
add r30, r24 ; add the array index
adc r31, r1 ; ...and add 0 to propagate the carry
lpm r24, Z
ret
@scottt посоветовал вам сначала написать на C, а затем посмотреть сгенерированную сборку. Считаю это очень хорошим советом, давайте ему следовать:
#include <stdint.h>
__flash const uint8_t my_array[] = {12, 34, 56, 78};
uint8_t read_data(uint8_t index)
{
return my_array[index];
}
Ключевое слово __flash
, определяющее «именованное адресное пространство», является встроенным Расширение C поддерживается gcc. В сгенерированная сборка немного отличается от предыдущей: вместо этого вычисления base_address + index
, gcc выполняет index − (−base_address)
:
read_data:
mov r30, r24 ; load Z = array index
ldi r31, 0 ; ...high byte of index is 0
subi r30, lo8(-(my_array)) ; subtract -(address of my array)
sbci r31, hi8(-(my_array)) ; ...high byte also
lpm r24, Z
ret
Это так же эффективно, как и предыдущая ручная сборка, за исключением того, что не требуется, чтобы регистр r1 был инициализирован нулем. Но сохранение r1 равным нулю в любом случае является частью ABI gcc, поэтому это не должно иметь никакого значения.
Роль линкера
Этот раздел предназначен для ответа на вопрос в комментарии: как мы можем получить доступ к массиву, если мы не знаем его адрес? Ответ: мы обращаемся к нему по имени, как в приведенных выше фрагментах кода. Выбор окончательного адреса для массива, а также замена имени на соответствующий адрес - это работа компоновщика.
Сборка (с avr-gcc -c
) и разборка (с avr-objdump -d
) первый фрагмент кода дает следующее:
my_array.o, section .text:
00000000 <my_array>:
0: 0c 22 38 4e ."8N
Если бы мы компилировали из C, gcc поместил бы массив в раздел .progmem.data вместо .text, но это не имеет большого значения. Числа «0c 22 38 4e» - это содержимое массива в шестнадцатеричном формате. Символы справа являются эквивалентами ASCII, «.» - это символы-заполнители для непечатаемых символов.
Объектный файл также содержит эту таблицу символов, показанную avr-nm
:
my_array.o:
00000000 T my_array
Означает, что символ «my_array» был определен как относящийся к смещению 0 в секции .text (подразумеваемой «T») этого объекта.
Сборка и разборка второго фрагмента кода дает следующее:
read_data.o, section .text:
00000000 <read_data>:
0: e0 e0 ldi r30, 0x00
2: f0 e0 ldi r31, 0x00
4: e8 0f add r30, r24
6: f1 1d adc r31, r1
8: 84 91 lpm r24, Z
a: 08 95 ret
Сравнивая разборку с реальным исходным кодом, можно увидеть что ассемблер заменил адрес my_array на 0x00, что почти гарантированно ошибаюсь. Но он также оставил записку компоновщику в форма «записей о перемещении», показанная avr-objdump -r
:
read_data.o, RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000000 R_AVR_LO8_LDI my_array
00000002 R_AVR_HI8_LDI my_array
Это сообщает компоновщику, что инструкции ldi
со смещениями 0x00 и 0x02 предназначены для загрузки младшего и старшего байтов (соответственно) окончательного адреса my_array. Объектный файл также содержит это таблица символов:
read_data.o:
U my_array
00000000 T read_data
Где строка «U» означает, что в файле используется неопределенный символ с именем «my_array».
Связывание этих частей вместе с подходящей функцией main () дает двоичный файл, содержащий среду выполнения C из avr-lbc вместе с нашим кодом:
0000003c <my_array>:
3c: 0c 22 38 4e ."8N
00000040 <read_data>:
40: ec e3 ldi r30, 0x3C
42: f0 e0 ldi r31, 0x00
44: e8 0f add r30, r24
46: f1 1d adc r31, r1
48: 84 91 lpm r24, Z
4a: 08 95 ret
Следует отметить, что не только компоновщик перемещал части к их конечным адресам, он также исправил аргументы ldi
инструкции, чтобы теперь они указывали на правильный адрес my_array.
Код должен выглядеть примерно так:
.section .text
.global main
main:
ldi r30,lo8(data)
ldi r31,hi8(data)
ldd r24,Z+3
sts output,r24
ld r24,Z
sts output,r24
ldi r24,0
ldi r25,0
ret
.global data
.data
data:
.byte 1, 2, 3, 4
.comm output,1,1
Объяснение
Для людей, которые раньше программировали на ассемблере с использованием инструментария GNU, есть уроки, которые можно перенести даже на незнакомые наборы инструкций:
- Вы резервируете место для массива с помощью директив ассемблера
.byte 1, 2, 3, 4
,.word 1, 2
(.word
- это 16 бит для AVR) или.space 100
. - При изучении нового набора команд напишите программы на C и попросите компилятор C сгенерировать вывод ассемблера. По мере чтения кода ассемблера найдите хороший справочник по программированию на ассемблере для набора команд.
Применяем этот трюк ниже.
Байт-массив.c
/* volatile our code doesn't get optimized out even when compiler optimization is on */
volatile char output;
char data[] = { 1, 2, 3, 4 };
int main(void)
{
output = data[3];
output = data[0];
return 0;
}
Создать ассемблер из C
avr-gcc -mmcu=atmega8 -Wall -Os -S byte-array.c
Это сгенерирует файл ассемблера byte-array.s
.
Byte-array.s
.file "byte-array.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
.section .text.startup,"ax",@progbits
.global main
.type main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
ldi r30,lo8(data)
ldi r31,hi8(data)
ldd r24,Z+3
sts output,r24
ld r24,Z
sts output,r24
ldi r24,0
ldi r25,0
ret
.size main, .-main
.global data
.data
.type data, @object
.size data, 4
data:
.byte 1
.byte 2
.byte 3
.byte 4
.comm output,1,1
.ident "GCC: (Fedora 4.9.2-1.fc21) 4.9.2"
.global __do_copy_data
.global __do_clear_bss
Прочтите это объяснение регистров указателя, чтобы узнать, как набор инструкций AVR использует регистровую пару r30
, r31
как регистр указателя Z
. Прочтите инструкции ld
, st
, ldi
, ldd
, sts
и std
.
Замечания по реализации
Если связать программу то разобрать ее:
avr-gcc -mmcu=atmega8 -Os byte-array.c -o byte-array.elf
avr-objdump -d byte-array.elf
00000000 <__vectors>:
0: 12 c0 rjmp .+36 ; 0x26 <__ctors_end>
2: 2c c0 rjmp .+88 ; 0x5c <__bad_interrupt>
4: 2b c0 rjmp .+86 ; 0x5c <__bad_interrupt>
6: 2a c0 rjmp .+84 ; 0x5c <__bad_interrupt>
8: 29 c0 rjmp .+82 ; 0x5c <__bad_interrupt>
a: 28 c0 rjmp .+80 ; 0x5c <__bad_interrupt>
c: 27 c0 rjmp .+78 ; 0x5c <__bad_interrupt>
e: 26 c0 rjmp .+76 ; 0x5c <__bad_interrupt>
10: 25 c0 rjmp .+74 ; 0x5c <__bad_interrupt>
12: 24 c0 rjmp .+72 ; 0x5c <__bad_interrupt>
14: 23 c0 rjmp .+70 ; 0x5c <__bad_interrupt>
16: 22 c0 rjmp .+68 ; 0x5c <__bad_interrupt>
18: 21 c0 rjmp .+66 ; 0x5c <__bad_interrupt>
1a: 20 c0 rjmp .+64 ; 0x5c <__bad_interrupt>
1c: 1f c0 rjmp .+62 ; 0x5c <__bad_interrupt>
1e: 1e c0 rjmp .+60 ; 0x5c <__bad_interrupt>
20: 1d c0 rjmp .+58 ; 0x5c <__bad_interrupt>
22: 1c c0 rjmp .+56 ; 0x5c <__bad_interrupt>
24: 1b c0 rjmp .+54 ; 0x5c <__bad_interrupt>
00000026 <__ctors_end>:
26: 11 24 eor r1, r1
28: 1f be out 0x3f, r1 ; 63
2a: cf e5 ldi r28, 0x5F ; 95
2c: d4 e0 ldi r29, 0x04 ; 4
2e: de bf out 0x3e, r29 ; 62
30: cd bf out 0x3d, r28 ; 61
00000032 <__do_copy_data>:
32: 10 e0 ldi r17, 0x00 ; 0
34: a0 e6 ldi r26, 0x60 ; 96
36: b0 e0 ldi r27, 0x00 ; 0
38: e4 e8 ldi r30, 0x84 ; 132
3a: f0 e0 ldi r31, 0x00 ; 0
3c: 02 c0 rjmp .+4 ; 0x42 <__SREG__+0x3>
3e: 05 90 lpm r0, Z+
40: 0d 92 st X+, r0
42: ac 36 cpi r26, 0x6C ; 108
44: b1 07 cpc r27, r17
46: d9 f7 brne .-10 ; 0x3e <__SP_H__>
00000048 <__do_clear_bss>:
48: 10 e0 ldi r17, 0x00 ; 0
4a: ac e6 ldi r26, 0x6C ; 108
4c: b0 e0 ldi r27, 0x00 ; 0
4e: 01 c0 rjmp .+2 ; 0x52 <.do_clear_bss_start>
00000050 <.do_clear_bss_loop>:
50: 1d 92 st X+, r1
00000052 <.do_clear_bss_start>:
52: ad 36 cpi r26, 0x6D ; 109
54: b1 07 cpc r27, r17
56: e1 f7 brne .-8 ; 0x50 <.do_clear_bss_loop>
58: 02 d0 rcall .+4 ; 0x5e <main>
5a: 12 c0 rjmp .+36 ; 0x80 <_exit>
0000005c <__bad_interrupt>:
5c: d1 cf rjmp .-94 ; 0x0 <__vectors>
0000005e <main>: ...
00000080 <_exit>:
80: f8 94 cli
00000082 <__stop_program>:
82: ff cf rjmp .-2 ; 0x82 <__stop_program>
Вы можете видеть, что avr-gcc
автоматически генерирует код запуска , включая:
- вектор прерывания (
__vectors
), который используетrjmp
для перехода к подпрограммам обслуживания прерываний. - инициализировать регистр состояния
SREG
и указатель стекаSPL/SPH
(__ctors_end
) - копирует содержимое сегмента данных из FLASH в RAM для инициализированных глобальных переменных с возможностью записи (
__do_copy_data
) - очищает сегмент BSS для неинициализированных доступных для записи глобальных переменных (
__do_clear_bss
и т. д.) - вызывает нашу функцию
main()
- вызывает
_exit()
, еслиmain()
когда-либо возвращается _exit()
- это простоcli
для отключения прерываний- и бесконечный цикл (
__stop_program
)
Похожие вопросы
Новые вопросы
arrays
Массив - это упорядоченная линейная структура данных, состоящая из набора элементов (значений, переменных или ссылок), каждый из которых идентифицируется одним или несколькими индексами. Когда вы спрашиваете о конкретных вариантах массивов, используйте вместо них следующие связанные теги: [vector], [arraylist], [matrix]. При использовании этого тега в вопросе, относящемся к языку программирования, пометьте вопрос используемым языком программирования.