Программирование DSP Blackfin: запуск программ из FLASH Thu, March 28 2024  

Поделиться

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

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

Blackfin: запуск программ из FLASH Печать
Добавил(а) microsin   

Этот документ описывает процесс настройки приложения Blackfin, чтобы оно работало в памяти FLASH (на примере процессора ADSP-BF533 Blackfin®), вместо загрузки его из VisualDSP++® через эмулятор. В качестве примера рабочей платформы используется плата разработчика ADSP-BF533 EZ-KIT Lite™.

Минимальные требования:

• VisualDSP++ 3.5 (базовый релиз или апдейт, или более свежая версия).
• Процессор ADSP-BF533 Blackfin, silicon revision 0.2 или более новая ревизия.

Для демонстрации процедур используется пример программы мигания светодиодом Blink для плат EZ-KIT Lite. Его можно найти в каталоге инсталляции VisualDSP++ (ищите в папке Blackfin\Examples папки проектов, где в имени встречается слово Blink, например это может быть каталог ..\Blackfin\Examples\ADSP-BF533 EZ-KIT Lite\LED Blink (C)).

В документе рассматривается два способа решения задачи запуска программы из FLASH:

• Запуск программы с помощью загрузчика, встроенного в процессор (boot ROM).
• Запуск программы напрямую из памяти flash, минуя bypass mode.

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

1. Выполните копию папки ..\Blackfin\Examples\ADSP-BF533 EZ-KIT Lite\LED Blink (C) в новое место на диске (с помощью Проводника Windows или другого файлового менеджера).
2. Запустите VisualDSP++.
3. Откройте проект через меню File -> Open -> Project -> выберите файл BF533 Flags C.dpj, который находится в корневой папке проекта.
4. Выполните сборку через меню Project -> Rebuild Project.
5. Загрузите скомпилированную программу в плату ADSP-BF533 EZ-KIT Lite (через внутрисхемный эмулятор или USB agent) и запустите его на выполнение под управлением сессии отладчика.
6. 6 светодиодов на плате (LED) должны мерцать, последовательно включаясь и выключаясь.

Эта программа работает полностью из памяти L1, используя память инструкций L1 и память данных L1 Data A и L1 Data B. Использование кэша не разрешено, память SDRAM также не используется.

В файле ISRs.c расположен код обработчика прерывания Timer0_ISR(), который использует код, показанный в Листинге 1. Этот код генерирует последовательность мигания светодиодами.

// Листинг 1. Обработчик прерывания таймера, который управляет светодиодами.
//--------------------------------------------------------------------------//
// Функция: Timer0_ISR                                                      //
//                                                                          //
// Описание: этот ISR запускается всякий раз, как истекает задержка Timer0. //
//           Старая маска LED сдвигается на 1 разряд; направление сдвига    //
//           зависит от флага sLight_Move_Direction, который изменяется     //
//           в коде FlagA_ISR.                                              //
//--------------------------------------------------------------------------//
EX_INTERRUPT_HANDLER(Timer0_ISR)
{
   static unsigned char ucActive_LED = 0x01;
 
   // Подтверждение обработки прерывания таймера:
   *pTIMER_STATUS = 0x0001;
 
   // Сдвиг старой маски светодиодов:
   if(sLight_Move_Direction)
   {
      if((ucActive_LED = ucActive_LED >> 1) == 0x00) ucActive_LED = 0x20;
   }
   else
   {
      if((ucActive_LED = ucActive_LED << 1) == 0x40) ucActive_LED = 0x01;
   }
 
   // Запись новой маски в разряды Port B (туда подключены светодиоды LED):
   *pFlashA_PortB_Data = ucActive_LED;
}

[Загрузка программы из микросхем FLASH]

Процессоры ADSP-BF533 Blackfin (как и многие другие процессоры Blackfin) имеют специальную функцию встроенного загрузчика (boot ROM), содержащего так называемое ядро загрузки (boot kernel). Если выводы на корпусе процессора BMODE подключены к лог. 0 и лог. 1 таким образом, что выбран режим загрузки из FLASH, то ядро обработает поток загрузки (специальным образом сформированные данные, так называемый boot stream), который сохранен в памяти flash, делая декомпозицию этого потока на блоки кода и данных (подробнее про выводы BMODE и про то, как осуществляется загрузка, см. статью [3]). Блоки копируются в области RAM, находящиеся в пределах адресного пространства памяти процессора. Boot stream создается утилитой загрузчика (loader utility, см. [4]) после сборки файла .DXE (это файл двоичного исполняемого кода), в результате получается файл загрузки *.ldr. Содержимое этого файла загрузки должно быть записано в память flash. После того, как boot kernel выполнит копирование всех блоков в их места назначения RAM, код kernel выполнит переход в начало память L1 Instruction, и с этого места начнется выполнение кода. Следовательно, все блоки кода и данных были скопированы в их конечные места назначения, прежде чем код программы начнет выполняться.

Выводы BMODE это специальные ножки на корпусе процессора, которые выбирают режим его запуска (boot mode) после включения питания или сброса.

ADSP BF532 TQFP176 BMODE pins

Выбор осуществляется подачей уровней лог. 0 и лог. 1 на ножки BMODE. Здесь для примера приведено описание работы ножек BMODE для нескольких процессоров (подробное описание выводов BMODE см. в даташите на Ваш процессор).

[ADSP-BF531, ADSP-BF532, ADSP-BF533, ADSP-BF538]

BMODE[1:0] Источник загрузки Адрес старта
00 Пропуск запуска загрузчика, встроенного в процессор, bypass mode, или Bypass Boot ROM. Запускается код из 16-битной асинхронной внешней памяти (Async Bank 0, обычно это память FLASH). 0x20000000
01 Работает код загрузчика (Boot ROM). Он разбирает поток данных загрузки (содержимое сгенерированного и записанного LDR-файла) из 8-битной или 16-битной памяти FLASH (подробнее см. [3]), и загружает оттуда данные в память процессора (в L1 или L3). 0xEF000000
10 То же самое, что и предыдущий вариант, но на этот раз поток загрузки берется из 8-битного устройства хоста SPI. Другими словами, передача данных загрузки происходит под управлением внешнего микроконтроллера. Процессор DSP играет роль подчиненного устройства на шине SPI (подробнее см. [3]).
11 То же самое, что предыдущие 2 варианта, но поток загрузки процессор получает из последовательной микросхемы flash с интерфейсом SPI (с адресным диапазоном 8, 16 или 24 бита). Процессор DSP играет роль главного устройства на шине SPI (подробнее см. [3]).

Создание потока загрузки:

1. Откройте диалог настройки свойств проекта через меню Project -> Project Options..., в разделе настроек Project, область Target, измените в выпадающем списке Type на выбор Loader file (до этого там могло быть выбрано Executable file). В чем разница: выбор Loader file генерирует файл с расширением *.ldr, который содержит в себе данные boot stream, а выбор Executable file генерирует файл *.dxe, который содержит двоичный исполняемый код (оба этих файла по умолчанию попадут в подкаталог Debug или Release, в зависимости от выбранной конфигурации).
2. В разделе Load, область Boot Mode убедитесь, что что радиокнопка стоит в положении Flash/PROM, и в области Output Width стоит выбор 16-bit.
3. В разделе Load -> Kernel снимите галочку Use boot kernel.
4. В разделе Load -> Splitter убедитесь, что не установлена галочка Enable ROM splitter.
5. Кликните на кнопке OK, чтобы подтвердить выбор и закрыть окно диалога Project Options.
6. Сохраните изменения в проекте (File -> Save All).
7. Пересоберите проект (Project -> Rebuild Project).

Когда проект пересобран (после создания файла Debug\BF533 Flags C.dxe), среда VisualDSP++ автоматически, в соответствии с последними Вашими настройками, запустит утилиту создания потока загрузки elfloader.exe [4], которая создаст boot stream из файла .DXE. В результате поток загрузки (boot stream) будет находиться в файле Debug\BF533 Flags C.ldr.

Программирование потока загрузки в плату EZ-KIT Lite:

1. Выберите Tools -> Flash Programmer [5]. Это плагин, который поставляется вместе со средой разработки VisualDSP++. Внимание: чтобы плагин Flash Programmer можно было запустить, предварительно должна быть активирована сессия внутрисхемного JTAG-эмулятора (через меню Session -> Select Session). После выбора в меню Tools -> Flash Programmer запустится окно диалога программатора.
2. В поле Driver выберите файл ...\ Blackfin\ Examples\ ADSP-BF533 EZ-KIT Lite\ Flash Programmer\ BF533EzFlashDriver.dxe. Примечание: Driver это обычная программа для процессора, которая служит для программирования памяти. Эта программа работает совместно с плагином Flash Programmer.
3. Кликните на кнопку Load Driver. В области окна Message center: должна появиться надпись "Success: Driver loaded.", сигнализирующая об успешной активизации программы драйвера.
4. Перейдите на закладку Programming. В области File format переставьте радиокнопку на Binary. В поле ввода Offset укажите значение 0x0. Выберите Data file: Debug\BF533 Flags C.ldr, файл загрузки, который был получен в Вашем проекте.
5. Кликните на кнопку Program. Boot stream из файла загрузки будет записан в память FLASH платы EZ-KIT Lite.
6. Кликните на кнопку OK, и выйдите из плагина Flash Programmer.
7. Выйдите из среды разработки VisualDSP++.
8. Выключите питание платы EZ-KIT Lite, и снова включите. Вы увидите, что что после включения питания светодиоды начали мигать.

Это означает, что при включении питания программа, записанная в память flash, успешно разбирается загрузчиком, записывается в L1 и после этого запускается на выполнение.

Использование Boot Kernel и SDRAM. Иногда программа может быть рассчитана на использование SDRAM - в том случае, если размера L1 по каким-либо причинам не хватает (в этом случае может быть включена опция USE_CACHE стандартного файла .LDF). Если SDRAM используется, то она должна быть предварительно инициализирована до того момента, как boot kernel сможет выполнить копирование блока из boot stream в память SDRAM. Этого можно достичь использованием блоков инициализации.

Утилите загрузки Elfloader можно предоставить файл инициализации. Это обычный файл .DXE, собранный для нашего процессора с использованием файла ADSP-BF533_ASM.ldf (его можно найти в папке ...\Blackfin\ Examples\ ADSP-BF533 EZ-KIT Lite\ LED Blink (ASM)\). Утилита загрузчика распаковывает секцию исполняемого кода файла инициализации (program section), и вставляет её в поток загрузки вместе со специальным маркером. Этот маркер показывает для boot kernel, что эту секцию кода следует запустить немедленно, сразу после её копирования в L1. Таким образом, этот первый блок можно использовать для запуска в работу памяти SDRAM перед тем, как компоненты приложения могут быть загружены в SDRAM (память SDRAM требует для своей работы соответствующего программного конфигурирования контроллера EBIU процессора [6], иначе SDRAM не сможет работать).

Объектный файл инициализации должен содержать код, который работает как функция, которая спроектирована с учетом сохранения всех используемых ею регистров; она должна сохранить на входе и восстановить на выходе все регистры, которые были модифицированы. Поскольку этот код инициализации вызывается наподобие функции, он должен завершаться инструкцией RTS. Код boot kernel предоставит для кода инициализации достаточное место под стек в памяти L1 scratchpad.

Для примера кода инициализации см. Листинг 1 в статье [3] (он взят из VisualDSP++ Loader Manual for Blackfin Processors [2]).

[Запуск программы из FLASH через Bypass Mode]

Когда процессор ADSP-BF533 Blackfin определил, что он сконфигурирован (теми же ножками BMODE) после сброса в bypass mode, то он не обрабатывает boot stream. Вместо этого он пропускает запуск boot ROM (bypass boot ROM, отсюда название режима bypass mode), и переходит на начальный адрес в памяти flash, и непосредственно запускает код, который там находится. Это означает, что код приложения сразу начинает работать в памяти flash, вместо того, чтобы быть скопированным в RAM (L1 или L3 SDRAM) и затем быть запущенным оттуда.

В этом режиме любой код или данные (L1 Instruction, L1 Data A или L1 Data B) пока не были скопированы, когда управление передано в приложение. Обычно приложение будет должно как минимум само инициализировать свои элементы данных в их первоначальные значения, перед тем как они будут модифицированы в процессе нормального выполнения приложения.

Секции данных могут быть инициализированы автоматически путем использования квалификаторов инициализации в файле .LDF, и путем разрешения инициализации памяти в процессе линковки приложения. Когда это сделано, конечный файл .DXE будет содержать таблицу данных, подобную содержащейся в boot stream (который генерируется утилитой загрузки elfloader.exe [4] при загрузке с участием boot ROM). Эта таблица описана в секции Memory Initializer Boot Stream.

Обработка данных инициализации (Data Stream) в приложении на ассемблере. Библиотеки кода (run-time libraries) содержат подпрограмму для обработки этой таблицы, которую программист может вызвать для осуществления инициализации данных. Эта подпрограмма называется _mi_initialize (с одним нижним подчеркиванием, что применяется для вызова из ассемблера). Она не принимает параметров, и вернет целочисленный результат. Ноль означает успешную инициализацию, и отрицательное значение означает, что таблица инициализации была ошибочной.

Альтернативно программист на ассемблере может обработать таблицу вручную. На эту таблицу указывает глобальная переменная ___inits (три символа подчеркивания). Этот символ является указателем, и обычно он является указателем NULL (указывает на адрес 0x00000000). Если файл .DXE был обработан во время линковки для инициализации run-time, то символ ___inits будет модифицирован так, чтобы он указывал на начало таблицы. Подробнее описание формата этой таблицы см. в разделе "Memory Initializer Boot Stream".

Обработка данных инициализации (Data Stream) в приложении на C/C++. Разработчику C/C++ не нужно явно вызывать run-time функцию mi_initialize (в этом случае у функции нет подчеркиваний, это для C/C++); это делает код start-up библиотек C/C++ run-time, он автоматически вызовет mi_initialize перед запуском функции main.

Ограничения mi_initialize. На момент написания этого документа поддержка run-time инициализации имела 2 ограничения:

• Не поддерживается запись в память L1 Instruction. Таким образом, нельзя копировать код из flash в L1 Instruction memory.
• Не предоставляются средства запустить код инициализации для SDRAM. Таким образом, mi_initialize нельзя использовать для копирования кода или данных из flash в SDRAM, за исключением тех случаев, когда работа памяти SDRAM была разрешена перед запуском процесса инициализации.

Разработчик на ассемблере может обойти эти ограничения путем прямой обработки таблицы данных. Разработчик C/C++ может обойти эти ограничения путем альтернативной реализации mi_initialize.

Приложение Blink не использует SDRAM, так что второе ограничение не создает проблемы для применения примера из этого документа. Чтобы разрешить первое ограничение в контексте этого документа, приложение Blink будет слинковано так, чтобы оно полностью работало из памяти flash.

[Создание Flash-based приложения]

Создание приложения, которое работает полностью из FALSH, состоит из 2 шагов. Сначала нужно подготовить измененный файл *.LDF, чтобы сменить привязку кода приложения и выровнять инициализацию данных. Затем проект должен быть сконфигурирован для генерации образа flash, предназначенного для прямого выполнения, не для загрузки (no-boot flash image).

Создание файла LDF для кода во Flash. Приложение Blink не имеет своего собственного файла .LDF; вместо этого оно использует файлы .LDF компилятора по умолчанию. Таким образом, начнем с создания .LDF-файла для проекта следующим образом:

1. Выберите в меню Tools -> Expert Linker -> Create LDF..., кликните Next.
2. Выберите тип проекта C, кликните Next.
3. Для однопроцессорной сессии ADSP-BF533 установки по умолчанию достаточны, поэтому тут опять кликните Next.
4. Кликните Finish.
5. Откроется окно графического редактора Expert Linker, закройте его.

Примечание: в некоторых случаях пункт меню Create LDF... будет недоступен, тогда файл LDF следует создать с помощью редактирования свойств проекта. Откройте окно диалога свойств проекта, в самой нижней части дерева настроек перейдите в пункт Add Startup Code/LDF. Переставьте радиокнопку на пункт Add an LDF and startup code, и нажмите OK, чтобы закрыть окно редактирования свойств проекта. Файл BF538 Flags C.ldf появится в корневой папке проекта (там же, где находится файл BF533 Flags C.dpj), и ссылка на него появится в папке Linker Files.

6. Выполните правый клик на сгенерированный файл BF533 Flags C.ldf в окне дерева файлов проекта (он находится в папке Linker Files), и выберите в контекстном меню Open with Source Window.

Размещение секций кода во Flash. Теперь файл .LDF нужно изменить так, чтобы код был привязан к областям карты памяти, где находится память flash. Это потребует следующих изменений:

• Привяжите секцию program_ram к MEM_ASYNC0 (вместо привязки к MEM_L1_CODE).
• Привяжите секцию l1_code к MEM_ASYNC0 (вместо MEM_L1_CODE_CACHE).

Из-за того, что этот код теперь отображен на flash, он будет напрямую запускаться из памяти flash.

[Размещение данных (Data) во Flash или RAM]

Данные констант (Constant Data). Для данных констант доступно два варианта: данные могут быть напрямую отображены на память flash, потому что они не меняются, или они могут быть отображены на RAM, и быть инициализированы run-time. Первый вариант экономит место под код и данные, но зато второй работает быстрее. Принятие решения зависит от программиста, исходя из предназначения приложения.

В этом примере мы предполагаем, что во входной секции constdata находятся постоянные данные, к которым, тем не менее возможен частый доступ со стороны приложения (например, это могут быть FIR-коэффициенты, т. е. коэффициенты не рекурсивного цифрового фильтра). Таким образом, мы отобразим эти входные секции на RAM, чтобы инициализировать их вручную, во время работы программы (run-time). Однако компилятор также генерирует некоторое количество таблиц, которые используются для конструктора C++ и поддержки исключений (exception), и эти таблицы вряд ли будут часто использоваться, так что мы их отобразим напрямую на память flash.

Это удобное решение для нашего примера, потому что constdata будет неправильным для выходной секции в файлах .LDF: через него также привязываются входные секции L1_data_a, cplb_data, data1 и voldata, которые не константы. Если бы мы решили поместить constdata в память flash, то тогда нужно гарантировать, что эти другие входные секции будут отдельно привязаны куда-то еще к RAM.

Мы разделим выходную секцию constdata на секции constdata и cplustables; входная секция constdata будет отображена на первую из них, в MEM_L1_DATA_A, и секции ctor, ctorl, .gdt, .gdtl, .edt, .cht, .frt и .frtl будут привязаны ко второй секции, в MEM_ASYNC0.

Кроме того, потому что секция constdata должна быть обработана run-time для её инициализации, мы пометим её квалификатором инициализации RUNTIME_INIT. Это используется во время линковки (link-time), когда конфигурируется инициализация run-time, чтобы показать, что эта секция имеет данные, которые нуждаются в сохранении в ROM и копировании в RAM run-time.

Эти две модифицированные секции из файла .LDF показаны в Листинге 3.

/* Листинг 3. Constant Data, размещенные в памяти FLASH. */
constdata RUNTIME_INIT
   {
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS($OBJECTS(L1_data_a) $LIBRARIES(L1_data_a))
      INPUT_SECTIONS($OBJECTS(cplb_data) $LIBRARIES(cplb_data))
      INPUT_SECTIONS($OBJECTS(data1) $LIBRARIES(data1))
      INPUT_SECTIONS($OBJECTS(voldata) $LIBRARIES(voldata))
      INPUT_SECTIONS($OBJECTS(constdata) $LIBRARIES(constdata))
   } >MEM_L1_DATA_A
 
cplustables
   {
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(ctor) $LIBRARIES(ctor) )
      INPUT_SECTIONS( $OBJECTS(ctorl) $LIBRARIES(ctorl) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.gdt) $LIBRARIES(.gdt) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.gdtl) $LIBRARIES(.gdtl) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.edt) $LIBRARIES(.edt) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.cht) $LIBRARIES(.cht) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.frt) $LIBRARIES(.frt) )
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(.frtl) $LIBRARIES(.frtl) )
   } >MEM_ASYNC0

Данные переменных (Variable Data). Для данных переменных также имеется 2 варианта на выбор: данные могут быть инициализированы нулем, или они могут быть инициализированы ненулевой константой. Оба варианта обрабатываются примерно одинаково; отличаются они только тем, что у второго варианта в определении переменной используется инициализирующий квалификатор.

Выходные секции (data, l1_data_a и l1_data_b) содержат данные, которые могут быть инициализированы произвольными константами. Каждая из этих секций помечена квалификатором RUNTIME_INIT, чтобы показать, что их содержимое должно копироваться из flash в соответствующие области RAM (MEM_L1_DATA_B, MEM_L1_DATA_A_CACHE и MEM_L1_DATA_B_CACHE соответственно). Они привязывают входные секции data1, voldata, L1_data_a, L1_data_b и cplb_data, которые все содержат некоторые ненулевые значения.

Выходная секция bsz отображает входные секции bsz, которые содержат только те данные, которые должны быть заполнены нулями в start-up. Эта выходная секция уже промаркирована квалификатором ZERO_INIT, так что run-time инициализация будет просто заполнять нулями ячейки секции вместо того, чтобы копировать туда байты из flash. Эта секция не нуждается в модификации.

Куча и стек (Heap, Stack). Эти секции, которые должны быть в RAM, не нуждаются в квалификаторах инициализации, так как они инициализируются явно библиотекой run-time. В можете решить, что удобно было бы пометить эти секции как ZERO_INIT, чтобы предварительно очистить их содержимое, однако это не требуется.

Таблицы инициализации. Секции bsz_init and .meminit должны быть привязаны к памяти flash. Секция bsz_init, которая очень маленькая, будет содержать указатель на начало инициализационных данных. Секция .meminit это специальная секция, которая заполняется после линковки таблицами, нуждающимися в выполнении run-time инициализации. В результате получается что-то похожее на boot stream: здесь содержаться блоки данных, которые копируются из памяти flash в области RAM.

Листинг 4 показывает, как эти секции отображаются в файле .LDF.

/* Листинг 4. Таблицы инициализации в памяти Flash. */
bsz_init
   {
      INPUT_SECTION_ALIGN(4)
      INPUT_SECTIONS( $OBJECTS(bsz_init) $LIBRARIES(bsz_init))
   } >MEM_ASYNC0
   .meminit {} >MEM_ASYNC0

RAM против ROM. Файл .LDF декларирует MEM_ASYNC0–3 как тип RAM. Это должно быть изменено для типа ROM. Иначе утилита ROM splitter, которая используется для генерации файла Intel hex, будет игнорировать эти секции, и ничего не будет программироваться в память flash.

Адрес старта (Start Address). Файл .LDF явно задает символ начала программы, чтобы указать адрес, где находится первая выполняемая инструкция программы. Обычно этот адрес находится в памяти L1 Instruction, но для приложения, которое пропускает загрузку и сразу начинает работать в памяти flash, это нужно поменять на адрес начала flash, как показано в Листинге 5.

/* Листинг 5. Обозначение адреса старта,
   чтобы он указывал на начало памяти Flash. */
RESOLVE(start,0x20000000)

Разрешение инициализации памяти. Инициализация памяти run-time разрешается путем добавления дополнительного флага на стадии линковки. Имеется 2 способа выполнить это:

• Если Вы собираете приложение в среде VisualDSP++, разрешите инициализацию памяти вводом -meminit в поле Additional options раздела Link окна диалога свойств проекта (Project Options).
• Если Вы собираете приложение из командной строки, разрешите инициализацию памяти добавлением опции -mem в командную строку компилятора ccblkfn.

Если инициализация памяти run-time была выбрана одним из этих методов, то линкер выполнит дополнительный проход после генерации файла .DXE, что даст следующие эффекты:

• Выходные секции, помеченные как ZERO_INIT, будут записаны в таблицу инициализации.
• Выходные секции, помеченные как RUNTIME_INIT, будут записаны в той же таблице. Дополнительно содержимое этих секций добавляется в эту таблицу.
• Таблица инициализации записывается выходную секцию .meminit.
• Указатель на начало таблицы записывается в выходную секцию bsz_init.

Когда приложение начинает выполнение, то происходит следующее:

• Библиотека run-time будет следовать за указателем в таблице инициализации.
• Для каждой секции, записанной в таблице инициализации, библиотека run-time выполнит заполнение нулями или копирование начальных значений из таблицы инициализации в эту секцию, в зависимости от квалификатора инициализации секции.

Таким образом, когда секции .meminit и bsz_init привязаны к flash (как было описано ранее), и эти секции отображены на изменяемую память пометкой подходящего квалификатора инициализации, инициализация памяти run-time будет гарантировать, что секции изменяемых данных будут правильно сконфигурированы, когда приложение начнет выполнение.

Создание образа No-Boot Flash. Файл загрузчика (loader file) создается подобным образом, как и образ файла для загрузки (ROM-based loader file). Поскольку чистый выполняемый код помещается напрямую в память flash вместо boot stream, используется опция ROM splitter.

В разделе Load свойств проекта (Project Options):

1. Перейдите в пункт Splitter, и поставьте галочку Enable ROM splitter. Из-за того, что разрешен ROM splitter, опции категорий Loader и Boot kernel игнорируются, так что их менять не нужно.
2. Установите формат в Hex.
3. Установите параметр Mask address в значение 29.
4. В поле ввода Output file: укажите имя для выходного файла.
5. Кликните OK.

Теперь сохраните изменения в проекте, и пересоберите его.

В процессе пересборки проекта, после создания Debug\BF533 Flags C.dxe, система VisualDSP++ запустит утилиту Elfloader для генерации файла загрузки (для которого было дано имя на шаге 4) из этого файла .DXE.

Примечание: некоторые версии VisualDSP++ могут генерировать предупреждение линкера li2131 "Input sections of incompatible init qualifier detected in the output section". Эти предупреждения срабатывают, когда входные секции квалификатора инициализации не полностью соответствуют выходным секциям квалификатора инициализации. Секция data1 библиотеки run-time собирается без квалификаторов, так что её отображение на выходную секцию с квалификатором RUNTIME_INIT приведет к выдаче этого предупреждения, которое в данном случае можно безопасно игнорировать.

Убедитесь, что входные секции не отображены на выходные секции с квалификаторами инициализации, которые несовместимы для содержимого входных секций. Например, ни входные секции с ненулевым содержимым не могут быть безопасно отображены на выходную секцию с квалификатором ZERO_INIT, ни входные секции с квалификатором ZERO_INIT не могут быть безопасно отображены на выходную секцию с квалификатором NO_INIT. Обе эти недопустимые комбинации означают, что содержимое секций будет инициализировано некорректно.

Загрузка Loader File в плату EZ-KIT Lite:

1. Выберите в меню Tools -> Flash Programmer... (сессия эмулятора JTAG при этом должна быть активна). Появится окно плагина Flash Programmer.
2. В поле Driver выберите файл ...\ Blackfin\ Examples\ ADSP-BF533 EZ-KIT Lite\ Flash Programmer\ BF533EzFlashDriver.dxe.
3. Кликните на кнопку Load Driver. В области окна Message center: должна появиться надпись "Success: Driver loaded.", сигнализирующая об успешной активизации программы драйвера.
4. Перейдите на закладку Programming. В области File format переставьте радиокнопку на Intel Hex. Выберите в поле Data file: файл загрузки, который был ранее указан в категории настройки выходного файла ROM splitter.
5. Кликните на кнопку Program. Boot stream из файла загрузки будет записан в память FLASH платы EZ-KIT Lite.
6. Кликните на кнопку OK, и выйдите из плагина Flash Programmer.
7. Выйдите из среды разработки VisualDSP++.
8. Выключите питание платы EZ-KIT Lite, и снова включите.

При включении питания код boot ROM процессора попытается интерпретировать запрограммированное содержимое памяти flash как поток загрузки (boot stream). Поскольку теперь это не boot stream, а чистый исполняемый код, то процесс загрузки потерпит неудачу. Вместо этого содержимое flash должно быть запущено напрямую, как это описано ниже.

Как запустить программу из памяти Flash. Для целей диагностики программу в памяти flash можно запустить под управлением эмулятора, с использованием программы, показанной в Листинге 6.

//Листинг 6. Запуск программы ROM Splitter, находящейся в памяти flash.
int main(void)
{
   void (*program)(void) = (void (*)(void))(0x20000000);
   (*program)();
}

Но чтобы программа выполнилась при включении питания без этого кода, процессор в момент сброса должен быть переведен в bypass mode. Это осуществляется установкой в нужное состояние ножек BMODE. Для платы EZ-KIT Lite Revision 1.2 это осуществляется замыканием R11 (более современные ревизии плат EZ-KIT имеют перемычки для управления уровнями ножек BMODE).

R11 размещен слева от процессора Blackfin (перемещаясь влево от процессора, Вы найдете C198, R11 и R10). У резистора R11 есть только 2 контакта, без соединителя между ними. Чтобы загрузить процессор в bypass mode, установите перемычку между этими двумя контактами в момент передергивания питания или в момент нажатия кнопки Reset (находится в левом верхнем углу платы EZ-KIT Lite). Вы увидите, что код из flash запустится, и светодиоды начнут мигать.

[Производительность кода, работающего из FLASH]

Память flash, описанная в этом апноуте, подключена напрямую к блоку интерфейса внешней шины, External Bus Interface Unit (EBIU) [6]. При сбросе для этого банка асинхронной памяти (Asynchronous Memory bank, соответствующей памяти flash) по умолчанию дается самый большой интервал тактов ожидания и удержания (wait states/hold states), чтобы они были совместимы с возможным подключением медленной внешней памяти flash. С этими задержками максимальная скорость выполнения одной 16-битной инструкции составит 15 системных тактов (частота системной шины SCLK). После сброса значение SCLK для платы ADSP-BF533 EZ-KIT Lite равна 54 МГц (27 МГц * 10 MSEL / 5 SSEL).

В системе инструкций архитектуры Blackfin [8], наиболее часто используемые инструкции закодированы 16 битами. Однако система команд также содержит 32-битные и даже 64-битные инструкции. Каждая выборка инструкции из flash имеет размер 16 бит, так что чтение 64-битной инструкции из flash потребует четырех 16-битных чтений из flash. Из-за того, что flash является "асинхронной" памятью, включение кэша инструкций и пометка памяти flash как "кэшируемой" не увеличит скорость кода, который выполняется первый раз. Однако если код из flash запустится снова, то он будет работать уже из кэша, так что последующие выполнения этих инструкций получат максимальную производительность, соответствующую выполнению одной инструкции за такт.

Если память flash подключенная к системе, поддерживает меньшее количество для wait states и hold time меньше, то максимальная скорость выборки инструкций из 16-битной памяти flash может достигать одной 16-bit битной выборке на 2 системных такта SCLK. Настройка тактов ожидания может быть осуществлена в функции инициализации загрузки (init booting function), как это описано в руководстве [2], где соответствующим образом конфигурируется контроллер EBIU.

Другой способ улучшить быстродействие состоит в использовании оверлеев (overlay). В этом методе сегменты кода передаются во внутреннюю память с помощью DMA перед тем, как начнет выполняться актуальный код приложения. Контроллер DMA [7] используется для перемещения кода в один из банков памяти, в то время как процессор выполняет код из другого банка.

Для дополнительной информации по контроллеру EBIU обратитесь к соответствующему руководству Вашего процессора (для ADSP-BF538 см. статью [6]).

[Memory Initializer Boot Stream]

Утилита инициализации памяти (Memory Initializer) запускается во время линковки, когда используется опция командной строки –mem компилятора, или когда используется опция командной строки –meminit линкера. Утилита Memory Initializer обрабатывает линкуемое приложение и собирает поток загрузки (boot stream) в секции .meminit. Формат этого потока описывается структурой, определенной в заголовочном файле < meminit.h >. Здесь эта структура рассматривается подробнее.

Указатель ___inits. Начало boot stream задает глобальный указатель ___inits (на ассемблере этот символ имеет 3 начальных знака нижнего подчеркивания) или __inits (на C используется 2 начальных знака нижнего подчеркивания). В приложении, которая не обрабатывается утилитой Memory Initializer, значение этого указателя равно 0, показывая тем самым, что нет потока загрузки для обработки. Когда утилита Memory Initializer обрабатывает приложение, она создает поток загрузки, и затем обновляет этот глобальный указатель, чтобы он указывал на начало потока загрузки.

Декларация этого указателя на языке C показана в Листинге 7.

//Листинг 7. Декларация указателя __inits.
typedef struct mi_table_header_t mi_table_header;
extern mi_table_header *__inits;

Table Header. Заголовок таблицы (table header) показывает формат и размер последующего потока загрузки. Это определено в структуре, показанной Листингом 8.

//Листинг 8. Структуры заголовка для Memory Initializer.
struct mi_magic_t {
   unsigned char version;
   unsigned char reserved;
   unsigned short magic;
};
 
struct mi_table_header_t {
   struct mi_magic_t magic;
   unsigned long num_blocks;
   struct mi_block_header_t blocks[1]; /* произвольное количество блоков */
};
 
typedef struct mi_magic_t mi_magic;

Поле magic идентифицирует формат boot stream. Для процессоров Blackfin, допустимы только следующие значения полей структуры mi_magic_t:

Таблица 1. Поля структуры mi_magic_t.

Поле Значение
version 0
reserved Зарезервировано для будущего использования
magic MI_MAGIC_BLACKFIN (0xFFFF)

Поле blocks[] определено как массив, у которого длина равна 1 блок. В реальности это показывает позицию первого блока в потоке. Поле num_blocks показывает общее количество блоков в потоке, и таким образом, он дает реальный размер поля blocks[].

Структура блока. Каждый блок в boot stream определяет порцию памяти для инициализации, либо путем копирования данных (включая код) из boot stream в указанную область памяти, либо заполнением нулями указанную область памяти. Структура блока показана в Листинге 9.

//Листинг 9. Структура одного блока для Memory Initializer.
struct mi_block_header_t {
   char *addr;
   unsigned long byte_count;
   unsigned long flags;
   unsigned long pattern_bytes;
};

Поле flags является набором бит, поделенным на несколько подсекций:

Таблица 2. Назначение бит поля flags.

Биты Использование
0-1 Тип памяти: MI_MEM_US (0x02)
2-5 Размер слова: MI_WS_8BITS (0x00)
6-8 Вид блока
9-31 Зарезервировано для будущего использования

Тип памяти (биты 0..1) показывают способ инициализации памяти блока. Для Blackfin это всегда устанавливается в значение MI_MEM_US (0x02). Этим показывается, что тип памяти не фиксирован (например, Program Memory или Data Memory).

Размер слова (биты 2..5) показывают размеры адресуемых единиц для памяти, которая инициализируется. Поскольку Blackfin имеет архитектуру с байтовой адресацией, то это поле всегда равно MI_WS_8BITS (0x00).

Вид блока (биты 6..8) показывает, как должен интерпретироваться блок при инициализации области памяти.

Виды блока. Вид блока показывает, какая инициализация должна быть для этой области памяти. Возможны следующие значения вида блока:

Таблица 3. Виды блока.

Значение Назначение
MI_BT_RAW (0x00) Область памяти будет инициализирована путем копирования данных из потока загрузки.
MI_BT_ZERO (0x01) Область память будет инициализирована путем заполнения нулями.
MI_BT_REP (0x03) Область памяти будет инициализирована путем повторяющимся заполнением шаблоном данных, находящимся в потоке загрузки.

Обратите внимание, что значение 0x02 не используется.

Raw Block (MI_BT_RAW). Для чисто двоичного блока (raw block) будут значимыми поля addr и byte_count. В потоке загрузки за блоком будут сразу идти byte_count байт данных, которые копируются по адресу addr. Если в потоке загрузки есть еще блоки, то следующий блок будет следовать за данными для этого raw block.

Zero Block (MI_BT_ZERO). Для этого вида блока также значимыми полями будут addr и byte_count. Zero block показывает, что эта область памяти начиная с адреса addr имеет размер byte_count байт, и должна быть заполнена нулями zero-filled. За этим блоком не идут данные, и если в потоке загрузки есть еще блоки, то следующий блок будет сразу идти за этим zero block.

Repeating Block (MI_BT_REP). Для этого вида блока значащими будут поля addr, byte_count и pattern_bytes. За блоком будет идти pattern_bytes байт данных, которые должны быть скопированы в byte_count байт памяти, начиная с адреса addr. Если в этом потоке загрузки есть еще блоки, то следующий блок будет идти сразу после повторяемого шаблона данных. Для VisualDSP++ 3.5 повторяемые шаблоны ограничены размерами нормальных данных: 1, 2 или 4 байта. Для будущих версий VisualDSP++ утилита Memory Initializer возможно будет способна детектировать более длинные последовательности повторяемых шаблонов. Здесь нет гарантии, что byte_count будет нацело делиться на pattern_bytes; шаблон должен копироваться с повторениями в указанную область памяти, пока не будет скопировано byte_count байт данных.

Последующие блоки. Для процессоров Blackfin блоки всегда выравнены на границу из 4 байт при размещении данных на устройстве flash. Это достигается следующим образом:

• Первый блок является частью заголовка таблицы (table header), который выравнен всегда. Таким образом, условие выравнивания первого блока выполняется.
• Для zero block нет никаких данных между zero block и следующим блоком, так что условие выравнивания следующего блока всегда выполняется (потому что размер структуры mi_block_header_t делится нацело на 4).
• Для raw block repeating block есть некие данные между этим блоком и следующим блоком, поэтому может быть длина данных, не соответствующая выравниванию адреса следующего блока на 4 байта. Если byte_count (для raw block) или pattern_bytes (для repeating block) не делятся нацело на 4, то будут вставлены 1..3 дополняющих байта перед данными следующего блока, чтобы гарантировать выравнивание адреса блоков на 4 байта.

Инициализация памяти L1 Instruction. Если секции файла .LDF, которые отображают код на L1 Instruction Memory, помечены как RUNTIME_INIT, то утилита Memory Initializer создаст блоки в boot stream, которые также будут инициализировать эти секции. На момент создания этого апноута библиотечная run-time функция mi_initialize не поддерживает эту возможность, так как L1 Instruction Memory не может быть записана как обычные данные. Если разработчику приложения нужно инициализировать память L1 Instruction, то boot stream должен быть обработан непосредственно, с использованием регистров управления кэшем, чтобы записать данные в память L1 Instruction.

[Ссылки]

1. EE-239 Running Programs from Flash on ADSP-BF533 Blackfin® Processors site:analog.com.
2. VisualDSP++ 5.0 Loader and Utilities Manual site:analog.com.
3. Как происходит загрузка ADSP-BF533 Blackfin.
4. Blackfin: утилита elfloader.exe.
5. VisualDSP API программирования FLASH для процессоров Blackfin.
6. ADSP-BF538: блок интерфейса внешней шины.
7. ADSP-BF538: DMA.
8. Blackfin: система команд (ассемблер) - часть 1.

 

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


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

Top of Page