Язык 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). Так что используйте резервирование регистров на собственный страх и риск. |