# Lab043. Передача JPEG с Pluto+ на независимый RTL-SDR ## 1. Цель лабораторной Передать тот же подготовленный JPEG, который использован в Lab042, с Pluto+ на отдельный RTL-SDR и восстановить его побайтово. В Lab042 передатчик и приёмник находились в одном Pluto+ и использовали общий опорный генератор. Lab043 должна убрать это упрощение и раздельно измерить три эффекта: 1. рассогласование несущих частот передатчика и приёмника; 2. относительное рассогласование частот дискретизации двух устройств; 3. начальную фазу выбора отсчёта символа внутри символьного интервала. Лабораторная считается успешной только при выполнении радиокритериев, прикладных критериев и программных проверок, определённых в разделе 11. Запуск программы сам по себе успехом не считается. ## 2. Границы работы Lab043 остаётся кабельной. Эфир добавил бы многолучёвость, внешние сигналы и неконтролируемые потери, хотя предмет этой работы состоит в синхронизации независимых устройств. В Lab043 не входят: - передача через антенны; - выбор рабочего диапазона 200-250 МГц; - ARQ и повторная передача; - исправление стираний и новая FEC; - потоковое видео; - скачки по частоте; - изменение логики управления и безопасности ровера. Эфирный тракт остаётся предметом следующей лабораторной. ## 3. Подтверждённое оборудование и окружение Состояние оборудования проверено непосредственным опросом. Перед реализацией и перед каждым аппаратным прогоном его необходимо проверить повторно. ### 3.1. Передатчик - Pluto+; - устройство должно открываться успешным `iio.Context`, а не проверяться только командой `ping`; - чип AD9361; - используется первый передающий канал. Конкретный рабочий URI IIO определяется повторным опросом перед опытом и сохраняется в метаданных запуска. Исторически работали сетевой URI `ip:192.168.2.1` и прямой USB IIO, но ни один из них нельзя считать доступным без текущей проверки. ### 3.2. Приёмник - USB ID `VID_0BDA&PID_2838`; - имя библиотеки: `Generic RTL2832U OEM`; - тюнер, сообщённый библиотекой: Fitipower FC0013; - основной интерфейс `MI_00` использует WinUSB от libwdi; - устройство открыто из Python через `pyrtlsdr` и `librtlsdr`; - установка 435 МГц подтверждена чтением значения обратно; - частота дискретизации 2 400 000 отсчётов/с работает; - получены комплексные отсчёты; - доступные аппаратные усиления находятся в диапазоне от -9,9 до 19,7 дБ и задаются дискретными ступенями устройства. Ошибка второго USB-интерфейса не считается отказом SDR. Основной интерфейс `MI_00`, через который работает `librtlsdr`, исправен. ### 3.3. Зафиксированное программное окружение - Python 3.13.1 x64; - `pyrtlsdr` 0.5.0; - `pyrtlsdrlib` 0.0.5; - поставляемая пакетом нативная `librtlsdr` v0.9.0; - `pyadi-iio` и `pylibiio` для Pluto+. Принятый способ доступа к RTL-SDR: API `pyrtlsdr` с поставляемой `pyrtlsdrlib` нативной библиотекой. Малый собственный DLL-адаптер не нужен. Реализация должна проверять версии и выдавать понятную ошибку, если библиотека или устройство недоступны. Пользовательский путь к DLL зашивать в код запрещено. ## 4. Физическая схема и безопасность Схема: `Pluto+ TX -> SMA-кабель -> AT30S 30 дБ -> переходник/кабель -> RTL-SDR RX` Обязательные условия перед каждым включением TX: 1. Антенны Pluto+ и RTL-SDR сняты. 2. AT30S включён последовательно в тракт, а не подключён к свободному порту. 3. Все разъёмы затянуты, кабель не отсоединяется при включённом TX. 4. Пользователь отдельно подтверждает эту физическую схему. 5. Первый заранее определённый рабочий режим: TX -30 дБ, RTL-SDR 19,7 дБ, несущая 435 МГц, номинально 2,4 Мвыб/с на обоих устройствах. 6. Автоматический перебор усилений, частот, порогов и способов синхронизации запрещён. 7. Если первый режим не проходит, программа сохраняет исходные данные и диагностику и останавливается. Следующий прогон разрешён только после разбора причины и с изменением ровно одного заранее названного параметра. При TX -30 дБ расчётный уровень после AT30S составляет около -53 дБм. Это значение следует из бюджета Lab042: около +7 дБм при TX 0 дБ, минус 30 дБ настройки TX и минус 30 дБ аттенюатора. Расчёт не заменяет проверку наличия аттенюатора и правильности схемы. ## 5. Номинальные параметры сигнала и обязательное чтение обратно | Параметр | Запрошенное значение | Происхождение | |---|---:|---| | Несущая TX и RX | 435 МГц | Кабельная рабочая точка Lab042 | | Символьная скорость | 20 000 симв/с | Lab018-Lab023 и Lab042 | | Частота дискретизации TX | 2 400 000 отсчётов/с | Поддерживается Pluto+ и проверена на RTL-SDR | | Частота дискретизации RX | 2 400 000 отсчётов/с | Проверена непосредственным опросом RTL-SDR | | Номинальное число отсчётов на символ | 120 | 2 400 000 / 20 000 | | Полоса TX | 200 кГц | Минимальная полоса AD9361, использованная в Lab042 | | Размер данных фрагмента | 512 байт | Lab042 | | Модуляция | BPSK | Lab018-Lab023 и Lab042 | Одинаковое номинальное значение 2,4 Мвыб/с выбрано специально. Lab043 измеряет физическую ошибку генераторов, а не искусственно созданную разницу номинальных частот дискретизации. Перед аппаратным опытом программа обязана установить и прочитать обратно: - частоту дискретизации TX Pluto+; - частоту дискретизации RX RTL-SDR; - центральную частоту TX Pluto+; - центральную частоту RX RTL-SDR; - усиление TX Pluto+; - фактически выбранную ступень усиления RTL-SDR. Для каждого параметра в отчёте и метаданных хранятся отдельные поля `requested` и `actual`. Если API не предоставляет независимого чтения обратно, поле `actual` имеет значение `N/A`, а не копию запрошенного значения. Передача не начинается, если частота дискретизации или несущая не прочитаны обратно либо отличаются от запрошенных больше, чем допускает API устройства и заранее установленная проверка. ## 6. Двухтоновая калибровка Перед BPSK Pluto+ передаёт два комплексных тона, симметричных относительно несущей: `-Fcal` и `+Fcal`, где `Fcal = 50 кГц`. 50 кГц выбраны до измерения: оба тона находятся внутри полосы 200 кГц, не сливаются около нуля и дают разнос 100 кГц для оценки масштаба частот. RTL-SDR измеряет положения двух пиков относительно своей настроенной несущей: `f_low` и `f_high`. ### 6.1. Рассогласование несущей `carrier_offset_hz = (f_low + f_high) / 2` `carrier_offset_ppm = carrier_offset_hz / 435000000 * 1e6` Положительный результат означает, что принятый спектр сдвинут вверх по частоте. Для компенсации сырые комплексные отсчёты умножаются на: `exp(-j * 2*pi*carrier_offset_hz*n/Fs_rx_actual)` Знак поправки проверяется независимо: после компенсации среднее положение двух тонов должно стать ближе к нулю. Рассогласование несущей не должно подменяться оценкой ошибки частоты дискретизации. ### 6.2. Относительная ошибка частот дискретизации `clock_scale = (f_high - f_low) / (2*Fcal)` `sample_clock_error_ppm = (clock_scale - 1) * 1e6` Здесь положительная ошибка означает, что масштаб передающего такта больше масштаба приёмного. Для перехода принятых отсчётов на временную сетку передатчика ожидаемая длина после передискретизации равна: `len_corrected = round(len_received * clock_scale)` Направление этой операции не принимается на веру. Оно подтверждается машинными тестами из раздела 8. После передискретизации накопленная ошибка символьного времени должна уменьшаться. ### 6.3. Начальная фаза выбора отсчёта символа После грубой CFO-коррекции и передискретизации приёмник отдельно ищет начальную фазу выбора отсчёта в диапазоне от 0 до 119 номинальных отсчётов на символ. Это не CFO и не ошибка такта. В результатах отдельно хранятся: - `carrier_offset_hz` и `carrier_offset_ppm`; - `clock_scale` и `sample_clock_error_ppm`; - `symbol_sample_phase` и остаточная ошибка символьного времени. ### 6.4. Формальное определение шумового фона Шумовой фон и превышение пиков вычисляются одинаковым детерминированным алгоритмом: 1. Из комплексных отсчётов вычитается среднее. 2. Оценивается спектральная плотность мощности методом Welch с окном Hann, `nfft = nperseg = 65536` и перекрытием 50 процентов. 3. Разрешение одного БПФ вычисляется по прочитанной обратно частоте как `delta_f = Fs_rx_actual / 65536`. При 2,4 Мвыб/с оно равно `36,62109375 Гц`. 4. Анализируется полоса от -100 до +100 кГц относительно настроенной несущей. 5. Нижний пик ищется в диапазоне от -80 до -20 кГц, верхний от +20 до +80 кГц. Эти непересекающиеся окна заданы до измерения. 6. После нахождения кандидатов ожидаемых тонов из оценки шума исключаются защитные зоны шириной +/-2 кГц вокруг измеренного положения каждого кандидата. Каждая полузона занимает около 55 разрешающих элементов БПФ при 2,4 Мвыб/с, поэтому утечка основного лепестка и ближайших боковых лепестков тона не попадает в медиану шума. 7. Шумовая мощность `P_noise` равна медиане линейных значений мощности всех оставшихся спектральных элементов анализируемой полосы. 8. Для каждого тона вычисляется `peak_excess_db = 10*log10(P_peak/P_noise)`. Оба пика должны превышать шумовой фон не менее чем на 10 дБ. Порог 10 дБ, полоса анализа, окна поиска и защитные зоны зафиксированы до аппаратного измерения. Если нет двух конечных пиков, недостаточно элементов шума или хотя бы один пик не проходит порог, оценка этого захвата недействительна. ### 6.5. Повторы и представление недействительных оценок Без изменения схемы выполняются три отдельных калибровочных захвата. Для каждого захвата отдельно сохраняются: - `f_low_hz` и `f_high_hz`; - `low_peak_excess_db` и `high_peak_excess_db`; - `carrier_offset_hz` и `carrier_offset_ppm`; - `clock_scale` и `sample_clock_error_ppm`; - остаточная CFO после коррекции; - остаточная ошибка разноса тонов в герцах и ppm; - признак достоверности и причина отказа. По трём захватам вычисляются среднее, минимум, максимум и стандартное отклонение каждой конечной оценки, а также число недействительных захватов. Стандартное отклонение считается по совокупности всех конечных оценок с `ddof=0`. Недействительное значение записывается как `NaN` в CSV и как `N/A` в текстовом отчёте. Оно никогда не заменяется нулём. Если конечных значений нет, все агрегаты этой величины равны `N/A`. ## 7. Диагностическая последовательность передачи Аппаратная часть выполняется по ступеням. Переход к следующей ступени разрешён только после сохранения результатов предыдущей: 1. двухтоновые калибровочные захваты; 2. известная короткая BPSK-последовательность; 3. символьная синхронизация и измерение BER известной последовательности; 4. один пакет с CRC; 5. десять пакетированных фрагментов JPEG; 6. сборка JPEG; 7. проверка размера и SHA-256. Известная последовательность задаётся до измерения как PRBS11 длиной 2047 бит с полиномом `x^11 + x^9 + 1` и фиксированным ненулевым начальным состоянием, записанным в метаданные. BER вычисляется прямым сравнением каждого принятого бита с ожидаемым до упаковки в пакет и до проверки CRC. CRC не может использоваться вместо измерения BER. ### 7.1. Контрольный опыт PRBS11 после проверки непрерывного async-приёма Короткая запись S и длинная запись L являются двумя отдельными аппаратными захватами. Каждый захват выполняется одним непрерывным `rtlsdr_read_async()` и содержит в этой последовательности: входной запас, двухтоновую калибровку, фиксированный защитный участок 0,10 с, один начальный маркер, непрерывную PRBS11 и выходной запас. Полезная PRBS11 длится 0,25 с в S и не менее 2,0 с в L. Внутри полезной PRBS11 нет периодической повторной синхронизации. Оценки CFO и масштаба частоты дискретизации для обработки PRBS11 берутся только из двух тонов в той же самой IQ-записи. Оценку из предыдущего двухтонового опыта или из другой записи применять запрещено. Если тоны в текущем захвате недостоверны, PRBS11 этой записи не интерпретируется. Для обоих захватов заранее фиксируются 435 МГц, номинальные 2 400 000 отсчётов/с TX и RX, 20 000 символов/с, TX -30 дБ, запрос усиления RTL-SDR 19,7 дБ и `Fcal = 50 кГц`. Асинхронный приём использует callback 262144 байта, 15 буферов и отбрасывает первый callback как разогревочный без перезапуска сеанса. Автоматический подбор параметров запрещён. Одна и та же IQ-запись обрабатывается режимами A, B и C. В B и C грубая CFO берётся из тонов этой записи; в C дополнительно применяется `clock_scale` этих же тонов. Разрешённая защищённая тонкая CFO-коррекция не заменяет двухтоновое измерение. Для длинной записи SRO дополнительно оценивается независимо по накопленному дрейфу символьной фазы. ## 8. Обязательные программные проверки до аппаратного опыта На синтетических комплексных данных проверяются четыре ошибки частоты дискретизации: +20, -20, +100 и -100 ppm. Для каждого случая тест обязан проверить: - знак оценки `sample_clock_error_ppm`; - величину оценки в заранее заданном допуске; - направление изменения длины при передискретизации; - уменьшение остаточной накопленной ошибки символьного времени после поправки. Для положительной ошибки длина должна изменяться в направлении, предписанном формулой `round(N * clock_scale)`, для отрицательной в противоположном. Тест должен упасть, если вместо `clock_scale` применена обратная величина. Допуск оценки и критерий уменьшения остаточной ошибки задаются в тесте до аппаратного измерения и печатаются в его результате. Дополнительно синтетические тесты проверяют: - знак грубой CFO-коррекции для положительной и отрицательной CFO; - уменьшение остаточной CFO после поправки; - отдельное восстановление начальной фазы выбора отсчёта символа; - явный `N/A`, если два тона не найдены достоверно; - формулу шумового фона и порог 10 дБ; - BER известной последовательности до CRC; - отсутствие ложного успеха при неполном пакете или изображении; - формирование кода возврата только по полному набору критериев. До прохождения этих проверок передатчик включать нельзя. ## 9. Порядок аппаратного эксперимента 1. Открыть Pluto+ успешным `iio.Context` и записать URI и идентификаторы. 2. Открыть RTL-SDR через `pyrtlsdr` и записать имя устройства и тюнера. 3. Установить и прочитать обратно параметры из раздела 5. 4. Получить отдельное подтверждение пользователя, что кабельная схема из раздела 4 собрана и AT30S находится в разрыве. 5. Запустить один непрерывный приём RTL-SDR за 0,5 с до передачи Pluto+. 6. Выполнить три отдельных двухтоновых калибровочных захвата. 7. Сохранить оценки каждого захвата, агрегаты и не менее одного эталонного сырого IQ-захвата. 8. Передать известную BPSK-последовательность и измерить BER. 9. Принять один пакет и проверить CRC. 10. Передать десять фрагментов JPEG и проверить критерии раздела 11. Если любая ступень не проходит, переход к следующей запрещён. Исходные данные и отрицательный результат сохраняются без подгонки. Изменение одного параметра для следующего запуска оформляется как отдельное заранее объяснённое действие. Весь опыт принимается RTL-SDR как одна непрерывная временная последовательность. Продолжительность приёма вычисляется из фактически сформированной передаваемой последовательности: `T_rx = 0,5 с + N_tx / Fs_tx_actual + 0,5 с`. Здесь `N_tx` является фактическим числом комплексных отсчётов всей сформированной TX-последовательности. Общая длительность опыта не задаётся произвольной константой. Поток разрешено читать блоками, например около 262144 отсчётов, но блоки объединяются без пропусков и перестановок в одну последовательность. Размер блока чтения является параметром реализации, а не физическим защитным интервалом. Десять отдельных запусков приёмника для десяти кадров запрещены, потому что они уничтожили бы наблюдаемое накопление ошибки тактов. Между BPSK-кадрами не добавляются новые интервалы. Используется структура Lab042: `GUARD_SYMBOL_COUNT` импортируется из её текущей конфигурации без копии числовой константы в Lab043, а формирование кадра повторяет ту же последовательность переднего интервала, символов, заднего интервала и RRC. Готовая функция Lab042 жёстко связана с 128 отсчётами на символ, поэтому при 120 отсчётах Lab043 переиспользуются её параметры, а не скрытая копия её глобальной частоты дискретизации. Если в дальнейшем отдельный интервал исчезнет из Lab042, Lab043 не создаёт новый без отдельного диагностического решения. ## 10. Три режима обработки одной записи Одна и та же сохранённая BPSK/IQ-запись обрабатывается тремя режимами: - режим A: без грубой CFO-коррекции и без коррекции частоты дискретизации; - режим B: только грубая CFO-коррекция из двухтоновой калибровки; - режим C: грубая CFO-коррекция, коррекция частоты дискретизации и защищённая тонкая CFO-коррекция. Во всех режимах начальная фаза выбора отсчёта символа оценивается и записывается отдельно. Для каждого режима сохраняются BER известной последовательности, остаточная CFO, остаточная временная ошибка, корреляция маркера, число найденных кадров, число разобранных заголовков и CRC пакетов. В режиме C защищённая функция `should_apply_cfo_correction` из `protocol/bpsk_radio.py` остаётся тонкой коррекцией после грубого измерения. Она не используется как единственный способ поиска сдвига в десятки килогерц, поскольку символьная оценка неоднозначна за пределами половины символьной скорости. Провал режимов A или B не считается провалом лабораторной. Они являются контрольными измерениями. Критерии передачи применяются к режиму C. ## 11. Критерии приёмки ### 11.1. Радиокритерии режима C - найдены все 10 кадров; - разобраны все 10 пакетов; - CRC32 сошлась у всех 10 пакетов. ### 11.2. Прикладные критерии режима C - восстановлены все 10 уникальных фрагментов; - JPEG собран без заполнения отсутствующих данных; - размер принятого JPEG совпадает с размером переданного; - SHA-256 принятого JPEG совпадает с SHA-256 переданного. ### 11.3. Общий результат Код возврата 0 разрешён только при одновременном выполнении: - всех радиокритериев; - всех прикладных критериев; - всех обязательных программных проверок раздела 8. Для подтверждения воспроизводимости заранее выбранная рабочая точка запускается три раза подряд без изменения схемы и параметров. Каждый повтор сохраняется отдельно. Три повтора являются проверкой воспроизводимости, а не статистической оценкой вероятности отказа. Отрицательный результат допустим и сохраняется полностью. Запрещено автоматически поднимать усиление, менять частоту, пороги или алгоритм синхронизации после неудачного прогона. ## 12. Измерения и артефакты ### 12.1. Измерения передачи Для каждого запуска и каждого режима обработки сохраняются: - запрошенные и фактические усиления, несущие и частоты дискретизации; - число принятых отсчётов; - отдельные оценки CFO, ошибки частоты дискретизации и начальной символьной фазы; - BER известной последовательности до CRC; - число найденных кадров и разобранных заголовков; - CRC каждого пакета; - решение защищённой тонкой CFO-коррекции и его диагностические величины; - корреляция маркера и корректно определённая EVM; - число и номера восстановленных фрагментов; - размер и SHA-256 переданного и принятого JPEG; - длительность сигнала и полезная скорость. Каждое число вычисляется из текущего запуска. Перенесённые константы снабжаются ссылкой на лабораторную-источник. Невычислимая величина записывается как `NaN` или `N/A`, но не как правдоподобный ноль. ### 12.2. План программных артефактов Предполагаемые файлы после отдельного согласования реализации: - `experiments/lab043_pluto_to_rtlsdr.py`; - `tests/test_lab043_calibration.py`; - `data/processed/lab043/lab043_calibration.csv`; - `data/processed/lab043/lab043_frames.csv`; - `data/processed/lab043/lab043_summary.csv`; - `data/processed/lab043/lab043_report.txt`; - графики калибровочного спектра, остаточной ошибки и доставки фрагментов. ### 12.3. Эталонный сырой IQ-захват Не менее одного полного эталонного захвата до CFO- и SRO-коррекций сохраняется в `data/raw/lab043/` и не добавляется в Git. Формат отсчётов: NumPy `.npy`, одномерный массив `complex64` в порядке приёма. Нормирование исходных 8-битных I/Q отсчётов и порядок I/Q должны быть однозначно описаны в метаданных. Рядом сохраняется UTF-8 JSON с тем же базовым именем и полями: - локальная метка времени и UTC; - идентификаторы устройств и модель тюнера; - версии Python, `pyrtlsdr`, `pyrtlsdrlib`, `librtlsdr`, `pyadi-iio` и `pylibiio`; - запрошенные и фактические частоты дискретизации, несущие и усиления; - число отсчётов, длительность, dtype и порядок байтов; - идентификатор и SHA-256 переданной формы сигнала; - результаты калибровки, применённые к этой записи; - SHA-256 файла `.npy`. IQ-файл содержит всю единую последовательность приёма. Ожидаемое число отсчётов вычисляется как `ceil(T_rx * Fs_rx_actual)`. Чтение выполняется последовательными блоками до достижения этого числа, последний блок обрезается только после получения требуемого количества отсчётов. ### 12.4. Разделение общего и лабораторного кода Общие математические примитивы обнаружения двух известных тонов, оценки грубой CFO и относительной ошибки частот дискретизации, грубой частотной коррекции и передискретизации размещаются в `protocol/bpsk_radio.py` либо в другом подходящем существующем модуле `protocol/`. Каждый такой перенос сопровождается быстрым синтетическим тестом. В `experiments/lab043_pluto_to_rtlsdr.py` остаются сценарий лабораторной, конкретное `Fcal`, три калибровочных захвата, методика оценки шума Lab043, режимы A/B/C, PRBS11, контрольный пакет, JPEG, отчётные артефакты, критерии приёмки и аппаратная последовательность. Общий код не зависит от конкретного JPEG и номера лабораторной. ## 13. Точка остановки перед аппаратным опытом Программная реализация и синтетические проверки разрешены. Перед аппаратным опытом необходимо отдельно: 1. пройти все обязательные программные проверки раздела 8; 2. повторно проверить доступность устройств и чтение параметров обратно; 3. получить подтверждение пользователя о схеме и отдельное разрешение на включение TX. Способ Python-доступа к RTL-SDR уже выбран и не является вопросом точки остановки. До отдельного разрешения передатчик не включается, усиление Pluto+ не изменяется, аппаратная калибровка и передача тонов, BPSK или JPEG не выполняются. Операции `git add`, `commit`, `push`, `pull` и `fetch` также не выполняются.