Лабораторная 11.47 — Плата принимает собственную пачку¶
Идея¶
Лабораторным с двумя платами нужны две платы и второй передатчик. Одна плата тоже может отправить пачку QPSK своего модема в ПЛИС через TX1 и принять её на RX1 по кабелю. Тогда TX и RX работают от одного опорного генератора: нет сдвига частоты несущей, нет блуждания генераторов, и весь модем можно проверить одной платой.
На стенде курса у платы B это сначала не получалось: кадр находился реже чем в одном приёме из десяти, и с большим числом ошибок. Причина оказалась не в модеме, а в том, как битстрим курса подаёт данные на ЦАП. Ей и посвящена эта лабораторная.
Стенд¶
плата B TX1 -> аттенюаторы (не меньше 30 dB в сумме) -> плата B RX1
- Никогда не соединяйте TX1 с RX1 напрямую. При TX 0 dB и 30 dB в тракте на вход приёмника приходит около −30 dBm; предел входа AD9361 +2,5 dBm.
- На стенде курса стояли переменный аттенюатор (0 dB) -> 30 dB -> сплиттер на 3 выхода; на остальные выходы были подключены RX1 платы A и RTL-SDR как независимые свидетели.
- У платы B нет вентилятора. Сценарий читает температуру Zynq с XADC до и после и не начинает работу выше 80 °C; на стенде она держалась в пределах 61–70 °C.
Чем особенна плата B¶
На плате B стоит образ курса, в дереве устройств которого нет драйвера ЦАП (нет
cf-ad9361-dds-core-lpc). Два следствия:
- После загрузки все каналы ЦАП берут данные со встроенного DDS, масштаб которого равен 0. Мост
подмешивает пачку модема в FIFO ЦАП на входе DMA (
course_dac_fifo_source_mux.v), поэтому, пока каналы на него не переключены (devmem 0x79024418 32 2и так же для 0x...58, 0x...98, 0x...D8), пачка вообще не передаётся. - Вендорский TX DMA никто не запускает. Об этом следующий раздел.
Дополнение, в тот же день: дерево устройств платы B отличалось от дерева платы A ровно этим одним
узлом (cf-ad9361-dds-core-lpc@79024000). После установки на B дерева платы A (старое сохранено на
карте как devicetree.dtb.course_nodds) у B появилось IIO-устройство ЦАП, и циклический буфер нулей
через iio_writedev поддерживает вендорский поток так же, как на плате A. Сценарий использует этот
путь, если устройство есть, а иначе переходит на dac_zero_stream.
ЦАП, который держит последний отсчёт¶
Когда каналы стоят на входе DMA, пачка уходит, но RTL-SDR на сплиттере показывает мощную непрерывную линию на частоте гетеродина. Её уровень над шумом RTL-SDR в разных состояниях:
| состояние платы B | линия на 915 МГц |
|---|---|
| гетеродин TX выключен | 12,9 dB |
| источник ЦАП ZERO | 13,5 dB |
| источник ЦАП DDS (по умолчанию после загрузки) | 14,5 dB |
| источник ЦАП DMA, после нескольких пачек | 87,7 dB |
Значит, это не аналоговая утечка гетеродина. Мультиплексор пишет FIFO ЦАП по стробу вендорского
потока и подставляет пачку, только пока она передаётся. На плате A всегда работает циклический буфер
DMA (iio_writedev), поэтому строб не останавливается. На плате B TX DMA никто не запускает, FIFO
пишется только во время пачки, и между пачками ЦАП продолжает выдавать последнее записанное слово,
ненулевой отсчёт пачки. Постоянное число на ЦАП означает несущую на частоте гетеродина. Плата A
показала, что во время пачки несущая всего на 3 dB слабее её и не пропадает никогда. На собственном
приёмнике платы B она превращается в постоянную составляющую такой же силы, как сигнал.
Исправление, используемое здесь¶
blocks/block_11_integrated_sdr_project/sw/dac_zero_stream.c поддерживает вендорский поток нулями.
Программа закрепляет страницу памяти, берёт её физический адрес из /proc/self/pagemap и гоняет по
ней по кругу ADI axi_dmac по адресу 0x7c420000 (память -> поток на ЦАП). Перед запуском
бездействующий драйвер ядра этого DMA отвязывается. По сигналу SIGTERM программа останавливает DMA,
поэтому останавливать её нужно обычным kill. После kill -9 DMA продолжил бы читать страницу,
которую ядро может отдать кому-то другому, и передавать её в эфир.
Сценарий ниже собирает программу компилятором arm-linux-gnueabihf-gcc -static из Vitis (как на
этапе 3 Лабораторной 8.14), запускает её, переключает каналы ЦАП, прогоняет кадры и снова её
останавливает.
Запуск¶
cd blocks/block_11_integrated_sdr_project/python
python lab_11_47_single_board_selfota.py --frames 60
python lab_11_47_single_board_selfota.py --frames 60 --no-zero-stream
Образ платы B 3d492301 (#66), TX 0 dB, RX 60 dB, дифференциальный режим Лабораторной 11.45, 915 МГц:
| кадров | захвачено | чистых | ошибок в полезной нагрузке | |
|---|---|---|---|---|
| с потоком нулей | 60 | 52 | 52 | 0 / 13312 |
| без потока нулей (отсчёт держится) | 60 | 5 | 0 | 220 / 1280 |
С потоком нулей каждый захваченный кадр чистый; без него связь практически теряется.
Упражнения¶
- Запустите с
--offsets 0 1 2 3 4 5 6 7. Почему на одной плате некоторые значенияstart_offsetникогда не дают захвата, а на стенде из двух плат работают все? Сравните с развёрткой фазы выборки в Лабораторной 5.8. - Запустите с ключом
--no-dc-blockerи без него. До исправления issue #66 DC-блокер стоил на этой связи кадров; сколько он стоит теперь и почему DC-блокер не мог убрать удерживаемый отсчёт? - Во время разбора в тракт предлагали поставить пассивный коаксиальный DC block. Почему он ничего бы не изменил?
- Комментарий в мультиплексоре объясняет, почему FIFO нужно писать с жёстким шагом. Что пошло бы не так, если бы RTL просто писал нули на своём такте всякий раз, когда пачка не выбрана?
Ответы
- На одной плате задержка от TX модема до его RX постоянна, поэтому кадр всегда приходит с одной и
той же фазой внутри символа. Каждое значение
start_offsetставит выборку на одну фазу; два из восьми попадают на границу символа, где глаз закрыт. На двух платах момент старта относительно принимаемого потока случаен, и каждое значение чаще всего попадает на хорошую фазу. - На стенде оба варианта захватили одни и те же кадры без ошибок (по 75 из 100 при восьми смещениях). Удерживаемый отсчёт создаёт несущую на частоте гетеродина, и в приёмник она приходит как постоянная составляющая, но она есть с самого начала и сравнима с сигналом; со сбросом на каждом кадре старого DC-блокера приёмник не захватил ни одного кадра. DC-блокер может убрать смещение только медленно и приближённо; устранить причину на передатчике лучше.
- На кабеле несущая находится на 915 МГц. DC block представляет собой последовательный конденсатор: он задерживает постоянное напряжение и пропускает радиосигнал. «Постоянной» несущая становится только после того, как приёмник перенёс её на нулевую частоту.
- FIFO переводит данные из тактового домена ПЛИС в такт ЦАП. Если число записей на одно чтение ЦАП зависит от того, что передаётся, заполнение FIFO, а вместе с ним задержка пачки до антенны, меняется от пачки к пачке. Комментарий в мультиплексоре фиксирует ровно это: кадр приходил с разной фазой внутри символа по повторяющемуся шаблону. Шаг записи должен следовать скорости чтения ЦАП.
Доказательства¶
board-b-dac-held-carrier-20261014.md: таблица RTL-SDR, взгляд платы A и сравнение DC-блокера на одной плате;- issue #67: поведение мультиплексора и варианты постоянного исправления (дерево устройств платы B или RTL).
Что лабораторная показывает и чего не показывает¶
Она показывает связь на одной плате, в которой каждый захваченный кадр принимается без ошибок, как только FIFO ЦАП получает данные правильно, и то, как деталь инфраструктуры далеко от модема может выглядеть как проблема модема. Мультиплексор она не исправляет: поток нулей остаётся обходным решением, которое должно работать, пока плата B передаёт.