В этой статье описан способ генерации синусоидального сигнала на ПЛИС через использование ROM памяти и реальный пример практического применения этого генератора для коротковолнового радиопередатчика RTTY (Radioteletype. — прим. Ред.). Будет описан способ передачи значения частоты из микроконтроллера в ПЛИС через SPI (англ. Serial Peripheral Interface, SPI bus — последовательный периферийный интерфейс, шина SPI — прим. Ред.). Используются отладочная плата LilyGO T-FPGA, в составе которой ПЛИС GW1NSR-LV4CQN48PC6/I5 и микроконтроллер ESP32-S3, ЦАП на основе DAC904, ide GOWIN FPGA Designer, Visual Studio Code с расширением PlatformIO и matlab 2020.
Дисклеймер
Туториал описывает процесс радиопередачи, поэтому прежде чем шалить, ознакомьтесь с нормативно-правовой базой государства, на территории которого предполагается шалость, получите радиопозывной и избавьтесь от паразитных гармоник.Здесь будут описаны конкретный опыт за последние пару дней и несколько экспериментов с отладочными платами. Это не опыт и эксперименты профессионального разработчика под ПЛИС и микроконтроллеры. Может быть, это прочитают специалисты, которые хорошо разбираются в темах, которые затронуты и дадут конструктивную критику.
Генератор синуса
При реализации этого генератора можно отталкиваться от этой статьи (ссылка работает через раз. С рабочего компьютера не открывается, а с домашнего открывается. — прим. Ред.). Большую помощь в освоении плис GOWIN может оказать блог Марсоход. В качестве исходного проекта для ПЛИС можно использовать пример для T-FPGA. Но необходимо обратить внимание на Constraints файл. В данном примере сигнал clk подключён к кнопке (46 номер на схеме). Для работы от кварца необходимо подключиться к номеру 45. Всё это доступно на схемах:
//Copyright (C)2014-2021 Gowin Semiconductor Corporation. //All rights reserved. //File Title: Physical Constraints file //GOWIN Version: 1.9.8.01 //Part Number: GW1NSR-LV4CQN48PC6/I5 //Device: GW1NSR-4C //Created Time: Tue 02 21 14:47:43 2023 IO_LOC "clk" 45; // это выход кварца 27 МГц IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP;
Для создания ROM памяти необходимо вызвать меню tools -> IP Core Generator или нажать иконку на панели инструментов. Дальше открыть Memory -> Block Memory -> pROM.
По двойному щелчку по pROM откроется диалоговое окно, в котором указываются необходимые параметры. В примере использованы 4096 12-битных отсчётов.
Необходимо обратить внимание на то, что ЦАП DAC904 14-битный и для него необходимо использовать 14-битные отсчёты, но лень.
Процедура генерации синусоидального сигнала в matlab
Стоит отдельно остановиться на Memory Initialization File. Наверняка существует немало способов сформировать .mi файл. Про него подробно написано в 7 главе UG285E. Здесь же предлагается пройти по пути генерации файла в matlab, а потом привести его к необходимому виду с помощью скрипта на python.
В matlab необходимо открыть скрипт содержащий код:
clear clc n = 0:4095 ; yn = sin(2*pi/4096*n) ; yn = round((yn+1)*2047); plot(n,yn); fid = fopen('C:\work\rom_test_3.coe','wt'); fprintf(fid,'#File_format=Hex,\n#Address_depth=4096,\n#Data_width=12,'); for i = 1 : 4096 if mod(i-1,1) == 0 fprintf(fid,'\n'); end fprintf(fid,'%03X,',yn(i)); end
При запуске этого скрипта будет построен график и в указанном каталоге появится файл коэффициентов .coe.
Для использования файла коэффициентов в качестве Memory Initialization File для pROM GOWIN необходимо избавиться от запятых в конце каждой строки. Для этого можно воспользоваться скриптом на python:
import os import sys res = '' if len(sys.argv)<2 : print("Not enough arguments, need file with data name param") filename = sys.argv[1] print(filename) def read_txt_file(filename): output = "" # инициализация with open(filename, 'r') as f: for line in f: output = output + line.replace(',\n', '\n') # strip() # rstrip(",\n") f.close() return output def write_txt_file(input): with open('1.mi', 'w') as file: file.write(input) # перезапись файла res = read_txt_file(filename) write_txt_file(res)
Если скрипт на python лежит в том же самом каталоге, что и файл коэффициентов сгенерированный в matlab и в командной строке написать C:\Users\s.novikov\Documents\work\271124>python3 probe1.py rom_test_3.coe
, то в рабочем каталоге появится файл 1.mi. Необходимо только вручную удалить запятую в самом конце файла:
Теперь файл готов для использования в качестве Memory Initialization File в диалоговом окне IP Customization pROM ide GOWIN FPGA Designer. GOWIN FPGA Designer предложит добавить сгенерированные файлы в текущий проект. Остаётся только согласиться:
Вернёмся к генератору синуса. Создадим ещё один IP:
module dds_addr (clk, rst_n, addr_out, strobe, FWORD); input clk, rst_n; // Resetting the system clock output [11: 0] addr_out; // The output address corresponding to the data in the ROM output strobe; parameter N = 32; parameter PWORD = 2048; // Phase control word (x/360) * 256 input [31:0] FWORD; // parameter FWORD = 159072862; // слово управления частотой F_out = B * (F_clk / 2 ** 32), fword = B 5KHZ // 858994 reg [N-1: 0] addr; // 32-bit battery reg strobe_r; always @ (posedge clk or negedge rst_n) begin if (!rst_n) begin addr <= 0; end else begin //Each word size outputs an address, if the word control frequency is 2, then the output of the address counter is 0, 2, 4... addr <= addr + FWORD; if (addr[N-1:N-12] + PWORD == 12'hc00) begin strobe_r <= 1'b1; end else begin strobe_r <= 1'b0; end end end //Assign the top eight bits of the battery address to the output address (ROM address assign addr_out = addr[N-1:N-12] + PWORD; assign strobe = strobe_r; endmodule
От оригинального кода из статьи (ссылка работает через раз. С рабочего компьютера не открывается, а с домашнего открывается. — прим. Ред.) этот код отличается заменой параметра FWORD на входной 32-битный сигнал FWORD. Это сделано для того, чтобы менять частоту в проекте коротковолнового передатчика. Теперь эти два IP можно включить в модуль для генерации синусоидального сигнала:
module top( input clk, input rst, output [11: 0] sin, output clk_o // это выход для сигнала тактирования ЦАП ); reg [31:0] fword; wire [11: 0] addr_out; // 12-битный адрес, соответствующий данным в ПЗУ wire [11: 0] sin_out; // --------------Phase-based module------------------------ dds_addr dds_addr_inst ( .clk(clk), // input wire clk .rst_n(1'b1), // input wire rst_n .addr_out(addr_out), // output wire [7 : 0] addr_out .strobe(), .FWORD(397682157) ); //---------------------------------------------------------- // Waveform Data Module Gowin_pROM rom_inst ( .dout(sin), //output [11:0] dout .clk(clk), //input clk .oce(), //input oce .ce(1'b1), //input ce .reset(1'b0), //input reset .ad(addr_out) //input [11:0] ad ); // assign clk_o = clk; это необходимо раскомментировать при подключении ЦАП к T-FPGA endmodule
Чтобы посмотреть на сгенерированный сигнал, можно воспользоваться Gowin Analyzer Oscilloscope. Это встроенный в ide инструмент для записи выборок и просмотра осциллограмм сигналов как SignalTap у Altera или ChipScope у Xilinx. Подробнее про использование этого инструмента можно почитать на Marsohod. Если проделать все шаги, которые написаны в статье про Использование Gowin Analyzer Osciloscope в FPGA проекте и открыть это в Gtkwave как в этой статье, то получится такое изображение:
На изображении сигнал с частотой 2500 КГц. Если воспользоваться формулой в комментарии на строке 8 в модуле dds_addr , и подставить значение FWORD = 397682157, то получится, что F_out = 2500000 Гц. Чем больше будет значение частоты выходного сигнала, тем безобразнее будет выглядеть изображение сигнала, потому что выбрана частота тактирования всего 27 МГц. Это будет хорошо видно, если увеличивать частоту и смотреть на сигнал.
Промежуточный эксперимент. Генерация сигнала на ЦАП
Для последующих экспериментов был подключён ЦАП на основе DAC904. Чтобы его добавить в проект, потребуется только выход для сигнала тактирования ЦАП и раскомментировать assign для этого выхода, который приравнивается к входному тактовому сигналу. (ЦАП 14-битный, а сигнал формируется 12-битный, подключены младшие биты. — прим. Авт.). В файле constraints потребуется сделать необходимые назначения, например:
//Copyright (C)2014-2021 Gowin Semiconductor Corporation. //All rights reserved. //File Title: Physical Constraints file //GOWIN Version: 1.9.8.01 //Part Number: GW1NSR-LV4CQN48PC6/I5 //Device: GW1NSR-4C //Created Time: Tue 02 21 14:47:43 2023 IO_LOC "clk" 45; IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP; IO_LOC "clk_o" 29; // 23 IO_PORT "clk_o" IO_TYPE=LVCMOS33 PULL_MODE=UP; IO_LOC "sin[0]" 20; IO_PORT "sin[0]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[1]" 21; IO_PORT "sin[1]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[2]" 18; IO_PORT "sin[2]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[3]" 19; IO_PORT "sin[3]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[4]" 16; IO_PORT "sin[4]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[5]" 17; IO_PORT "sin[5]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[6]" 13; IO_PORT "sin[6]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[7]" 14; IO_PORT "sin[7]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[8]" 34; IO_PORT "sin[8]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[9]" 35; IO_PORT "sin[9]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[10]" 31; IO_PORT "sin[10]" IO_TYPE=LVCMOS33 PULL_MODE=NONE; IO_LOC "sin[11]" 32; IO_PORT "sin[11]" IO_TYPE=LVCMOS33 PULL_MODE=NONE;
В экспериментах в качестве ЦАП была использована отладочная плата. Она была запитана от T-FPGA (на фото с обложки видно два фиолетовых провода, которые подключены к гребёнке 3.3V T-FPGA. — прим. Авт.).
Данный ЦАП на отладочной плате допускает подключение питания и логики 3.3 В. Чтобы изменить напряжение на I/O пинах FPGA, можно воспользоваться примером LilyGO.
При клонировании репозитория для автоматической настройки PlatformIO необходимо изменить версию библиотеки «arduino-esp32» на 2.0.6 иначе проект не настраивался автоматически
platform_packages = framework-arduinoespressif32@https://github.com/espressif/arduino-esp32.git#2.0.6
Для использования напряжения на I/O FPGA 3,3 В необходимо изменить параметры, которые передаются в методы setALDO3Voltage()
и setALDO4Voltage()
на 3300:
Скрытый текст
#include "Arduino.h" #include "Wire.h" #include "XPowersLib.h" //https://github.com/lewisxhe/XPowersLib #include "pins_config.h" XPowersAXP2101 PMU; void led_task(void *param); void setup() { Serial.begin(115200); Serial.println("Hello T-FPGA-CORE"); xTaskCreatePinnedToCore(led_task, "led_task", 1024, NULL, 1, NULL, 1); bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_IIC_SDA, PIN_IIC_SCL); if (result == false) { Serial.println("PMU is not online..."); while (1) delay(50); } PMU.setDC4Voltage(1200); // Here is the FPGA core voltage. Careful review of the manual is required before modification. PMU.setALDO1Voltage(3300); // BANK0 area voltage PMU.setALDO2Voltage(3300); // BANK1 area voltage PMU.setALDO3Voltage(3300); // BANK2 area voltage PMU.setALDO4Voltage(3300); // BANK3 area voltage PMU.enableALDO1(); PMU.enableALDO2(); PMU.enableALDO3(); PMU.enableALDO4(); } void loop() { PMU.setChargingLedMode(XPOWERS_CHG_LED_ON); delay(20); PMU.setChargingLedMode(XPOWERS_CHG_LED_OFF); delay(random(300, 980)); } void led_task(void *param) { pinMode(PIN_LED, OUTPUT); while (true) { digitalWrite(PIN_LED, 1); delay(20); digitalWrite(PIN_LED, 0); delay(random(300, 980)); } }
Этим исходным кодом необходимо запрограммировать ESP32-S3 до подключения ЦАП.
Высокочастотный выход используемого ЦАП является 50-омным, поэтому посмотреть его обычным щупом осциллографа не получится. С помощью специального щупа на выходе можно увидеть такой сигнал:
Чтобы изменить частоту в модуль dds_addr_inst (подключён в модуле top. — прим. Авт.) необходимо передать другое значение. Например, на выходе необходима частота 5 МГц. Из формулы необходимо вывести неизвестную . 5000000 * 4294967296 / 27000000 = 795364314. Укажем это значение в подключаемом экземпляре:
// ... это кусок кода из top модуля // --------------Phase-based module------------------------ dds_addr dds_addr_inst ( .clk(clk), // input wire clk .rst_n(1'b1), // input wire rst_n .addr_out(addr_out), // output wire [7 : 0] addr_out .strobe(), .FWORD(795364314) ); //---------------------------------------------------------- // ...
И получим вот такой сигнал:
Сигнал уже не такой «красивый», но его частота 5 МГц.
Скрытый текст
Дальнейшее увеличение частоты делает изображение ещё более непривлекательным:
А уменьшение частоты снова приводит к красивым картинкам:
Получается, что, изменяя значение FWORD, можно управлять частотой. Сигнал хорошо слышно в приёмнике, который стоит рядом на столе, несмотря на плохие изображения сигнала на осциллографе.
Коротковолновый радиопередатчик на основе описанного генератора синуса
Для управления частотой можно использовать микроконтроллер ESP32-S3 на плате T-FPGA. Для создания передатчика можно воспользоваться этим исходным кодом. В этом проекте изменяется частота аппаратного DDS генератора сигналов. Чтобы сделать передатчик на основе описанного в этой статье генератора, необходимо считать значение FWORD и передавать его в ПЛИС. В репозитории T-FPGA есть примеры для ESP32-S3 и FPGA, в которых мигание светодиода, подключённого к FPGA, управляется из микроконтроллера. Из ESP32 в FPGA по SPI передаётся 8-битное значение, в зависимости от которого светодиод либо включается, либо выключается.
В случае управления частотой генератора из этой статьи необходимо передавать 32-битное значение. Поэтому предлагается разбить вычисленное на ESP32-S3 32-битное значение на 4 части по 8 бит, передать их по SPI в ПЛИС, а там собрать и отправить в dds_addr_inst.
По аналогии с исходным кодом создана функция send_frequency(uint32_t &freq)
:
void send_frequency(uint32_t &freq){ uint32_t fword; uint64_t tmp; tmp = (uint64_t)freq*(uint64_t)4294967296; fword = tmp / (uint32_t)27000000; uint8_t buff[4]; buff[0] = fword & 0xff; buff[1] = fword >> 8 & 0xff; buff[2] = fword >> 16 & 0xff; buff[3] = fword >> 24 & 0xff; // старший fpga_spi_blink(true); for(uint8_t i=0;i<4;++i){ digitalWrite(PIN_FPGA_CS, 0); SPI.beginTransaction(SPISettings(1000000, SPI_MSBFIRST, SPI_MODE3)); uint8_t fpga_output = SPI.transfer(buff[i]); SPI.endTransaction(); digitalWrite(PIN_FPGA_CS, 1); } }
На 14 строке передаётся значение 0x01 для синхронизации посылки. Тогда для приёма посылки на стороне FPGA можно использовать небольшой конечный автомат:
always@(posedge clk)begin if(ready_fword == 1'b1) // если приняли все 4 части fword_valid = fword; // переписываем в регистр конечный результат end reg [3:0] state_reg; always@(posedge rxd_flag or negedge rst)begin if(!rst) led<=1'b0; else if(rxd_out==8'h01) // значение 0x01 для синхронизации посылки begin ready_fword <= 1'b0; state_reg <= 0; fword <= 0; end else begin case(state_reg) 4'd0: begin fword <= fword + rxd_out; state_reg <= 1; end 4'd1: begin fword <= fword + (rxd_out << 8); state_reg <= 2; end 4'd2: begin fword <= fword + (rxd_out << 16); state_reg <= 3; end 4'd3: begin fword <= fword + (rxd_out << 24); state_reg <= 0; ready_fword <= 1'b1; end default: begin fword <= 0; state_reg <= 0; ready_fword <= 1'b0; end endcase end end
Весь исходный код проекта доступен на Github. При программировании ESP32 иногда возникает необходимость повторного программирования FPGA. Теперь, если подключить к высокочастотному разъёму ЦАП 50-омную антенну (антенны для КВ достигают в размерах 160-ти метров. Для эксперимента на столе подойдёт даже обычная телескопическая антенна, которую можно даже и не выдвигать. — прим. Авт.) и поставить на столе рядом с передатчиком радиоприёмник, то в динамик будет слышен специфический звук RTTY:
Для декодирования RTTY можно использовать программу MultiPSK:
На этом всё. Такой генератор синуса использовался на FPGA Xilinx для отладки софта, который отвечал за передачу на компьютер принятого тестового сигнала радиочастотной микросхемой. Чтобы понять, что микросхема сконфигурирована правильно и работает корректно на приём, необходимо было визуализировать сигнал, который она приняла. А для этого его необходимо было передать на компьютер. И когда было непонятно, а что именно не работает — микросхема на приём или канал передачи данных, генератор синуса очень помог, потому что был использован в качестве заведомого рабочего источника сигнала. Ещё генератор синуса очень пригодился, когда отлаживался канал передачи данных из ПЛИС Xilinx через USB-to-FIFO микросхему на компьютер. Имея заведомо рабочий генератор сигнала, можно с высокой вероятностью утверждать работает канал передачи данных или не работает. В этот раз генератор помог с передачей информации в КВ диапазоне. Хочется добавить, что подобным образом запросто можно реализовать передачу сигналов азбуки Морзе, FT8 и ещё многих других видов сигналов и модуляций. Причём имея радиолюбительский позывной, качественную антенну, усилитель и фильтр, удаляющий паразитные (вторые, третьи и т.д.) гармоники и зеркальный канал, можно передавать свой сигнал на очень существенные расстояния, в основном ночью, но это уже другая история.
Спасибо.
С. Н.
ссылка на оригинал статьи https://habr.com/ru/articles/862842/
Добавить комментарий