![](https://habrastorage.org/web/960/188/51a/96018851aabc486e8964e3ae9653c1c8.jpg)
Так иногда бывает, что занимаешься одной технической проблемой, но, по мере погружения в задачу и во время поиска ее решения, появляются «побочные продукты». Так случилось и в этот раз. Я исследовал различные методы измерения временных интервалов с помощью ПЛИС. В одном из предложенных методов измерений был использован динамический сдвиг фазы тактовой частоты с PLL. Позже пришла идея: используя свойства PLL можно попробовать сделать в ПЛИС простейший радиопередатчик с фазовой модуляцией.
И кое-что получилось!
Пожалуй нужно немного рассказать, что такое PLL. PLL (Phase Locked Loop) — это устройство фазовой автоподстройки частоты генератора. Выглядит примерно вот так:
![](https://habrastorage.org/web/907/d44/ce2/907d44ce23954ae1aa0f7f427d59faf9.png)
Здесь есть ГУН — Генератор, Управляемый Напряжением. Он выдает желаемую частоту, которая через обратную связь идет на фазовый детектор. Фазовый детектор определяет разность фаз между опорной частотой F0 и получившейся частотой F1, разность фаз — это сигнал ошибки, который отфильтровывается и воздействует на ГУН, заставляя его колебаться чуть быстрее или чуть медленнее. Так, на выходе ФАПЧ получается частота, синхронная с опорной.
В микросхемах ПЛИС, например, от компании Интел (эх… когда-то была Альтера) серии MAX10 есть встроенный PLL, который выглядит вот так:
![](https://habrastorage.org/web/100/5fc/707/1005fc707ed04f0384ce752c5b942583.png)
Кажется, что это что-то гораздо более сложное, чем то, что изображено выше. Но нет, если присмотреться внимательно, то видны общие черты: ГУН, Генератор Управляемый Напряжением — это VCO, Voltage Controlled Oscilator. PFD — это фазовый детектор, LF — Loop Filter, фильтр фазовой ошибки.
Кроме всего прочего, PLL внутри ПЛИС имеет набор счетчиков делителей. Например, делитель частоты M в цепи обратной связи позволяет получить на выходе PLL частоту в несколько раз выше, чем опорная. Так же имеются выходные счетчики C0-C4, которые позволяют на пяти выходах PLL получить сетку частот с разными делителями.
Есть внутри PLL еще компоненты, которые почему-то обычно не изображаются на структурных схемах в документации Altera/Intel — это схемы управляющие перезагрузкой и перенастройкой PLL. Логические схемы в ПЛИС могут на лету перезагружать коэффициенты счетчиков делителей в PLL и еще они позволяют сдвигать фазу выходных частот PLL. Конечно, перезагрузить на лету параметры PLL — это не очень простая операция, которая к тому же занимает определенное время. А вот сдвигать фазу выходной частоты PLL можно довольно просто и быстро. Причем, разрешающая способность по фазе напрямую зависит от частоты Fvco. Сдвигать фронт тактовой частоты можно на 1/8 периода Fvco. Например, входная частота на PLL Fin=100МГц, а делитель M в цепи обратной связи к фазовому детектору равен 13-ти. Тогда Fvco=1300МГц, а разрешение по фазе для выходной частоты PLL Fout=100МГц будет составлять всего 3,46 градуса.
Для разработки проекта для ПЛИС Altera/Intel используется среда САПР Quartus Prime и в ней есть средства настройки экземпляров PLL: Megawizard Plug-In Manager. С его помощью можно устанавливать нужные свойства PLL:
![](https://habrastorage.org/web/fb0/f01/7a7/fb0f017a7f4d43f3a473af40ab00254e.png)
Здесь как раз и видно какие получаются Fvco и разрешение по фазе для выходных частот.
Для управления фазой выходных частот у компонента PLL есть дополнительные сигналы: SCANCLK, PHASESTEP, PHASEUPDOWN, PHASECOUNTERSELECT, PHASEDONE.
В документации Altera/Intel написано, как управлять этими сигналами, чтобы получить единичный сдвиг фазы на избранной выходной частоте.
![](https://habrastorage.org/web/ce7/037/42f/ce703742f33743d2bd5ecd2cd35c4c08.png)
Получается так: сигнал PHASEUPDOWN определяет в какую сторону нужно двигать фазу. PHASECOUNTERSELECT определяет сигнал какой именно тактовой частоты PLL будет сдвинут (например, если нужно сдвинуть частоту c1, то PHASECOUNTERSELECT=3'b011 — это есть в документации). Цикл сдвига фазы начинается с установки сигнала PHASESTEP и заканчивается, когда PLL выставит в ноль PHASEDONE. Если нужно сдвинуть фазу значительно, то придется выполнить несколько таких циклов. Все это при желании можно даже просимулировать в ModelSim, как это делается я писал вот здесь.
Теперь, дальше — интереснее. Плавно перехожу к моему «радиопередатчику»:
![](https://habrastorage.org/web/d89/4b9/c34/d894b9c3434e4647a92e007ef09f2399.jpg)
Я использую плату Марсоход3bis на чипе ПЛИС Altera/Intel MAX10. Плата имеет встроенный программатор на базе двухканального чипа FTDI. Причем один канал FT2232HL используется под JTAG (загрузка ПЛИС, отладка в SignalTap), а второй канал используется для передачи данных в плату, как последовательный порт.
К плате, прямо к двум цифровым пинам-выходам подключены два куска провода по 0,75 метра. Это антенна, «полуволновый вибратор». Без выходного аналогового фильтра на излучаемый диапазон, конечно, не хорошо, но сама антенна уже какой-то фильтр, да и мощность передатчика невелика…
Проект написан на языке Verilog HDL — всего-то пара десятков строк кода:
module top(
input wire CLK100MHZ,
input wire key0,
input wire key1,
input wire FTDI_BD0, //serial RX line
output wire [19:0]io
);
wire wc0;
wire wc1;
wire wlocked;
wire wpdone;
wire up_down;
reg pstep;
wire scanclk; assign scanclk = wc0;
reg [7:0]cnt8;
always @( posedge scanclk )
cnt8 <= cnt8 + 8'h01;
mypll mypll_ (
.areset( ~key0 ),
.inclk0(CLK100MHZ),
.phasecounterselect( 3'b011 ),
.phasestep( pstep ),
.phaseupdown( up_down ),
.scanclk(scanclk),
.c0(wc0),
.c1(wc1),
.locked(wlocked),
.phasedone( wpdone )
);
wire [7:0]w_rx_byte;
wire w_byte_ready;
reg [1:0]byte_rdy;
always @( posedge wc0 )
byte_rdy <= {byte_rdy[0],w_byte_ready};
serial receiver(
.reset( ~wlocked ),
.clk100( wc0 ), //100MHz
.rx( FTDI_BD0 ),
.sbyte( 8'h00 ),
.send( 1'b0 ),
.rx_byte( w_rx_byte ),
.rbyte_ready( w_byte_ready ),
.tx(),
.busy(),
.rb()
);
reg [7:0]current_pll_phase = 0;
wire [7:0]signal; assign signal = w_rx_byte[7:0];
assign up_down = signal>current_pll_phase;
reg [3:0]state = 0;
always @( negedge scanclk )
begin
case(state)
0: begin
//wait recv byte
if( byte_rdy ) state <= 1;
end
1: begin
//do we really need to change phase?
if( current_pll_phase==signal ) state <= 0;
else state <= 2;
end
2: begin
//wait phase done
if( ~wpdone ) state <= 3;
end
3: begin
state <= 4;
end
4: begin
state <= 1;
end
endcase
if( pstep && (~wpdone) )
if( up_down )
current_pll_phase <= current_pll_phase + 6'h1;
else
current_pll_phase <= current_pll_phase - 6'h1;
if( ~wpdone )
pstep <= 1'b0;
else
if( state==2 )
pstep <= 1'b1;
end
assign io[17:0] = 0;
assign io[18] = wc1;
assign io[19] = ~wc1;
endmodule
Весь проект для САПР Intel Quartus Prime можно взять на GitHub: github.com/marsohod4you/Fpga-PM-Radio.
В проекте есть PLL с двумя выходами c0 и c1, на каждом из них 100МГц. Выход c0 используется для тактирования всей схемы, а вот выход c1 — это и есть несущая 100МГц, частота моего «радиопередатчика» (FM-диапазон). Ее я и собираюсь модулировать по фазе.
Я собираюсь посылать сырой аудиофайл с компьютера в плату через последовательный порт. При формате данных 8 бит на одну аудиовыборку, моно, 22050Гц, наиболее удобна скорость последовательного порта 230400 бод. В самом деле, каждый байт при последовательной передаче имеет старт бит и один или два стоп бита. Если два стоп бита, то получается 11 передаваемых бит на байт данных. Значит 230400/11=20945 выборок в секунду. Это конечно не идеал, не 22050, но чуть-чуть похоже. Ну получится у меня звук немного ниже, чем нужно, мне для моих экспериментов не важно… Итак в проекте есть приемник последовательных данных.
Полученный из последовательного порта байт w_rx_byte внутри ПЛИС сравнивается с внутренней переменной (регистром) current_pll_phase. Разница меж ними — это и есть количество шагов по сдвигу фазы, которые нужно сделать в одну или другую сторону. Таким образом выполняется фазовая модуляция на выходе c1 у PLL.
Теперь второй вопрос. Предположим, что передатчик с фазовой модуляцией работает, а чем слушать?
Я исхожу из того, что ФМ и ЧМ непосредственно связаны друг с другом интегральным/дифференциальным законом. Мгновенная частота радиосигнала — это производная от его фазы. Производная синуса — это косинус. Вряд ли слушатель радиопередачи отличит их на слух (шутка). В общем, я решил попробовать передавать аудио файл моим «радиопередатчиком» с фазовой модуляцией, а слушать мою «радиопередачу» обычным FM-приемником. Я использую мобильный телефон с гарнитурой в качестве FM приемника.
Вот как это выглядит:
Удивительно, но передаваемая мной музыка устойчиво слышна в FM радиоприемнике мобильного телефона на расстоянии до 10-15 метров.
PS: В России 7 мая отмечается День радио. В 1895 году выдающийся русский физик и изобретатель Александр Попов в Санкт-Петербургском университете продемонстрировал созданную им первую в мире искровую беспроводную приемо-передающую радиосистему. В России этот факт был принят за точку отсчета начала радиосвязи.