Наконец-то пришла пора продолжить изучение возможностей платы Zynq QMTech и SoC XC7Z020. Следующая интересная задача, которую я для себя придумал в качестве обучающей — оснастить плату Wi-Fi модулем Realtek RTL8822CS и, если Wi-Fi модуль будет не нужен, а нужна будет ещё одна флешка — вторым портом для SD-карточки. Если интересны подробности того, как я это всё реализовал — добро пожаловать под кат.
![](https://habrastorage.org/getpro/habr/upload_files/1f4/0b1/842/1f40b1842a5c4beba0adc49d0afcdc35.png)
Важно! Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте, с чего можно начать, при изучении отладочных плат на базе Zynq. Я не являюсь профессиональным разработчиком под ПЛИС и SoC Zynq, не являюсь системным программистом под Linux и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…
Сформулируем цель и задачи
Итак, у нас есть плата, описанная в этой (https://habr.com/ru/post/559946/) статье и есть желание прикрутить к ней Wi-Fi модуль Realtek RTL8822CS. Модуль, разумеется, должен корректно инициализироваться в ОС Linux и обеспечить пропускную способность до 150 Мбит\с (с чем связано такое ограничение и именно этой цифрой — ниже). В качестве альтернативного варианта для использования выведенного SDIO — необходимо организовать второй порт для подключения SD-карт.
Исходя из этого можно сформировать несколько задач, решив которые мы можем достигнуть цели:
-
Проверить возможность выноса линий SDIO через EMIO (т.е. через пины PL-части Zynq) и подготовить bitstream-файл;
-
Проверить работоспособность SDIO через EMIO в приложении baremetal с использованием microSD-карточки;
-
Изготовить мезонинные платы для Wi-Fi модуля и смонтировать на них компоненты;
-
Подготовить First Stage Bootloader, Device Tree Source, U-Boot, Linux Kernel в нужной конфигурации для запуска Wi-Fi модуля;
-
Подготовить модуль ядра для работы Wi-Fi модуля, а именно модуль cfg80211, который является линуксовым API для 802.11 устройств;
-
Подготовить модуль ядра 88x2cs, который является драйвером для модуля Wi-Fi RTL8822CS который работает поверх SDIO-шины;
-
Проверить работоспособность сети, сделать сканы эфира, посмотреть рабочие уровни, сделать замеры скорости downstream/upstream-трафиком через iPerf;
Итак. Перейдем к реализации намеченного, в целом, не выглядит сильно сложно =)
Настройка проекта в Vivado и конфигурация PL
В этой главе я кратко (без скриншотов) опишу процедуру создания нового проекта с акцентами на важных моментах. Если необходима инструкция с картинками — можно обратиться к моим предыдущим статьям.
Открываем Vivado и создаем проект через кнопку Create Project. Откроется мастер настройки нового проекта, быстро пройдемся по шагам.
-
Даём имя проекту, например SDIO_EMIO и указываем папку сохранения;
-
Указываем, что это RTL Project, оставляем включенной галочку Do not specify sources at this time;
-
Находим модель SoC использованного на плате. В моем случае это xc7z020clg400-1;
-
Нажимаем Finish.
В левом меню IP Integrator нажимаем Create Block Design и создаем новый дизайн. Назовём его для простоты block_design.
Добавляем примитив процессорной системы Zynq на диаграмму через нажатие правой кнопкой Add IP — ZYNQ7 Processing System. Сразу же выполним предложенные действия от автоматизатора, через команду на зеленом поле Run Block Automation и откроем настройки процессорной системы.
Я заранее сохранил шаблон настроек PS, чтобы в каждом проекте не прописывать всё заново. Взять его можно тут (https://github.com/megalloid/zynq_qmtech/blob/main/ZynqQMTech.tcl). Через меню Presets — Apply Configuration применяем его и сразу переходим в меню Peripherial I/O Pins.
Ставим галочку возле меню SD 1 и проверяем, что выбран вариант подключения через EMIO:
![](https://habrastorage.org/getpro/habr/upload_files/522/4e6/25a/5224e625a4ecf1a10e2699c08bd368cc.png)
Остальные настройки я оставил без изменений, в т.ч. частоты тактирования SDIO, PL. Получаем следующую картину (если развернуть группу сигнальных линий SDIO_1):
![](https://habrastorage.org/getpro/habr/upload_files/1b4/f6f/385/1b4f6f3853c024147049ddb371be1fa0.png)
В данном случае нас интересуют линии CLK, CMD и 4 линии DATA, что в общем-то является стандартным набором для SDIO-шины. Но вот незадача, каждая из линий организована тремя отдельными сигналами. Для решения этой задачи нам необходимо руками создать буфер IOBUF, который будет управляться через активный пин T с тремя состояниями и разрулит Input, Output сигналы как нам нужно для использования его с двунаправленным сигналом I/O. Выглядит он следующим образом:
![](https://habrastorage.org/getpro/habr/upload_files/6c4/d76/58b/6c4d7658be9b0cf6c1c2d277866bb42e.png)
Т.к. в стандартной библиотеке графических примитивов он отсутствует — нужно будет создать свой IP-модуль и добавить его на схему. Для каждого из сигнала он будет свой.
Через меню Sources — Add Sources (Alt + A) добавляем Design Source — Create File, выбираем тип файла Verilog и назовём его iobuf_cmd.
Запишем следующее содержимое:
module iobuf_cmd ( input iobuf_i, // Вход буфера input iobuf_t, // Входной сигнал с тремя состояниями, high=input, low=output output iobuf_o, // Выход буфера inout iobuf_io // Двунаправленный порт (подключается напрямую к top-level порту) ); IOBUF #( .DRIVE(12), // Указывается Drive Strength для сигналов .IBUF_LOW_PWR("FALSE"), // Low Power - "TRUE", High Perforrmance = "FALSE" .IOSTANDARD("LVCMOS33"), // Указывается стандарт сигнала .SLEW("FAST") // Указывается slew rate ) IOBUF_inst ( .O(iobuf_o), // Выход буфера .IO(iobuf_io), // Двунаправленный порт .I(iobuf_i), // Вход буфера .T(iobuf_t) // Входной сигнал с тремя состояниями ); endmodule
То же самое сделаем для сигнала CLK, только файл назовём iobuf_clk. Запишем в него следующее содержание:
module iobuf_clk ( input iobuf_i, // Вход буфера input iobuf_t, // Входной сигнал с тремя состояниями, high=input, low=output output iobuf_o, // Выход буфера inout iobuf_io // Двунаправленный порт (подключается напрямую к top-level порту) ); IOBUF #( .DRIVE(12), // Указывается Drive Strength для сигналов .IBUF_LOW_PWR("FALSE"), // Low Power - "TRUE", High Perforrmance = "FALSE" .IOSTANDARD("LVCMOS33"), // Указывается стандарт сигнала .SLEW("FAST") // Указывается slew rate ) IOBUF_inst ( .O(iobuf_o), // Выход буфера .IO(iobuf_io), // Двунаправленный порт .I(iobuf_i), // Вход буфера .T(iobuf_t) // Входной сигнал с тремя состояниями ); endmodule
С SDIO DATA немного сложнее т.к. она состоит из нескольких линий. Так же создадим файл iobuf_data и запишем в него следующее содержимое:
module iobuf_data ( input [3:0] iobuf_i, // Вход буфера input iobuf_t, // Входной сигнал с тремя состояниями, high=input, low=output output [3:0]iobuf_o, // Выход буфера inout [3:0] iobuf_io // Двунаправленный порт (подключается напрямую к top-level порту) ); generate genvar i ; for (i=0; i<4; i=i+1) begin IOBUF #( .DRIVE(12), // Указывается Drive Strength для сигналов .IBUF_LOW_PWR("FALSE"), // Low Power - "TRUE", High Perforrmance = "FALSE" .IOSTANDARD("LVCMOS33"), // Указывается стандарт сигнала .SLEW("FAST") // Указывается slew rate ) IOBUF_inst ( .O (iobuf_o[i]), // Выход буфера .IO (iobuf_io[i]), // Двунаправленный порт .I (iobuf_i[i]), // Вход буфера .T (iobuf_t) // Входной сигнал с тремя состояниями ); end endgenerate endmodule
Для каждой из линии создается свой экземпляр примитива IOBUF.
Поочередно размещаем добавленные модули на диаграмму через клик правой мышкой по Diagram — Add Module. Соединяем сигналы с нужными портами и получится следующее:
![](https://habrastorage.org/getpro/habr/upload_files/60c/884/937/60c884937e087e0faa2b3445b80bfb74.png)
Хотелось бы обратить внимание на две важные вещи:
-
На порт iobuf_t модуля для сигнала CLK я добавил Constant с шириной в 1 бит и со значением 0, т.к. управляющего T-пина для CLK не предусмотрено;
-
Сделал порты iobuf_io как External и дал им соответствующие названия;
-
Соединил порты Input с портами Output;
На текущем этапе можно запустить синтез и назначить выходы портов на реальные физические порты. Для этого нужно:
-
Нажимаем на меню block_design правой кнопкой и выбираем команду Create HDL Wrapper.
-
В меню IP Integrator выбираем Generate Block Design;
-
Выбираем Synthesis Option — Global и нажимаем Apply, затем Generate.
-
Дожидаемся окончания генерации;
-
Выбираем Run Synthesis для старта синтеза нашего дизайна.
-
Дожидаемся окончания синтеза;
После окончания синтеза переходим в меню Open Synthesized Design и откроется меню Package с нижним меню I/O Ports. Далее необходимо:
-
Отметить подходящие физические I/O пины
-
Убедиться, что включена подтяжка PULLUP на всех пинах кроме CLK;
В моем случае получается следующее:
![](https://habrastorage.org/getpro/habr/upload_files/e38/96e/4c8/e3896e4c823181edc37e95018b770af4.png)
Сохраняем настройки, будет предложено создать файл с constraints. Называем его как удобно и нажимаем Ok. После нажимаем Generate bitstream, соглашаемся с тем, что синтез устарел и ждем окончания всех необходимых процедур до получения сообщения:
![](https://habrastorage.org/getpro/habr/upload_files/36a/53d/b4a/36a53db4a28eae08bea232d61d4c997b.png)
После этого можем экспортировать BSP в SDK и запустить проверку работоспособности SDIO в baremetal. Делается это через меню File — Export — Export Hardware, ставим Include bitstream. Нажимаем Ok и запускаем SDK через команду File — Launch SDK.
Проверка SDIO через microSD-карту в Baremetal
Прежде чем приступить к проверке необходимо подключить microSD-карту к пинам, которые мы размечали в следующем разделе. Смотрим в первую очередь на распиновку microSD-карты:
![](https://habrastorage.org/getpro/habr/upload_files/594/d64/fd7/594d64fd794748b2345a683a28f90da6.png)
С пинами VDD (3.3V) и VSS (GND) всё в целом понятно. С другими пинами есть нюанс, для пина CMD и шины DATA требуется подтяжка к высокому уровню через 10 кОм резисторы. В моем случае, чтобы не городить навесным монтажом резисторы, я взял одну из плат, заготовленных для Wi-Fi модуля и запаял на него все что нужно:
![](https://habrastorage.org/getpro/habr/upload_files/3a0/797/40f/3a079740ff6982ba2b59b4f298ac483f.png)
Если посмотреть на принципиальную схему этой платы — все выглядит вот таким образом:
![](https://habrastorage.org/getpro/habr/upload_files/76e/ab6/fef/76eab6fef6c96a4b520b67d4177ca83e.png)
Питание подключено напрямую в соответствии с тем, как оно выведено на гребенку. Для удобства отладки логическим анализатором — я подпаивал МГТФ-проводки.
После того, как все необходимые линии подключены — можно вернуться в SDK к созданию проекта. После запуска SDK обзываем проект SDIO_EMIO и нажимаем Next, выбираем шаблон проекта Hello World.
![](https://habrastorage.org/getpro/habr/upload_files/e68/cae/1f9/e68cae1f9e9d65ce3c694c6b7130053c.png)
Открываем файл helloworld.c из древа проекта и пишем в него следующий код, который подключит нужные библиотеки и поможет нам быстро сделать проверку:
#include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "xsdps.h" #include "xparameters.h" #include "xil_types.h" int main() { int Status; static XSdPs SdInstance; XSdPs_Config *SdConfig; init_platform(); xil_printf("\r\nSD Raw Read/ Write Test\r\n"); SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID); if (NULL == SdConfig) { xil_printf("XSdPs_LookupConfig failed\r\n"); return XST_FAILURE; } else { xil_printf("XSdPs_LookupConfig ok \r\n"); } Status = XSdPs_CfgInitialize(&SdInstance, SdConfig, SdConfig->BaseAddress); if (Status != XST_SUCCESS) { xil_printf("XSdPs_CfgInitialize failed \r\n"); return XST_FAILURE; } else { xil_printf("XSdPs_CfgInitialize ok \r\n"); } Status = XSdPs_CardInitialize(&SdInstance); xil_printf("XSdPs_CardInitialize... \r\n"); if (Status != XST_SUCCESS) { xil_printf("XSdPs_CardInitialize failed \r\n"); } else { xil_printf("XSdPs_CardInitialize ok \r\n"); } }
Кликаем правой кнопкой по проекту SDIO_EMIO и выбираем Build Project. Смотрим, что проект собирается без проблем. Важный момент — выбрать правильный SDIO-контроллер в строке SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID);
Подключаем JTAG-отладчик и после сборки проекта заливаем bitstream-файл выполнив команду Xilinx — Program FPGA и дожидаемся сигнала FPGA DONE на плате.
Открываем Serial Port, который принадлежит нашей плате. Например: minicom -D /dev/ttyUSB0. Узнать какой порт принадлежит плате можно запустив команду udevadm monitor | grep ttyUSB и выткнуть-воткнуть miniUSB кабель из платы. Там можно увидеть сообщения по которым можно понять какой у нас порт. Например:
megalloid@megalloid-lenovo:~$ udevadm monitor | grep ttyUSB KERNEL[191576.781021] remove /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) KERNEL[191576.781124] unbind /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191576.781170] remove /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV [191576.796568] remove /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) UDEV [191576.804931] unbind /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV [191576.811644] remove /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191579.176521] add /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) KERNEL[191579.177593] add /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) KERNEL[191579.177706] bind /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV [191579.217863] add /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial) UDEV [191579.225195] add /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty) UDEV [191579.229537] bind /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
После запускаем проект также кликнув по корневому значку проекта выбрав меню Run As — Launch on hardware (System Debugger). В выводе в консоль мы должны увидеть следующее:
![](https://habrastorage.org/getpro/habr/upload_files/509/c9f/e1c/509c9fe1c167cc3f9ae22df02ed60e50.png)
Что означают эти сообщения? Это значит, что драйвер успешно “достучался” до карточки и может спокойно начать с ней обмен. Значит наш SDIO-контроллер работает. Идём дальше.
Подробнее о standalone-драйвере можно почитать тут: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841881/Zynq+SD+Standalone+driver
Трассировка и изготовление мезонинных плат для Wi-Fi модуля
Следующим шагом нужно подготовить платы, на которые я запаяю радиомодуль, со всей необходимой обвязкой. В качестве первого радиомодуля был взят модуль от FN-Link с Realtek RTL8822CS чипом, модель модуля 6222B-SRC. Подробнее можно посмотреть тут: https://wikidevi.wi-cat.ru/Fn-Link_6222B-SRC , Datasheet: https://www.signal.com.tr/pdf/cat/6222B-SRC_03.pdf
Кратко скажу, что это простой MIMO 2×2 Dual Band модуль Wi-Fi с поддержкой 802.11ac и Bluetooth 4.2 или 5.0 в зависимости от модели.
Так как модуль мы будем подключать к гребенке PLS на плате с шагом в 2.54мм. — было решено сделать такой же разъем и на мезонинной плате.
Распиновка со стороны платы:
![](https://habrastorage.org/getpro/habr/upload_files/fd6/bea/ba3/fd6beaba330e9c276e89c1c70635dc0e.png)
Распиновка со стороны модуля:
![](https://habrastorage.org/getpro/habr/upload_files/c08/551/665/c08551665f95f38e517da467eb9b9225.png)
Решено было вывести в т.ч. и Bluetooth для дальнейших экспериментов. В итоге общая обвязка модуля выглядит следующим образом:
![](https://habrastorage.org/getpro/habr/upload_files/647/798/510/6477985104680c2496ae428262f1ef6d.png)
Всё в целом достаточно примитивно и в соответствии с референсной схемой и небольшими изменениями, такими как пин включения\выключения питания модуля и прочих мелких доработок. Антенный тракт было решено вывести на u.FL-разъем, для удобства и возможности подключения любых подходящих для 2.4/5ГГц антенн.
Получившиеся платы достаточно просты в трассировке и не представляют собой никакого технического ноу-хау, но линии данных нужно выровнять т.к. SDIO-шина тут достаточно скоростная, чтобы пренебречь таким важным аспектом трассировки.
Результат трассировки в 2D:
![](https://habrastorage.org/getpro/habr/upload_files/e6a/088/8e6/e6a0888e65459464618817f0dd7e2089.png)
Результат трассировки в 3D:
![](https://habrastorage.org/getpro/habr/upload_files/521/50f/f46/52150ff46241c57edceb55ef6fe13b8d.png)
Вид сверху:
![](https://habrastorage.org/getpro/habr/upload_files/2ca/5cc/e63/2ca5cce635682082f3f3895c559998d7.png)
Вид снизу:
![](https://habrastorage.org/getpro/habr/upload_files/d51/b58/48e/d51b5848e2903bdc36415732f10b45e7.png)
Вид платы после монтажа компонентов:
![](https://habrastorage.org/getpro/habr/upload_files/381/d11/d09/381d11d093d12b6ab6a05cfd90a13316.png)
Т.к. плата изначально разрабатывалась не только для этой версии Wi-Fi модуля — остались не установленными некоторые компоненты.
В целом можно переходить к подготовке всего необходимого для работы с радиомодулем с точки зрения основных управляющих сигналов и подготовки необходимых файлов для работы радиомодуля в Linux. Об этом в следующих главах.
Небольшое дополнение проекта в Vivado
Для того, чтобы была возможность запустить радиомодуль необходимо соблюсти два условия:
-
Подать низкое напряжение на затвор P-канального ключа VT1, т.е. на линию WL_BT_ON, чтобы подать питание на радиомодуль;
-
Подать высокое напряжение на ножку WL_REG_ON радиомодуля, чтобы запустить его;
Поэтому на этом этапе нам будет нужно слегка модернизировать проект и добавить 2 сигнала с AXI GPIO-контроллера и завести их на соответствующие ножки:
-
Для транзисторного ключа это ножка 11 на общей гребенке, что соответствует физическому пину W20;
-
Для сигнала WL_REG_ON это 25-я ножка на той же гребенке, что соответствует в моем случае пину V17;
Как работать с AXI GPIO Я рассказывал в этой статье (https://habr.com/ru/post/565368/) и на деталях повторяться не буду. В нашем случае нужно выполнить лишь несколько шагов:
-
Добавляем в дизайн IP-модуль AXI GPIO;
-
Выполняем предложенные шаги автоматизации предложенные Vivado, отметив все галочки;
-
Появится блок AXI GPIO, необходимо зайти в его опции и поставить галочку All Outputs, поставить ширину шины GPIO Width в значение 2 (нужно 2 сигнала) и в Default Output вписать 0x00000002, чтобы по умолчанию после загрузки bitstream-файла оба пина приняли необходимое значение;
-
Называем External Interface от AXI GPIO как MGMT_GPIO и сохраняемся. Должно получиться следующее:
![](https://habrastorage.org/getpro/habr/upload_files/234/98f/107/23498f1072db7330570a736b1a6085e3.png)
-
Синтезируем дизайн, проверяем, что всё прошло успешно;
-
Переходим в меню Open Synthesized Design и назначаем MGMT_GPIO[0] пин W20, а MGMT_GPIO[1] пин V17;
-
Изменяем у этих двух пинов I/O Std на LVCMOS3.3 и сохраняем;
-
Запускаем генерацию bitstream-файла через Generate bitstream;
-
Экспортируем сгенерированный bitstream и всё что необходимо для дальнейших шагов через команду File — Export — Export Hardware;
Все изменения, необходимые на этом этапе мы внесли. Переходим к подготовке всего необходимого для работы с радиомодулем в Linux;
Подготовка FSBL, Device Tree Sources, U-Boot
На этом этапе можно переходить к созданию загрузчиков, файлу описания железа и компиляции ядра Linux. Многие моменты и детали Я целенаправленно опущу т.к. уже рассказывал в предыдущей статье (https://habr.com/ru/post/565368/) а отличающимся моментам посвящу основную часть повествования.
Первым этапом надо подготовить FSBL. Для этого нужно:
-
В меню Xilinx SDK переходим в меню File — New — Application Project;
-
Именуем его как FSBL;
-
Нажимаем клавишу Next;
-
Из заготовок выбираем Zynq FSBL;
-
Дожидаемся окончания компиляции и проверяем, что появился файл FSBL.elf
Вторым этапом необходимо подготовить Device Tree Source/Blob файл который будет использован для сборки U-Boot. Для этого сделаем следующее:
-
Добавляем BSP Repository из репозитория device-tree-xlnx. Для этого нужно выбрать меню Xilinx — Repositories и указать папку с device-tree-xlnx в секцию Global Repositories.
-
Добавляем BSP Project через меню File — New — Board Support Package, даём ему имя и выбираем внизу меню device_tree и в следующем меню нажимаем Ok, если не требуется внесения каких-либо изменений.
-
Выполним редактирование файла zynq-7000.dtsi и находим секцию описывающую SDIO1 в строке sdhci1: mmc@e0101000;
Дополняем секцию следующим содержанием (файл можно взять тут — https://github.com/megalloid/zynq_qmtech/blob/main/zynq-7000.dtsi) :
![](https://habrastorage.org/getpro/habr/upload_files/68a/fa8/9da/68afa89dac5d3025b33798e58a3833c8.png)
Переходим в папку с сгенерированными dts-файлами в системной консоли:
megalloid@megalloid-lenovo:~$ cd Zynq/Projects/SDIO_EMIO/SDIO_EMIO.sdk/device_tree/
Компонуем исходники:
gcc -I my_dts -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o system.dts system-top.dts
Компилируем в blob:
dtc -I dts -O dtb -o system.dtb system.dts
Всё. Device Tree готов.
Следующим этапом необходимо подготовить U-Boot для нашей платы. Нужно выполнить несколько простых шагов:
-
Перейти в папку с U-Boot: cd u-boot-xlnx
-
Задать переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm
-
Очистить сырцы от предыдущих результатов компиляции и применить конфиг для Zynq по умолчанию: make distclean; make xilinx_zynq_virt_defconfig
-
Положить в папку с U-Boot полученный в предыдущем шаге system.dtb в папку arch/arm/dts/ c именем zynq-qmtech.dtb
-
Указываем какой Device Tree файл использовать: export DEVICE_TREE=»zynq-qmtech»
-
Далее необходимо зайти в меню конфигуратора и изменить ряд опций: make menuconfig
-
Опция для того, чтобы активировать поддержку AXI GPIO, которая используется для включения питания модуля и подачи сигнала WL_REG_ON на радиомодуле: Device Drivers — GPIO Support — Xilinx GPIO Driver
-
Сохраняем конфиг (можно взять тут) в файл .config
-
Запускаем сборку: make -j$(nproc) и дожидаемся окончания компиляции;
После этого можно скомпоновать FSBL, Bitstream-файл и U-Boot в один загрузочный файл BOOT.BIN. Подробное описание компоновки можно найти в моей прошлой статье (https://habr.com/ru/post/565368/). В целом надо выполнить несколько простых шагов:
-
Найти файл в каталоге с проектом First stage bootloader. По умолчанию он находится в SDIO_EMIO/SDIO_EMIO.sdk/FSBL/Debug/FSBL.elf
-
Найти Bitstream-файл для программируемой логики. Найти его можно в каталоге SDIO_EMIO/SDIO_EMIO.sdk/block_design_wrapper_hw_platform_0/block_design_wrapper.bit
-
Найти бинарный файл U-Boot. Данный файл после компиляции лежит в папке с U-Boot: u-boot-xlnx/u-boot.elf
-
Открываем в Xilinx SDK пункт меню Xilinx — Create Boot Image;
-
Выбираем куда сохранить новый bif-файл, я его сохраняю в корневом каталоге проекта;
-
Добавляем поочередно файлы FSBL.elf, block_design_wrapper.bit, u-boot.elf в секцию Boot Image Partitions;
-
Нажимаем Create Image и видим как в каталоге с bif-файлом появляется BOOT.BIN;
Теперь необходимо залить этот файл на загрузочную SD-карту и можно попробовать загрузиться. Если мы видим приглашение от U-Boot и что светится светодиод FPGA DONE — значит мы всё выполнили правильно.
![](https://habrastorage.org/getpro/habr/upload_files/d57/46a/95a/d5746a95acb71fa17314158be3af4802.png)
Можно перейти к проверке того, проинициализирован ли AXI GPIO в U-Boot и проверим их. Для этого сначала выведем список устройств которые проинициализированы. Это можно сделать через команду dm tree.
![](https://habrastorage.org/getpro/habr/upload_files/a9a/d52/c2f/a9ad52c2f7ca103c3f6b91c3bf6ba6c3.png)
Отлично. Видно, что у нас на борту есть два SDIO-контроллера, и два GPIO-контроллера. Попробуем изменить состояние ножки транзистора. Для этого нужно:
-
Посмотреть какие адреса в GPIO доступны и какие отвечают за те, или иные ножки: gpio status -a
-
В конце вывода видно, что есть Bank GPIO с двумя ножками, которые соответствуют значению по умолчанию:
-
Проверки ради можно изменить состояние ножки gpio@412000000 через команду: gpio toogle gpio@412000000 или gpio set gpio@412000000 / gpio clear gpio@412000000 для установки логической 1 или 0 соответственно
-
Так. Загрузчик подготовлен. Теперь можно переходить к подготовке ядра Linux и RootFS со всем необходимым для нас содержимым
Компиляция ядра Linux и подготовка RootFS с использованием Buildroot
Так же как и прошлых главах, для исключения необходимости подробного объяснения каждого шага, сошлюсь на предыдущую статью (https://habr.com/ru/post/565368/) как скомпилировать ядро Linux. Перейдем к выполнению шагов, необходимых для сборки ядра с необходимыми модулями и драйверами. Для этого нужно:
-
Перейти в папку с клонированным репозиторием linux-xlnx;
-
Обновить его (при необходимости): cd linux-xlnx; git fetch -p; git checkout master; git pull;
-
Экспортируем также переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm
-
Делаем очистку от результатов предыдущих сборок: make distclean
-
Загружаем дефолтный конфиг: make ARCH=arm xilinx_zynq_defconfig;
-
Изменим дефолтный конфиг загрузив меню конфигурации: make ARCH=arm menuconfig
-
Изменим опции конфигурации (можно взять тут https://github.com/megalloid/zynq_qmtech/blob/main/kernel.config) , устанавливаем опцию (проследим, что там вместо [M] в поле выбора стоит символ звездочки [*] : Networking Support — Wireless — cfg80211
-
Сохраняем конфиг в файл .config и выходим из меню конфигурации;
-
Запускаем процесс компиляции: make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage -j8
-
Компилируем модули ядра: make ARCH=arm -j8 modules
-
Копируем uImage из каталога linux-xlnx/arch/arm/boot/ на загрузочную SD-карту;
Далее необходимо подготовить образ RootFS с всеми необходимым userspace-утилитами, типа wpa_supplicant, wpa_cli, etc. Собирать RootFS будем с помощью системы сборки buildroot, статью о которой Я писал до этого (https://habr.com/ru/post/567408/). Итак, перейдем к сборке, для этого нужно:
-
Заходим в папку с buildroot: cd buildroot
-
Обновляем его до последней версии: git fetch -p; git checkout master; git pull;
-
Делаем очистку от результатов предыдущих сборок: make distclean
-
Запускаем меню конфигурации: make -C /home/megalloid/buildroot O=$PWD ARCH=arm nconfig
Изменяем опции конфигурации по списку ниже;
-
Опция 1: Target options — Target Architecture — ARM (little endian)
-
Опция 2: Target options — Target Architecture Variant — cortex-A9
-
Опция 3: Target options — Enable NEON SIMD extension support — [*]
-
Опция 4: Target options — Enable VFP extension support — [*]
-
Опция 5: Target options — Target ABI — EABIhf
-
Опция 6: Target options — Floating point strategy — NEON
-
Опция 7: Build options — Number of jobs to run simultaneously — 8 (по количеству ядер CPU)
-
Опция 8: Toolchain — C library — glibc
-
Опция 9: Toolchain — Kernel headers — Custom Git Repository
-
Опция 10: Toolchain — URL of custom repository — </home/megalloid/linux-xlnx>
-
Опция 11: Toolchain — Custom repository version — <хэш-сумма от git show-ref | grep master из папки linux-xlnx>
-
Опция 12: Toolchain — Custom kernel headers series — 5.15.x
-
Опция 13: Toolchain — Install glibc utilities — [*]
-
Опция 14: Toolchain — GCC compiler Version — gcc 9.x
-
Опция 15: Toolchain — Enable C++ support — [*]
-
Опция 16: Toolchain — Enable compiler link-time-optimization support — [*]
-
Опция 17: Toolchain — Enable compiler OpenMP support — [*]
-
Опция 18: Target packages — Miscellaneous — haveged
-
Опция 19: Target packages — Networking applications — dhcpd
-
Опция 20: Target packages — Networking applications — dropbear
-
Опция 21: Target packages — Networking applications — ifupdown scripts
-
Опция 22: Target packages — Networking applications — hostapd
-
Опция 23: Target packages — Networking applications — iperf
-
Опция 24: Target packages — Networking applications — iperf3
-
Опция 25: Target packages — Networking applications — fping
-
Опция 26: Target packages — Networking applications — iw
-
Опция 27: Target packages — Networking applications — iwd
-
Опция 28: Target packages — Networking applications — wpa_supplicant
-
Опция 28: Target packages — Networking applications — wireless tools
-
Опция 29: Filesystem images — cpio the root filesystem — [*]
-
Опция 30: Filesystem images — Compression method — lzma
-
Опция 31: Host Utilites — host dosfstools
-
Опция 32: Host Utilites — host genimage
-
Опция 33: Host Utilites — host mtools
-
После этой длительной конфигурации выбираем Save и сохраняем конфиг (можно взять тут и подложить к себе https://github.com/megalloid/zynq_qmtech/blob/main/buildroot.config);
-
Нажимаем F9 и запускаем процесс сборки: make -C /home/megalloid/buildroot O=» class=»formula inline»>PWD ARCH=arm BR2_JLEVEL=»
(nproc) — 1))»
-
Ждём когда закончится компиляция, самое время заварить и выпить кофе =)
Теперь когда образ RootFS собрался надо его подписать и привести к виду, в котором его сможет корректно переварить U-Boot:
-
Переходим в папку с готовыми образами: cd buildroot/images
-
Производим подпись: mkimage -A arm -T ramdisk -C gzip -d rootfs.cpio uramdisk.image.gz
-
Складываем uramdisk.image.gz на загрузочную SD-карту;
Настраиваем загрузку образов в U-Boot:
-
Надо загрузиться в консоль U-Boot и записать туда: setenv mmc_boot “fatload mmc 0 0x2000000 uramdisk.image.gz; fatload mmc 0 0x4000000 uImage; fatload mmc 0 0x4500000 system.dtb; bootm 0x4000000 0x2000000 0x4500000;”
-
Сохраняем эту переменную через saveenv и перезагружаемся;
-
Дожидаемся загрузки ОС;
Теперь можно переходить к компиляции драйвера и непосредственно настройке Wi-Fi модуля.
Компиляция модуля ядра 88х2cs для Wi-Fi модуля
Переходим к последнему элементу, необходимому для работы Wi-Fi модуля — драйверу нашего Wi-Fi модуля. На просторах GITHUB я нашел вполне себе собираемый и рабочий вариант драйвера. Чтобы собрать драйвер out-of-tree нужно сделать следующее:
-
Склонировать исходные коды себе: git clone https://github.com/megalloid/rtl88x2cs
-
Переключим ветку на версию драйвера совместимую с 5.15: git checkout tune_for_jethub_5_14
-
Запускаем компиляцию: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=»/home/megalloid/linux-xlnx» (где KSRC — путь до папки с ядром LInux из предыдущего шага);
-
На выходе мы получим в этой папке файл 88x2cs.ko, его нужно положить туда же на загрузочную SD-карту;
Проверяем, загрузится ли модуль ядра и появится ли wlan-интерфейс:
-
Монтируем SD-карту в файловую систему: mount /dev/mmcblk0p1 /mnt
-
Переходим в каталог SD-карты: cd /mnt/
-
Выполняем insmod 88x2cs.ko для загрузки модуля ядра;
Запускаем программу ifconfig — и видим, что появился wlan0 интерфейс:
![](https://habrastorage.org/getpro/habr/upload_files/71f/efc/c9a/71fefcc9af149185e1a6ac8b0e0f437b.png)
Это говорит о том, что модуль ядра успешно загрузился и готов к работе. В следующей главе перейдем к настройке wlan0 интерфейса и проверке работоспособности Wi-Fi, прогону скорости через iPerf;
Про модули ядра можно почитать тут: https://habr.com/ru/post/117654/
Настройка Wi-Fi интерфейса и измерение скорости с использованием iPerf
Для настройки Wi-Fi необходимо первым делом создать конфиг, удобнее всего в этом случае его создать на SD-карте в виде файла wpa_supplicant.conf.
Выполним в консоли Zynq следующие команды:
-
Создадим файл конфигурации: touch /mnt/wpa_supplicant.conf
-
Открываем его на редактирование: vi /mnt/wpa_supplicant.conf
-
Записываем в него следующее содержимое и сохраняем:
network={ ssid="ASUS" scan_ssid=1 key_mgmt=WPA-PSK psk="megapassword" }
-
Запускаем wpa_supplicant: wpa_supplicant -B -Dnl80211 -iwlan0 -c/mnt/wpa_supplicant.conf -P/var/run/wpa_supplicant.pid -dd -f/mnt/wpa_supplicant.log
-
Через некоторое время появится IP-адрес на интерфейсе wlan0 и будет установлена связь с роутером;
Теперь можно прогнать трафик с компьютера (который подключен к роутеру кабелем) на Zynq и обратно. Для этого нужно:
-
Запустить на компьютере: iperf3 -s
-
Запустить на Zynq: iperf3 -c <IP компьютера> -N -i1 -t60 -u
-
Чтобы пустить трафик в обратном направлении на Zynq нужно выполнить: iperf3 -c <IP компьютера> -N -i1 -t60 -R -u
Наблюдаем результат:
# iperf3 -c 192.168.2.3 -N -i1 -u -b300M Connecting to host 192.168.2.3, port 5201 [ 5] local 192.168.2.233 port 55279 connected to 192.168.2.3 port 5201 [ ID] Interval Transfer Bitrate Total Datagrams [ 5] 0.00-1.00 sec 15.9 MBytes 133 Mbits/sec 11481 [ 5] 1.00-2.00 sec 16.8 MBytes 141 Mbits/sec 12154 [ 5] 2.00-3.00 sec 17.0 MBytes 143 Mbits/sec 12336 [ 5] 3.00-4.00 sec 17.0 MBytes 142 Mbits/sec 12275 [ 5] 4.00-5.00 sec 16.3 MBytes 137 Mbits/sec 11812 [ 5] 5.00-6.00 sec 16.3 MBytes 137 Mbits/sec 11831 [ 5] 6.00-7.00 sec 17.0 MBytes 143 Mbits/sec 12332 [ 5] 7.00-8.00 sec 10.4 MBytes 87.4 Mbits/sec 7545 [ 5] 8.00-9.00 sec 14.8 MBytes 124 Mbits/sec 10736 [ 5] 9.00-10.00 sec 15.3 MBytes 128 Mbits/sec 11050 - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams [ 5] 0.00-10.00 sec 157 MBytes 132 Mbits/sec 0.000 ms 0/113552 (0%) sender [ 5] 0.00-10.03 sec 157 MBytes 131 Mbits/sec 0.066 ms 0/113551 (0%) receiver iperf Done. # iperf3 -c 192.168.2.3 -N -i1 -u -b300M -R Connecting to host 192.168.2.3, port 5201 Reverse mode, remote host 192.168.2.3 is sending [ 5] local 192.168.2.233 port 50266 connected to 192.168.2.3 port 5201 [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams [ 5] 0.00-1.00 sec 12.0 MBytes 101 Mbits/sec 0.043 ms 0/8693 (0%) [ 5] 1.00-2.00 sec 12.2 MBytes 102 Mbits/sec 0.072 ms 0/8842 (0%) [ 5] 2.00-3.00 sec 12.6 MBytes 105 Mbits/sec 0.161 ms 16279/25380 (64%) [ 5] 3.00-4.00 sec 12.1 MBytes 101 Mbits/sec 0.032 ms 16435/25185 (65%) [ 5] 4.00-5.00 sec 12.5 MBytes 105 Mbits/sec 0.076 ms 17924/27002 (66%) [ 5] 5.00-6.00 sec 12.4 MBytes 104 Mbits/sec 0.320 ms 16380/25350 (65%) [ 5] 6.00-7.00 sec 12.6 MBytes 105 Mbits/sec 0.055 ms 16807/25910 (65%) [ 5] 7.00-8.00 sec 12.5 MBytes 105 Mbits/sec 0.196 ms 17230/26287 (66%) [ 5] 8.00-9.00 sec 12.3 MBytes 103 Mbits/sec 0.050 ms 16305/25184 (65%) [ 5] 9.00-10.00 sec 12.6 MBytes 106 Mbits/sec 0.100 ms 11046/20165 (55%) - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams [ 5] 0.00-10.03 sec 359 MBytes 300 Mbits/sec 0.000 ms 0/259695 (0%) sender [ 5] 0.00-10.00 sec 124 MBytes 104 Mbits/sec 0.100 ms 128406/217998 (59%) receiver iperf Done.
Скорость в целом соответствует заявленным Xilinx цифрам: около 20 Мбайт\сек (около 150 Мбит\с). В итоге если будем иметь подходящие условия в эфире — эта скорость будет максимальной и в 2.4 ГГц и 5 ГГц. Результат соответствует всем ожиданиям.
Подведем итог
После длительного кропотливого исследования без каких-либо how-to удалось обуздать эту железку. На общую проработку этого всего ушло около недели, что в целом доставило немало удовольствия по мере продвижения к решению задачи. Буду надеяться, что мой опыт кому-нибудь пригодится и будет высоко оценен. Спасибо!
ссылка на оригинал статьи https://habr.com/ru/post/658487/
Добавить комментарий