Генератор синуса и коротковолновый радиопередатчик

от автора

В этой статье описан способ генерации синусоидального сигнала на ПЛИС через использование 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. Всё это доступно на схемах:

Рис 1. Схема для T-FPGA

Рис 1. Схема для T-FPGA
//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.

Рис. 2. IP Core Generetor

Рис. 2. IP Core Generetor

По двойному щелчку по pROM откроется диалоговое окно, в котором указываются необходимые параметры. В примере использованы 4096 12-битных отсчётов.

Необходимо обратить внимание на то, что ЦАП DAC904 14-битный и для него необходимо использовать 14-битные отсчёты, но лень.

Рис. 3. Параметры для экземпляра ROM памяти.

Рис. 3. Параметры для экземпляра ROM памяти.

Процедура генерации синусоидального сигнала в 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.

Рис. 4. Интерфейс matlab и построенный график синуса

Рис. 4. Интерфейс matlab и построенный график синуса

Для использования файла коэффициентов в качестве 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. Необходимо только вручную удалить запятую в самом конце файла:

Рис. 5. При автоматическом удалении запятых из файла коэффициентов последнюю запятую необходимо удалить вручную.

Рис. 5. При автоматическом удалении запятых из файла коэффициентов последнюю запятую необходимо удалить вручную.

Теперь файл готов для использования в качестве Memory Initialization File в диалоговом окне IP Customization pROM ide GOWIN FPGA Designer. GOWIN FPGA Designer предложит добавить сгенерированные файлы в текущий проект. Остаётся только согласиться:

Рис. 6. Добавление сгенерированного IP в проект.

Рис. 6. Добавление сгенерированного IP в проект.

Вернёмся к генератору синуса. Создадим ещё один 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 как в этой статье, то получится такое изображение:

Рис. 7. Изображение сигнала в Gtkwave

Рис. 7. Изображение сигнала в Gtkwave

На изображении сигнал с частотой 2500 КГц. Если воспользоваться формулой в комментарии на строке 8 в модуле dds_addr Fout = B * (Fclk / 2**32), и подставить значение 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. — прим. Авт.).

Рис. 8. Провода для питания отладочной платы ЦАП.

Рис. 8. Провода для питания отладочной платы ЦАП.

Данный ЦАП на отладочной плате допускает подключение питания и логики 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-омным, поэтому посмотреть его обычным щупом осциллографа не получится. С помощью специального щупа на выходе можно увидеть такой сигнал:

Рис. 9. Сигнал на высокочастотном выходе ЦАП. FWORD =  397682157

Рис. 9. Сигнал на высокочастотном выходе ЦАП. FWORD = 397682157

Чтобы изменить частоту в модуль dds_addr_inst (подключён в модуле top. — прим. Авт.) необходимо передать другое значение. Например, на выходе необходима частота 5 МГц. Из формулы Fout = B * (Fclk / 2**32)необходимо вывести неизвестную B = Fout * (2**32/Fclk). 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) );   //---------------------------------------------------------- // ...

И получим вот такой сигнал:

Рис. 10. Сигнал на высокочастотном выходе ЦАП. FWORD =   795364314

Рис. 10. Сигнал на высокочастотном выходе ЦАП. FWORD = 795364314

Сигнал уже не такой «красивый», но его частота 5 МГц.

Скрытый текст

Дальнейшее увеличение частоты делает изображение ещё более непривлекательным:

Рис. 11. Сигнал на высокочастотном выходе ЦАП. FWORD = 1590728628

Рис. 11. Сигнал на высокочастотном выходе ЦАП. FWORD = 1590728628

А уменьшение частоты снова приводит к красивым картинкам:

Рис. 12. Сигнал на высокочастотном выходе ЦАП. FWORD = 198841078

Рис. 12. Сигнал на высокочастотном выходе ЦАП. FWORD = 198841078

Получается, что, изменяя значение 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:

Рис. 13. Интерфейс программы MultiPSK и результат декодирования радиопередачи

Рис. 13. Интерфейс программы MultiPSK и результат декодирования радиопередачи

На этом всё. Такой генератор синуса использовался на FPGA Xilinx для отладки софта, который отвечал за передачу на компьютер принятого тестового сигнала радиочастотной микросхемой. Чтобы понять, что микросхема сконфигурирована правильно и работает корректно на приём, необходимо было визуализировать сигнал, который она приняла. А для этого его необходимо было передать на компьютер. И когда было непонятно, а что именно не работает — микросхема на приём или канал передачи данных, генератор синуса очень помог, потому что был использован в качестве заведомого рабочего источника сигнала. Ещё генератор синуса очень пригодился, когда отлаживался канал передачи данных из ПЛИС Xilinx через USB-to-FIFO микросхему на компьютер. Имея заведомо рабочий генератор сигнала, можно с высокой вероятностью утверждать работает канал передачи данных или не работает. В этот раз генератор помог с передачей информации в КВ диапазоне. Хочется добавить, что подобным образом запросто можно реализовать передачу сигналов азбуки Морзе, FT8 и ещё многих других видов сигналов и модуляций. Причём имея радиолюбительский позывной, качественную антенну, усилитель и фильтр, удаляющий паразитные (вторые, третьи и т.д.) гармоники и зеркальный канал, можно передавать свой сигнал на очень существенные расстояния, в основном ночью, но это уже другая история.

Спасибо.

С. Н.


ссылка на оригинал статьи https://habr.com/ru/articles/862842/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *