Теория
Есть 3 основных варианта создания и настройки GPIO выводов из ARM процессора.
Информация по большей части взята из документации ug585-Zynq-7000-TRM.
в сердце Zybo находится XC7Z010-1CLG400C, это SoC (System on Chip), который состоит из ARM процессора и FPGA. Их можно рассматривать как 2 раздельных устройства, у каждого из них имеются свои подключения к ножкам микросхемы, к тому же между ними есть внутренние соединения для обмена данными. Более детально это можно увидеть на следующей схеме:
| Рис.1 структурная схема интерфейсов |
Помимо специализированных интерфейсов есть GPIO выводы которые могут работать на вход, выход либо быть отключены (Hi-Z состояние). К тому же на них могут быть настроены прерывания. Есть 2 типа таких выводов. Первые носят название MIO (multiplexed I/O), данные выводы соединены напрямую между ножками микросхемы и процессором. Также имеется набор EMIO (extended multiplexed I/O) выводов, они соединяют процессор (PS) и FPGA (PL). Помимо них можно также использовать AXI (Advanced eXtensible Interface) для передачи данных между PS и PL.
Далее будут подробно рассмотрены все 3 способа.
К соединениям MIO и EMIO подключены мультиплексоры которые выбирают режим их работы. Большинство выводов помимо возможности работать как простой GPIO могут быть сконфигурированы под определённые интерфейсы, UART, I2C, и т.д. Большинство изи них могут быть направлены как на выводы MIO так и EMIO. Но могут отличаться параметры интерфейсов в зависимости от выбранного вывода. К примеру Ethernet через MIO будет подключён по RGMII, для уменьшения числа ножек, а через EMIO по GMII.
| Рис.2 Мультиплексирование выводов |
| Рис.3 схематическое представление соединений |
Стоит отметить что выводы EMIO можно подвести к любым доступным ножкам PL, выводы MIO жёстко прописаны к конкретным ножкам PS.
| Рис.4 соединения между выводами |
Несмотря на рисунок выводы MIO[7:8] доступны только для вывода.
У Zynq-7000 имеется 54 MIO вывода, каждый из которых может работать на вход выход либо быть отключён (Hi-Z). 192 EMIO сигнала соединяющих PS и PL, это можно расценивать как 64 вывода. 192 получается суммированием возможностей. 64 входа, 64 вывода и 64 сигнала включения. Ведь все эти сигналы должны быть переведены в PL часть для того чтобы их можно было соединить с любым PL выводом.
На форумах есть несколько тем с просьбами разъяснить к каким выводам относятся EMIO и как их воспринимать. Их удобнее всего воспринимать как 3 набора регистров по 64 бит каждый.
Данные сигналы разделены на банки, 2 банка для MIO и 2 банка для EMIO:
| Рис.5 Соединение сигналов к соответствующим банкам |
Банк 0 контролирует выводы MIO[53:32]
Банк 0 контролирует выводы EMIO[31: 0]
Банк 0 контролирует выводы EMIO[63:32]
Выводы можно настраивать как независимо друг от друга так и в пределах всего банка. Выходное напряжение для банков MIO должно быть одинаково внутри банка, но может отличаться между банками, для EMIO это не имеет значения, т.к. они выводятся через PL.
К каждой ножке может быть программно подключен Pull - UP резистор. На каждый вывод может быть независимо настроено прерывание на изменение (фронт, срез) либо уровень (высокий, низкий).
У каждого банка есть свой набор регистров отвечающих за конфигурацию выводов:
| Рис.6 краткое описание конфигурационных регистров |
Насчёт AXI можно сказать, что это интерфейс передачи данных между PS и PL частью. Его можно в принципе использовать где угодно, просто это основное соединение между PS и PL у XILINX.
Для простоты восприятия, в нашем случае, использование AXI интерфейса можно описать следующим образом:
PS выступает как мастер и выдаёт наружу адрес ячейки к которой он хочет обратиться, за частую размер ячейки 32 бита. можно либо записывать, либо считывать значение из ячейки. В PL нужно каким-либо образом реализовать адресуемую память которая будет работать с AXI интерфейсом. Это задача FPGA части корректно обрабатывать принимаемые данные и отправлять обратно запрашиваемые данные.
Настройка
Прежде чем использовать порты, их нужно настроить, для этого в Vivado нужно открыть настройки IP ядра processing system, перейти в раздел MIO configuration, далее убедиться, что стоит галочка рядом с GPIO MIO. Банк 0 (MIO[31:0]) на плате Zybo подключен к 3.3 В, банк 1 (MIO[53:32]) к 1.8 В.
Можно заметить, что не все выводы отображены, это из-за того, что к ним уже подключена другая периферия и их нельзя использовать как GPIO. Для нас главное чтобы был доступен вывод MIO7, к нему подключен светодиод.
| Рис.7 настройка MIO |
| Рис.8 настройка EMIO |
| Рис.9 настройка AXI |
| Рис.10 Настройка тактовой частоты AXI |
| Рис.11 готовое к подключению ядро процессора |
Подключение
Далее нужно подключить всё необходимое. MIO подключён по умолчанию к соответствующему выводу FPGA. соединим EMIO. Если раскрыть вкладку GPIO_0, на ядре процессора, можно будет увидеть 3 вывода, вход, выход и включение вывода (out enable), для перевода в Hi-Z состояние. Нам нужно вывести наружу выход для управления светодиодом, можно нажать на соответствующий вывод правой кнопкой и выбрать "Make External", в таком случае схема будет иметь следующий вид:
| Рис.12 подключённый EMIO вывод |
| Рис.13 настройки AXI GPIO |
| Рис.14 блок дизайн вместе с AXI GPIO |
Для этого сперва нужно обновить wrapper:
| Рис.15 обновление wrapper |
| Рис.16 переход в корректный режим отображения вкладок |
| Рис.17 настройка выходных ножек |
SYSFS
Сперва перейдём в папку /sys/class/gpio.
cd sys/class/gpioВыведем её содержимое.
lsтам должно быть файлы "export", "unexport" и папка "gpiochip906"
| Рис.18 содержимое папки gpio |
Для того чтобы подключить необходимую ножку, в нашем случае MIO[7], необходимо выполнить команду:
echo 913 > export906 + 7 = 913. Таким образом мы запишем в файл export значение 913 благодаря чему появится дополнительная папка "gpio913":
| Рис.19 новая папка gpio913 |
cd gpio913 ls
| Рис.20 содержимое папки gpio913 |
| Рис.21 зажигание светодиода |
С помощью данных команд можно написать свою программу или скрипт который будет обрабатывать GPIO выводы. Примеры можно посмотреть здесь и здесь.
Скрипт можно написать либо сразу в Linux используя какой-нибудь текстовый редактор, к примеру vim, который установлен по умолчанию. Либо переместив его с компьютера, как это сделать было описано здесь.
Я использовал VIM и создал файл blink_bash.sh в корневой директории, .sh - расширение для скриптового файла. После создания файла, необходимо разрешить его запуск, для этого используется команда:
chmod +x blink_bash.shДля запуска скрипта:
./blink_bash.shКод скрипта:
# blink MIO LED from Bash script echo exporting pins echo 913 > sys/class/gpio/export # MIO[7] echo pins exported echo configuring direction to out echo out > sys/class/gpio/gpio913/direction echo direction configured to out echo blink LED 10 times for i in $(seq 1 10); do echo 1 > sys/class/gpio/gpio913/value sleep 1 # delay 1 second echo 0 > sys/class/gpio/gpio913/value sleep 1 # delay 1 second echo LED blinked $i times done # clean stuff, just because we can echo unexport pins echo 913 > sys/class/gpio/unexport # MIO[7] echo pins unexported echo script finishedТакже скрипт можно запускать в фоновом режиме, для этого в конце дописывается $:
./blink_bash.sh $Также можно создать программу которая будет мигать светодиодом. В той файловой системе (rootfs) которую мы используем нет компилятора, для того чтобы скомпилировать код, нужно либо установить его туда, либо произвести кросс-компиляцию на другом устройстве. Я пошёл по второму пути. Как скомпилировать я нашёл здесь. Сперва нужно установить необходимый компилятор "arm-linux-gnueabihf-gcc", в принципе он используется при сборке Linux проекта. Также он поставляется вместе с Vivado. Для получения доступа к нему через Vivado, нужно использовать команду source и указать адрес к настройкам которые хранятся в папке с Vivado:
source tools/Xilinx/Vivado/2019.1/settings64.sh
Создадим исходный файл blin_bin.c, вставим в него код взятый отсюда.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main ()
{
int exportfd;
exportfd = open( "/sys/class/gpio/export" , O_WRONLY );
if( exportfd < 0)
{
printf( "Cannot open GPIO to export it \n");
exit (1);
}
write( exportfd ,"913",4);
close( exportfd );
printf(" GPIO exported successfully \n " );
int directionfd = open("/sys/class/gpio/gpio913/direction", O_RDWR);
if( directionfd < 0){
printf("cannot open the file");
exit(1);
}
write( directionfd , "out" , 4);
close (directionfd);
/* Get the GPIO value ready to be toggled */
int valuefd = open( "/sys/class/gpio/gpio913/value" , O_RDWR );
if ( valuefd < 0){
printf(" Cannot open GPIO value \n " );
exit (1);
}
printf(" GPIO value opened , now toggling ...\n " );
/* toggle the GPIO forever , press control + c to stop it */
while (1)
{
int i ;
for(i=0;i<0xFF;i++){}
printf(" GPI0 -off\n");
write( valuefd , "1" , 2);
usleep(500000);
printf(" GPI0 -on\n");
write( valuefd , "0" , 2);
usleep(500000);
}
printf("blink program finished");
}
Для его компиляции используется команда:
arm-linux-gnueabihf-gcc blink_bin.c -o blink_binПосле этого появится бинарный файл blink_bin. Его нужно перенести в Zybo, как это сделать было описано здесь.
Затем нужно выставить ему права на запуск и выполнить его:
chmod +x blink_bin ./blink_binСкорей всего появится ошибка:
-/bin/ash: ./blink_bin: not found
| Рис.22 файл не найден |
Как оказалось, благодаря следующим источникам: 1, 2, 3, 4. Скомпилированный файл ссылается на динамическую библиотеку которой нет в нашей системе на Zybo. Было обновление после версии Vivado 2015.4 (скорей всего это связано с Linux, ARM Linaro а не Xilinx), после которого используемой динамической библиотекой для скомпилированных программ стала /lib/ld-linux-armhf.so.3, её и не может найти наша программа. Раньше использовалась /lib/ld-linux.so.3, которая находится в нашей системе на Zybo. Для того чтобы узнать какую динамическую библиотеку использует программа нужно выполнить
readelf -l blink_bin
которая выдаст:
Elf file type is DYN (Shared object file)
Entry point 0x505
There are 9 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x0008c8 0x000008c8 0x000008c8 0x00008 0x00008 R 0x4
PHDR 0x000034 0x00000034 0x00000034 0x00120 0x00120 R 0x4
INTERP 0x000154 0x00000154 0x00000154 0x00019 0x00019 R 0x1
[Requesting program interpreter: /lib/ld-linux-armhf.so.3]
LOAD 0x000000 0x00000000 0x00000000 0x008d4 0x008d4 R E 0x10000
LOAD 0x000eac 0x00010eac 0x00010eac 0x0015c 0x00160 RW 0x10000
DYNAMIC 0x000eb4 0x00010eb4 0x00010eb4 0x000f8 0x000f8 RW 0x4
NOTE 0x000170 0x00000170 0x00000170 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000eac 0x00010eac 0x00010eac 0x00154 0x00154 R 0x1
Section to Segment mapping:
Segment Sections...
00 .ARM.exidx
01
02 .interp
03 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame
04 .init_array .fini_array .dynamic .got .data .bss
05 .dynamic
06 .note.ABI-tag .note.gnu.build-id
07
08 .init_array .fini_array .dynamic .got
либо
objdump -j .interp -s blink_bin
blink_bin: file format elf32-little Contents of section .interp: 0154 2f6c6962 2f6c642d 6c696e75 782d6172 /lib/ld-linux-ar 0164 6d68662e 736f2e33 00 mhf.so.3.Для того чтобы избежать проблем с динамическими библиотеками, для компиляции можно воспользоваться командой:
arm-linux-gnueabihf-gcc blink_bin.c -o blink_bin -static
Это вставит весь код из библиотеки в бинарный файл, этот файл запуститься, но он весит в 100 раз больше.
Как оказалось файлы которые указаны как динамические библиотеки, на который ссылается файл, на самом деле ссылки на настоящие библиотеки. Я скачал оба файла и оказалось, что "ld-linux-armhf.so.3" и "ld-linux.so.3" отличаются только названием. Они оба ссылаются на библиотеку "ld-2.29.so".
Таким образом для запуска нашего файла, нужно либо добавить необходимый файл "ld-linux-armhf.so.3". Либо создать его самому. В нашем случае файл "ld-linux.so.3" ссылается на файл "ld-2.13.so". это можно проверить командой:
ls -l lib
Она покажет содержимое папки lib в которой находятся библиотеки:
| Рис.23 содержимое папки lib |
Но всё же, сделаем ссылку на файл ld-linux.so.3, если он вдруг измениться (к примеру новая библиотека добавится), нам не нужно будет ничего менять. Для этого нужно выполнить команду:
ln -s ld-linux.so.3 /lib/ld-linux-armhf.so.3
Тогда в папке lib появится файл ld-linux-armhf.so, который будет ссылаться на файл ld-linux.so.3, который находится в той же папке. После этого файл можно запустить.
Стоит отметить, что после выключения питания все файлы пропадут, т.к. всё находится в RAM памяти, для того чтобы избежать этого необходимо изменять rootfs в загрузочном файле BOOT.bin, о том как это сделать было описано ранее.
Стоит отметить, что после выключения питания все файлы пропадут, т.к. всё находится в RAM памяти, для того чтобы избежать этого необходимо изменять rootfs в загрузочном файле BOOT.bin, о том как это сделать было описано ранее.
dev/mem
К выводам можно также обращаться напрямую через регистры. Для этого используется команда devmem. Адреса регистров можно найти в документации ug585 стр. 1345.
Адрес для AXI можно посмотреть в Vivado открыв блок дизайн и нажав на вкладку "Address Editor":
| Рис.24 адрес AXI ядра |
| Рис.25 команда devmem |
devmem 0x41200000 32 1
Чтобы потушить
devmem 0x41200000 32 0
Для работы с AXI доступно несколько различных регистров, можно работать также как с GPIO, настраивать на ввод, вывод, HI-Z и использовать прерывания. Подробнее в документации.
С выводами MIO[7] и EMIO[0] немного посложней, из-за того что их сперва нужно настроить используя несколько регистров. По умолчанию они настроены на вход.
С выводами MIO[7] и EMIO[0] немного посложней, из-за того что их сперва нужно настроить используя несколько регистров. По умолчанию они настроены на вход.
devmem 0xE000A204 32 0x80 devmem 0xE000A208 32 0x80 devmem 0xE000A000 32 0xFF7F0080
| Рис.26 зажигание светодиода на MIO[7] |
devmem 0xE000A284 32 1 devmem 0xE000A288 32 1 devmem 0xE000A048 32 1
Команда devmem всего лишь автоматизирует запись в файл dev/mem который отображает реальную память процессора. Всё, что находится в RAM памяти доступно через файл dev/mem. Для работы с ним нужно скомпилировать программу для чтения и записи в этот файл. Вот пример для работы с AXI, взято отсюда.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#define LEDBASE 0x41200000
#define MAP_SIZE 4096UL
#define LED_BIT 1
int main(int argc, char **argv) {
int fd;
void *map_base;
unsigned char *led_val;
off_t offset;
offset = LEDBASE;
if ((fd = open ("/dev/mem", O_RDWR | O_SYNC)) == -1) {
printf ("/dev/mem not opened.\n");
return -1;
}
map_base = mmap (0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
if (map_base == (void *) -1) {
close(fd);
printf ("/dev/mem mapping failed.\n");
}
led_val = map_base;
while(1)
{
printf ("LED is %s \n", (*led_val & 1) ? "On": "Off");
fflush (stdout);
sleep(1);
printf ("Changing LED Status\n");
fflush (stdout);
*led_val ^= LED_BIT;
sleep(1);
}
if (munmap(map_base, MAP_SIZE) == -1) {
printf("unmap failed\n");
}
close (fd);
return 0;
}
В заключении стоит добавить, что работа с памятью напрямую через команду devmem, либо с помощью файла /dev/mem, по умолчанию возможна только с правами администратора. Если обычный пользователь попробует воспользоваться данными методами, ему будет отказано в доступе, это сделано ради обеспечения безопасности. Для того чтобы работать с периферией, определённые отделы памяти объявляют доступными пользователю, либо создают файлы которые драйвер сам обрабатывает и определяет, что следует поместить в регистры. Это делается посредством драйверов, пример этого sysfs, который был использован выше. Для указания направления мы прописывали "out" драйвер сам определял, что нужно записать в регистр.Получается, что для работы с AXI интерфейсом нужны либо права администратора, либо подходящий драйвер.
Целую ваши ноги) спасибо огромное
ОтветитьУдалить#!/bin/bash не момешает в начале скипта... и / перед sys
ОтветитьУдалитьХорошая статья. Спасибо. Только не понял как вы определили адрес MIO[7] (913). У меня другая плата и адрес оказался 1023, хотя регистры совпали. Как и где его увидеть (адрес MIO), что бы не подбирать? Может подскажите.
ОтветитьУдалитьНа "Рис.18 содержимое папки gpio"
Удалитьможно увидеть папку "gpiochip906" которая содержит файлы для работы с MIO[0].
Она была там изначально.
Т.к. мне нужен MIO[7], мне нужна папка с номером 906 + 7 = 913. т.е. gpiochip913.
Возможно в вашем случае, изначально была папка "gpiochip1016".
Тогда 1016 + 7 = 1023.
Но я в этом не уверен. Таким образом я нашёл адрес для моей платы.