Портирование кода IAR на AVR GCC Печать
Добавил(а) microsin   

C-CPP-compilerЯзык C был изначально разработан с целью портирования кода. Имеется два вида портирования - перенос приложения на другую платформу (другая операционная система и/или процессор), и перенос приложения на другой компилятор. Портирование на другой компилятор может быть усложнено, когда программа относится ко встраиваемым системам (embedded system - приборы и автоматика, построенные на микроконтроллере). Например, стандарт языка C странным образом не определяет правила для декларирования и определения обработчиков прерывания (Interrupt Service Routine, ISR). Разные компиляторы это делают по-разному, и используют разные регистры микроконтроллера, а также используют нестандартные структуры языка. В этой статье описаны некоторые методы и указания по портированию программ для AVR (код firmware) с проприетарного компилятора IAR (IDE проектирования IAR Embedded Workbench for AVR) на тулчейн GNU (AVR GCC, на платформе Windows это пакет WinAVR). Имейте в виду, что статья может оказаться неполным набором указаний по такому портированию. Здесь и далее перевод руководства "Porting From IAR to AVR GCC" с сайта www.nongnu.org.

[Регистры AVR]

Заголовочные файлы, описывающие адресное пространство регистров ввода/вывода (IO header files), содержат мнемонические идентификаторы для всех имен регистров и имен бит для каждой конкретной модели микроконтроллера. IAR имеет индивидуальные файлы заголовков для каждого процессора, и они должны быть подключены к коду, когда в нем используются имена регистров. Название подключаемого заголовочного файла часто связано с моделью применяемого микроконтроллера. Например:

#include < iom169.h >

Примечание: IAR не всегда использует те же самые имена регистров или бит, которые определены в даташите Atmel для микроконтроллеров AVR.

AVR GCC также имеет индивидуальные заголовочные файлы для каждого процессора. Однако реальный тип процессора указывается как опция командной строки компилятора (опция -mmcu=тип_микроконтроллера). Это часто делается через переменные makefile, как например MCU=atmega32. Такой вариант реализации позволяет Вам подключать всегда один и тот же заголовочный файл для любого типа процессора:

 #include < avr/io.h >

Примечание: прямой слеш в имени файла avr/io.h для разделения подпапок может использоваться и в дистрибутивах тулчейна для Windows, и это является рекомендуемым методом для подключения файла заголовка IO (несмотря на то, что в именах путей Windows изначально использовался обратный слеш \). Использование прямого слеша / предпочтительнее, так как обратный слеш используется для указания символов типа \0, \n, \r и т. п.

Компилятор (из опции командной строки -mcu) знает тип процессора, и может с помощью одного вышеуказанного файла подключить тот индивидуальный заголовочный файл IO, который действительно необходим. Достоинство такого метода в том, что Вам нужно указать только один стандартный заголовочный файл avr/io.h, и можете проще портировать Ваше приложение на другой тип процессора без необходимости корректировки всех модулей для подключения нового файла заголовка IO.

Тулчейн AVR (WinAVR + библиотека avr-libc) пытается точно придерживаться оригинальных названий регистров и бит, которые можно найти в даташите AVR. Может быть некоторое несоответствие между именами регистров в IO заголовках IAR по сравнению с IO заголовками AVR GCC.

[Обработчики прерывания ISR (Interrupt Service Routines)]

Как уже упоминалось, язык C не имеет собственного стандарта для определения ISR. Так что у каждого компилятора может оказаться свой собственный способ, как это нужно делать. IAR декларирует ISR так:

#pragma vector=TIMER0_OVF_vect
__interrupt void MotorPWMBottom()
{
    // код обработчика прерывания
}

В AVR GCC Вы декларируете ISR так:

ISR(PCINT1_vect)
{ 
    // код обработчика прерывания
}

AVR GCC использует макрос для определения ISR. Этот макрос требует подключения файла заголовка:

#include < avr/interrupt.h >

Имена разных векторов прерываний можно найти в индивидуальном файле заголовка IO, который должен подключаться через файл avr/io.h.

Примечание: имена векторов прерываний в AVR GCC были изменены для соответствия именам векторов в IAR. Это значительно упрощает портирование приложений с IAR на AVR GCC.

[Встроенные в язык подпрограммы, соответствующие командам ассемблера (Intrinsic Routines)]

IAR имеет некоторые подпрограммы intrinsic, такие как __enable_interrupts(), __disable_interrupts(), __watchdog_reset().

Эти функции intrinsic компилируются в специальные коды команд ассемблера AVR (SEI, CLI, WDR).

Имеются эквивалентные макросы в AVR GCC, однако все они не размещены в одном заголовочном файле. AVR GCC имеет sei() для замены __enable_interrupts() и cli() для __disable_interrupts(). Оба эти макроса размещены в файле заголовка avr/interrupts.h.

Также AVR GCC имеет макрос wdt_reset(), который заменяет __watchdog_reset(). Однако весь программный интерфейс сторожевого таймера (Watchdog Timer API) в AVR GCC можно найти в заголовочном файле avr/wdt.h.

[Переменные, размещенные в памяти Flash]

Язык C не был разработан для Гарвардской архитектуры процессоров, в которой заданы разные адресные пространства памяти (отдельно для кода и отдельно для переменных). Это означает, что нужны некоторые нестандартные пути для определения переменных, которые размещены в области памяти программ (для AVR это память Flash).

IAR использует нестандартное ключевое слово для декларирования переменной в памяти программ:

__flash int mydata[] = ....

AVR GCC использует атрибуты переменной для достижения того же самого эффекта:

int mydata[] __attribute__((progmem))

Примечание: см. руководство пользователя GCC (GCC User Manual) для дополнительной информации по атрибутам переменной.

Библиотека avr-libc предоставляет удобный макрос для атрибута переменной, размещаемой во Flash:

#include < avr/pgmspace.h >
.
.
.
int mydata[] PROGMEM = ....

Примечание: макрос PROGMEM раскрывается в атрибут переменной progmem. Этот макрос требует подключения заголовочного файла avr/pgmspace.h. Это канонический метод для определения переменной в области памяти программ.

Чтобы прочитать данные из flash, используются макросы pgm_read_*(), также заданные в файле avr/pgmspace.h. В этом файле определены все макросы, которые нужны для работы с памятью программ (Flash).

Есть также метод для определения переменных в памяти программ, который является общим для двух компиляторов (IAR и AVR GCC). Создайте заголовочный файл, который имеет следующие определения:

#if defined(__ICCAVR__) // IAR C Compiler
  #define FLASH_DECLARE(x) __flash x
#endif
#if defined(__GNUC__) // GNU Compiler
  #define FLASH_DECLARE(x) x __attribute__((__progmem__))
#endif

Этот кусок кода проверяет версию компилятора (какой компилятор используется - IAR или GCC) и определяет макрос FLASH_DECLARE(x), который будет декларировать переменную в памяти программ, используя подходящий метод для каждого компилятора. Вы можете использовать его так:

FLASH_DECLARE(int mydata[] = ...);

[Функция main() без возврата значения (Non-Returning main)]

Декларирование main() без кода возврата в IAR делается так:

__C_task void main(void)
{
    // код программы
}

Чтобы выполнить то же самое в AVR GCC, сделайте следующее:

void main(void) __attribute__((noreturn));
 
void main(void)
{
    // код программы
}

Примечание: см. руководство пользователя GCC (GCC User Manual) для дополнительной информации по атрибутам функции.

В AVR GCC нужно задать прототип для main(), где будет указано, что функция main() имеет тип "noreturn". Затем определите main() как обычно. Имейте в виду, что возвращаемый тип для функции main() теперь будет void.

[Резервирование регистров]

Компилятор IAR позволяет пользователю зарезервировать регистры общего назначения от r15 и ниже путем использования опций компилятора и следующих ключевых слов синтаксиса:

__regvar __no_init volatile unsigned int filteredTimeSinceCommutation @14;

Эта строка блокирует r14 для исключительного использования в Вашем коде под именем переменной "filteredTimeSinceCommutation". Это означает, что компилятор не может использовать этот регистр для генерации общего кода.

Чтобы выполнить то же самое в AVR GCC, сделайте следующее:

register unsigned char counter asm("r3");

Обычно таким способом можно использовать регистры от r2 до r15.

Примечание: не резервируйте r0 или r1, которые используются внутри компилятора для временного хранения данных и для нулевой величины. Резервирование регистров не рекомендуется в AVR GCC, так как это выводит эти регистры из-под контроля компилятора, что может ухудшить качество генерируемого объектного кода (который, кстати говоря, и так хуже кода, генерируемого компилятором IAR). Так что используйте резервирование регистров на собственный страх и риск.