Сэмплирование в airspy mini
Введение
Я решил немного отвлечься от программирования микроконтроллеров и сделать то, до чего руки давно не доходили: добавить поддержку airspy mini в sdr-server.
airspy mini - это SDR приёмник, который конвертирует аналоговый радиосигнал в цифровой (АЦП) и передаёт в компьютер для обработки. Как и его ближайший и наиболее распространённый конкурент RTL-SDR v3 он основан на чипе R820T. Его основным отличием является хорошая чувствительность и большой динамический диапазон. Это достигается за счёт того, что каждый сэмпл имеет разрешение 12 бит против 8 бит RTL-SDR. Помимо этого airspy mini сэмплирует на достаточно большой скорости и поддерживает только 4 значения: 10Мгц, 6Мгц, 3Мгц и 4.096Мгц. Мне это показалось интересным и я решил разобраться почему.
Супергетеродинный приёмник
Чтобы разобраться что к чему, я решил начать с самого начала: понять как работает супергетеродинный приёмник на примере RTL-SDR.
Если отбросить все ненужные блоки, то схема выглядит следующим образом:
RTL-SDR состоит из двух частей: аналогового тюнера R820T и цифрового демодулятора RTL2832U. Тюнер предназначен для того, чтобы выделить нужную частоту и преобразовать её до промежуточной. Работать с промежуточной частотой гораздо проще и поскольку она значительно меньше основной, то и требования к элетронике значительно ниже. Второй чип получает на вход сигнал на промежуточной частоте и преобразует в цифровой вид. А вот дальше происходит интересное. RTL2832U создан как приёмник DVB-T сигнала, поэтому он имеет блоки демодуляции DVB-T сигнала, применения автокорректирующих кодов протокола и финальное преобразование в видео-поток. Но умельцы смогли найти специальный режим “тестирования” при котором чип отправляет по USB сырой сигнал. Это и послужило взрывному росту популярности SDR приёмников и RTL-SDR.
Поскольку меня больше всего интересовал принцип сэмплирования, то я решил сосредоточится на нём. Для примера я взял сигнал на частоте 146Мгц. Он попадает в тюнер и через усилитель сигнала в смеситель. Смеситель - это специальный блок, который на вход получает две частоты F1 и F2, а на выходе выдаёт сигнал, где есть F1+F2 и F1-F2. Сумма частот отфильтровывается, а вот разница используется дальше.
Для того, чтобы сгенерировать F2, которая по частоте близка к нужной, используется блок PLL. Он получает на вход крайне стабильную заранее фиксированную частоту. В случае с RTL-SDR - это 28.8Мгц от специального термокомпенсированного генератора (TXCO). Далее, PLL повышает входящую частоту пока она не совпадёт с нужной. В данном случае, чтобы получить частоту близкую к 146Мгц, нужно использовать делитель 5: 28.8 * 5 = 144.
После смесителя нужный сигнал оказывается на частоте 2Мгц. Дальше он фильтруется и отправляется в RTL2832U.
АЦП в RTL2832U работает на частоте 28.8Мгц. На каждый такт АЦП генерирует один сэмпл, поэтому согласно теореме Найквиста-Шеннона на выходе можно получить сигналы от 0 до 14.4Мгц. Но для DVB-T - такое количество сэмплов слишком много, поэтому они дополнительно децимируются. После этого сигнал переводится в I/Q сигнал и отправляется в USB.
Согласно даташиту, RTL2832U позволяет получать частоту из внешнего источника и было бы логично использовать тот же TXCO кристалл и для RTL2832U, но я нигде не нашёл этому подтверждения. Вероятно в RTL2832U используется не такой точный (~100ppm) внутренний кристалл.
К сожалению, спецификации на RTL2832U изначально были закрыты, и даже сейчас информации в Интернете не так много, поэтому остаётся только гадать как именно понижается частота после АЦП. Многие энтузиасты со всего мира пытались проанализировать результат работы тех или иных регистров, и выяснили, что чип может выдавать в USB сэмплы со скоростью от 225Кгц до 3.2Мгц. На практике получалось максимум 2.8Мгц.
Ещё стоит отметить, что вся система работает не с какой-то одной частотой, а с диапазоном частот. Это значит, что приёмник может одновременно получать сигналы с различных устройств. Это активно используется в sdr-server.
airspy mini
В отличие от RTL-SDR, в airspy mini вместо RTL2832U используется полноценный микроконтроллер - LPC4370.
Самое интересное в нём - это два блока:
- ADCHS - 12-битный АЦП, который может работать на частоте до 80Мгц!
- CGU, Clock Generation Unit. Он позволяет на уровне конфигурации прошивки связывать разные источники частоты с потребителями. Схематически он выглядит следующим образом:
CGU позволяет контролировать совершенно разные часы процессора. Начиная от скорости работы I2C интерфейса, а заканчивая собственной инициализацией. Я раньше не работал с полноценными ARM ядрами, и поэтому такая гибкость меня крайне удивила. Даже в ESP32, на котором основан мой другой проект r2lora, контролирование частот спрятано внутри фреймворка.
Внутри CGU есть множество разных блоков, которые могут быть логически соединены друг с другом. Например, в airspy mini используется источник частоты GP_CLKIN (24Мгц), который далее может быть подсоединён к:
- PLL, чтобы получить дробное изменение частоты
- делитель IDIVB, когда нужно получить целочисленное изменение частоты. Например, с 24Мгц получить 3Мгц.
- соединение напрямую, когда нужно получить максимальную скорость сэмплирования и избежать шумов PLL
Так как один такт АЦП будет создавать 1 сэмпл, то контролируя скорость работы АЦП, можно получить необходимую скорость сэмплирования. Всё просто.
Методом пристального вглядывания в скриншоты платы, я не смог понять используется ли термокомпенсированный генератор (TXCO) частоты. Судя по вот этой фотографии, там есть что-то похожее на внешний генератор:
На официальном сайте ничего про это не написано, но независимые исследователи пишут, что генератор хороший и ppm низкий.
Пример конфигурации
Если вооружиться всем этим знанием, то можно добавить допонительные скорости сэмплирования в конфигурацию. Например, конфигурация 2.4MSPS выглядит так:
{
/*
airspy_sys_samplerate_t airspy_m4_conf
*/
{
/* PLL0AUDIO */
0x00000000, // uint32_t pll0audio_mdiv;
0x00000000, // uint32_t pll0audio_npdiv;
0x00000000, // uint32_t pll0audio_ctrl_flags; DirectI=PLL0AUDIO_CTRL_FLAG_DIRECT_I or/and DirectO=PLL0AUDIO_CTRL_FLAG_DIRECT_O */
/* IDIVB not used set it to 0 */
4, // uint8_t adchs_idivb; /* 0 to 15 (0 means direct connection GP_CLKIN to ADCHS_CLK) */
{ 0, 0, 0 } /* uint8_t padding[3] */
},
/* airspy_m0_conf_t airspy_m0_conf */
{
1200000, // Freq 4.8MHz => 2.4MHz IQ => IF Freq = 1.2MHz (r820t_if_freq)
16, // uint8_t r820t_bw;
0, // uint8_t padding0;
0 // uint16_t padding1;
}
}