Исполнение ассемблерного кода
Иногда в программах, разрабатываемых на языке C, требуется напрямую использовать команды ассемблера
с целью оптимизации кода или просто из-за невозможности выполнить те или иные
операции средствами C.
Компилятор WinAVR
поддерживает
вставку в исходный текст программы фрагментов на ассемблере. 
Для выполнения ассемблерного кода доступен специальный оператор
asm. Его синтаксис:
asm(код:   список_выходных_операндов,   список_входных_операндов)
Поясним использование оператора asm на простом примере чтения значения из порта D:
asm("in  
%0,    %1"   :   
"=r"    (value)    :  
"I"    (_SFR_IO_ADDR(PORTD))    );
Смысл каждой части оператора asm, отделенной с помощью двоеточия:
1.    Ассемблерная
команда, определенная как строковая константа: "in %0,
%1".
2.    Список выходных
операндов, разделенных запятыми. В нашем примере есть только один операнд: "=r"   (value).
3.    Список входных
операндов, разделенных запятыми. В нашем примере есть только один операнд: 
"I"   (_sfr_io_addr (PORTD)).
Вторая и третья части оператора asm предназначены для определения связи между
регистрами микроконтроллера и операндами C. В самих ассемблерных инструкциях
ссылки на операнды создаются с помощью символа "%" и порядкового номера операнда (начиная с нуля). Так,
в рассмотренном выше примере ссылке %0
соответствует входной операнд "=r" (value), а ссылке
%1 — выходной операнд 
"I" (_SFR_IO_ADDR(PORTD)).
Синтаксис операндов мы рассмотрим чуть позже, а пока исследуем
часть ассемблерного листинга, который мог быть получен в результате компиляции
представленного выше оператора asm:
in  r24,   12
В данном случае для хранения значения, считанного из порта D
компилятор выбрал регистр г24, хотя
это мог бы быть и любой другой регистр. Компилятор мог бы даже выполнить
неявную загрузку или сохранение значения или решить вообще не включать
пользовательский ассемблерный код.
Все эти решения — часть оптимизационной стратегии компилятора.
Например, если бы значение переменной ни разу не использовалось в оставшейся
части программы, то представленный выше код, скорее всего, был бы исключен. Во
избежание подобного, к оператору asm следует
добавить атрибут volatile:
asm volatile("in %0,
%1" : "=r" (value) : "I" (_SFR_IO_ADDR(PORTD))) ;
Если ассемблерные инструкции не используют операндов, то
соответствующие части оператора asm могут быть опущены. Например, в
случае общего запрета прерываний это будет выглядеть следующим образом:
asm
volatile("cli"::);
Ассемблерный код.
В первой части оператора asm можно использовать любые
команды AVR-ассемблера. При этом
для улучшения удобочитаемости каждую инструкцию можно помещать в отдельную
строку с помощью символьных литералов перевода строки:
asm
volatile("nop\n"
       "nop\n" 
       "nop\n" 
       "nop\n" 
       : :);
Кроме того, можно использовать некоторые специальные символы,
соответствующие тем или иным регистрам микроконтроллера:
_SREG__—
регистр состояния;
__SP_H__— старший байт указателя стека;
__SP_L__— младший байт указателя стека;
__tmp_reg__— регистр r0, используемый для промежуточного хранения;
__zero_reg__— регистр rl, всегда нулевой.
Входные и выходные операнды.
Каждый входной и выходной операнд описывается строкой уточнений,
после которой следует выражение языка С в круглых скобках. Компилятор WinAVR поддерживает уточнения, перечисленные в табл.
3.4.
таблица 3.4. Уточнения в определении входных
операндов оператора asm
| Уточнение |  | Использование | Диапазон значений | 
| a |  | Обычные старшие регистры | r16..r23 | 
| b |  | Регистры двойной длины для указателя базы | y. z | 
| d |  | Старшие регистры | r16..r31 | 
| е |  | Регистры двойной длины — указатели | х, у, z | 
| G |  | Вещественная константа | 0,0 | 
| I |  | Шестиразрядная положительная целая константа | 0..63 | 
| J |  | Шестиразрядная отрицательная целая константа | -63..0 | 
| К |  | Целая константа | 2 | 
| L |  | Целая константа | 0 | 
| 1 |  | Младшие регистры | r0...r15 | 
| М |  | Восьмиразрядная целая константа | 0..255 | 
| N |  | Целая константа | -1 | 
| 0 |  | Целая константа | 8, 16,24 | 
| Р |  | Целая константа | 1 | 
| q |  | Указатель стека | SPH:SPL | 
| r |  | Любой регистр | r0..r31 | 
| t |  | Временный регистр | r0 | 
| w |  | Специальные старшие регистровые пары | r24, r26,
  r28, r30 | 
| x |  | Регистр-указатель двойной длины X | х (r27:r26) | 
| y |  | Регистр-указатель двойной длины Y | у (r29:r28) | 
| z |  | Регистр-указатель двойной длины Z | z (r31
  :r30) | 
Символам уточнений могут предшествовать модификаторы (если модифика ftp не
указан, операнд считается "только для чтения"):
= — операнд
"только для записи" (для всех выходных операндов); 
& — регистр
должен использоваться только для вывода.
Входные операнды — только для чтения. Но что делать, если
необходимо чтобы один и тот же операнд был и входным, и выходным одновременно?
Для этого во входном операнде можно использовать в качестве уточнения цифру,
соответствующую порядковому номеру выходного операнда. Например:
asm volatile("swap 
%0"   :   "=r"   (value)    :  
"0"   (value));
Этот оператор поменяет местами полубайты восьмиразрядной
переменной value. Ограничитель "0"
указывает компилятору использовать тот же входной регистр, что и первый
операнд.
В тех случаях, когда код реализует различные регистры,
используемые для входных и выходных операндов, к выходному операнду следует добавить
моди фикатор &.
Например:
asm volatile("in
%0,%l"    "\n\t" "out
%1, %2"  "\n\t" :
"=&r" (input) : "I" (_SFR_IO_ADDR(port)), ) ;
В этом примере входное значение считывается из порта, а затем выходное
в тот же самый порт записывается значение. Если бы компилятор выбрал для ввода
и вывода один и тот же регистр, то после выполнения первой ассемблерной команды
выходное значение было бы потеряно. Благодаря использованию модификатора
&, компилятор распознал, что для выходного значения следует использовать
любой регистр, не занятый под входные операнды. Возвращаясь к примеру
перестановки, код перестановки старшего и младшего байта некоторого
16-тираз-рядного значения будет выглядеть следующим образом:
asm
volatile("mov __tmp_reg__, %A0" "\n\t"
"mov
%A0, %B0"         "\n\t"
"mov
%B0, __tmp_reg__" "\n\t"
: "=r" (value)
: "0" (value) ) ;
Пример
перестановки байтов 32-хбитного значения:
asm
volatile("mov __tmp_reg__, %A0"  
"\n\t"
"mov
%A0, %D0"                    "\n\t"
"mov
%D0, __tmp_reg__"   "\n\t"
"mov
__tmp_reg__, %B0"   "\n\t"
"mov
%B0, %C0"                    "\n\t"
"mov
%C0, __tmp_reg__"   "\n\t"
: "=r" (value)
: "0" (value) );
Если операнды не помещаются в один регистр, компилятор
автоматически назначит дополнительные регистры, общий размер которых будет
достаточным для хранения всего операнда. В рассмотренных примерах спецификации
%А0 соответствует младший байт первого операнда, а спецификации %А1 — младший
байт второго операнда. Следующему байту первого операнда соответствует %В0, сле-.дующему — %со и т.д.
Резервирование регистров.
В том случае, если в операторе asm должны быть указаны регистры, которые не передаются в качестве операндов,
об этом следует каким-то образом уведомить компилятор. Для этой цели служит еще
одна, четвертая часть оператора asm, которая
в общем случае является необязательной, — часть резервированных регистров.
Рассмотрим пример реализации автоинкремента
восьмиразрядного значения, на которое указывает переменная-указатель, без
прерывания какой-либо подпрограммой обслуживания прерывания или параллельным
процессом (мы должны использовать указатель, поскольку инкрементированное
значение должно быть сохранено до разрешения прерываний).
asm
volatile( )
"cli"                                 "\n\t"
"id
r24, %a0"                "\n\t"
"inc
r24"                         "\n\t"
"st
%a0, r24"                "\n\t"
"sei"                       "\n\t"
:  
"e"   (ptr) :    "r24"
В результате компилятор сгенерирует следующий ассемблерный код:            '
cli
Id
r24, Z inc r24 st Z, r24
sei
Для того чтобы избежать резервирования регистра г24, можно
воспользоваться специальным буферным регистром__tmp_reg__:
asm
volatile(
"cli"                                                  
"\n\t"
"Id
__tmp_reg__, %aO"                "\n\t"
"inc
__tmp_reg__"                         "\n\t"
"st
%a0, __tmp_reg__"                "\n\t"
"sei"                                                   "\n\t"
:    "e"   
(ptr) );
Еще одна проблема заключается в том, что рассматриваемый код не
может быть использован в тех программных секциях, где прерывания запрещены и не
должны активизироваться, поскольку в конце используется команда общего разрешения
прерываний sei. Конечно, можно сохранять текущее
состояние микроконтроллера, однако в таком случае потребуется еще один
регистр. Опять таки, это можно реализовать без резервирования фиксированного
регистра, а с помощью локальной переменной языка С:
{
unsigned
char s;
asm
volatile(
"in
%0, __SREG__"                         "\n\t"
"cli"                                                  
"\n\t"
"ld
__tmp_reg__, %al"     
"\n\t"
"inc
__tmp_reg__"                         "\n\t"
"st
%al, __tmp_reg__"       "\n\t"
"out
__SREG__, %0"                       "\n\t"
:
"=&r" (s) : "e" (ptr)
);
}
Теперь ассемблерный код модифицирует переменную, на которую
указывает ptr, однако компилятор может этого не
распознать и сохранить значение в каком-либо другом регистре. Кроме того,
значение переменной может модифицировать сама программа на С, а компилятор не
обновит ячейку памяти по причинам оптимизации. Во избежание подобных проблем
можно воспользоваться специальным резервирующим определением "memory":
unsigned
char s; asm volatile(
"in
%0, _SREG__"                         "\n\t"
"cli"                                                  
"\n\t"
"ld
__tmp_reg__, %al"      
"\n\t"
"inc
__tmp_reg__"                         "\n\t"
"st
%al, __tmp_reg__"      
"\n\t"
"out
__SREG__, %0"                       "\n\t"
:
"=&r" (s)
: "e" (ptr)
: "memory"
Определение "memory"
сообщает компилятору о том, что ассемблерный код может модифицировать любую
ячейку памяти. В результате компилятор перед выполнением ассемблерного кода
будет обновлять все переменные, содержимое которых уже содержится в регистрах.
И, конечно же, после выполнения этого кода все регистры будут восстановлены в
исходное состояние.