Программирование DSP Типовой процесс разработки в среде VisualDSP++ Thu, November 21 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Типовой процесс разработки в среде VisualDSP++ Печать
Добавил(а) microsin   

Здесь приведен перевод раздела документации [1] с описанием стадий общей процедуры разработки программного обеспечения в среде VisualDSP.

[Основные стадии разработки]

Основная часть руководства [1] описывает линковку, которая является критической стадией разработки программного обеспечения для встраиваемых (embedded) приложений.

Утилита линковщика (linker) получает на входе объектные (*.doj) и библиотечные (*.dlb) файлы, чтобы сгенерировать на выходе исполняемые файлы (обычно *.dxe), которые могут быть загружены в симулятор или целевой процессор. Линкер также генерирует map-файлы (в формате XML, см. [2]) и другую дополнительную информацию, используемые отладчиком (чтобы была возможна отладка по исходному коду программы). Отладочная информация встраивается в исполняемый файл (когда при компиляции была активна конфигурация проекта Debug). После запуска линкера Вы тестируете полученный результат с помощью симулятора или эмулятора JTAG. Подробнее про описание процедуры отладки см. руководство VisualDSP++ и online Help.

На заключительном этапе разработки Вы обрабатываете исполняемый файл (или файлы, если их несколько) загрузчиком (loader) или сплиттером (splitter), чтобы создать двоичный код, подходящий для использования в реальном процессоре (подробнее про загрузчик и сплиттер см. [3, 4]). Выходной файл может находится в памяти другого процессора (в хосте), в микросхеме последовательной памяти, или он может быть прошит в PROM (ПЗУ). Руководство VisualDSP++ 5.0 Loader and Utilities Manual описывает функциональность loader/splitter для целевых процессоров (также см. [3, 4]).

Разработку встраиваемого программного обеспечения можно разбить на 3 фазы:

1. Компиляция и ассемблирование - для этой фазы входными будут файлы исходного кода на языке C (*.c), C++ (*.cpp) и ассемблера (*.asm), и в результате на выходе будут получаться объектные файлы (*.doj).

2Линковка - под управлением файла настроек линкера (linker description file, *.ldf), командной строки линкера и опций диалога настройки проекта VisualDSP++ утилита линкера получает на входе объектные файлы (*.doj) и файлы библиотек (*.dlb), и генерирует на выходе выполняемый (executable, *.dxe) файл. Если это указано, также генерируются файлы обшей памяти (shared memory, *.sm) и файлы оверлея (overlay, *.ovl).

3Loading или splitting - исполняемый (*.dxe) файл, а также файлы shared memory (*.sm) и overlay (*.ovl), обрабатываются для получения выходного файла или файлов (обычно в формате Intel HEX [5], двоичном BIN или в виде образа загрузки *.ldr). Для процессоров TigerSHARC и Blackfin это загружаемые файлы (boot-loadable, *.ldr) или файлы не загружаемых образов ПЗУ (non-bootable PROM image), которые выполняются из внутренней (обычно SRAM L1) и/или внешней памяти процессора (обычно SDRAM L3 или FLASH).

VisualDSP Program Development Flow

Не загружаемый (non-bootable) файл выполняется непосредственно из внешней памяти процессора, в то время как загружаемый (boot-loadable) с помощью кода Boot ROM или другого загрузчика обрабатывается, и его содержимое по секциям записывается (загружается) во внутреннюю память процессора (обычно это L1 SRAM и/или L3 SDRAM). Загружаемый файл обычно программируется во внешнюю энергонезависимую память (EPROM, это чаще всего параллельная FLASH или SPI FLASH), установленную на плате целевой системы (иногда эта память может находится на кристалле процессора, как например у ADSP-BF538F). Утилита загрузчика (loader utility) генерирует загружаемые файлы в различных форматах, которые принимают большинство программаторов (это чаще всего формат Intel HEX-32). Для расширенного использования поддерживаются другие форматы и режимы загрузки с внешних носителей данных (подробнее см. [3]).

Не загружаемый образ EPROM выполняется из внешней памяти, пропуская встроенный механизм загрузки (т. е. этот код не обрабатывается загрузчиком наподобие Boot ROM). Подготовка не загружаемого образа EPROM называется splitting. Это название произошло от английского "split", т. е. "разделить" - в этом контексте разделение означает, что часть кода может быть загружаемым, т. е. он обрабатывается и загружается с помощью Boot ROM, а другая часть кода находится в памяти процессора в состоянии, непосредственно готовом к выполнению.

В большинстве случаев (кроме процессоров Blackfin) разработчики имеют дело с процессорами арифметики фиксированной и плавающей точки, используя сплиттер вместо утилиты загрузки, чтобы сгенерировать не загружаемый файл образа памяти.

Последовательность загрузки процессора и дизайн программы приложения диктуют метод вызова утилиты загрузчика/сплиттера, чтобы преобразовать исполняемый файл *.dxe:

• Для процессоров Blackfin функции утилиты загрузчика и сплиттера выполняет одна программа elfloader.exe [3]. Сплиттер вызывается с другим набором опций командной строки, отличающимся от опций вызова утилиты загрузчика.

В VisualDSP++ 5.0 как дополнение к опции -readall утилита загрузчика для процессоров ADSP-BF51x, ADSP-BF52x, ADSP-BF54x Blackfin может автоматически вызвать программу сплиттера. Для дополнительной информации см. описание опции командной строки -readall #.

• Для процессоров TigerSHARC и SHARC операции сплиттера обрабатываются специальной программой сплиттера elfspl21k.exe.

[Компиляция и ассемблирование]

Этот процесс начинается с обработки файлов исходного кода на языке C, C++ и/или ассемблера. Компилятор (или разработчик, если он пишет на ассемблере) помещает каждую отдельную последовательность инструкций или данных в определенную именованную секцию, что дает основные компоненты, с которыми работает линкер.

Первый шаг в сторону генерации исполняемого файла это компиляция или ассемблирования файлов C, C++ или ассемблера в объектные файлы. Среда разработки VisualDSP++ назначает для объектных файлов расширения *.doj (см. рис. 1-1).

Linker Utilities Manual Compiling and Assembling fig1 1

Рис. 1-1. Компиляция и ассемблирование.

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

Некоторые входные секции могут содержать дополнительную информацию, дающую возможность реализовать функционал отладки программы по исходному коду (source-level debugging) и другие функции VisualDSP++. Линкер отображает каждую входную секцию (через соответствующую секцию в исполняемом коде) на сегмент памяти процессора. Сегмент памяти - это непрерывный блок адресного пространства целевой системы, все ячейки которого обладают одинаковыми свойствами или общим функциональным назначением (например, различают сегменты памяти L1_code, L1_data, FLASH_code и т. д.).

Распределение адресного пространства по секциям задается в файле описания линкера *.ldf. Каждая входная секция файла в файле *.ldf требует для себя уникального имени, как это указано в исходном коде (см. врезку ниже). В зависимости от того, какой исходный файл, на языке C, C++ или ассемблера, для именования целевых секций памяти используются разные способы (см. статью [7]).

Директива .SECTION задает секцию памяти в исходном коде на языке ассемблера. Эта директива должна быть указана перед соответствующим кодом или данными. Пример кода для процессора SHARC:

.SECTION/DM asmdata;    // декларируется секция asmdata
.VAR input[3];          // декларируется буфер данных в секции asmdata 
 
.SECTION/PM asmcode;    // декларируется секция asmcode
   R0 = 0x1234;         // три строки кода, находящиеся в секции asmcode
   R1 = 0x4567;
   R3 = R1 + R2;

В выше приведенном примере входная секция /dm asmdata содержит массив input, и входная секция /pm asmcode содержит 3 строки кода программы. Пример для процессора Blackfin:

.SECTION Library_Code_Space;     // директива секции
.GLOBAL _abs;
 
_abs:
   R0 = ABS R0;                  // взять абсолютное значение регистра
   RTS;
_abs.end;

В примере выше ассемблер поместит глобальный символ/метку _abs и код после неё во входную секцию Library_Code_Space, когда этот файл будет обработан и преобразован в объектный код.

В этом пример линкер знает, что код связан с меткой _abs, потому что он обозначен меткой начала _abs: и меткой окончания _abs.end. Для некоторых функций линкера, особенно для функции уничтожения неиспользуемого кода (unused section elimination, уничтожение не используемых секций, см. описание ELIMINATE_SECTIONS() в [7]), линкер должен быть в состоянии определить конец кода или данных, связанных с меткой. В коде ассемблера конец блока данных функции может быть помечен меткой с таким же именем в начале метки, и добавлением суффикса .end к нему. Также можно рассмотреть использование "." для случая, когда метка не будет появляться в таблице символов, что может упростить отладку.

Листинг 1-1 показывает использование меток .end в коде ассемблера.

//Листинг 1-1. Использование меток в коде ассемблера.
start_label:
                           // код
start_label.end            // метка конца секции кода
 
new_label:
                           // код
new_label.END:             // метка конца может быть указана в верхнем регистре
 
one_entry:                 // функция one_entry, в которой находится код
                           // в секции second_entry
second_entry:              // еше код
.one_entry.end:
.second_entry.end:         // применение "." опускает завершающую метку
                           // в таблице символов

Обычно код на языке C/C++ не указывает имя входной секции, и тогда компилятор использует имя по умолчанию. По умолчанию имя входной секции program (для кода code) и data1 (для данных). Дополнительные имена секций определены в файле .ldf проекта. Для дополнительной информации по привязке к памяти (memory mapping) см. "Указание отображения на память").

[Пример 1]

В файлах исходного кода C/C++ Вы можете использовать специальное расширения языка section("имя_секции"), чтобы указать имя секции для кода, переменной или объекта. Во время обработки следующего кода компилятор сохранит переменную temp во входную секцию ext_data объектного файла .doj, и сохранит сгенерированный для функции func1 код в секции под именем extern (вообще-то не очень хорошо давать имя extern для секции - во избежании путаницы с декларированием внешних переменных).

...
section ("ext_data") int temp;         /* Директива секции */
section ("extern")   void func1(void) { int x = 1; }
...

[Пример 2]

Расширение section ("имя_секции") указывать не обязательно. Но если оно указано, то прикладывается только к той декларации, вместе с которой используется. Имейте в виду, что новая функция (func2) не имеет директивы section ("extern"), и поэтому будет размещена во входной секции по умолчанию program. Для дополнительной информации по секциям LDF см. "Указание отображения на память".

section ("ext_data") int temp;
section ("extern") void func1(void) { int x = 1; }int func2(void) { return 13; }

Для получения информации о именах секции по умолчанию см. руководство "VisualDSP++ 5.0 C/C++ Compiler and Library Manual" для соответствующего целевого процессора, и раздел "Размещение кода в целевом процессоре" статьи [6].

Четко различайте имена входных секций, имена выходных секций и имена сегментов памяти, потому что эти типы имен появляются в файле .ldf проекта. Обычно используются имена по умолчанию. Однако в некоторых ситуациях Вы можете захотеть использовать имена не по умолчанию. Такая ситуация возникает, к примеру, когда разные функции или переменные (в одном и том же исходном файле) размещаются в разных сегментах памяти (например, быстрый код должен быть размещен в памяти "L1_code", а не очень критичный к времени выполнения в "L3_code" или даже "FLASH_code").

[Линковка]

После того, как исходные файлы кода скомпилированы и ассемблированы в объектные файлы, используется линкер для комбинирования объектных файлов в исполняемый файл. По умолчанию инструментарий VisualDSP дает исполняемым файлам расширение .dxe (см. рис. 1-2).

Linker Utilities Manual Linking Diagram fig1 2

Рис. 1-2. Диаграмма процесса линковки.

Линковка позволяет Вашему коду эффективно работать в целевом окружении (на выбранном процессоре встраиваемой системы). Детально линковка описана в статье [6].

При разработке нового проекта используйте Мастер Проекта, Project Wizard (для процессоров Blackfin) или Expert Linker (для процессоров SHARC и TigerSHARC), чтобы сгенерировать файл .ldf для проекта. Для дополнительной информации см. Главу "4. Expert Linker" или ищите в online help топик "Project Wizard".

Препроцессор линкера и ассемблера. Программа препроцессора линкера и ассемблера (pp.exe) вычисляет и обрабатывает команды препроцессора в исходных файлах. С этими командами Вы направляете препроцессор определять макросы и символические константы, подключать файлы заголовков, проверять наличие ошибок и управлять процессом компиляции и ассемблирования с помощью условных операторов.

Препроцессор pp запускается ассемблером или линкером из командной строки операционной системы, или из рабочего окружения разработки VisualDSP++. Эти инструменты принимают и передают эту информацию команд в препроцессор. Препроцессор также может работать из командной строки с использованием собственных опций командной строки.

"." символ идентификатора. Препроцессор ассемблера/линкера воспринимает символ точки "." как часть идентификатора.

Препроцессор соответствует ассемблеру, который использует "." как часть директив ассемблера и как допустимый символ в метках. Это поведение создает возможную проблему для пользователей, которые пишут макросы препроцессора, которые полагаются на идентификаторы для разрыва, когда встречается символ ".", что обычно происходит при обработке имен регистров. Например:

#define Loadd(reg, val) \
        reg.l = val;   \
        reg.h = val;

Пример выше не будет работать в VisualDSP++ 5.0, потому что VisualDSP++ 5.0 не предоставляет любую замену, поскольку reg не обрабатывается как отдельный идентификатор. Этот макрос должен быть переписан с использованием оператора ##, вот так:

#define Loadd(reg, val)  \
        reg ## .l = val; \
        reg ## .h = val;

Препроцессор поддерживает стандарт ANSI C препроцессинга с расширениями, но отличается от стандартного препроцессора ANSI C в некоторых случаях. Для информации по препроцессору pp см. руководство "VisualDSP++ 5.0 Assembler and Preprocessor Manual".

У компилятора есть свой собственный препроцессора, который разрешает использование команд препроцессора в исходном коде C/C++. Препроцессор компилятора запускается перед компиляцией. Для дополнительной информации см. руководство "VisualDSP++ 5.0 C/C++ Compiler and Library Manual" для используемой Вами архитектуры процессора.

[Загрузка и деление кода (Loading и Splitting)]

После отладки файла .dxe он обрабатывается лоадером (loader) или сплиттером (splitter) [3], чтобы создать выходной файл (файлы) прошивки, которые пригодны для использования процессором в конечной системе. Также конечный файл (файлы) могут находиться в другом процессоре (хосте) или прошиты в ПЗУ (PROM).

Для дополнительной информации обратитесь к руководству "VisualDSP++ 5.0 Loader and Utilities Manual" (также см. [3, 4], где предоставлено подробное описание процессов и опций, используемых для генерации файлов-образов загрузки (boot-loadable loader, файлы .ldr) для соответствующего целевого процессора. Это руководство также описывает утилиту сплиттера [3], которая создает не загружаемые, а исполняемые файлы, команды из которых выполняются в адресном пространстве процессора (внешняя память, обычно FLASH).

В общем:

• Процессоры SHARC ADSP-2106x/ADSP-21160 используют утилиту создания образов загрузки (loader, консольная программа elfloader.exe). В результате получается загружаемый файл (boot-loadable image, файл с расширением .ldr), который размешается во внешней для процессора памяти (PROM или процессор хоста). Утилита сплиттера (splitter utility, elfspl21k.exe) используется для генерации не загружаемых файлов образов для PROM, код из которых не загружается, а выполняется непосредственно из внешней памяти процессора (часто используется с процессорами ADSP-21065L).

• Процессоры SHARC ADSP-2116x/2126x/2136x/2137x/2147x/2148x используют loader (elfloader.exe), чтобы получить загружаемый образ (boot-loadable image, файл .ldr), который передается в оперативную память процессора (обычно SRAM L1 и/или SDRAM L3), после чего запускается там на выполнение. Чтобы получить загружаемый файл, утилита loader обрабатывает данные из файла boot-kernel (.dxe) и одного или большего количества других исполняемых файлов (.dxe).

• Процессоры TigerSHARC используют утилиту loader (elfloader.exe), чтобы получить загружаемый образ (boot-loadable image, файл .ldr), который передается в оперативную память процессора (обычно SRAM L1 и/или SDRAM L3), после чего запускается там на выполнение. Чтобы получить загружаемый файл, утилита loader обрабатывает данные из файла boot-kernel (.dxe) и одного или большего количества других исполняемых файлов (.dxe).

• Процессоры TigerSHARC и SHARC используют утилиту сплиттера (splitter utility, elfspl21k.exe), чтобы сгенерировать не загружаемые файлы образа PROM, которые запускаются из внешней памяти процессора.

• Процессоры Blackfin обычно используют утилиту loader (elfloader.exe), чтобы получить загружаемый образ (boot-loadable image, файл .ldr file), который размещается во внешней для процессора памяти (PROM или процессор хоста. Также это может быть память с параллельным или последовательным интерфейсом, и память, находящаяся в корпусе процессора - как у ADSP-BF504F или ADSP-538F). Чтобы получить загружаемый файл, утилита loader обрабатывает данные из файла boot-kernel (.dxe) и одного или большего количества других исполняемых файлов (.dxe). Также может использоваться утилита сплиттера [3] для той же цели, что и у других процессоров.

Рис. 1-3 упрощенно показывает процедуру применения утилиты loader. В этом примере на её вход поступает один исполняемый (executable, .dxe) файл. Утилита loader может обработать на входе до двух файлов .dxe плюс один файл boot kernel (.dxe).

Linker Utilities Manual Using Loader to Create Output File fig1 3

Рис. 1-3. Использование утилиты Loader для создания выходного файла.

Например, когда сбрасывается процессор TigerSHARC, порция boot kernel образа переносится в ядро процессора. Затем порция инструкций и данных образа загружается ядром boot kernel во внутреннюю память RAM (как показано на рис. 1-4).

Linker Utilities Manual Booting from LDR File fig1 4

Рис. 1-4. Загрузка из файла .LDR.

В среде разработки VisualDSP++ имеются файлы ядер загрузки (boot kernel files, .dxe), которые используются автоматически, когда Вы запускаете загрузчик (loader). Вы также можете модифицировать поведение boot kernel путем изменения его исходных файлов (они поставляются в составе VisualDSP++) и последующей их компиляции.

Рис. 1-5 показывает, как обрабатываются несколько входных файлов. В этом случае 2 исполняемых файла (.dxe), файлы общей памяти (shared memory, .sm) и файлы оверлея (overlay, .ovl) поступают на вход утилиты loader для создания одного файла образа загрузки (.ldr). Этот пример иллюстрирует генерацию загружаемого файла для архитектуры с несколькими процессорными ядрами (multiprocessor architecture).

Файлы .sm и .ovl должны находиться в одной и той же директории, которая также содержит входной файл (файлы) .dxe, или в текущей рабочей директории. Если Ваша система не использует общие области памяти (shared memory) или оверлеи, то файлы .sm или .ovl не требуются.

Linker Utilities Manual Input Files for Multiprocessor System fig1 5

Рис. 1-5. Входные файлы для многопроцессорной системы.

Этот пример показывает 2 исполняемых файла, которые имеют общую память (share memory), и где также добавлены оверлеи. Результат в виде выходного файла содержит информацию из всех входных файлов.

[Ссылки]

1. VisualDSP++ 5.0 Linker and Utilities Manual site:analog.com.
2Q070. VisualDSP: как посмотреть MAP-файл линкера?
3Blackfin: утилита elfloader.exe.
4Blackfin: запуск программ из FLASH.
5Intel HEX: описание формата файла.
6. Использование линкера VisualDSP++.
7Linker Description File.

 

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


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

Top of Page