А у нас в проектах GAS. А у вас?

Рейтинг пользователей: / 7
ХудшийЛучший 
Статьи - Программирование

gas

Лучше 3 дня потерять, зато потом за пол-часа долететь!
Из мультфильма «Крылья, ноги, хвост...»

GAS – это не полезное ископаемое, а GNU ASSEMBLER, что означает свободно распространяемый ассемблер. Ну а проекты наши, как обычно, это проекты для микроконтроллеров AVR, поэтому речь пойдет о версии GAS для AVR, то есть AVR-AS.

AVR-AS входит в комплект WinAVR, именно он в конечном итоге транслирует результат работы компилятора Си в объектные файлы, хотя для конечного пользователя этот процесс и остается незаметным в большинстве случаев. Если в проекте имеются ассемблерные модули – это так же работа для AVR-AS, однако, никто и ничто не препятствует использовать его для целиком ассемблерных проектов.

Любители писать программы для AVR на ассемблере вынуждены работать по сути с безальтернативным ассемблером, поставляемым фирмой Atmel вместе с IDE AVR Studio. Сама студия – весьма удобный продукт, а вот ассемблер... А вот ассемблер подкачал. Даже его «продвинутая» версия под номером 2 имеет весьма убогие возможности, что не делает жизнь программиста проще. Использование же AVR-AS для разработки программ позволит во многих случаях сделать труд программиста более комфортным.

Ассемблер – это низкоуровневый язык, по существу предоставляющий всего лишь некое удобное средство записи машинных кодов. Такое мнение бытует у многих программистов, но оно верно лишь отчасти. Хороший ассемблер может иметь в своем арсенале средства, при помощи которых процесс разработки ассемблерных программ приближается к работе программиста Си. Например, таким ассемблером можно считать MASM или TASM для 80x86 процессоров – они позволяют использовать при программировании даже объектно-ориентированные подходы! Но для AVR все это по большей части лишнее, но все-таки немного комфорта не помешает.

А основной источник комфорта для ассемблерного программиста – это макросы. Те, кто привык к макросам из avrassembler от Atmel, скажут: «Подумаешь, макрос! Это всего лишь автозамена нескольких команд одной!». Увы, в том ассемблере это действительно так, но в рассматриваемом AVR-AS макрос – это очень мощное средство! Подчас настолько мощное, что программисту без опыта работы с ним все его «навороты» кажутся совершенно бесполезными, так как совершенно непонятны. Макроязык в AVR-AS – это уже почти язык высокого уровня, только используется для составления инструкций не микроконтроллеру, а компилятору. То есть эти макросы по сути позволяют создавать программу по ходу ее компиляции!

Рассмотрим простой пример: допустим, нам требуется иметь массив структур, состоящих из трех байтов, причем он должен быть проинициализирован определенными повторяющимися значениями. На Си это будет выглядеть примерно так:

unsigned char array[][] = {
{1,2,3}, {1,2,3}, {1,2,3}
};

Способ записи самый обычный, но только представьте себе, как бы программист поступил, если бы ему потребовалось таким образом проинициализировать массив из 200 элементов?! Даже для Си с его препроцессором описать такой массив в программе очень непросто, и чаще всего сводится к рутинному ручному вводу кучи одинаковых значений. Одно радует – редакторы позволяют сделать это довольно просто, но вид программы от этого лучше не становится.

А теперь посмотрите, как описание такого массива из 200 (!!!) повторяющихся структур будет выглядеть в программе для AVR-AS:

array:
.rept	200
.byte	1,2,3
.endr

Как видите, это даже проще, чем на Си. А все благодаря макро-оператору (или псевдооператору) rept, который заставляет компилятор повторить свое содержимое указанное число раз – в нашем случае 200. В итоге «в руки» компилятору попадет такой исходник:

array:
.byte	1,2,3
.byte	1,2,3
... и так еще 198 раз

Получается, компилятор сам построил программу за программиста, руководствуясь заданием – разве это не напоминает работу с высокоуровневыми языками программирования?!

Псевдооператор rept – очень прост, но почему-то его нет и не было в Atmel-овском ассемблере... Однако, возможности AVR-AS не сводятся только к этому! В нем имеется большое количество возможностей и псевдооператоров, при помощи которых можно строить собственные чрезвычайно гибкие макросы.

Кратко рассмотрим некоторые из этих возможностей.

Макросы в привычном смысле

Обычный макрос определяется ключевым словом .macro и завершается ключевым словом .endm – тут все так же, как и в avrassembler. А вот теперь разница:

  • макрос может иметь именованные параметры, перечисляемые после имени макроса, причем внутри макроса с этими параметрами можно работать как с символами или из значением;
  • для параметров макроса можно задать некоторые атрибуты, например, указать, что какой-то параметр обязательно должен присутствовать, а какой-то или какие-то являются необязательными (аналог многоточия в параметрах сишной функции);
  • работу макроса можно завершить досрочно при помощи псевдооператора .exitm (аналог break в Си).

Например:

.macro pushwc	data, reg
// макрос занесения в стек константного значения с использованием вспомогательного регистра
ldi	reg, lo8(\data)
push	reg
ldi	reg, hi8(\data)
push	reg
.endm

Этот макрос называется pushwc и имеет 2 параметра: data и reg, причем первый параметр – это число, а второй – вспомогательный регистр. Макрос загружает число data в стек, начиная с младшего байта. Обратите внимание на то, что перед указанием имени параметра ставится косая черта – это признак того, что берется ЗНАЧЕНИЕ параметра. Если черту убрать, то будет использован СИМВОЛ параметра.

Вот макрос с параметрами, имеющими значение по умолчанию:

.macro pushwc	data, reg=r18
.endm

Для упрощения содержимое макроса не показано. В этом макросе мы задаем наличие двух параметров, причем второй имеет значение по умолчанию, равное r18. Теперь мы можем использовать в программе макрос так:

pushwc	1234,

и тогда параметр reg будет использовать значение r18, но можем и указать иное значение второго параметра:

pushwc	1234, r20

Можно потребовать, чтобы часть параметров были обязательными, а часть – по усмотрению программиста:

.macro pushwc	data:req, reg

Такое описание указывает, что параметр data всегда должен быть задан, в то время как о параметре reg ничего не говорится, т.е. он может и отсутствовать.

Для макросов с произвольным числом параметров нужно использовать такую форму:

.macro pushwc	arg:vararg

В данном случае атрибут vararg для параметра arg обозначает, что этот параметр содержит в себе все параметры до конца строки. Разумеется, этот параметр может быть либо единственным, либо последним среди прочих:

.macro pushwc	n, reg, arg:vararg // правильно
.macro pushwc	n, arg:vararg, reg // не правильно

О том, как использовать произвольное число параметров макроса, будет рассказано позже, после рассмотрения итераторов.

Итераторы

Итератор – это псевдооператор ассемблера, позволяющий выполнить некую последовательнсоть действий определенное число раз. Итераторы могут быть простыми или итераторами с перебором параметров. Завершается итератор ключевым словом .endr, а начинается с ключевого слова, определяющего его тип.

Простой итератор .rept

Записиывается так:

.rept	N
// повторяемые действия
.endr

Означает, что повторяемые действия надо просто повторить N раз. В качестве этих действий могут быть любые операторы или псевдооператоры ассемблера, а так же макросы. Пример использования этого итератора мы уже рассмотрели ранее.

Итератор с перебором параметров .irp

Записывается так:

.irp	var, <список аргументов>
// повторяемые действия
.endr

Этот макрос берет первый аргумент из списка и присваивает его значение параметру var, выполняет повторяемые действия, затем берет следующий аргумент из списка и так далее, пока список не кончится. То есть итератор перебирает все переданные ему аргументы, позволяя внутри повторяемых действий обработать каждый по-своему. Вот простой пример:

.irp	reg, r0,r2,r4,r6
push	reg
.endr

После исполнения макроса получится следующее:

push	r0
push	r2
push	r4
push	r6

То есть итератор поочередно использует перечисленные регистры для сохранения их в стеке. Так как нет ограничений на количество параметров в списке, напрашивается идея использования этого итератора в комплексе с макросом с переменным числом аргументов:

.macro		pushnow	arg:vararg
.irp	reg, \arg
push	reg
.endr
.endm

В программе мы просто обратимся к макросу pushnow с перечислением сохраняемых регистров, и они окажутся сохраненными в стеке.

Итератор с перебором символов .irpc

Этот итератор очень похож на предыдущий, с той лишь разницей, что перебор ведется не по списку параметров, а по символам строки, переданной в качестве параметра:

.irps	c, 0123
push	r\c
.endr

В результате работы этого макроса получатся следующие команды:

push	r0
push	r1
push	r2
push	r3

Кроме итераторов и макросов в AVR-AS имеется большой набор операторов для различных функций.

Оператор присваивания .set

Записывается так:

.set	<символ>,<значение>

Назначает символу новое значение, например:

.set	tmp, r0 // делает tmp синонимом r0.

В качестве значения можно использовать выражения с использованием математических и логических операторов, записываемых в стиле Си, причем имеется возможность использовать предыдущее значение символа (учтите, что символ – это не переменная, под него не выделяется память или регистр процессора, это информационная единица, существующая только для компилятора):

.set	var, 0	// присваивает var нулевое значение
.set	var, 12 // присваивает var значение 12
.set	var, var + 3 // присваивает var значение 15

Самое главное: при математических вычислениях AVR-AS использует для чисел разрядность хост-платформы, то есть если в Си для AVR при вычислениях по умолчанию используется int (16 бит со знаком), то в AVR-AS для Windows будут использоваться числа 32 бита со знаком.

Оператор проверки условия .if

Этот оператор имеет классическую форму записи в стиле Си или несколько альтернативных вариантов. Классическая запись:

.if <выражение>
// действия, если выражение НЕ РАВНО НУЛЮ
.endif

или

.if <выражение>
// действия, если выражение НЕ РАВНО НУЛЮ
.else
// действия, если выражение РАВНО НУЛЮ
.endif

Выражение здесь понимается, как в Си: оно может состоять из математических и/или логических опреторов, причем результат вычисляется по правилам Си и ненулевое значение считается ИСТИНОЙ, а нулевое – ЛОЖЬЮ.

Альтернативные варианты служат для упрощения записи некоторых характерных проверок.

.ifeq <выражение> // если выражение эквивалентно нулю
.ifne <выражение> // если выражение не эквивалентно нулю
.ifdef <символ> // если символ определен
.ifndef <символ> // если символ не определен
.ifb <аргумент> // если аргумент ПУСТ
.ifnb <аргумент> // если аргумент НЕ ПУСТ
.ifc <строка1>, <строка2> // если строки 1 и 2 совпадают посимвольно (с учетом регистра).
 

Строки могут быть в кавычках или без них, но следует помнить, что для строк без кавычек символы-разделители будут обозначать конец строки, поэтому если строка содержит пробел или запятую, она должна заключаться в кавычки.

Так же имеются вариант для условий больше нуля, меньше или равно и т.д. – ввиду простой возможности их заменить классической формой оператора, они не рассматриваюстя.

Рассмотренных возможностей уже достаточно для составления весьма гибких макросов.

Пример, который, безусловно, существенно облегчит жизнь программисту.

В обработчике прерываний необходимо сохранить контекст программы. Это означает, что при входе в обработчик надо сохранить состояние регистра SREG, и так же всех регистров, используемых в самом обработчике, а перед выходом из него – восстановить их значения. Обычно для этих целей используется стек. Главная «грабля» в этом случае заключается в том, что порядок сохранения регистров в стеке должен быть обратным по отношению к порядку извлечения регистров из стека, иначе ничего не будет работать. Если в обработчике используется 1-2 регистра – ошибиться с порядком их восстановления сложно, но если их требуется больше – проблема уже становится заметной. Да и вручную писать многочисленные push-pop не очень-то приятно.

Поставим задачу: сделать пару взаимодополняющих макросов ENTER и LEAVE, первый из которых будет сохранять в стеке любые указанные регистры, а второй – восстанавливать их. Кроме того, эти макросы должны без лишних телодвижений сохранять SREG. Названия макросов взяты по аналогии с командами от 80x86-процессоров, выполняющих почти то же самое. Для макроса ENTER должно использоваться переменное число параметров, а для LEAVE параметры вообще не должны использоваться – он сам должен уметь извлекать из стека нужные регистры в нужном порядке.

Тогда обработчик прерывания будет оформляться примерно так:

TIMER0_OVF_vect:
ENTER	r2, r5, r16, ZL, ZH
// решаем свои задачи с использованием перечисленных регистров
LEAVE
reti

Согласитесь, элегантно? Ну, тогда займемся составлением макросов.

Прежде всего, разберемся с тем, как вообще эту задачу мы решали бы на абстрактном языке высокого уровня. Вырисовывается примерно такой алгоритм для ENTER: сначала сохраним в стеке SREG, а затем будем перебирать все параметры макроса, каким-то образом помечая найденные регистры, и сохранять их в стеке. Для LEAVE напрашивается такой алгоритм: в качестве исходных данных используем «отметки», сделанные в ENTER, перебираем эти отметки и для каждого отмеченного регистра делаем восстановление из стека (перебор ведем в обратном порядке по отношению к ENTER), а затем восстанавливаем SREG.

Запишем скелет алгоритмов:

.set	selector, 0	// в этой переменной будем отмечать нужные регистры
.macro ENTER	arg:vararg // макрос имеет переменное число параметров
push	r0		// всегда сохраняем r0
in	r0, _SFR_IO_ADDR(SREG) // извлекаем значение SREG
push	r0		// сохраняем его в стеке
// тут надо перебрать аргументы и отметить их битами в selector
// затем надо перебрать биты selector от МЛАДШЕГО к старшему и
// сохранить соответствующие регистры в стеке
.endm
.macro LEAVE
// тут надо перебрать биты selector от СТАРШЕГО к младшему и
// извлечь соответствующие регистры из стека
pop	r0		// извлечем значение SREG
out	 _SFR_IO_ADDR(SREG), r0 // восстановим SREG
pop	r0		// восстановим r0
.endm

Как видите, наиболее сложные места я пока описал в виде комментариев. Очевидно, что косвенным эффектом нашего алгоритма является неизбежное сохранение r0, поэтому при использовании макроса ENTER можно не беспокоиться о нем, т.е. r0 будет сохранен даже при использовании макроса без параметров.

У AVR 32 рабочих регистра, что просто требует от нас отмечать сохраненный в стеке регистр битом с соответствующим номером. Отметка бита будет делаться традиционно, как мы привыкли в Си – при помощи логического ИЛИ:

.set	selector, selector | (1<< reg) // reg - это номер регистра

Ну а проверка, соответственно, при помощи логического И:

.ifne	selector & (1<<reg)

Остается лишь разобраться с тем, как перебирать параметры и биты. Тут мы приходим к необходимости организации циклов. Простой итератор нам не подходит, т.к. заранее число параметров нам не известно, а итератор с перебором работает и без этого знания – но нам-то нужно знать НОМЕР регистра! Причем в аргументах регистры мы можем передать в любом порядке, поэтому придется определять номер очередного регистра так же перебором среди всех имеющихся.

Пусть очередной параметр у нас хранится в переменной reg. Как же определить НОМЕР соответствующего регистра? Только путем перебора среди всех доступных регистров:

.set	i, 1
.irp	p, r1, r2, r3 ... r30, r31 // надо перечислить все регистры по порядку!!!
.if p == \reg // если регистр совпадает с аргументом макроса
.set 	selector, selector | (1 << i) // сделаем отметку в нужном бите
.endif
.set	i, i+1 // и продолжим счет
.endr

Вышеописанный макрос установит бит в selector, соответствующий параметру reg. Теперь все это можно добавить в скелет макроса ENTER:

.set	selector, 0	// в этой переменной будем отмечать нужные регистры
.macro ENTER	arg:vararg // макрос имеет переменное число параметров
push	r0		// всегда сохраняем r0
in	r0, _SFR_IO_ADDR(SREG) // извлекаем значение SREG
push	r0		// сохраняем его в стеке
// тут надо перебрать аргументы и отметить их битами в selector
.set	i, 1
.irp	p, r1, r2, r3 ... r30, r31 // надо перечислить все регистры по порядку!!!
.if p == \reg // если регистр совпадает с аргументом макроса
.set 	selector, selector | (1 << i) // сделаем отметку в нужном бите
.endif
.set	i, i+1 // и продолжим счет
.endr
// затем надо перебрать биты selector от МЛАДШЕГО к старшему и
// сохранить соответствующие регистры в стеке
.endm

Остается выполнить заключительную часть макроса – снова перебрать, но уже биты selector, и сохранить в стеке регистры, для которых бит установлен. Это сделать совсем просто:

.set	i, 1
.rept	31
.ifne selector & (1<< i)
push	i
.endif
.set	i, i+1
.endr

Итак, макрос ENTER готов! Вот как он выглядит:

.set	selector, 0	// в этой переменной будем отмечать нужные регистры
.macro ENTER	arg:vararg // макрос имеет переменное число параметров
push	r0		// всегда сохраняем r0
in	r0, _SFR_IO_ADDR(SREG) // извлекаем значение SREG
push	r0		// сохраняем его в стеке
// тут надо перебрать аргументы и отметить их битами в selector
.set	i, 1
.irp	p, r1, r2, r3 ... r30, r31 // надо перечислить все регистры по порядку!!!
.if p == \reg // если регистр совпадает с аргументом макроса
.set 	selector, selector | (1 << i) // сделаем отметку в нужном бите
.endif
.set	i, i+1 // и продолжим счет
.endr
// затем надо перебрать биты selector от МЛАДШЕГО к старшему и сохранить нужные регистры
.set	i, 1
.rept	31
.ifne selector & (1<< i)
push	i
.endif
.set	i, i+1
.endr
.endm

После сделанного макрос LEAVE делается ну совсем элементарно:

.macro LEAVE
.set	i, 31
.rept	31
.ifne	(1<< i)
// если бит в маске установлен - регистр извлекается из стека
pop	i
.endif
// перебор битов ведется в обратном порядке!!!
.set	i, i - 1
.endr
// всегда восстанавливаем SREG и R0
pop		r0
out		_SFR_IO_ADDR(SREG), r0
pop		r0
.endm

Эти макросы можно сохранить в файле, который затем подключать к любому своему проекту директивой #include, и писать обработчики прерываний станет просто удовольствием! Ведь часто бывает так, что в процессе работы надо еще один регистр задействовать или наоборот, удается обойтись без сохраненного в стеке, и приходится править push-pop-ы. А теперь достаточно только подкорректировать обращение к макросу ENTER – и все, можно не беспокоиться о прочем!

Подтверждается известный из мультика принцип: «лучше три дня потерять, зато потом за пол-часа долететь!». Согласитесь, что с avrassembler-ом лететь не получилось бы вообще.

P.S. Данный материал был бы не возможен без помощи, оказанной человеком, широко известным в узких кругах под ником ReAl – именно ему принадлежит мысль использовать битовые отметки сохраненных регистров.

 Обсудить на форуме (0 комментариев).

Добавить комментарий

Правила комментирования

Запрещается вводить оскорбительные тексты, использовать нецензурные выражения, публиковать экстремистские призывы, давать ссылки на сайты, не имеющие отношения к теме обсуждения. Все сообщения, нарушающие данные требования, будут удалены без уведомлений, а их авторы - заблокированы.
Незарегистрированные посетители не могут вводить ссылки, BB-коды и т.п., зарегистрированные - могут. Подробности о возможностях, доступных зарегистрированным посетителям, см. в разделе Помощь.


Защитный код
Обновить



Комментарии
Статистика
Просмотров:
mod_vvisit_counterСегодня2070
mod_vvisit_counterВчера3252
mod_vvisit_counterНа этой неделе14143
mod_vvisit_counterНа прошлой неделе29120
mod_vvisit_counterВ этом месяце124243
mod_vvisit_counterЗа все время11349369

Ваш IP: 54.81.72.117
 , 
27 Апр. 2017