
На хабре уже было несколько статей про использование старых плат управления Antminer S9, которые продаются сейчас по доступной цене. Я решил сделать нечто полезное для радиолюбителя. Сегодня расскажу о проекте очередного антенного переключателя. В XXI веке он будет с ПЛИС и двухъядерным АРМ процессором.
В одной из своих предыдущих статей я рассказывал о проекте антенного переключателя для спутниковых антенн. Время идёт, 4 антенн мне показалось мало, решил установить ещё, получилось в итоге 7 антенн. Но тянуть новый кабель управления на крышу оказалось задачей сложной: согласование с ТСЖ убивало идею на корню. Решил как-то переключать антенны используя имеющуюся у меня проводку. В итоге пришёл к выводу, что могу выделить только 3 провода на управление. Два сразу ушли на питание и землю, остался один провод на передачу сигналов.
Перебрал в памяти все доступные протоколы, работающие по одному проводу и, в итоге, решил остановиться на использовании передачи DTMF сигналов. Они низкочастотные, помехоустойчивые, хорошо передаются по обычному кабелю ПВС.
DTMF (англ. Dual-Tone Multi-Frequency) — это двухтональный многочастотный аналоговый сигнал. Он используется в системах телекоммуникаций и голосовой связи для отправки команд или сигналов через голосовой канал по телефону или аналогичному устройству связи. DTMF основан на матрице тонов, где каждая строка соответствует высокочастотной группе, а каждый столбец — низкочастотной группе. Нажимая клавишу на клавиатуре телефона, пользователь генерирует комбинацию из одного сигнала из каждой группы. Например, нажатие клавиши «1» генерирует тон, состоящий из низкочастотного тона «697 Гц» и высокочастотного тона «1209 Гц»
К моему счастью, наши китайские товарищи производят несколько удобных модулей, позволяющих организовать управление реле с помощью DTMF.
Вот что я использовал в проекте:

Модуль кодирования и передачи AE11A04 обладает возможностью передачи как с управлением по параллельному интерфейсу к МК, так и с помощью простого нажатия кнопок. Последняя возможность оказалась бесценной для тестирования программы.

Программируемый модуль реле DTMF Relay MT8870. Принимает DTMF команды и переключает реле. Я использую его в режиме включения одновременно только одного реле, т.е. при приёме команды все реле отключаются, а включается только одно реле, соответствующее принятому коду DTMF. На модуле есть 8 реле, я использую только 6. Важно, что при приёме кода DTMF более 8, модуль никак не реагирует. Я использую эту особенность для передачи сигналов подтверждения включения реле.

Если кто-то подумал, что эти реле модуля используются для коммутации антенн, то нет. Они лишь включают обмотки вот такого высокочастотного коаксиального 6 портового реле.
Мне хотелось сделать так, что б я мог убедиться, что команда на переключение принята и нужное реле включилось. Поэтому мне нужен был какой-то сигнал подтверждения включения реле. Я организовал это следующим образом: при получении команды включается одно из 6 реле. Для этого используются тональные сигналы от 1 до 6. Далее вступает в работу схема на нескольких таймерах 555 и диодно-транзисторной логике. При включении любого реле схема формирует набор сигналов для модуля передачи таким образом, что в ответ на включение реле передаётся DTMF сигнал с кодом, который на 8 больше, чем номер включённого реле. Т.е. при приёме сигнала DTMF 1 включается реле 1 и обратно отправляется сигнал с DTMF кодом 9. Всё это происходит по одной сигнальной линии, поэтому плата реле тоже принимает эту команду, но не реагирует на неё никак. Сигнал же принимается на пульте управления еще одним модулем:

Этот модуль обладает только интерфейсом с МК, он у меня служит для приема сигнала подтверждения.
Для того чтобы сигналы точно доходили до реле и обратно, я дополнительно установил на линию НЧ усилители на модулях PAM8403:

Это цифровой усилитель НЧ, он должен быть по идее нагружен на динамик, а не на линию связи. Поэтому для согласования всех входов и выходов сигналов с единой линией передачи я использовал трансформаторы от старого телефонного аппарата. У такого трансформатора есть 3 обмотки. Одна используется как нагрузка усилителя вместо динамика, вторая подключена к приёмнику DTMF, а третья обмотка соединена с линией передачи.
В ходе отладки выяснилось, что включенный усилитель мешает приёму сигналов даже если ничего не передаёт. У PAM8403 есть сигнал отключения, которым может управлять МК. Я использую эту возможность для того, чтобы включать усилитель только на момент передачи сигнала DTMF. В таком случае он почти не мешает приёму. Почти потому, что если подать на выход выключенного усилителя внешний сигнал достаточного большого уровня, то внутри усилителя что-то начинает ограничивать сигнал. Может диоды в мосфетах, может еще что. Не так важно. Добавление в конструкцию разных фильтров и схем ограничения сигнала полностью решило проблему. Удалось организовать двустороннюю передачу DTMF по одному проводу, что и требовалось изначально. Осталось подключить всё это к Antminer S9.
Как я уже отмечал, у всех DTMF модулей параллельный интерфейс управления. 4 бита данных и сигнал строба для передачи. Я подумал, что не хочу иметь дело с большим числом сигнальных линий, поэтому сразу сделал переходники к модулям с шины I2C с помощью широко известных микросхем PCF8574.
По ходу работы над проектом захотелось добавить еще много всего разного:
Информацию выводить на цветной дисплей. Для этого взял 2.4 дюймовый SPI дисплей с контроллером ST7789
Клавиатура для управления реле. Использовал дешевую матричную телефонную клавиатуру. С программированием решил не заморачиваться, купил для клавиатуры модуль контроллера на TCA8418
Для звукового контроля нажатия клавиш на клавиатуре добавил пьезо динамик
У меня для антенн используются МШУ, решил еще и управление включением МШУ добавить в конструкцию
Для контроля передачи DTMF кодов добавил еще отдельный НЧ усилитель и небольшой динамик. В старые времена по звуку писка модемов определяли скорость подключения, а я теперь по обмену DTMF на слух, не глядя на дисплей, могу определить какая антенна включена
Измеритель тока потребления на модуле INA226. Это бесценный инструмент диагностики как там всё работает на крыше в коммутаторе. По току потребления можно диагностировать множество неполадок
-
Переходник USB-UART на FT232. Используется для подключения к компьютеру и управления включением антенн с компьютера.
Таким образом, для моего проекта требуется:
шина I2C для приёмо-передатчиков DTMF, датчика тока и клавиатуры
шина SPI для дисплея
отдельные линии управления реле питания МШУ
UART для связи с компьютером
Я всё это пишу так подробно, поскольку Antminer S9 это плата, основанная на ПЛИС Zynq7010 и все эти интерфейсы сначала нужно сконфигурировать для работы в САПР Vivado/Vitis. Я использую версию Vivado 2022.2.
Большинство сигнальных линий на плате выведены на разъемы как показано на картинке:

Некоторые из контактов можно переопределить, но вот шины, I2C например, разведены однозначно. Глядя на эту картинку, нужно определить на какой контакт какой сигнал пойдёт. Всё это соответствие потом настраивается в constarints проекта Vivado.
Собственно, следующий шаг – это создание нового проекта в Vivado для Zynq7010. Как на хабре, так и в интернете уже есть достаточно информации по тому, как создать проект для платы Antminer S9. Имеет смысл сразу взять базовый проект, где уже проведены основные настройки. На сайте Astralab (https://astra.org.ru/) и сопутствующем телеграм канале много информации по этому вопросу. Если же начинать всё делать с самого начала, то придётся сконфигурировать тактовые генераторы, доступ к DDR и NAND памяти, базовую периферию типа порта Ethernet.
Далее нужно сконфигурировать нужные нам шины. Большинство настроек делается в форме настроек ядра ARM Zynq. Там включаются шины SPI, I2C, определяются порты GPIO.
Для моего проекта я использовал возможности ПЛИС только в небольшом объеме. Но для начала освоения ПЛИС считаю это достаточным. Использовал UART lite для организации последовательного порта, добавил так же счётчик, управляемый из GPIO. Он формирует на выходе частоту около 1 кГц, использую это для работы «пищалки».
Полная схема проекта в Vivado выглядит так:

Важно, что шину I2C недостаточно просто включить в блоке процессора. На иллюстрации видно, что нужно еще добавить буферы, обеспечивающие двунаправленную передачу данных по шине.
Единственным блоком, написанным на Verilog, стал счётчик для работы «пищалки». Его код очень простой:
module khz_gen
#(
parameter CNT_MAX = 16'd49_999 // Parameter definition parameters (macro definition)
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg buzzer_out
);
reg [15:0] cnt; // 16 -bit width counter
/* Counter CNT */
always@ (posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0) // When the reset signal is valid (when reset), CNT clear zero
cnt <= 16'd0;
else if (cnt == CNT_MAX) // Count the maximum value, CNT clearing zero
cnt <= 16'd0;
else
cnt <= cnt + 16'd1; // Self -increase every clock rising along CNT 1
/* Buzzer device controlled by counter */
always@ (posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0) // Initial state
buzzer_out = 1'b0;
else if (cnt == CNT_MAX) // CNT is remembered once, the output level reverses once
buzzer_out = ~buzzer_out;
else // CNT is not full, buzzer level is maintained
buzzer_out = buzzer_out;
endmodule
После настройки шин, их нужно привязать, как я писал выше, к конкретным пинам и сигнальным линиям на плате Antminer S9. Это всё описываем в файле constraints примерно так:

Одним из сложных для меня моментов стало определение номеров портов GPIO. Дело в том, что в Zynq есть, упрощенно говоря, две шины GPIO: одна (MIO) подключена к процессорному ядру, а другая (EMIO) к ПЛИС. Нумерация сигналов в каждой шине начинается с 0. Но, например, для указания номера GPIO в devicetree используется сквозная нумерация. Пины MIO с 0 по 53 сохраняют номера, а номера EMIO идут не с 0, а начинаются уже с 54. Но это еще не всё. Когда пин GPIO мапируется в linux, то его номер опять изменяется. В моем случае номер GPIO в Linux меньше на 11.
Теперь запускаем генерацию bitstream. После успешного завершения генерации экспортируем Hardware с включением bitstream. Полученный XSA файл используется для создания загрузчика FSBL.
Открываем Vitis, создаём там новый проект создания FSBL для Linux, указываем в качестве источника ранее экспортированный XSA файл из Vivado. Запускаем сборку проекта. В результате должен получиться файл начального загрузчика fsbl.elf для проектируемой системы.
Это всё были обязательные шаги по использованию Antminer S9 в вашем проекте. Дальше есть очень много вариантов. Можно написать прикладную программу для устройства прям в Vitis на C++, либо сначала сделать сборку Linux для устройства и уже писать прикладную программу для ОС Linux. Тут каждый решает сам что удобнее, но я думаю, что использование Linux оптимально, хоть это и достаточной сложный путь. Полная инструкция описана на сайте Xilinx https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/460653138/Xilinx+Open+Source+Linux
Вариантов запуска Linux так же очень много: можно использовать разные ядра, сборки, файловые системы и т.п. Всё это запутывает новичков.
Для себя я решил, что мне нужен простой linux, который будет запускаться с SD карточки и корневая файловая система будет полностью в оперативной памяти. Возможность что-либо писать на диски для работы переключателя антенн не нужна, как необязателен и доступ в интернет. Т.е. мне нужен довольно базовый вариант linux, который мог бы запустить мое управляющую программу и всё.
В качестве отступления упомяну, что перед сборкой ОС по традиции еще нужно создать загрузчик ОС. В этом качестве принято использовать систему U-Boot. В git репозитарии Xilinx есть готовые исходники, сконфигурированные для сборки U-Boot для Antminer S9.
git clone https://github.com/Xilinx/u-boot-xlnx.git
cd u-boot-xlnx
git checkout xilinx-v2022.2
Так что стоит просто взять их и скомпилировать.
cd u-boot-xlnx
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
make distclean
make bitmain_Antminer_s9_defconfig
make
У себя я в конфигурации U-Boot убрал поддержку watchdog, в остальном всё оставил как есть. Если планируете использовать плату Antminer S9 в нескольких проектах, то единожды скомпилированный U-Boot вполне можно использовать в каждом проекте. В отличие от FSBL, который может меняться при изменении схемотехники в Vivado, и который нужно перекомпилировать при каждом изменении в Vivado, U-Boot не требует такого внимания.
На плате Antminer S9 разведён системный порт UART. Скорее всего, вам понадобится переходник USB-3.3в UART для подключения к порту. С помощью этого порта можно получить доступ к консоли U-Boot, настроить и записать файл конфигурации загрузки Linux. Команда загрузки Linux из U-Boot с карты SD будет примерно такая:
fatload mmc 0 0x4000000 image.itb; bootm 0x4000000;
Полезно там же настроить IP адреса для загрузки ОС по TFTP. Загрузка нового ядра ОС в ходе отладки по Ethernet гораздо быстрее, чем запись на SD карту.
На плате Antminer S9 так же предусмотрен порт JTAG, но не распаян разъём. Мне JTAG не пригодился в работе над проектом, но на случай сложной отладки может понадобиться.
Хорошее руководство по сборке подходящего мне варианта Linux нашёл здесь https://github.com/farbius/linux-vitis-zynq. Практически всё сделал как там написано. Используется возможность настройки external tree для сборки buildroot. Вы просто добавляете в необходимые папки нужные вам специфические файлы и в файле конфигурации проекта указываете пакеты для включения в сборку. Запускаете компиляцию и получаете на выходе готовый образ Linux с готовой корневой файловой системой.
У меня скрипт сборки Linux такой:
mkdir -p tmp/
cd tmp/
fold="buildroot"
if [ -d "$PWD/$fold" ]; then
echo "Buildroot repo is already cloned"
else
git clone https://github.com/buildroot/buildroot.git
cd buildroot/
git checkout tags/2023.05
cd ..
fi
mkdir -p zynq_lin
cd zynq_lin/
make --directory=../buildroot/ BR2_EXTERNAL=../../external-tree/ zynq_defconfig O=$PWD
make
В моём простом случае в external-tree я поменял в первую очередь devicetree (файл system-top.dts), где указал включить шины I2C, SPI, UARTLite, Ethernet, указал пины GPIO. D файле прописаны фейковые устройства на шинах SPI, I2C. Это нужно для корректной инициализации.
Далее сконфигурировал интерфейс Ethernet, настроив статические адреса. Использование Ethernet хоть и не нужно для работы системы, но удобно для загрузки и обновления файлов. Не нужно каждый раз при обновлении вынимать SD карту, можно просто подключиться к Linux по SSH и записать новые файлы.
В основе сборки лежит подготовленный Xilinx Artix-7 linux, там уже все настроено для работы на Zynq7000, я включил только пакеты поддержки UARTLite, SPI, убрал все Python пакеты.
Заключительная доработка — это автозапуск моей прикладной программы с помощью init скрипта. Приложение я сохраняю на SD карте вне корневой файловой системы. Это позволяет мне не пересобирать весь образ при изменении прикладной программы.
Запускаем make, идём пить чай, так как сборка ядра и файловой системы занимает не один десяток минут. На выходе получаем готовый образ Linux. Финальный шаг – сборка загрузочного образа SD карты. Для этого нам понадобятся все ранее скомпилированные файлы. Bitstream полученный из Vivado, FSBL.elf, u-boot.elf.
Предварительно нужно подготовить файл boot.bif примерно такого содержания:
img : {[bootloader] fsbl.elf system_top.bit u-boot.elf}
С помощью такой команды создаётся загрузчик:
bootgen -image ./boot.bif -o i ./BOOT.bin -log debug
Из всего этого получается BOOT.bin, который записываем вместе с линуксом (файл image.itb) на SD карту. (Не забудьте установить на плате Antminer S9 джамперы для загрузки с карты SD).
Я очень поверхностно и схематично описываю все предыдущие шаги, поскольку они неоднократно освещались как на хабре, так и в многочисленных руководствах в интернете по Zynq7000. Главное пройти этот путь хотя бы один раз, дальше сборка Linux для Antminer S9 не будет представлять проблемы.
После всех этих продолжительных манипуляций получаем систему, на которой наконец можно запустить желаемую прикладную программу. Осталось её написать.
Тут вынужден сделать еще одну оговорку. В своем проекте я решил не использовать обработчики прерываний дабы не писать загружаемые модули ядра для Linux. Т.е., например, нажатие клавиш на клавиатуре я получаю путем регулярного опроса. Железо конечно поддерживает работу по прерываниям, но изучать написание драйверов Linux я не хотел. Так что всё по-простому. Но и проект не сложный, как показали замеры загрузка ЦП не превышает в таком режиме 3%. Надо понимать, что вычислительный возможности Zynq на порядки превосходят потребности в ресурсах приложения. Почти любая Arduino подошла бы, но за недорого у нас есть Antminer S9, почему бы и не воспользоваться возможностью её использования для любительского проекта?
Как любитель Rust я не упустил возможности написать управляющую программу на этом языке. Ранее писал под STM32, там либо один поток, либо асинхронщина в embassy, а тут прям раздолье – полноценный Linux с потоками, сотнями мегабайт оперативной памяти и другими плюшками. Можно практически ни в чём себе не отказывать и всё равно не загрузить систему полностью. Однако, я не стал пользоваться графической подсистемой linux, работа с дисплеем сделана с помощью крейта embedded-graphics.
Для разработки использовал практически те же самые крейты, что и для эмбеддед программ для STM32. Поскольку интерфейсы rust embedded hal практически одинаковы для STM32 и Linux, код получается очень прохожий. Главное отличие, конечно же, в инициализации и доступе к физическим устройствам SPI, I2C, delay и т.д. Код же драйверов остался практически тем же.
Например, для доступа к пинам в linux я использую крейт gpiocdev-embedded-hal. С его ним доступ к пину осуществляется так:
let pin_dc = gpiocdev_embedded_hal::OutputPin::new("/dev/gpiochip0", 55, PinState::Low)
.unwrap_or_else(|_err| {
println!("Couldn't open pin55.");
process::exit(1);
});
pin_dc.set_high().ok();
Для доступа к I2C используется стандартный linux-embedded-hal:
let i2cdev = I2cdev::new("/dev/i2c-0").unwrap_or_else(|_err| {
println!("Couldn't open i2c");
process::exit(1);
});
let i2c_cell = AtomicCell::new(i2cdev);
let i2c = AtomicDevice::new(&i2c_cell);
Как и описанная мной в прошлом реализация переключателя антенн (https://habr.com/ru/articles/820145/), новая версия так же поддерживает протокол связи с ПК easycom, но в ещё более простом варианте, достаточном для синхронизации с программой PST-Rotator.
Плата Antminer S9 довольно крупная, поэтому вся конструкция собрана в старом Mini ITX корпусе. Накладки на дисплей и клавиатуру напечатаны на 3D принтере. В боре получилось вот так:

На экран выводится информация о координатах спутника, на который наводятся антенны, номер и апертура подключенной антенны, статус включения реле и т.п.
Ну и в заключение, фото установленных на крыше антенн, ради переключения которых всё это и затевалось. В металлическом шкафе находится блок с реле.

Все исходные тексты описанной программы и сборки Linux можно найти на гитхабе по ссылке (https://github.com/lesha108/antswitcher.git)
В следующей статье расскажу как использую плату Antminer S9 для проекта специализированного секвенсора для трансивера. Полагаю, что благодаря невысокой цене и богатой функциональности эта плата послужит основной еще для многих радиолюбительских проектов.
iliasam
Думаю, что формирование DTMF можно с легкостью сделать на ПЛИС.
Декодирование - тоже, понадобится только внешний компаратор (хотя, возможно, можно линии LVDS использовать).