Здравствуйте, уважаемые хабравчане! Давно уже являюсь читателем Хабра, но до сих пор не мог найти достойной темы для публикации. И вот, наконец, хорошенько прошерстив Хабр и GT, удивился отсутствию публикаций, посвященных программируемой подсистеме реального времени (PRU‐ICSS) линейки процессоров SitaraTM фирмы TI.
Наиболее популярной и доступной отладочной платой с процессором AM335x является так называемый «одноплатник» BeagleBone Black (White,Green). И именно наличие PRU делает BeagleBone наиболее предпочтительным для использования в hardware-проектах по сравнению с другими бюджетными одноплатниками типа *Pi. Кроме того, в некоторых случаях BBB-PRU может достаточно эффективно заменить связку ПК-МК-ПЛИС.
В данной статье приведен краткий обзор подсистемы PRU и режимов работы высокоскоростных портов ввода/вывода, рассмотрен пошаговый пример инициализации высокоскоростных портов вывода (Enhanced GPIO) и произведена оценка их производительности.
Введение
Сразу оговорюсь, что не буду подробно останавливаться на характеристиках и настройках самого BeagleBone, так как данные темы достаточно хорошо освещены в интернете, просто в конце приведу наиболее полезные, на мой взгляд, ресурсы. А сконцентрируюсь непосредственно на подсистеме PRU‐ICSS.
Аналогичные PRU решения, из числа популярных, мною найдены только для Intel Edison(кстати, tutorial на эту тему). Но при схожей цене Edison уступает по производительности и характеристикам.
ВАЖНО! Не все описанные далее режимы работы PRU и не в полном объеме возможно реализовать с помощью BeagleBone из-за физических ограничений топологии платы.
Значительная часть приведенных в публикации материалов является переводом, адаптацией, модификацией или комбинацией ресурсов, приведенных в полезных источниках в конце статьи.
Итак, что же представляет собой подсистема реального времени?
Обзор PRU‐ICSS
PRU-ICSS состоит из двух 32-битных ядер, имеющих RISC-архитектуру и работающих на частоте 200МГц. Каждое ядро имеет свою область памяти, а также совместную с Linux область памяти, может использовать выводы общего назначения, расположенные на разъемах P8-P9, и формировать прерывания.
PRU является важным дополнением всей платформы BeagleBone, позволяющим обеспечивать поддержку для приложений с жесткими временными ограничениями. Но стоит отметить, что PRU не является аппаратным ускорителем, позволяющим повысить быстродействие Linux-приложений. На PRU можно возложить выполнение отдельных функций и задач, таких как реализация программных высокоскоростных протоколов передачи данных, в том числе и нестандартных, или цифровой обработки сигналов датчиков в режиме реально времени. Также можно просто реализовать дополнительную аппаратуру, например шестой UART ttyO6.
Архитектура PRU
Не буду углубляться в перевод мануалов, назову основные характеристики системы и прокомментирую некоторые слайды из презентаций и схемы из мануалов.
Основным преимуществом PRU является короткое время доступа к локальным памяти и периферии. В тактах опорной частоты оно даже ниже, чем у подсистемы ARM. Более подробное описание задержек записи/чтения приведено здесь.
Подсистема PRU включает в себя следующие блоки:
- Два ядра PRU, каждое включает в себя:
- 8KB памяти инструкций;
- 8KB памяти данных;
- Высокоскоростной интерфейс шины OCP для доступа к памяти и периферии ARM;
- Порты ввода/вывода (eGPIO) с поддержкой асинхронного захвата и последовательного вывода;
- Умножитель с возможностью накопления (MAC);
- Быстродействующая временная память (Scratchpad memory):
- 3 блока, в каждом 30 32-битных регистров;
- Прямой доступ обеспечивает возможность быстрой синхронизации между ядрами PRU;
- Один контроллер прерываний (INTC):
- Прием до 64 внешних событий;
- 10 каналов прерываний;
- Аппаратная приоритизация событий;
- Один комплект периферии для промышленного Ethernet:
- Один таймер с 10 событиями захвата и 8 сравнения;
- Два сигнала синхронизации;
- Два 16-битных сторожевых таймера;
- Цифровые порты ввода/вывода;
- 12KB памяти общего назначения;
- Формирование 16 программных событий;
- Один двухпортовый модуль Ethernet MII;
- Один порт MDIO;
- Один приемопередатчик UART c тактовой частотой 192МГц;
- Один модуль захвата (ECAP);
- Поддержка гибкого управления питанием;
Теперь более подробно рассмотрим структуру быстродействующих портов ввода/вывода, что непосредственно является темой приведенного ниже урока и предметом исследования.
Управление портами ввода и вывода осуществляется с помощью регистров R31 и R30 соответствено. Примечательно, что регистр R31 также используется для формирования системных прерываний. Таким образом, запись в R31 генерирует прерывание, а чтение из регистра возвращает информацию о состоянии портов ввода (GPI) и контроллере прерываний (INTC).
Высокая скорость портов ввода/вывода обеспечивается прямым доступом PRU, в отличие от ядра ARM, у которого доступ к GPIO осуществляется через несколько уровней соединений.
Режимы работы GPIO
Режимы задаются путем установки соответствующих битов в конфигурационном регистре CFG. Прямое включение является режимом по-умолчанию и не требует дополнительных настроек.
Порты ввода (GPI — R31) имеют 4 режима работы:
- Прямое включение:
- Регистр PRU<n>_DATAIN (pru<n>_r31_status[16:0]) подключен напрямую к соответствующему PRU<n>
- Каждый PRU имеет независимые порты ввода, таким образом, их общее количество составляет 34;
- Схема прямого включения GPI
- Параллельный захват 16-ти бит:
- Регистр PRU<n>_DATAIN (pru<n>_r31_status[16:0]) захватывает данные по положительному или отрицательному фронту тактовой частоты, формируемой в регистре PRU<n>_CLOCK (pru<n>_r31_status[16]);
- Схема параллельного захвата
- Регистр PRU<n>_DATAIN (pru<n>_r31_status[16:0]) захватывает данные по положительному или отрицательному фронту тактовой частоты, формируемой в регистре PRU<n>_CLOCK (pru<n>_r31_status[16]);
- Сдвиговый 28-битный регистр ввода:
- Регистр PRU<n>_DATAIN (pru<n>_r31_status[0]) осуществляет захват с последующим сдвигом;
- Частота выборки определяется соответствующим значением предделителей в конфигурационном регистре CGF;
- Схема сдвигового регистра
- Режим MII_RT:
- Регистр mii_rt_r31_status [29:0] находится под управлением модуля MII_RT;
Порты вывода (GPO — R30) имеют 2 режима работы:
- Прямое включение:
- Регистр PRU<n>_DATAOUT (pru<n>_r30[15:0]) подключен напрямую к соответствующему PRU<n>
- Каждый PRU имеет независимые порты вывода, таким образом, их общее количество составляет 32;
- Схема прямого включения GPO
- Сдвиговый регистр вывода:
- Регистр PRU<n>_DATAOUT (pru<n>_r30[0]) осуществляет вывод с последующим сдвигом по положительному фронту тактовой частоты, формируемой в регистре PRU<n>_CLOCK (pru<n>_r30[1]);
- Частота выборки определяется соответствующим значением предделителей в конфигурационном регистре CGF;
- В данном режиме реализуется двойная буферизация с помощью 16-битных теневых регистров gpo_sh0 и gpo_sh1;
- Каждый теневой регистр имеет независимый сигнал управления загрузкой pru<n>_r30[29:30] (PRU<n>_LOAD_GPO_SH [0:1]);
- Выдача значений начинается после установки бита pru<n>_r30[31](PRU<n>_ENABLE_SHIFT) в 1;
- Если не обновлять значения теневых регистров, то будет продолжаться циклическая выдача предустановленных значений;
- Выдача значений прекращается после сброса бита pru<n>_r30[31](PRU<n>_ENABLE_SHIFT) в 0;
- Схема сдвигового регистра
Стоит заметить, что PRU может также обращаться к обычным портам ввода/вывода и другой периферии ядра ARM через шину OCP, но это займет больше времени.
Разработка под PRU
Для создания программы под PRU необходимы следующие действия:
- Установить PRU‐ICSS package (если не предустановлено);
- Создать описание дерева устройств используемой периферии и PRU, скомпилировать и загрузить его;
- Написать программу для PRU (*.p) и скомпилировать ее (*.bin);
- Написать программу загрузки и контроля исполнения(*.c) для программы PRU и скомпилировать ее;
PRU‐ICSS package служит средством загрузки приложений из Linux в PRU и состоит из двух секций: низкоуровневый драйвер ядра и пользовательские библиотеки. Низкоуровненвый драйвер ядра (uio_pru) обеспечивает взаимодействие ядра PRU c пользовательской библиотекой PRUSSDRV и берет на себя функции питания PRU, инициализации тактирования PRU, выделения памяти для PRU и регистрации прерываний PRU. Библиотека PRUSSDRV, в свою очередь, позволяет запускать и останавливать PRU, обеспечивает доступ PRU к периферии и внешней памяти, управляет прерываниями PRU. Также в состав PRU‐ICSS package входит компилятор ассемблера PRU — pasm.
Программный стек PRU‐ICSS package:
Таким образом, программа *.c под Linux с помощью функций библиотеки PRUSSDRV загружает исполнительный файл (*.bin) в PRU, создает общие области памяти, посылает и принимает прерывания.
Отдельно стоит упомянуть, что для PRU фирма TI выпустила отладчик prudebug и компилятор C.
Компактное и информативное описание набора инструкций можно найти здесь.
Области возможного применения
Как уже отмечалось ранее, основной задачей PRU является разгрузка основного ядра ARM путем выполнения задач, строго ограниченных по времени. Таким задачами могут является реализация протоколов передачи данных или блоков цифровой обработки сигналов. Так на BeagleBone PRU можно реализовать до 25 каналов ШИМ или 4 дополнительных программных UART. Наиболее успешными проектами, демонстрирующими всю мощь PRU, являются BeagleLogic, MachineKit и LEDscape.
Примеры использования PRU-cape:
Установка PRU
Приступим, наконец, к практической части.
Итак, экспериментировать буду на имеющейся плате Beaglebone Black ревизии A5C. Использую образ Linux bone-debian-7.8-lxde-4gb-armhf-2015-03-01-4gb.img.xz, загружаемый с внешней SD карточки памяти. Для доступа к BBB использую SSH PuTTY, для обмена файлами WinSCP.
root@beaglebone:~# uname -a
Linux beaglebone 3.8.13-bone70 #1 SMP Fri Jan 23 02:15:42 UTC 2015 armv7l GNU/Linux
В последних версиях ядра для программирования PRU по умолчанию используется фреймворк remoteproc, но его я еще не освоил, поэтому будем использовать библиотеку PRUSSDRV, описанную ранее. Чтобы это стало возможно, включаем модуль:
root@beaglebone:~# modprobe uio_pruss
Убеждаемся, что остальные необходимые компоненты предустановлены
root@beaglebone:~# ls -a /usr/include | grep pruss
pruss_intc_mapping.h
prussdrv.h
root@beaglebone:~# ls -a /usr/lib | grep pruss
libprussdrv.a
libprussdrv.so
libprussdrvd.a
libprussdrvd.so
root@beaglebone:~# pasm
PRU Assembler Version 0.86
Copyright (C) 2005-2013 by Texas Instruments Inc.
Usage: pasm [-V#EBbcmLldz] [-Idir] [-Dname=value] [-Cname] InFile [OutFileBase]
...
root@beaglebone:~# lsmod | grep pru
uio_pruss 4066 0
Выполняем предварительные настройки, прописав необходимые переменные среды в автозагрузку:
echo "export SLOTS=/sys/devices/bone_capemgr.*/slots" >> ~/.profile
#echo "export SLOTS=/sys/devices/platform/bone_capemgr/slots" >> ~/.profile #для вресий ядра от 4
echo "export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins" >> ~/.profile
echo "export PINGROUPS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pingroups" >> ~/.profile
source .profile
Отключение HDMI/EMMC
Почти вся периферия PRU выведена на порты, основной функцией которых является HDMI/EMMC. Поэтому для успешного взаимодействия PRU с внешним миром необходимо отключить инициализацию HDMI/EMMC.
Для используемого образа делается это достаточно просто — необходимо лишь раскомментровать нужную строчку в /boot/uEnv.txt. Поэтому редактируем его
nano /boot/uEnv.txt
, чтобы в итоге получилось так:...
##Disable HDMI/eMMC
cape_disable=capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN,BB-BONE-EMMC-2G
...
GPIO:direct mode
Воспользовавшись информативными таблицами разъемов P8 и P9, можно обнаружить, что в BeagleBone доступно 8 быстродействующих портов вывода для PRU0 и 14 — для PRU1. Как описано ранее, необходимо предварительно отключить HDMI/EMMC и сконфигурировать выводы в нужный режим. Так как хочется выжать побольше из BBB, будем работать с PRU1 и конфигурировать выводы pr1_pru1_pru_r30[0:13]. Также у нас еще остается один незадействованный вход pr1_pru1_pru_r31_16, расположенный на разъеме P9.26. Его используем для запуска программы по внешней кнопочке.
Оптимальным примером демонстрации работы портов общего назначения PRU, по моему мнению, будет реализация функции программируемой аппаратной задержки по внешнему событию. Задержку будем передавать с помощью пользовательской программы под linux через общую область памяти. PRU непосредственно реализует аппаратную задержку и выводит ее значение, а именно младшие 14 бит, на доступные выводы.
Таким образом, вырисовывается следующий алгоритм программы:
- Запускаем пользовательскую программу с указанием желаемой задержки в качестве аргумента;
- Программа инициализирует PRU, передает значение задержки и запускает подпрограмму PRU;
- Подпрограмма PRU ждет внешнего события (нажатия кнопки);
- После обнаружения события отсчитывает заданную задержку;
- Уведомляет пользовательскую программу о завершении посредством прерывания;
- Пользовательская программа получает прерывание о завершении работы подпрограммы;
- После чего деактивирует PRU и завершается сама;
Direct GPO device tree overlays
Итак, для реализации задуманной программы, необходимо настроить 14 портов BBB на вывод и 1 порт на ввод, а также непосредственно запустить подсистему PRU.
На основе примеров из github'а составил следующее описание дерева устройств:
PRU_DGPO-00A0.dts
/dts-v1/;
/plugin/;
/ {
compatible = "ti,beaglebone", "ti,beaglebone-black";
/* identification */
part-number = "PRU_DGPO";
version = "00A0";
/* state the resources this cape uses */
exclusive-use =
/* PRU1 Direct Output */
"P8.20", /* pru1: pr1_pru1_pru_r30_13 */
"P8.21", /* pru1: pr1_pru1_pru_r30_12 */
"P8.28", /* pru1: pr1_pru1_pru_r30_10 */
"P8.27", /* pru1: pr1_pru1_pru_r30_8 */
"P8.30", /* pru1: pr1_pru1_pru_r30_11 */
"P8.29", /* pru1: pr1_pru1_pru_r30_9 */
"P8.40", /* pru1: pr1_pru1_pru_r30_7 */
"P8.39", /* pru1: pr1_pru1_pru_r30_6 */
"P8.42", /* pru1: pr1_pru1_pru_r30_5 */
"P8.41", /* pru1: pr1_pru1_pru_r30_4 */
"P8.44", /* pru1: pr1_pru1_pru_r30_3 */
"P8.43", /* pru1: pr1_pru1_pru_r30_2 */
"P8.46", /* pru1: pr1_pru1_pru_r30_1 */
"P8.45", /* pru1: pr1_pru1_pru_r30_0 */
/* PRU1 Direct Input */
"P9.26", /* pru1: pr1_pru1_pru_r31_16 */
/* the hardware ip uses */
"pru1";
fragment@0 {
target = <&am33xx_pinmux>;
__overlay__ {
pru_pru_pins: pinmux_pru_pru_pins { // The PRU pin modes
pinctrl-single,pins = <
0x084 0x0D /* lcd_pclk.pr1_pru1_pru_r30_13, MODE5 | OUTPUT | PRU */
0x080 0x0D /* lcd_pclk.pr1_pru1_pru_r30_12, MODE5 | OUTPUT | PRU */
0x0e8 0x0D /* lcd_pclk.pr1_pru1_pru_r30_10, MODE5 | OUTPUT | PRU */
0x0e0 0x0D /* lcd_vsync.pr1_pru1_pru_r30_8, MODE5 | OUTPUT | PRU */
0x0ec 0x0D /* lcd_ac_bias_en.pr1_pru1_pru_r30_11, MODE5 | OUTPUT | PRU */
0x0e4 0x0D /* lcd_hsync.pr1_pru1_pru_r30_9, MODE5 | OUTPUT | PRU */
0x0bc 0x0D /* lcd_data7.pr1_pru1_pru_r30_7, MODE5 | OUTPUT | PRU */
0x0b8 0x0D /* lcd_data6.pr1_pru1_pru_r30_6, MODE5 | OUTPUT | PRU */
0x0b4 0x0D /* lcd_data5.pr1_pru1_pru_r30_5, MODE5 | OUTPUT | PRU */
0x0b0 0x0D /* lcd_data4.pr1_pru1_pru_r30_4, MODE5 | OUTPUT | PRU */
0x0ac 0x0D /* lcd_data3.pr1_pru1_pru_r30_3, MODE5 | OUTPUT | PRU */
0x0a8 0x0D /* lcd_data2.pr1_pru1_pru_r30_2, MODE5 | OUTPUT | PRU */
0x0a4 0x0D /* lcd_data1.pr1_pru1_pru_r30_1, MODE5 | OUTPUT | PRU */
0x0a0 0x0D /* lcd_data0.pr1_pru1_pru_r30_0, MODE5 | OUTPUT | PRU */
0x180 0x36 /* uart1_rxd.pr1_pru1_pru_r31_16, MODE6 | INPUT | PRU */
>;
};
};
};
fragment@1 {
target = <&ocp>;
__overlay__ {
test_helper: helper {
compatible = "bone-pinmux-helper";
pinctrl-names = "default";
pinctrl-0 = <&pru_pru_pins>;
status = "okay";
};
};
};
fragment@2 { // Enable the PRUSS
target = <&pruss>;
__overlay__ {
status = "okay";
};
};
};
Далее этот файл необходимо скомпилировать, скопировать в /lib/firmware и загрузить:
echo "Compiling the overlay from .dts to .dtbo"
dtc -O dtb -o PRU_DGPO-00A0.dtbo -b 0 -@ PRU_DGPO-00A0.dts
echo "Copy PRU_DGPO-00A0.dtbo to /lib/firmware"
cp PRU_DGPO-00A0.dtbo /lib/firmware
echo "Loading overlay:"
sh -c "echo 'PRU_DGPO' > $SLOTS"
Затем стоит проверить процесс загрузки.
В успешном случае должно быть так:
root@beaglebone:~# dmesg | tail
[12566.485091] bone-capemgr bone_capemgr.9: slot #7: generic override
[12566.485149] bone-capemgr bone_capemgr.9: bone: Using override eeprom data at slot 7
[12566.485197] bone-capemgr bone_capemgr.9: slot #7: 'Override Board Name,00A0,Override Manuf,PRU_DGPO'
[12566.485506] bone-capemgr bone_capemgr.9: slot #7: Requesting part number/version based 'PRU_DGPO-00A0.dtbo
[12566.485554] bone-capemgr bone_capemgr.9: slot #7: Requesting firmware 'PRU_DGPO-00A0.dtbo' for board-name 'Override Board Name', version '00A0'
[12566.492347] bone-capemgr bone_capemgr.9: slot #7: dtbo 'PRU_DGPO-00A0.dtbo' loaded; converting to live tree
[12566.494050] bone-capemgr bone_capemgr.9: slot #7: #3 overlays
[12566.555682] bone-capemgr bone_capemgr.9: slot #7: Applied #3 overlays.
root@beaglebone:~# cat $SLOTS
0: 54:PF---
1: 55:PF---
2: 56:PF---
3: 57:PF---
4: ff:P-O-- Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-- Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
6: ff:P-O-- Bone-Black-HDMIN,00A0,Texas Instrument,BB-BONELT-HDMIN
7: ff:P-O-L Override Board Name,00A0,Override Manuf,PRU_DGPO
root@beaglebone:~# cat $PINS | grep 00d
pin 32 (44e10880) 0000000d pinctrl-single
pin 33 (44e10884) 0000000d pinctrl-single
pin 40 (44e108a0) 0000000d pinctrl-single
pin 41 (44e108a4) 0000000d pinctrl-single
pin 42 (44e108a8) 0000000d pinctrl-single
pin 43 (44e108ac) 0000000d pinctrl-single
pin 44 (44e108b0) 0000000d pinctrl-single
pin 45 (44e108b4) 0000000d pinctrl-single
pin 46 (44e108b8) 0000000d pinctrl-single
pin 47 (44e108bc) 0000000d pinctrl-single
pin 56 (44e108e0) 0000000d pinctrl-single
pin 57 (44e108e4) 0000000d pinctrl-single
pin 58 (44e108e8) 0000000d pinctrl-single
pin 59 (44e108ec) 0000000d pinctrl-single
root@beaglebone:~# cat $PINS | grep 036
pin 96 (44e10980) 00000036 pinctrl-single
Чтобы подробнее разобраться с настройками древа устройств последних релизов Debian, рекомендую обратиться сюда.
Вообще древо устройств в Linux — это отдельная тема, которая требует глубокого рассмотрения. Интересующиеся могут начать знакомство с Device Tree здесь и здесь.
Пользовательская программа
Как сказано ранее, основными задачами пользовательской программы являются:
- Инициализация PRU;
- Обмен данными с PRU через общую область памяти;
- Запуск подпрограммы PRU;
- Формирование и обработка прерываний и событий PRU;
Parallel_output.c :
#include <stdio.h>
#include <stdlib.h>
#include <prussdrv.h>
#include <pruss_intc_mapping.h>
#define PRU_NUM 1 // using PRU1 for these examples
int main (int argc, char* argv[])
{
unsigned int ret;
if(getuid()!=0){
printf("You must run this program as root. Exiting.\n");
exit(EXIT_FAILURE);
}
if(argc!=2) {
printf("Usage is Parralel_output and integer number of delay \n");
printf(" e. g. ./Parralel_output 100\n");
return 2;
}
char *p;
unsigned int cyc = (unsigned int) strtol(argv[1], &p, 10);
printf("Delay for %d cycles\n", cyc);
// Initialize structure used by prussdrv_pruintc_intc
// PRUSS_INTC_INITDATA is found in pruss_intc_mapping.h
tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
// Allocate and initialize memory
prussdrv_init ();
ret = prussdrv_open(PRU_EVTOUT_0);
if (ret)
{
printf("prussdrv_open open failed\n");
return (ret);
}
// Map PRU's interrupts
prussdrv_pruintc_init(&pruss_intc_initdata);
// Write a number of cycles into PRU1 Data RAM0
prussdrv_pru_write_memory(PRUSS0_PRU1_DATARAM , 0, &cyc, 4);
// Load and execute the PRU program on the PRU
prussdrv_exec_program (PRU_NUM, "./Parallel_output.bin");
// Wait for event completion from PRU, returns the PRU_EVTOUT_0 number
int n = prussdrv_pru_wait_event (PRU_EVTOUT_0);
printf("PRU program completed, event number %d.\n", n);
// Disable PRU and close memory mappings
prussdrv_pru_disable(PRU_NUM);
prussdrv_exit ();
return EXIT_SUCCESS;
}
Компилируем:
gcc Parallel_output.c -o Parallel_output -lpthread -lprussdrv
Подпрограмма PRU
Для написания подпрограммы PRU используется ассемблер PASM. Подпрограмма исполняется на одном из двух ядер PRU независимо от Linux. Синхронизация PRU и Linux осуществляется посредством событий и прерываний.
Кроме описанного выше алгоритма добавим в подпрограмму еще несколько инструкций, чтобы оценить время их выполнения.
Parallel_output.p:
.origin 0 // start of program in PRU memory
.entrypoint START // program entry point (for a debugger)
#define PRU0_R31_VEC_VALID 32 // allows notification of program completion
#define PRU_EVTOUT_0 3 // the event number that is sent back
START:
WBC r31.t16 // wait bit clear - i.e., button press
// Toggle 4 times Parallel output pins
MOV r30, 0xffff
MOV r30, 0x0000
MOV r30, 0xffff
MOV r30, 0x0000
LBCO r30, C24, 0, 4 // load PRU1 Data RAM into r30 (use c24 const addr)
CYCLE:
SUB r30, r30, 1 // Decrement REG30 by 1 - i.e., parallel output current value on pins
QBNE CYCLE, r30, 0 // Loop to CYCLE, unless REG30=0
END: // notify the calling app that finished
MOV R31.b0, PRU0_R31_VEC_VALID | PRU_EVTOUT_0
HALT // halt the pru program
Компилируем:
pasm -b Parallel_output.p
Запуск PRU и оценка производительности
Для того чтобы оценить скорость выполнения инструкций PRU, буду использовать осциллограф Tektronix MSO4032 350MHz с 16-канальным логическим анализатором, имеющим разрешение 2 нс.
Фото системы в сборе
Наконец, запускаем программу:
root@beaglebone:/home/debian/Desktop/Direct-GPO# ./Parallel_output 15
Delay for 15 cycles
PRU program completed, event number 1.
Так как цикл задержки включает в себя 2 инструкции (SUB и QBNE), каждая по 5нс, то аппаратная задержка будет составлять arg*10нс. Для вышеприведенного примера задержка должна составить 150 нс. Измерения проводятся без учета тестовых начальных инструкций.
Для наглядности инструкции наложены на осциллограмму:
Как видно, время выполнения инструкции MOV в пределах погрешности, обусловленной разрешением логического анализатора — 2нс, и вполне вписывется в заявленные 5нс. Задействованы все 14 выводов и 1 ввод, как и запланировано.
Выполнение инструкции WBC — ожидание события — занимает ~25 нс, но это время постоянно и его не составит труда учесть при необходимости.
Далее приведены осциллограммы для разных значений аргументов, измерение задержки производится с помощью курсоров осциллографа.
Осциллограмма для задержки в 150 нс
root@beaglebone:~# ./Parallel_output 15
Осциллограмма для задержки в 15 мкс
root@beaglebone:~# ./Parallel_output 1500
Осциллограмма для задержки в 159,23 мкс
15923 — просто рандомное число, близкое к 2^14, чтобы задействовать все выводы.
root@beaglebone:~# ./Parallel_output 15923
15923 — просто рандомное число, близкое к 2^14, чтобы задействовать все выводы.
hex2dec('3E32') = 15922.
Итоги
Таким образом, с помощью вышеописанного примера продемонстрированы возможности BeagleBone PRU в части формирования сигналов с разрешением до 5нс на 14 выводах одновременно, рассмотрены основные принципы управления и конфигурации PRU, а также программная модель PRU.
Исходники можно найти здесь.
Полезные источники
Значительная часть примеров и принципов работы взята из материалов Derek Molloy:
- Непосредственно его сайт;
- Плейлист, посвященный BeagleBone;
- Сайт его книги по BB с примерами;
- Репозиторий;
Огромное спасибо ему за все это!
→ Интересный сайт, посвященный применению BBB в CNC и не только.
→ Подключение интернет на BBB (раз, два и три).
→ Репозиторий device tree overlay для BB.