Программирование DSP uClinux на архитектуре Blackfin DSP Mon, September 16 2019  

Поделиться

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

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

uClinux на архитектуре Blackfin DSP Печать
Добавил(а) microsin   

За последние годы Linux стала популярным выбором операционной системы не только для рынка PC и серверов, но также и для разработки встраиваемых устройств - в частности для пользовательской электроники, маршрутизаторов и сетевых коммутаторов, приложений Интернета, промышленных приложений и автомобильной электроники.

Достоинство Embedded Linux в том, что она свободна от лицензионных отчислений, поставляется с открытым исходным кодом, и представляет компактное решение, дающее надежную основу для разработки проекта и его развития в будущем. Linux это полнофункциональная операционная система (OS), с поддержкой различных стандартов сетевых соединений и протоколов файловых систем - очень важное требование для встраиваемых систем, потому что им требуется обеспечить "соединяться и вычислять везде и всегда", в любых рабочих условиях.

Поскольку Linux имеет модульную структуру, она легко ужимается путем удаления её программ-утилит, инструментальных средств и других системных сервисов, которые не нужны в целевом встраиваемом окружении, где работает приложение. Это очевидное достоинство для компаний, использующих Linux на рынке встраиваемых устройств, которое позволяет быстрее выводить продукцию на рынок, с достаточной гибкостью и надежностью.

Для разработчиков встраиваемых приложений стыковка архитектур процессоров Blackfin и uClinux может быть представлять определенный интерес. Процессоры Blackfin [2] комбинируют в себе вычислительную мощь DSP и функционал микроконтроллеров, что удовлетворяет требованиям приложений цифрового звука и коммуникаций. Комбинация ядра DSP с традиционной архитектурой микроконтроллера на одном чипе позволяет избежать ограничений, сложности и высокой стоимости традиционных гетерогенных многопроцессорных систем.

Все процессоры Blackfin комбинируют в одной аппаратной архитектуре, специально спроектированной под цели обработки сигналов, с достоинствами набора инструкций стиля RISC и возможностями системы команд типа "одна инструкция / много данных (Single-Instruction Multiple-Data, SIMD), позволяющий упростить создание мультимедийных приложений. Ядро Micro Signal Architecture (MSA) и двойной блок вычислений MAC (Multiply Accumulator, умножение с накоплением), модифицированная Гарвардская архитектура, которая была разработана для поддержки не параллельных производительных вычислений, а также стандартный принцип организации потока выполнения программы и операции произвольного манипулирования битами - все это активно используется в OS. Оба блока MAC можно использовать в операциях вычислений, занимающих один или два цикла на инструкцию, что дает широкую полосу обработки данных. Например существует инструкция двойной обработки MAC Blackfin:

R3 = (A1 += R7.H * R6.H), R2 = (A0 += R7.L * R6.L);

Как показано на рис. 1 ниже, одноядерные процессоры Blackfin имеют 2 больших блока встроенной памяти, обеспечивающих широкополосный доступ со стороны ядра (так называемая память L1). Эти блоки памяти доступны для ядре на полной скорости, без дополнительных задержек (на частотах до 756 МГц). Эти два блока памяти находятся непосредственно рядом с ядром процессора, и её можно конфигурировать либо как память данных или инструкций, либо как кэш [3].

uClinux Blackfin single core fig01

Рис. 1. Одноядерный процессор Blackfin.

Когда имеется сконфигурированный кэш, то скорость выполнения кода из "внешней" памяти SDRAM будет почти на том же самом уровне, как и скорость выполнения кода из внутренней памяти L1. Эта возможность особенно хорошо подходит для запуска ядра uClinux, которое не помещается целиком во внутреннюю память Blackfin. Также при программировании на языке C заботу об оптимизации доступа к памяти можно переложить на кэш системы.

Существует множество коммерческих и некомммерческих форков ядер и дистрибутивов Linux. Одно из специальных ответвлений - ядро uClinux (www.uclinux.org). Это порт ядра Linux, разработанный для аппаратной совместимости, без использования блока управления памятью (Memory Management Unit, MMU).

Хотя патч ядра uClinux было добавлен в официальное ядро Linux 2.6.x, наиболее свежую версию uClinux и соответствующие разработки можно найти на страничке uClinux Project Page и Blackfin/uClinux Project Page (www.blackfin.uclinux.org). Существуют патчи, используемые коммерческими версиями Linux, содержащие дополнительные улучшения, средства разработки и документацию, которые дают лицензированным пользователям дополнительные преимущества в виде ускорения создания мощных приложений на основе uClinux.

Дополнительно www.uclinux.org предоставляет разработчикам дистрибутив uClinux, который включает три разных ядра (2.0.x, 2.4.x, 2.6.x) вместе с требуемыми библиотеками, сервисный инструментарий, базовые оболочки командной строки (Linux shells) и широкий диапазон дополнительных программ, таких как веб-сервер, аудиоплеер, языки программирования и графическую утилиту конфигурирования. Есть также программы, специально разработанные с учетом требований минимального использования памяти и эффективного использования ресурсов.

Один из примеров - busybox [4], двоичное приложение для множественного запуска, в котором содержится функционал множества программ. Например, если busybox слинкован с ls и содержит код ls, то он работает как команда ls. Выгода от такого подхода в том, что busybox сохраняет некоторые накладные расходы для уникальных библиотек, и эти маленькие модули могут использовать общий код. В целом дистрибутив uClinux более чем адекватен для компиляции образа Linux в целях построения коммуникационного устройства, такого как роутер, без написания каких-либо строк кода.

Несмотря на то, что Linux не была изначально разработана для использования во встраиваемых системах, она нашла свое применения в большом количестве встраиваемых устройств. Начиная с релиза ядра версии 2.0.x и появления коммерческой поддержки Linux на встраиваемых процессорах, виден взрывообразный рост применения во встраиваемых устройствах этой OS.

Почти каждый день появляется новое устройство или гаджет, которое использует Linux в качестве своей операционной системы, что в большинстве случаев остается незамеченным конечными пользователями. Сегодня большое количество доступных широкополосных маршрутизаторов, файерволов, точек доступа Wi-Fi и даже плееры DVD применяют для своей работы Linux (см. примеры Linux-устройств [5]). Операционная система uClinux, так же как и Linux, предоставляет большое количество драйверов для любого сорта оборудования и протоколов. Если учесть, что фактически Linux не требует лицензионных отчислений, то становится понятно, почему многие разработчики выбирают Linux для своих устройств.

[Linux на DSP-процессорах]

В прошлом DSP использовались в определенном спектре специальных приложений, включающих звуковые карты, модемы, устройства телекоммуникации, медицинское оборудование и некоторые виды военных применений и других приложений, где применялась чистая обработка сигналов. Эти DSP-системы специально разрабатывались для своей прикладной задачи с учетом жестких ограничений по стоимости и размерам.

По мере того, как процессоры DSP становились более мощными и гибкими, удовлетворяя растущие требования в военной технике, медицине и связном оборудовании, они все еще не обладали достаточным количеством ресурсов для работы в продвинутых операционных системах. Эти традиционные DSP были очень мощными и гибкими для определенных приложений, но все еще могли быть довольно дорогими. Такие DSP часто можно найти в специальной кластерной аппаратуре обработки сигналов, где не нужен запуск операционных систем наподобие Linux на самом DSP. Причина здесь в том, что фактически в этих системах DSP получает свои данные для обработки от дополнительного процессора. Таким образом, в такие DSP записывается только "базовая" системная программа обработки данных.

С быстрым развитием рынка приложений мультимедиа и смартфонов появились DSP нового типа. Ранее коммуникационные системы разрабатывались так, что DSP работал как сопроцессор вместе с другим процессором (процессор хоста), выполняющим сервисные функции. В таком сценарии операционная система работает на хост-процессоре, и обработка сигналов осуществляется в процессоре DSP. Такой двухпроцессорный дизайн нельзя признать оптимальным, он не эффективен в плане удобства поддержки, цены, потребляемой мощности и размера аппаратуры. Может быть другой подход - переделка традиционного процессора DSP, чтобы он удовлетворял требованиям современных операционных систем, и между тем сохранял возможности архитектуры DSP.

Этот подход был реализован разработчиками процессора Blackfin - они сделали процессора с продвинутыми функциями DSP на основе Гарвардской архитектуры с ортогональным набором команд в стиле RISC. Также реализована продвинутая адресация, управление стеком и привилегированные режимы выполнения кода. Такой процессор уже не просто DSP, это мощное устройство, удовлетворяющее широкому спектру индустриальных, коммуникационных и мультимедиа приложений.

В комбинации с мощностью операционной системы наподобие Linux открываются безграничные возможности. Тем не менее производители обычных процессоров не спят и разрабатывают новые процессоры для поставки их на тот же рынок. Разработка процессоров идет по принципу пяти 'P': price, performance, power consumption, peripherals и penguins (цена, производительность, потребляемая мощность, расширение периферии и пингвины).

[Различия между Linux и uClinux]

Поскольку Linux и uClinux поддерживают философию UNIX в плане многопользовательской, многозадачной OS, ядро должно предпринять специальные меры для обеспечения правильного и безопасного функционирования тысячи процессов от разных пользователей в одной системе. Модель безопасности UNIX, после которой была разработана Linux, защищает каждый процесс в его собственном рабочем окружении, с отдельным частным адресным пространством. Каждый процесс также защищен от процессов, запущенных другими пользователями.

Кроме того, система виртуальной памяти (Virtual Memory, VM) накладывает дополнительные требования на блок управления памяти (Memory Management Unit, MMU), наподобие динамического выделения памяти и отображения произвольных регионов памяти на память частного процесса.

Некоторые процессоры, наподобие Blackfin, не предоставляют полноценный MMU. Эти процессоры более эффективны в плане потребляемой мощности, значительно дешевле и иногда имеют более высокую производительность. Даже на процессорах, поддерживающих виртуальную память, некоторые разработчики систем предпочитают выбор запуска своего приложения на uClinux, потому что uClinux может работать значительно быстрее, чем обычная Linux на том же процессоре. Работа MMU может представлять значительные расходы процессорного времени.

Даже когда MMU имеется, он иногда не используется в системах с жестким ограничениями на выполнение действий кода в реальном времени (RTOS). Переключение контекста и взаимодействие между процессами (Inter Process Communication, IPC) могут также работать на uClinux в несколько раз быстрее.

Для поддержки Linux на процессорах без блока MMU, в uClinux реализованы следующие компромиссы:

1. Отсутствует реальная защита памяти (сбойный процесс может привести к полному отказу системы).
2. Нет ветвления системных вызовов (fork system call).
3. Есть только простое выделение памяти.
4. Также есть другие незначительные отличия от обычной OS Linux.

Отсутствие защиты памяти не представляет реальной проблемы в большинстве встраиваемых систем. Linux очень стабильная платформа, в частности для встраиваемых устройств, где сбои ПО наблюдаются редко. Даже в основанных на MMU системах Linux программные баги в пространстве ядра системы могут привести к отказу всей системы в целом. Поскольку у Blackfin есть защита памяти, но нет виртуальной памяти, система Blackfin/uClinux имеет лучшую защиту, чем другие системы без MMU, и она не будет "падать" так же часто, как uClinux, работающая на других процессорах.

Есть два наиболее общих принципиальных причины падения uClinux - переполнение стека и обращение по нулевому указателю.

Переполнение стека. Когда Linux работает на архитектуре с полной поддержкой MMU, блок MMU предоставляет программам Linux в общем не ограниченный стек и пространство кучи. Это реализовано за счет виртуализации физической памяти. Однако у большинства встраиваемых систем Linux в наличии фиксированное количество памяти SDRAM, и нет SWAP (файла подкачки), поэтому они не будут "не ограниченными" по ресурсам памяти.

Программы, где есть утечка памяти, все еще могут привести к краху всей системы Linux со встроенным блоком MMU. Поскольку uClinux не может поддерживать виртуальную память, она выделяет область стека в конце области данных во время компиляции исполняемого кода. Если стек на uClinux вырос до слишком большого размера, то он перезапишет статические данные и области кода. Это означает, что разработчик, который ранее не обращал внимания на использование стека в приложении, должен теперь знать о требованиях стека к памяти.

У Blackfin/uClinux есть опция компилятора, чтобы встроить в код проверку стека. Если во время компиляции установлена опция fstack-limit-symbol=_stack_start, то компилятор добавит дополнительный код, который будет проверять, что стек не вышел за пределы зарезервированного пространства. Это будет гарантировать, что не будут происходить случайные падения системы из-за повреждений данных памяти из-за переполнения стека. Когда приложение скомпилировано в этой опцией и превысило свой лимит стека, оно корректно завершится. Тогда разработчик может увеличить размер стека приложения во время компиляции, или во время запуска программы с помощью утилиты flthdr. В системах, находящихся в процессе производства, проверка стека может быть либо удалена (что повысит производительность и уменьшит размер кода), либо оставлена для увеличения устойчивости.

Обращение по null-указателю. Blackfin MMU предоставляет частичную защиту памяти, и может отделить пространство памяти пользователя от пространства ядра (кода, работающего в режиме supervisor). На Blackfin/uClinux первые 4 килобайта памяти, начиная с адреса NULL, зарезервировано в качестве буфера для неправильных разименований указателя (т. е. для обращений по неправильному адресу). Если приложение использует не инициализированный указатель (традиционная ошибка программиста), через который оно прочитает или запишет первые 4k памяти, то приложение зависнет. Это обеспечит меньшую вероятность случайных падений системы из за использования не инициализированных указателей. Другие реализации uClinux начнут записывать поверх ядра.

Второй момент несколько более проблематичный. В ПО, написанном для UNIX или Linux, разработчики иногда используют системный вызов fork (дословно переводится как "вилка", или "ветвление"), когда хотят что-нибудь выполнить параллельно. Вызов fork() делает точную копию оригинального процесса и выполняет его одновременно с оригиналом. Чтобы сделать это эффективно, он использует MMU для отображения памяти от родительского процесса на дочерний, и копирует только те области памяти, в которые дочерний процесс записывает данных.

Таким образом, uClinux не может предоставить системный вызов fork(). Однако она предоставляет vfork(), специальную версию fork(), в котором родительский процесс приостанавливается, пока выполняется дочерний процесс. Таким образом программа, которая использует системный вызов fork(), должна быть модифицирована для использования вместо этого vfork() или потоков POSIX, которые поддерживает uClinux, потому что они используют общее пространство памяти, включая стек.

Третий момент - обычно нет проблемы с поддержкой malloc в системе uClinux, однако в некоторых случаях должны быть выполнены некоторые незначительные модификации. Выделение памяти в uClinux может быть очень быстрым, но с другой стороны процесс может выделить всю доступную память. Поскольку память может быть выделена только непрерывными блоками, то фрагментация памяти может иногда создать проблему.

Большинство ПО, доступного для Linux или UNIX (коллекцию программного обеспечения можно найти на сайте freshmeat.net) можно непосредственно скомпилировать под uClinux. Для остальных программ и утилит требуется только незначительные усилия по портированию или подстройке кода. Только очень малое количество программ не заработают на uClinux, и большинство таких программ не нужны для встраиваемых приложений.

[Программирование в среде uClinux]

Когда выбирается аппаратное обеспечение для проекта, разработчики должны тщательно обосновывать свой выбор, ориентируясь не только на цену и доступность комплектующих, но также и на наличие доступных драйверов с исходным кодом и документации, а также на средства для разработки, которые делают жизнь проще. Т. е. упрощают отладку ядра, драйверов, целевых приложений. Особенно это важно при разработке с использованием открытого софта, который поставляется "как есть", без каких-либо гарантий. Должна быть понятная методика тестирования для ядра, драйверов, библиотек, должен быть в наличии удобный инструментарий разработки и отладки.

Linux Test Project (LTP) [6] можно рассмотреть в качестве примера совместного проекта, запущенного SGI и поддерживаемого IBM, цель которого предоставить наборы тестов разработчикам открытого ПО, которые обеспечат надежность и устойчивость Linux. Тесты LTP содержат набор инструментов для проверки ядра Linux и его функций. Компания Analog Devices, Inc. спонсировала портирование LTP на архитектуры, работающие на uClinux. 

Правило "Trust is good, control is better" ("доверяй, но проверяй") применяется не только к ядру, но и ко всем другим инструментам, применяемым в процессе разработки, они тоже должны быть протестированы. Если Вы не можете доверять своему компилятору или отладчику, то проиграли. Blackfin/uClinux использует DejaGnu [7] для упрощения и автоматизирования более 44000 тестов тулчейна, проверки их ожидаемых результатов запуска, когда они работают на целевой аппаратуре. В дополнение к этим тестам, включенным в пакет Blackfin/uClinux, есть также автоматизированные стресс-тесты ядра и драйверов устройств, использующие скрипты. Все эти тесты можно легко воспроизвести, потому что они хорошо задокументированы.

Типовая среда разработки на uClinux состоит из дешевой платы Blackfin STAMP board [8], GNU Compiler Collection [9] (кросс-компилятор gcc) и binutils (линкер, ассемблер, и т. д.) для процессора Blackfin. Дополнительно нужны некоторые инструменты GNU наподобие awk, sed, make, bash ... плюс tcl/tk, хотя они обычно устанавливаются по умолчанию вместе с десктоп-версией дистрибутива Linux.

Все исходные коды и инструменты (компилятор binutils, отладчик gnu), которые нужны для создания рабочего ядра uClinux на процессорах Blackfin, можно свободно получить на сайте blackfin.uclinux.org. Для использования двоичных пакетов rpms нужен дистрибутив Linux наподобие RedHat или SuSE. У разработчиков, которые не могут установить Linux на свой PC, есть альтернатива.

Cooperative Linux (coLinux) [10] это относительно новое средство предоставления сервисов Linux на хосте Windows. Оно уже есть в виде решения "из коробки", которое можно свободно загрузить по ссылке [11]. Этот пакет поставляется с полным дистрибутивом Blackfin uClinux, включая все приложения пространства пользователя и графический инсталлятор в стиле Windows.

После установки окружения разработки и распаковки дистрибутива uClinux может начаться разработка.

Сначала разработчик использует графическую утилиту конфигурации, чтобы выбрать соответствующий пакет поддержки платы (Board Support Package, BSP) для своей целевой аппаратурыe. Поддерживаемые платформы включают STAMP для BF533, BF537 или EZ-KIT для Dual Core Blackfin BF561. Другие деривативы Blackfin, наподобие BF531, BF532, BF536 или BF534, не перечислены, но также поддерживаются, однако для них нет готового конфигурационного файла по умолчанию.

После того, как ядро по умолчанию сконфигурировано и успешно скомпилировано, получается полнофункциональное ядре Linux и образ файловой системы, которые можно загрузить и выполнить или прошить через NFS, tftp или протокол Kermit на целевом железе с помощью предварительно установленного на нем загрузчика u-boot [12]. После успешного завершения этого процесса может быть продолжена дальнейшая разработка.

Традиционно первым разработанным приложением может быть программа "Hello World", пример кода которой показан ниже.

#include < stdio.h >
 
int main ()
{
   printf ("Hello, World\n");
   return 0;
}

Сохраните это текст в файл hello.c и скомпилируйте его на PC разработчика:

bfin-uclinux-gcc -Wl,-elf2flt hello.c -o hello

Получится выходной исполняемый файл 'hello'.

Когда скомпилированная программа должна запускаться на uClinux, то должен использоваться компилятор -bfin-uclinux-gcc. Исполняемый код линкуется вместе с runtime-библиотекой uClibc. uClibc это библиотека C для разработки встраиваемых систем Linux. Она намного меньше, чем библиотека GNU C, однако почти все приложения, поддерживаемые библиотекой glibc, также отлично работают и с библиотекой uClibc.

Библиотечные вызовы функций наподобие printf() вовлекают системный вызов, говоря операционной системе напечатать строку в stdout (это консоль). Опция командной строки elf2flt говорит линкеру генерировать плоский двоичный файл (flat binary) - elf2flt преобразует полностью линкованный файл ELF-объекта, созданный тулчейном, в файл binary flat (BFLT) для использования вместе с uClinux.

Следующий шаг - загрузка 'hello' в целевое железо. Для этого есть много способов. Один из удобных - поместить 'hello' в NFS или SAMBA экспортируемый файл на хосте разработки и смонтировать общий каталог (share) на целевой системе uClinux. Другие альтернативы - поместить 'hello' в корневой каталог web-сервера и использовать команду wget на целевой плате. Или просто использовать ftp или tftp для передачи исполняемого файла.

Для этого простого примера отладка не нужна, однако по мере роста сложности программ станут важны доступные инструменты отладки. Иногда приложение просто завершается после своего запуска, без вывода сообщения об ошибке. Причин такого поведения может быть бесконечное множество, но чаще всего ошибка может быть довольно простой, например нельзя открыть файл, драйвер устройства, и т. п.

Утилита strace это инструмент отладки, который вводит на печать трассировку всех системных вызовов, сделанных другой программой. Системные вызовы и сигналы это события, которые происходят на интерфейсе user/kernel (между пользовательским кодом и ядром операционной системы). Подробное исследование этого взаимодействия очень полезно для изоляции бага, проверки корректности поведения кода и попыток отследить конкуренцию за доступ к ресурсу (capture race conditions).

Если использование strace не дает быстрый результат, разработчики могут следовать типовой процедуре всех разработчиков Linux - вставить в код операторы printf или printk, после чего перекомпилировать и заново запустить программу, чтобы выяснить проблему в поведении кода.

Из-за того, что такой традиционный метод может быть слишком утомительным, применяют стандартный отладчик Linux (Linux GNU Debugger, GDB) с его графическим интерфейсом. GDB поддерживает пошаговое выполнение, обратную трассировку, точки останова (breakpoints), точки отслеживания изменения памяти (watchpoints), просмотр переменных, и т. д. Есть несколько вариантов устроить подключение gdb к gdbserver на целевой плате. Gdb может подключаться через Ethernet, Serial или JTAG (rproxy). Для отладки на уровне ядра, например драйверов устройств, разработчики могут использовать kgdb Blackfin patch для отладчика gdb.

Если целевое приложение не работает из-за скрытой не эффективности, то ключевой способ решить проблему это использовать профилировщик кода. OProfile это системный профайлер для для систем на основе Linux, который может организовать профилирование всего выполняющегося кода с низкими накладными расходами. OProfile использует аппаратные счетчики производительности CPU, чтобы разрешить прифилирование разной интересующей статистики, также включая профайлинг затрат процессорного времени. Профилируется весь код: обработчики прерываний аппаратуры и программных прерываний, модули ядра, само ядро, общие библиотеки, и приложения пользователя.

Компилятор Blackfin gcc показывает очень хорошую производительность в сравнении с другими доступными компиляторами (см. GCC Code-Size Benchmark Environment, CSiBE [13]). Но иногда может понадобиться выполнить ручную оптимизацию, чтобы задействовать все возможности набора инструкций, которые предоставляет архитектура процессора. Здесь есть несколько альтернатив: использовать встраивание кода ассемблера (inline assembly), макросы ассемблера или вызываемый из C код на ассемблере.

Пример: код ассемблера, вызываемый из C. Чтобы программа на C могла вызвать функцию, написанную на ассемблере, программа C должна знать имя функции. Для этого прототип функции декларируется через external.

extern int minimum (int, int);

В файле ассемблера используется то же самое имя функции в виде метки на адресе перехода, куда попадает ветвление при вызове функции. Имена, определенные в C, используются в коде ассемблера с лидирующим символом нижнего подчеркивания. Например, так функция minimum будет определена на языке ассемблера:

.global _minimum;
 
__minimum:
   R0 = MIN(R0, R1);
   RTS;  /* возврат из подпрограммы (ReTurn Subroutine) */

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

В этом случае регистры R0 и R1 соответствуют первому и второму параметрам функции. Возвращаемое значение функции находится в регистре R0. Разработчики должны быть довольны такой удобной runtime-моделью C передачи параметров, используемой в архитектуре.

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

Процессоры DSP напрямую поддерживают одноцикловую дробную арифметику с фиксированной точкой (fract) и целочисленную арифметику, при этом frac арифметика используется в реальной цифровой обработке сигналов, и целочисленная арифметика используется для таких операций управления, как вычисление адреса памяти, счетчики цикла и управление ходом вычислений.

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

Самое большое положительное frac число 0.99999, и самое большое по модулю отрицательное число -1.0 (полная шкала frac). Для преобразования frac обратно в целое число frac должно быть умножено на коэффициент масштабирования, чтобы результат был всегда между ±2^(N-1) для целых чисел со знаком и в диапазоне до 2^(N) для целых чисел без знака.

Стандартный дистрибутив uClinux содержит богатый набор библиотек C для сжатия данных, криптографии и других целей (openssl, libpcap, libldap, libm, libdes, libaes, zlib, libpng, libjpeg, ncurses и т. д.) Дистрибутив Blackfin/uClinux [11] дополнительно включает библиотеки libaudio, libao, libSTL, flac, tremor, libid3tag, mpfr и т. п.

Кроме того, разработчики Blackfin/uClinux интегрировали библиотеку DSP в uClinux, где находятся тщательно оптимизированные ассемблерные функции с низкими затратами MIPS для выполнения всех видов общих алгоритмов сигнальной обработки, такой как конволюция, FFT, DCT и фильтры IIR/FIR.

Следующим шагом в разработке могут быть специальные приложения для целевого устройства, или портирование дополнительного софта. Некоторая часть разработки может быть выполнена в скриптах shell или языках программирования типа Perl или Python. Там где есть особые требования к скорости обработки, применяется язык C. Вместе с экстраординарной поддержкой протоколов и драйверов Linux предоставляет мощную среду для разработки новых приложений.

[Пример чтения сенсора изображения]

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

В качестве примера такой идеальной реализации можно привести процессор Blackin, у которого есть несколько гибких, не зависящих друг от друга контроллера прямого доступа к памяти (Direct Memory Access, DMA). Транзакции DMA по перемещению данных могут происходить между областями внутренней памяти и периферийными устройствами, поддерживающими функцию DMA. Дополнительно передачи DMA могут быть выполнены между поддерживающей DMA периферией и внешними устройствами памяти, включая контроллер SDRAM и контроллер асинхронной памяти.

Процессор Blackfin среди других интерфейсов предоставляет параллельный интерфейс (Parallel Peripheral Interface, PPI), который можно напрямую подключить к параллельным преобразователям D/A и A/D (ЦАП и АЦП), видео кодеры и декодеры ITU-R-601/656, и другие периферийные устройства общего назначения, такие как сенсоры CMOS камеры. PPI состоит из выделенного вывода тактов, до 3 выводов синхронизации фрейма и до 16 выводов данных.

На рис. 2 ниже показан пример простой реализации подключения сенсора изображений CMOS к процессору Blackfin, без необходимости применения дополнительных аппаратных компонентов.

uClinux Blackfin MT9T001 camera fig02

Рис. 2. Схема подключения Micron CMOS Camera Sensor.

Ниже приведен пример исходного кода простой программы, которая читает данные из этого сенсора. Код подразумевает, что драйвер PPI скомпилирован в состав ядра OS или загружен как модуль ядра. Есть два разных драйвера PPI: обычный полнофункциональный драйвер, поддерживающий различные рабочие режимы PPI (ppi.c), и простой PPI Frame Capture Driver (adsp-ppifcd.c). В этом примере использовался последний вариант.

#define WIDTH  1280
#define HEIGHT 1024
 
// put_image_png: преобразование сырых данных изображения
//                в файл формата PNG и запись его в stdout.
static void put_image_png (char *image, int width, int height)
{
   int y;
   char *p;
   png_infop info_ptr;
   png_structp png_ptr;
   
   png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
                                      NULL, NULL, NULL);
   info_ptr = png_create_info_struct (png_ptr);
   png_init_io (png_ptr, stdout);
   png_set_IHDR (png_ptr, info_ptr, width, height,
                 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT);
   png_write_info (png_ptr, info_ptr);
   p = image;
   for (y=0; y < height; y++)
   {
      png_write_row (png_ptr, p);
      p += width;
   }
   png_write_end (png_ptr, info_ptr);
   png_destroy_write_struct (&png_ptr, &info_ptr);
}
 
int main (int argc, char *argv[])
{
   int fd;
   char *buffer;
   
   // Выделение памяти для сырых данных изображения:
   buffer = (char*)malloc(WIDTH*HEIGHT);
   // Открытие /dev/ppi:
   fd = open ("/dev/ppi0", O_RDONLY, 0);
   if (-1 == fd)
   {
      printf ("Не получилось открыть /dev/ppi\n");
      free (buffer);
      exit (1);
   }
   ioctl (fd, CMD_PPI_SET_PIXELS_PER_LINE,  WIDTH);
   ioctl (fd, CMD_PPI_SET_PIXELS_PER_FRAME, HEIGHT);
   // Чтение сырых данных картинки через PPI:
   read (fd, buffer, WIDTH*HEIGHT);
   put_image_png (buffer, WIDTH, HEIGHT);
   close (fd);    // закрытие PPI
   free (buffer);
   
   return 0;
}

Приложение открывает драйвер устройства PPI, выполняет некоторые действия по управлению вводом/выводом (I/O controls, ioctls), устанавливает количество точек на строку и количество захватываемых строк. После этого приложение делает системный вызов чтения, и драйвер взводит транзакцию DMA. Начало нового фрейма детектируется периферийным устройством PPI путем мониторинга синхроимпульсов строки и кадра.

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

После получения данных растра они конвертируются в формат PNG (Portable Network Graphic), где используется библиотека libpng, включенная в дистрибутив uClinux. Сконвертированный образ затем записывается в stdout. Если предположить, что скомпилированный исполняемый код программы называется readimg, то следующая командная строка выполнит программу и запишет картинку в файл:

readimg > myimage.png

I2C/TWI. Видео, аудио и сенсоры неподвижных изображений (фотокамеры) широко используют I2C-совместимый двухпроводный интерфейс (Two Wire Interface, TWI) в качестве шины конфигурирования системы. Шина конфигурации дает доступ к внутренним регистрам управляемого устройства со стороны мастера шины (например, можно управлять яркостью и другими параметрами). Обычно устройства I2C управляются драйвером ядра. Однако также есть возможность доступа ко всем устройствам из пространства пользователя через интерфейс /dev. Следующий пример кода показывает, как записать значение 0x248 в регистр 9 подчиненного устройства I2C, идентифицированного адресом I2C_DEVID:

#define I2C_DEVID (0xB8 >> 1)
#define I2C_DEVICE "/dev/i2c-0"
 
extern int i2c_write_register (char, unsigned char, unsigned char, unsigned short);
 
i2c_write_register (I2C_DEVICE, I2C_DEVID, 9, 0x0248);
 
#define I2C_SLAVE_ADDR  0x38
 
int i2c_write_register (char *device,
                        unsigned char client,
                        unsigned char reg,
                        unsigned short value)
{
   int addr = I2C_SLAVE_ADDR;
   char msg_data[32];
   struct i2c_msg msg = {addr, 0, 0, msg_data};
   struct i2c_rdwr_ioctl_data rdwr = {&msg, 1};
   int fd, i;
   
   if ( (fd = open (device, O_RDWR)) < 0 )
   {
      fprintf (stderr, "Ошибка, нельзя открыть %s\n", device);
      exit (1);
   }
   
   if ( ioctl (fd, I2C_SLAVE, addr) < 0 )
   {
      fprintf (stderr, "Ошибка, нельзя привязать адрес %x\n", addr);
      close (fd);
      exit (2);
   }
   
   msg.len = 3;
   msg.flags = 0;
   msg.data[0] = reg;
   msg.data[2] = (0xFF & value);
   msg.data[1] = (value >> 8);
   msg.addr = client;
   
   if ( ioctl (fd, I2C_RDWR, &rdwr) < 0 )
   {
      fprintf (stderr, "Ошибка, нельзя записать в I2C\n", addr);
      close (fd);
      exit (3);
   }
   
   close (fd);
   return 0;
}

[Кросс-компиляция]

Сила Linux в неистощимом количестве готовых приложений, поставляемых под разными открытыми лицензиями, которые могут быть кросс-компилированы для запуска на встраиваемой системе uClinux.

Linux или UNIX не единственная платформа, есть широкий выбор подобных операционных систем. Большинство программа поставляются в виде исходного кода вместе с так называемым скриптом 'configure'. Это shell-скрипт, который должен быть запущен для распознавания конфигурации текущей системы, чтобы использовались корректные опции компилятора, пути библиотек и инструментария.

Когда нет скрипта configure, разработчик может вручную модифицировать Makefile, чтобы добавить в него специфические изменения для целевого процессора, или может интегрировать это в дистрибутив uClinux ( подробные инструкции можно найти в документации [14]). Скрипт configure обычно большой, и требует много времени для своего выполнения. Когда этот скрипт создан из свежего релиза autoconf, он будет работать на Blackfin/uClinux с минимальными изменениями или даже без каких-либо модификаций.

Скрипт configure из пакета исходного кода может быть запущен для кросс-компиляции следующей командной строкой:

CC='bfin-uclinux-gcc –O2 -Wl,-elf2flt' ./configure --host=bfin-uclinux --build=i686-linux

Альтернативно:

./configure --host=bfin-uclinux --build=i686-linux LDFLAGS='-Wl,-elf2flt' CFLAGS=-O2

Есть как минимум 2 события, которые могут остановить выполнение скрипта: (1) некоторые файлы, используемые скриптом, слишком старые, или (2) отсутствуют необходимые инструменты или библиотеки. Возможно, что предоставленные скрипты слишком старые для правильного выполнения на bfin-uclinux, или они не распознают bfin-uclinux как возможную цель компиляции. Разработчику нужно заменить config.sub более свежей версией (например, в актуальном исходном каталоге gcc). Только в очень редких случаях компиляция не поддерживается в скриптом configure.in, вручную написанном автором и используемым утилитой autoconf. В этом случае последний файл может быть изменено для удаления или изменения строк, где наблюдается ошибка.

[Network Oscilloscope Demo]

Демонстрационное приложение сетевого осциллографа Network Oscilloscope Demo, показанное на рис. [3], основано на приложении VoIP Linphone Application или Networked Audio Player, включенном в дистрибутив Blackfin/uClinux. Назначение Network Oscilloscope Project - демонстрировать простой remote GUI (Graphical User Interface, графический интерфейс пользователя) механизм управления для предоставления общего доступа к данным, распространяемым по сети TCP/IP. Кроме того, это демонстрирует интеграцию нескольких open source проектов и библиотек как сборочных блоков одного приложения.

Например gnuplot, утилита командной строки для отображения данных файла, используется для построения графиков, и thttpd, веб-сервер с поддержкой CGI (Common Gateway Interface), используется для обработки запросов HTTP. CGI обычно используется для динамически генерируемых веб-страниц. Это простой протокол для коммуникаций между формами WEB и определенной программой. Скрипт CGI может быть написан на любом языке, включая C/C++, прочитан через stdin, записан в stdout, с применением чтения переменных окружения.

Network Oscilloscope работает следующим образом. Подключаемый через сеть web-браузер соединяется с HTTP-сервером, работающим на uClinux, где находится скрипт CGI. Браузер запрашивает через скрипт запуск программы. Параметры из формы HTML, такие как частота оцифровки (sample frequency), настройки срабатывания (trigger settings) и опции отображения передаются в программу через рабочее окружение. Вызванная программа оцифровывает данные подключенного через SPI внешнего АЦП (ADC), используя драйвер устройства uLinux (adsp-spiadc.c).

Приходящие выборки сигнала обрабатываются и сохраняются в файл. Затем программа CGI запускает утилиту gnuplot как процесс, и запрашивает генерацию картинки PNG или JPEG на основе оцифрованных данных и настроек формы. WEB-сервер принимает вывод программы CGI, и туннелирует их web-браузеру, который отображает график сигнала как страничку HTML.

uClinux Blackfin WEB app example fig03

Рис. 3. Приложение Network Oscilloscope Demo.

Простая программа на языке C может использоваться для предоставления данных в ответ на запрос CGI.

#include < stdio.h >
#include < stdlib.h >
#include < string.h >
 
int main (void)
{
   print("Content-type: text/html\n\n");
   print("< html>< head>< title>CGI DEMO< /title>< /head>\n");
   print("< body bgcolor=\"#FFFFFF\">\n");
   print("< h1>Hello CGI World!< /h1>\n");
   print("Если Вы смогли прочитать это, то CGI работает.\n");
   print("< /body>< /html>\n");
   fflush(stdout);
   return 0;
}

[uClinux в качестве RTOS]

Поскольку Linux изначально была разработана для использования в качестве сервера и десктопа, где нет специальных требований к реал-тайму, как у сравнимых по сложности и размеру операционных систем. Тем не менее Linux, и в частности uClinux имеет отличные возможности по реализации "мягкого реалтайма". Это означает, что хотя Linux или uClinux не может гарантировать определенную задержку для обработки прерывания или реакции планировщика в сравнении с другими операционными системами подобной сложности, она показывает очень хорошие параметры производительности. Если нужен так называемый "жесткий реалтайм" от системы, который может гарантироваться предсказуемым временем задержки обработки прерывания или реакции планировщика задач, то это может быть достигнуто несколькими способами:

1) Предоставление возможностей RTOS в форме нижележащего минимального ядра real-time, такого как RT-Linux (см. сайт rtlinux.org) или RTAI (сайт rtai.org). Оба этих решения используют небольшое real-time kernel, которое запускает Linux как задачу реального времени с низким приоритетом. Программы, которым требуется предсказуемое время реакции на события, разрабатываются для запуска на real-time kernel, и они специальным образом кодируются для достижения целей реалтайма. Все другие задачи и сервисы работают поверх Linux kernel, и могут утилизировать все предоставляемый Linux функции. Такой подход может гарантировать детерминированную латентность прерываний с сохранением гибкости и возможностей Linux.

2) Предоставление возможностей RTOS при использовании Xenomai [15]. Xenomai это real-time фреймворк разработки, взаимодействующий с Linux kernel, чтобы предоставить широкую, предсказуемую поддержку интерфейса жесткого реалтайма для приложений пространства пользователя, с гладкой интеграцией в рабочее окружение GNU/Linux. Фреймворк базируется на абстрактном ядре RTOS nucleus, которое можно использовать для построения любого вида real-time интерфейсов. Ядро экспортирует набор традиционных сервисов RTOS. Любое количество экземпляров RTOS, так называемых "skins", можно собрать поверх nucleus, при этом каждый экземпляр может предоставить свой специфический интерфейс для приложений, через использование служб одного традиционного ядра. Отдельно от своего традиционного и POSIX интерфейсов, Xenomai также предоставляет эмуляторы для VxWorks, VRTX, pSOS+ и uITRON. Тем, кому нужно узнать больше об этом проекте, можно обратиться к онлайн-документации [15].

Первоначальный порт для Blackfin включенный в Xenomai v2.1, обеспечивает латентность планировщика для самого худшего случая на Blackfin BF533 немного меньше 50 мкс под нагрузкой, с ожидаемым в будущем улучшением 10..20 мкс.

Xenomai и RTAI используют Adeos [16] как нижележащий слой абстракции от аппаратуры (Hardware Abstraction Layer, HAL). Adeos обеспечивает реалтайм для ядра Linux. Здесь можно установить несколько приоритезированных доменов O/S, существующих одновременно на одном железе, и связывающихся друг с другом через конвейер прерываний.

Xenomai, как и Adeos, была портирована на архитектуру Blackfin автором Philippe Gerum, который ведет оба этих проекта. Эта разработка была в значительной степени спонсирована OpenWide, специализирующемся на встраиваемых и real-time решениях для Linux [17].

Тем не менее в большинстве случаев жесткий реалтайм не требуется, в частности от потребительских приложений мультимедиа, где ограничения по реакции во времени диктуются удобством интерфейса пользователя, задержками отображения видео и требованиями качественного проигрывания звука. Физически детектируемые пользователем задержки времени в интерфейсе пользователя могут находится в диапазоне миллисекунд, что не составляет проблем для таких быстрых чипов, как процессор Blackfin. В новом стабильном релизе Linux kernel 2.6.x эти параметры улучшены с появлением нового планировщика O(1).

Рис. 4 и 5 показывают время переключения контекста для ядра по умолчанию Linux 2.6.x, работающего на Blackfin/uClinux:

uClinux Blackfin context switch performance fig04

Рис. 4. Зависимость времени переключения контекста от количества работающих потоков.

uClinux Blackfin context switch performance fig05

Рис. 5. Зависимость среднего времени переключения от загруженности системы.

Время переключения контекста было измерено с помощью lat_ctx от lmbench. Процессы соединены в кольцо с помощью каналов Unix (pipes). Каждый процесс читает токен из своего канала, может выполнять какую-то работу, и затем записывать токен в следующий процесс. По мере увеличения количества процессов эффект от использования кэша снижается. Для 10 процессов среднее время переключения контекста составляет 16.2 мкс, со стандартной девиацией .58, 95% времени, в пределах 17 мкс.

[Заключение]

Процессоры Blackfin предоставляют хорошее соотношение производительности и цены (800 MMAC @ 400 МГц для цены меньше $5 за чип), продвинутые функции по управлению питанием и маленькие корпуса mini-BGA. Это предоставляет очень эффективное в плане цены и габаритов решение, с очень малым потреблением энергии, что очень важно для переносных устройств. Продвинутые DSP-функции Blackfin и возможности обработки мультимедиа подойдут не только для приложений звука и видео, но также для любого вида индустриальных, автомобильных и коммуникационных устройств.

Инструменты разработки хорошо протестированы, задокументированы и включают все необходимое как для быстрого старта, так и успешного завершения проекта. Другое достоинство интеграции процессора Blackfin с uClinux - доступность широкого диапазона приложений, драйверов, библиотек и поддержки протоколов, часто в исходных кодах, как свободное ПО. В большинстве случаев требуется только базовая кросс-компиляция, чтобы получить рабочее программное решение.

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

[Ссылки]

1. uClinux on the Blackfin DSP Architecture site:embedded.com.
2. Blackfin ADSP-BF538.
3Использование кэша в процессорах Blackfin.
4. BUSYBOX site:busybox.net.
5. LinuxDevices.com Archive site:linuxdevices.org.
6. Linux Test Project site:github.com.
7. DejaGnu site:gnu.org.
8. ADSP-BF537 STAMP Board Support Package site:analog.com.
9. GCC, the GNU Compiler Collection site:gcc.gnu.org.
10. Cooperative Linux site:colinux.org.
11. Blackfin Linux site:analog.com.
12. u-boot site:sourceforge.net.
13. GCC Code-Size Benchmark Environment site:szeged.github.io.
14. Blackfin/uClinux Documentation site:uclinux.org.
15. Xenomai Real-time system site:xenomai.org.
16. ADEOS Project Overview site:eorc.jaxa.jp.
17. Open Wide site:smile.eu.

 

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


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

Top of Page