Хабрахабр

[Из песочницы] Распределённая система управления на базе SoC FPGA

Реализация связки прошивки ПЛИС, ПО микроконтроллера NIOS и управляющего ПО под Linux на базе Altera Cyclone V SoC с использованием Avalon Mailbox для создания на их основе распределенной системы управления.
В распределенной системе управления приходится решать множество разных задач на разных уровнях.

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

Здесь ключевую роль играет возможность отладки софта внутри ОС по JTAG и отслеживание происходящего на уровне периферии МК на любом break-point`е. Есть задачи, которые удобно решать на уровне микроконтроллера (далее – МК), либо вообще без ОС (bare-metal), либо с минималистичными ОС реального времени.

В таких задачах процессор бывает просто лишним. И есть задачи, которые стоит решать уже на уровне ПЛИС, так как никакого микроконтроллера может не хватить для грамотного параллельного управления разной высокочастотной электроникой, например, драйверами шаговых приводов с обработкой данных энкодеров и регуляторами скоростей.

И всю систему управления очень удобно расположить вообще на одном кристалле, что собственно и будет сделано. Количество исполнительных устройств и их различных функций в системе управления сильно возрастает, если речь идет о разработке прибора, например, с трех-степенным манипулятором, парой-тройкой серводвигателей, дюжиной дискретных устройств, кучей периферии на всех популярных интерфейсах (SPI, I2C, UART и т.д.) и сложной логикой с математическим анализом внутри. В итоге все три уровня управления ПК-МК-ПЛИС и их взаимодействия переместятся внутрь кристалла.

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

04 на борту.
Исходные тексты всех программ доступны на GitHub. Для экспериментов нам понадобится реальная или виртуальная машина с Ubuntu 16.

Представим, что все исполнительные устройства системы управления ПК-МК-ПЛИС сводятся к параллельным портам ввода-вывода. Для примера в качестве датчиков и исполнительных устройств ограничимся набором кнопок и светодиодов, а управлять ими будем из командной строки терминала.

Часть ПК уже интегрирована в чип и выполнена на базе 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 = }, 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.

Вместо этого в контекстном меню проекта soc_nios_bsp нужно выбрать в меню NIOS II пункт BSP Editor и включить опции enable_small_c_library и enable_reduced_device_drivers, чтобы прошивка особо не разрасталась. В первую очередь нужно сразу собрать BSP, но не традиционным способом. В дальнейшем, так как параметры сборки сохранятся, пересобрать BSP можно просто выбором в меню NIOS II пункта Generate BSP. Затем собрать, нажав Generate.

В файле 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.

Если все сделано правильно, в консоли NIOS II должно появится сообщение «Turn the switch ON to activate the timer». Теперь можно запускать отладку из Eclipse.

Установка ОС Linux на плату

Подробным образом весь процесс описан здесь в разделах с 1 по 10. Рекомендуется использовать более свежие свежие версии тулчейна, бутлоадера и ядра, чем те, которые можно найти по ссылке. Обратите внимание, что для сборки данной версии бутлоадера не подойдет тулчейн с компилятором выше 6-й версии.

Для генерации device tree вместо предложенной утилиты sopc2dts лучше использовать скрипт sopc2dts.jar, причем можно указать сразу --type dtb.

Для сборки необходимо принудительно указать переменные окружения CC как путь к arm-linux-gnueabihf-gcc и CXX как путь к arm-linux-gnueabihf-g++ из тулчейна. Для получения системы настоятельно рекомендуется использовать самый свежий Buildroot. В настройках тулчейна при конфигурации 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».

Добавим в переменные окружения две новых записи CROSS_COMPILE и KDIR, указывающие на полный путь с префиксом тулчейна и путь к исходникам ядра соответственно. В настройках проекта выключим опции «Use default build command» и «Generate Makefiles automatically», так как для сборки модуля ядра понадобится команда make TARGET=nios_mailbox TARGET_DIR=Default. Всё. В список дефайнов надо добавить __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.

Преобразуем прошивку в hex формат запуском в SoC EDS 15. Соберем новую версию прошивки NIOS, убрав из программы весь отладочный вывод в JTAG. Полученную прошивку нужно добавить как файл инициализации для памяти NIOS в Qsys, затем пересобрать Qsys, пересобрать проект ПЛИС и записать новую прошивку на карту памяти. 1 Command Shell команды «elf2hex --input=soc_nios.elf --output=soc_nios.hex --width=32 --base=0x4000 --end=0x7fff --record=4».

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

Не бойтесь использовать такие сложные связки как ПЛИС-МК-ПК на основе SoC в своих проектах. В данной статье продемонстрировано, что реализовать такую систему не так уж и сложно. Можно даже добавить несколько микроконтроллеров и связать их вместе подобным образом.

Система управления, созданная на базе изложенных выше принципов, была внедрена автором в один из электронных приборов и доказала свою работоспособность в реальном мире.

Теги
Показать больше

Похожие статьи

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть