Домашняя страница библиотеки_c AVR

Синтаксис языка C

Библиотека языка C GNU glibc

 Страницы развития библиотеки_с AVR

Главная страница

Инструкция пользователя

Содержание библиотеки_c

Часто задаваемые вопросы

Содержание по алфавиту

Демонстрационные проекты


 

Исполнение ассемблерного кода

Иногда в программах, разрабатываемых на языке 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" сообщает компилятору о том, что ассемблерный код может модифицировать любую ячейку памяти. В результате компилятор перед выполнением ассемблерного кода будет обновлять все переменные, содержимое которых уже содержится в регистрах. И, конечно же, после выполнения этого кода все регистры будут восстановлены в исходное состояние.

Hosted by uCoz