Реализация связки прошивки ПЛИС, ПО микроконтроллера NIOS и управляющего ПО под Linux на базе Altera Cyclone V SoC с использованием Avalon Mailbox для создания на их основе распределенной системы управления.
В распределенной системе управления приходится решать множество разных задач на разных уровнях.
Некоторые задачи целесообразно решать на уровне встраиваемого ПК с полноценной ОС. Полноценная ОС хороша тем, что в ней уже реализовано и отлажено много полезных инструментов, таких как многопоточность, готовые драйвера, библиотеки, разные фреймворки и прочее. И все это можно разрабатывать на языках высокого уровня, особо не вдаваясь в детали реализации на нижнем уровне.
Есть задачи, которые удобно решать на уровне микроконтроллера (далее – МК), либо вообще без ОС (bare-metal), либо с минималистичными ОС реального времени. Здесь ключевую роль играет возможность отладки софта внутри ОС по JTAG и отслеживание происходящего на уровне периферии МК на любом break-point`е.
И есть задачи, которые стоит решать уже на уровне ПЛИС, так как никакого микроконтроллера может не хватить для грамотного параллельного управления разной высокочастотной электроникой, например, драйверами шаговых приводов с обработкой данных энкодеров и регуляторами скоростей. В таких задачах процессор бывает просто лишним.
Количество исполнительных устройств и их различных функций в системе управления сильно возрастает, если речь идет о разработке прибора, например, с трех-степенным манипулятором, парой-тройкой серводвигателей, дюжиной дискретных устройств, кучей периферии на всех популярных интерфейсах (SPI, I2C, UART и т.д.) и сложной логикой с математическим анализом внутри. И всю систему управления очень удобно расположить вообще на одном кристалле, что собственно и будет сделано. В итоге все три уровня управления ПК-МК-ПЛИС и их взаимодействия переместятся внутрь кристалла.
В таком случае неизбежно возникает задача создания транспортного уровня, связывающего всю эту сложную логику между собой. Для связки МК-ПЛИС это решается, фактически, созданием очередного периферийного устройства на общей шине МК со своим набором регистров. Но задача создания транспортного уровня ПК-МК будет решаться немного иначе.
Для экспериментов нам понадобится реальная или виртуальная машина с Ubuntu 16.04 на борту.
Исходные тексты всех программ доступны на GitHub.
Представим, что все исполнительные устройства системы управления ПК-МК-ПЛИС сводятся к параллельным портам ввода-вывода. Для примера в качестве датчиков и исполнительных устройств ограничимся набором кнопок и светодиодов, а управлять ими будем из командной строки терминала.
Все элементы ПЛИС, в том числе МК, будем синтезировать. Часть ПК уже интегрирована в чип и выполнена на базе Cortex A9, шины которого выведены в ПЛИС и могут быть использованы напрямую. Поэтому все, что придется сделать, это подключить к ядру ОС модули, необходимые для связи с синтезированными узлами в ПЛИС через стандартные средства.
В качестве аппаратной платформы используем комплект DE0-Nano-SoC.
Возьмем за основу базовый проект my_first_hps-fpga_base из набора DE0-Nano-SoC CD-ROM (rev.B0 / rev.C0 Board). Проект содержит в себе предварительно настроенную среду с правильно выставленными портами ПЛИС, готовым блоком Cyclone V Hard Processor System с настроенными параметрами памяти и набором вспомогательных элементов в Qsys. Для работы с проектом нам понадобится Quartus Prime 15.1 с пакетом поддержки Cyclone V и SoC Embedded Design Suite.
Внесем некоторые изменения в проект. Добавим ядро NIOS, память для него (16 Кб 32-битной ширины) и порт JTAG. Укажем в параметрах NIOS адреса векторов из добавленной памяти.
Avalon Mailbox является симплексным, поэтому нам нужно два модуля (наподобие RX и TX линий обычного UART). Сигнал прерывания каждого из модулей нужно подключить на тот процессор, для которого модуль является приемным.
Добавим по одному порту (8 бит) ввода и вывода для дальнейшего тестирования системы.
После добавления всех элементов можно сделать автоматический подбор адресов и прерываний.
Создадим порты для кнопок и светодиодов в коде верхнего модуля.
Подключим порты к soc_system.
Соберем проект и получим прошивку ПЛИС, на базе которой будем дальше работать.
В среде NIOS II EDS, которая по сути – Eclipse со всеми нужными плагинами, создадим новый проект soc_nios из шаблона “NIOS II Application and BSP”. В результате получится два проекта: непосредственно прошивка и BSP.
В первую очередь нужно сразу собрать BSP, но не традиционным способом. Вместо этого в контекстном меню проекта soc_nios_bsp нужно выбрать в меню NIOS II пункт BSP Editor и включить опции enable_small_c_library и enable_reduced_device_drivers, чтобы прошивка особо не разрасталась. Затем собрать, нажав Generate. В дальнейшем, так как параметры сборки сохранятся, пересобрать BSP можно просто выбором в меню NIOS II пункта Generate BSP.
В файле system.h из проекта BSP можно увидеть все параметры периферии МК, которые были добавлены ранее в схему Qsys.
Более подробно о NIOS и о том, как собирать для него проекты, можно почитать тут.
Осталось собрать проект. Размер прошивки NIOS должен составить меньше 16 Кб.
Для тестирования прошивки на железе нужно создать новую конфигурацию отладчика. После прошивки ПЛИС из Quartus Programmer в меню Debug Configurations выбираем вариант NIOS II Hardware, обновляем все интерфейсы и во вкладке Target Connections находим jtaguart_1. Это тот самый JTAG для NIOS, который мы ранее добавили в Qsys.
Теперь можно запускать отладку из Eclipse. Если все сделано правильно, в консоли NIOS II должно появится сообщение «Turn the switch ON to activate the timer».
Подробным образом весь процесс описан здесь в разделах с 1 по 10. Рекомендуется использовать более свежие свежие версии тулчейна, бутлоадера и ядра, чем те, которые можно найти по ссылке. Обратите внимание, что для сборки данной версии бутлоадера не подойдет тулчейн с компилятором выше 6-й версии.
Для генерации device tree вместо предложенной утилиты sopc2dts лучше использовать скрипт sopc2dts.jar, причем можно указать сразу --type dtb.
Для получения системы настоятельно рекомендуется использовать самый свежий Buildroot. Для сборки необходимо принудительно указать переменные окружения CC как путь к arm-linux-gnueabihf-gcc и CXX как путь к arm-linux-gnueabihf-g++ из тулчейна. Далее ввести используемые версии компилятора, ядра и библиотеки (их подскажет сама система в процессе сборки). В настройках тулчейна при конфигурации Buildroot надо обязательно указать путь к тулчейну, а также префикс $(ARCH)-linux-gnueabihf и включить поддержку SSP, RPC и C++.
Для удобства можно добавить в Buildroot пакеты nano, mc и openssh.
Далее, собирать весь софт верхнего уровня будем в Eclipse с плагином GNU MCU Eclipse plug-in. Создадим новый workspace для ARM проектов и в глобальных настройках Eclipse в разделе Workspace Tools Path укажем соответствующий путь к установленной версии Linaro.
Первым делом сделаем драйвер для Mailbox`ов. Создадим в Eclipse новый проект nios_mailbox из шаблона «Hello World ARM C Project».
В настройках проекта выключим опции «Use default build command» и «Generate Makefiles automatically», так как для сборки модуля ядра понадобится команда make TARGET=nios_mailbox TARGET_DIR=Default. Добавим в переменные окружения две новых записи CROSS_COMPILE и KDIR, указывающие на полный путь с префиксом тулчейна и путь к исходникам ядра соответственно. В список дефайнов надо добавить __GNUC__, __KERNEL__ и MODULE. Всё. Теперь можно писать код.
Модуль ядра будет реагировать на прерывание из железа и должен как-то сообщать об этом миру приложений. Для этой цели нам понадобится создать свой сигнал.
Драйвер будем создавать на базе platform_device, каждый Mailbox будет как miscdevice, а в конечном итоге в системе будет виден как файл устройства в каталоге /dev. Более подробно о драйверах и вообще можно почитать тут. Важно понимать, что Mailbox`ов у нас может быть теоретически сколько угодно, а драйвер на всех один, и он должен все их инициализировать и пронумеровать.
Если особо не вдаваться в детали, то разработка драйвера сводится к реализации стандартных операций чтения и записи в файл, плюс небольшой бонус в виде специальной функции ioctl(), которая нужна для проброса драйверу id процесса, который его использует. Это нужно для того, чтобы знать, какому процессу в системе сигнализировать о возникновении аппаратного прерывания. Сам обработчик прерывания выглядит довольно просто и очень похож на свой аналог в NIOS.
Осталось собрать проект. Для этого нам нужно написать специальный Makefile. Выглядеть он будет так.
И еще нам понадобится создать файл Kbuild с одной строчкой.
Соберем проект традиционным способом. В результате получим модуль ядра nios_mailbox.ko, который скопируем на систему и установим при помощи insmod. Если все сделано правильно, в консоли Linux, открытой по USB, при нажатии соответствующей кнопки на плате должно появится сообщение от ядра "[ .........] NIOS Mailbox new mail!".
Конечно, в драйвер надо бы добавить буфер для принимаемых данных по прерыванию, так как чтение прикладной программой может не успевать за потоком данных от железа. И сам драйвер лучше собирать с опцией stripped, для экономии места в embedded-системе. Однако, эти вопросы оставим читателю для самостоятельного изучения.
Вот мы и добрались до написания консольного приложения. Создадим в Eclipse новый проект soc_test из шаблона «Hello World ARM C++ Project». В разделе Settings в Target Processor выберем архитектуру cortex-a9, в Cross ARM GNU G++ Linker добавим -pthread. Во вкладке Build Artifact можно убрать расширение файла. Все остальные настройки можно оставить по-умолчанию.
Осталось собрать проект. В результате получим исполняемый файл для архитектуры ARM-9, который скопируем на систему. Если все сделано правильно, то после запуска в консоли появится сообщение «Enter command (»read"(«r»), «write»(«w»), «reverse»), «q» to exit".
Инсталляцию модуля ядра добавляем в автозагрузку Linux.
Соберем новую версию прошивки NIOS, убрав из программы весь отладочный вывод в JTAG. Преобразуем прошивку в hex формат запуском в SoC EDS 15.1 Command Shell команды «elf2hex --input=soc_nios.elf --output=soc_nios.hex --width=32 --base=0x4000 --end=0x7fff --record=4». Полученную прошивку нужно добавить как файл инициализации для памяти NIOS в Qsys, затем пересобрать Qsys, пересобрать проект ПЛИС и записать новую прошивку на карту памяти.
Загружаемся и сразу запускаем тестовое приложение. И если все было сделано правильно, то получаем рабочую систему.
Не бойтесь использовать такие сложные связки как ПЛИС-МК-ПК на основе SoC в своих проектах. В данной статье продемонстрировано, что реализовать такую систему не так уж и сложно. Можно даже добавить несколько микроконтроллеров и связать их вместе подобным образом.
Система управления, созданная на базе изложенных выше принципов, была внедрена автором в один из электронных приборов и доказала свою работоспособность в реальном мире.
Введение
В распределенной системе управления приходится решать множество разных задач на разных уровнях.
Некоторые задачи целесообразно решать на уровне встраиваемого ПК с полноценной ОС. Полноценная ОС хороша тем, что в ней уже реализовано и отлажено много полезных инструментов, таких как многопоточность, готовые драйвера, библиотеки, разные фреймворки и прочее. И все это можно разрабатывать на языках высокого уровня, особо не вдаваясь в детали реализации на нижнем уровне.
Есть задачи, которые удобно решать на уровне микроконтроллера (далее – МК), либо вообще без ОС (bare-metal), либо с минималистичными ОС реального времени. Здесь ключевую роль играет возможность отладки софта внутри ОС по JTAG и отслеживание происходящего на уровне периферии МК на любом break-point`е.
И есть задачи, которые стоит решать уже на уровне ПЛИС, так как никакого микроконтроллера может не хватить для грамотного параллельного управления разной высокочастотной электроникой, например, драйверами шаговых приводов с обработкой данных энкодеров и регуляторами скоростей. В таких задачах процессор бывает просто лишним.
Количество исполнительных устройств и их различных функций в системе управления сильно возрастает, если речь идет о разработке прибора, например, с трех-степенным манипулятором, парой-тройкой серводвигателей, дюжиной дискретных устройств, кучей периферии на всех популярных интерфейсах (SPI, I2C, UART и т.д.) и сложной логикой с математическим анализом внутри. И всю систему управления очень удобно расположить вообще на одном кристалле, что собственно и будет сделано. В итоге все три уровня управления ПК-МК-ПЛИС и их взаимодействия переместятся внутрь кристалла.
В таком случае неизбежно возникает задача создания транспортного уровня, связывающего всю эту сложную логику между собой. Для связки МК-ПЛИС это решается, фактически, созданием очередного периферийного устройства на общей шине МК со своим набором регистров. Но задача создания транспортного уровня ПК-МК будет решаться немного иначе.
Для экспериментов нам понадобится реальная или виртуальная машина с Ubuntu 16.04 на борту.
Исходные тексты всех программ доступны на GitHub.
Архитектура системы управления
Представим, что все исполнительные устройства системы управления ПК-МК-ПЛИС сводятся к параллельным портам ввода-вывода. Для примера в качестве датчиков и исполнительных устройств ограничимся набором кнопок и светодиодов, а управлять ими будем из командной строки терминала.
Все элементы ПЛИС, в том числе МК, будем синтезировать. Часть ПК уже интегрирована в чип и выполнена на базе Cortex A9, шины которого выведены в ПЛИС и могут быть использованы напрямую. Поэтому все, что придется сделать, это подключить к ядру ОС модули, необходимые для связи с синтезированными узлами в ПЛИС через стандартные средства.
В качестве аппаратной платформы используем комплект DE0-Nano-SoC.
Получение прошивки ПЛИС
Возьмем за основу базовый проект my_first_hps-fpga_base из набора DE0-Nano-SoC CD-ROM (rev.B0 / rev.C0 Board). Проект содержит в себе предварительно настроенную среду с правильно выставленными портами ПЛИС, готовым блоком Cyclone V Hard Processor System с настроенными параметрами памяти и набором вспомогательных элементов в Qsys. Для работы с проектом нам понадобится Quartus Prime 15.1 с пакетом поддержки Cyclone V и SoC Embedded Design Suite.
Внесем некоторые изменения в проект. Добавим ядро NIOS, память для него (16 Кб 32-битной ширины) и порт JTAG. Укажем в параметрах NIOS адреса векторов из добавленной памяти.
Avalon Mailbox является симплексным, поэтому нам нужно два модуля (наподобие RX и TX линий обычного UART). Сигнал прерывания каждого из модулей нужно подключить на тот процессор, для которого модуль является приемным.
Добавим по одному порту (8 бит) ввода и вывода для дальнейшего тестирования системы.
После добавления всех элементов можно сделать автоматический подбор адресов и прерываний.
Создадим порты для кнопок и светодиодов в коде верхнего модуля.
// Ports
wire [7:0] port_out;
assign LED = port_out;
wire [7:0] port_in;
assign port_in = {{2{1'b0}}, SW, KEY};
Подключим порты к soc_system.
// FPGA Partion
.port_out_export(port_out), // port_out.export
.port_in_export(port_in), // port_in.export
Соберем проект и получим прошивку ПЛИС, на базе которой будем дальше работать.
Алгоритм
Итак, создадим систему, которая будет делать следующее:
- При включении тумблера активируется таймер;
- По таймеру с частотой 1 Гц будет загораться один из светодиодов по порядку;
- По нажатию кнопки будет меняться направление;
- При получении команды READ из ПК будет отправляться номер текущего активного светодиода на стандартную консоль Linux;
- При получении команды WRITE из ПК будет меняться текущий активный диод;
- При получении команды REVERSE из ПК будет меняться направление, так же, как от кнопки;
- По нажатию другой кнопки на консоль ПК будет отправляться количество переключений светодиодов с момента последнего реверса.
На стороне МК
В среде NIOS II EDS, которая по сути – Eclipse со всеми нужными плагинами, создадим новый проект soc_nios из шаблона “NIOS II Application and BSP”. В результате получится два проекта: непосредственно прошивка и BSP.
В первую очередь нужно сразу собрать BSP, но не традиционным способом. Вместо этого в контекстном меню проекта soc_nios_bsp нужно выбрать в меню NIOS II пункт BSP Editor и включить опции enable_small_c_library и enable_reduced_device_drivers, чтобы прошивка особо не разрасталась. Затем собрать, нажав Generate. В дальнейшем, так как параметры сборки сохранятся, пересобрать BSP можно просто выбором в меню NIOS II пункта Generate BSP.
В файле system.h из проекта BSP можно увидеть все параметры периферии МК, которые были добавлены ранее в схему Qsys.
Более подробно о NIOS и о том, как собирать для него проекты, можно почитать тут.
Для решения задачи на уровне МК нам понадобятся:
- Обработчик прерываний от таймера;
void TIMER_0_ISR(void* context) { IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_0_BASE, 0); IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_0_BASE, ALTERA_AVALON_TIMER_CONTROL_CONT_MSK); led += step; if(led > LED_MAX) { led = 0; } if(led < 0) { led = LED_MAX; } IOWR_ALTERA_AVALON_PIO_DATA(PORT_OUT_0_BASE, (1 << led)); count++; IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_0_BASE, ALTERA_AVALON_TIMER_CONTROL_CONT_MSK | ALTERA_AVALON_TIMER_CONTROL_ITO_MSK); }
- Обработчик прерываний от Mailbox;
void MAILBOX_HPS2NIOS_ISR(void* context) { IOWR_ALTERA_AVALON_MAILBOX_INTR(MAILBOX_SIMPLE_HPS2NIOS_BASE, 0); //NOTE: Order is important! CMD register should be read after PTR register buffer[1] = IORD_ALTERA_AVALON_MAILBOX_PTR(MAILBOX_SIMPLE_HPS2NIOS_BASE); buffer[0] = IORD_ALTERA_AVALON_MAILBOX_CMD(MAILBOX_SIMPLE_HPS2NIOS_BASE); alt_printf("Reading: 0x%x 0x%x\n\r", buffer[0], buffer[1]); newMail = true; IOWR_ALTERA_AVALON_MAILBOX_INTR(MAILBOX_SIMPLE_HPS2NIOS_BASE, ALTERA_AVALON_MAILBOX_SIMPLE_INTR_PEN_MSK); }
- Парсер сообщений и функция записи в Mailbox;
- Функции опроса кнопок и управления светодиодами.
Осталось собрать проект. Размер прошивки NIOS должен составить меньше 16 Кб.
Для тестирования прошивки на железе нужно создать новую конфигурацию отладчика. После прошивки ПЛИС из Quartus Programmer в меню Debug Configurations выбираем вариант NIOS II Hardware, обновляем все интерфейсы и во вкладке Target Connections находим jtaguart_1. Это тот самый JTAG для NIOS, который мы ранее добавили в Qsys.
Теперь можно запускать отладку из Eclipse. Если все сделано правильно, в консоли NIOS II должно появится сообщение «Turn the switch ON to activate the timer».
На стороне ПК
Установка ОС Linux на плату
Подробным образом весь процесс описан здесь в разделах с 1 по 10. Рекомендуется использовать более свежие свежие версии тулчейна, бутлоадера и ядра, чем те, которые можно найти по ссылке. Обратите внимание, что для сборки данной версии бутлоадера не подойдет тулчейн с компилятором выше 6-й версии.
Для генерации device tree вместо предложенной утилиты sopc2dts лучше использовать скрипт sopc2dts.jar, причем можно указать сразу --type dtb.
Для получения системы настоятельно рекомендуется использовать самый свежий Buildroot. Для сборки необходимо принудительно указать переменные окружения CC как путь к arm-linux-gnueabihf-gcc и CXX как путь к arm-linux-gnueabihf-g++ из тулчейна. Далее ввести используемые версии компилятора, ядра и библиотеки (их подскажет сама система в процессе сборки). В настройках тулчейна при конфигурации Buildroot надо обязательно указать путь к тулчейну, а также префикс $(ARCH)-linux-gnueabihf и включить поддержку SSP, RPC и C++.
Для удобства можно добавить в Buildroot пакеты nano, mc и openssh.
Далее, собирать весь софт верхнего уровня будем в Eclipse с плагином GNU MCU Eclipse plug-in. Создадим новый workspace для ARM проектов и в глобальных настройках Eclipse в разделе Workspace Tools Path укажем соответствующий путь к установленной версии Linaro.
Драйвер
Первым делом сделаем драйвер для Mailbox`ов. Создадим в Eclipse новый проект nios_mailbox из шаблона «Hello World ARM C Project».
В настройках проекта выключим опции «Use default build command» и «Generate Makefiles automatically», так как для сборки модуля ядра понадобится команда make TARGET=nios_mailbox TARGET_DIR=Default. Добавим в переменные окружения две новых записи CROSS_COMPILE и KDIR, указывающие на полный путь с префиксом тулчейна и путь к исходникам ядра соответственно. В список дефайнов надо добавить __GNUC__, __KERNEL__ и MODULE. Всё. Теперь можно писать код.
Модуль ядра будет реагировать на прерывание из железа и должен как-то сообщать об этом миру приложений. Для этой цели нам понадобится создать свой сигнал.
#define NIOS_MAILBOX_REALTIME_SIGNO 44
Драйвер будем создавать на базе platform_device, каждый Mailbox будет как miscdevice, а в конечном итоге в системе будет виден как файл устройства в каталоге /dev. Более подробно о драйверах и вообще можно почитать тут. Важно понимать, что Mailbox`ов у нас может быть теоретически сколько угодно, а драйвер на всех один, и он должен все их инициализировать и пронумеровать.
Если особо не вдаваться в детали, то разработка драйвера сводится к реализации стандартных операций чтения и записи в файл, плюс небольшой бонус в виде специальной функции ioctl(), которая нужна для проброса драйверу id процесса, который его использует. Это нужно для того, чтобы знать, какому процессу в системе сигнализировать о возникновении аппаратного прерывания. Сам обработчик прерывания выглядит довольно просто и очень похож на свой аналог в NIOS.
static irq_handler_t nios_mailbox_isr(int irq, void *pdev)
{
struct nios_mailbox_dev *dev = (struct nios_mailbox_dev*)platform_get_drvdata(pdev);
spin_lock(&dev->lock);
//NOTE: Order is important! CMD register should be read after PTR register
dev->data[1] = ioread32(dev->regs + ALTERA_AVALON_MAILBOX_SIMPLE_PTR_OFST * sizeof(u32));
dev->data[0] = ioread32(dev->regs + ALTERA_AVALON_MAILBOX_SIMPLE_CMD_OFST * sizeof(u32));
spin_unlock(&dev->lock);
if(dev->task)
{
send_sig_info(dev->sinfo.si_signo, &dev->sinfo, dev->task);
}
return (irq_handler_t)IRQ_HANDLED;
}
Осталось собрать проект. Для этого нам нужно написать специальный Makefile. Выглядеть он будет так.
all:
@echo 'KDIR=$(KDIR)'
@echo 'CROSS_COMPILE=$(CROSS_COMPILE)'
@if [ ! -d $(CURDIR)/$(TARGET_DIR) ]; then mkdir $(CURDIR)/$(TARGET_DIR); fi
cp $(TARGET).c $(CURDIR)/$(TARGET_DIR)
cp $(TARGET).h $(CURDIR)/$(TARGET_DIR)
cp Kbuild $(CURDIR)/$(TARGET_DIR)
$(MAKE) -C $(KDIR) ARCH=arm M=$(CURDIR)/$(TARGET_DIR)
clean:
rm -rf main $(CURDIR)/$(TARGET_DIR)
И еще нам понадобится создать файл Kbuild с одной строчкой.
obj-m := $(TARGET).o
Соберем проект традиционным способом. В результате получим модуль ядра nios_mailbox.ko, который скопируем на систему и установим при помощи insmod. Если все сделано правильно, в консоли Linux, открытой по USB, при нажатии соответствующей кнопки на плате должно появится сообщение от ядра "[ .........] NIOS Mailbox new mail!".
Конечно, в драйвер надо бы добавить буфер для принимаемых данных по прерыванию, так как чтение прикладной программой может не успевать за потоком данных от железа. И сам драйвер лучше собирать с опцией stripped, для экономии места в embedded-системе. Однако, эти вопросы оставим читателю для самостоятельного изучения.
Приложение
Вот мы и добрались до написания консольного приложения. Создадим в Eclipse новый проект soc_test из шаблона «Hello World ARM C++ Project». В разделе Settings в Target Processor выберем архитектуру cortex-a9, в Cross ARM GNU G++ Linker добавим -pthread. Во вкладке Build Artifact можно убрать расширение файла. Все остальные настройки можно оставить по-умолчанию.
Для решения задачи на уровне приложения нам понадобятся:
- Обработчик сигнала;
void Nios::mailbox_nios2hps_signal_handler(int signo, siginfo_t *info, void *unused) { if(info->si_signo == NIOS_MAILBOX_REALTIME_SIGNO) { sem_post(&mailbox_nios2hps_signal_semaphore); } }
Парсер сообщений от Mailbox;
void *Nios::mailbox_nios2hps_data_reader(void *args) { uint64_t mes; while(1) { while(sem_wait(&mailbox_nios2hps_signal_semaphore)); if(lseek(mailbox_nios2hps, 0, SEEK_SET) != 0) { cerr << "Failed to seek mailbox_nios2hps to proper location" << endl; continue; } read(mailbox_nios2hps, &mes, sizeof(mes)); printf("[HARDWARE] Reading: 0x%08x 0x%08x\n", (uint32_t)mes, (uint32_t)(mes >> 32)); switch ((uint32_t)mes) { case LED_NUMBER: printf("Active led %lu\n", (uint32_t)(mes >> 32)); break; case SWITCH_COUNT: printf("Led switched %lu times\n", (uint32_t)(mes >> 32)); break; default: break; } } return NULL; }
- Функция отправки сообщений в Mailbox;
void Nios::mailbox_hps2nios_write(uint64_t mes) { if(lseek(mailbox_hps2nios, 0, SEEK_SET) != 0) { cerr << "Failed to seek mailbox_hps2nios to proper location" << endl; } else { printf("[HARDWARE] Writing: 0x%08x 0x%08x\n", (uint32_t)mes, (uint32_t)(mes >> 32)); write(mailbox_hps2nios, &mes, sizeof(mes)); } }
- Процедура настройки с файлами устройств, которые появились после установки драйвера;
Nios::Nios () { struct sigaction backup_action; pid = getpid(); mailbox_nios2hps = open("/dev/nios_mailbox_0", O_RDONLY); if(mailbox_nios2hps < 0) { cerr << "Could not open \"/dev/nios_mailbox_0\"..." << endl; exit(1); } memset(&mailbox_nios2hps_action, 0, sizeof(struct sigaction)); mailbox_nios2hps_action.sa_sigaction = mailbox_nios2hps_signal_handler; mailbox_nios2hps_action.sa_flags = SA_SIGINFO | SA_NODEFER; sigaction(NIOS_MAILBOX_REALTIME_SIGNO, &mailbox_nios2hps_action, &backup_action); if(ioctl(mailbox_nios2hps, IOCTL_SET_PID, &pid)) { cerr << "Failed IOCTL_SET_PID" << endl; close(mailbox_nios2hps); sigaction(NIOS_MAILBOX_REALTIME_SIGNO, &backup_action, NULL); exit(1); } mailbox_hps2nios = open("/dev/nios_mailbox_1", (O_WRONLY | O_SYNC)); if(mailbox_hps2nios < 0) { cerr << "Could not open \"/dev/nios_mailbox_1\"..." << endl; close(mailbox_nios2hps); sigaction(NIOS_MAILBOX_REALTIME_SIGNO, &backup_action, NULL); exit(1); } pthread_create(&nios2hps_data_reader_thread, NULL, mailbox_nios2hps_data_reader, NULL); }
- Парсер консольных команд.
Осталось собрать проект. В результате получим исполняемый файл для архитектуры ARM-9, который скопируем на систему. Если все сделано правильно, то после запуска в консоли появится сообщение «Enter command (»read"(«r»), «write»(«w»), «reverse»), «q» to exit".
Запуск и проверка системы
Инсталляцию модуля ядра добавляем в автозагрузку Linux.
Соберем новую версию прошивки NIOS, убрав из программы весь отладочный вывод в JTAG. Преобразуем прошивку в hex формат запуском в SoC EDS 15.1 Command Shell команды «elf2hex --input=soc_nios.elf --output=soc_nios.hex --width=32 --base=0x4000 --end=0x7fff --record=4». Полученную прошивку нужно добавить как файл инициализации для памяти NIOS в Qsys, затем пересобрать Qsys, пересобрать проект ПЛИС и записать новую прошивку на карту памяти.
Загружаемся и сразу запускаем тестовое приложение. И если все было сделано правильно, то получаем рабочую систему.
Выводы
Не бойтесь использовать такие сложные связки как ПЛИС-МК-ПК на основе SoC в своих проектах. В данной статье продемонстрировано, что реализовать такую систему не так уж и сложно. Можно даже добавить несколько микроконтроллеров и связать их вместе подобным образом.
Система управления, созданная на базе изложенных выше принципов, была внедрена автором в один из электронных приборов и доказала свою работоспособность в реальном мире.
Комментарии (4)
jok40
18.04.2018 13:16Не совсем понятна необходимость в NIOS-процессоре в середине цепочки. HPS может взаимодействовать с FPGA напрямую и программу в нём можно дебажить без проблем.
Fandir
18.04.2018 17:49+1Аналогичный вопрос нафига юзать ущербный NIOS, если вы используете SoC систему, где целых 2 900 МГц АРМа на которых вполне можно запустить Linux с GUI, если вам АРМ не нужен нафига было брать систему с SoC она как бы не бюджетная)
Mogwaika
19.04.2018 16:36А из линукса с плисой можно как-нибудь проще общаться?
Поднять какой-нибудь spi с двух сторон и общаться через него хоть через скрипты на питоне и чтобы готовые драйверы использовать?
igor_suhorukov
Вопрос такого подхода в цене разработке, качестве результата и времени.
Вряд ли кому-либо удастся быстро и качественно повторить функционал LinuxCNC.
В проекте MachineKit эта задача решается за счет real time ядра linux и программ для сопроцессоров Programmable Real-time Unit (PRU) на чипе AM335x.