В статье описывается опыт использования ARM ядра, встроенного в ПЛИС GOWIN GW1NSR-4C, в качестве процессора общего назначения для формирования PSK31 сигнала. Сигнал формируется с помощью генератора синуса, который был описан в предыдущей статье. Используются отладочная плата LilyGO T-FPGA, в составе которой ПЛИС GW1NSR-LV4CQN48PC6/I5, ЦАП на основе DAC904, ide GOWIN FPGA Designer и образовательная версия GMD.
Дисклеймер
Туториал описывает процесс радиопередачи, поэтому прежде чем шалить, ознакомьтесь с нормативно-правовой базой государства, на территории которого предполагается шалость, получите радиопозывной и избавьтесь от паразитных гармоник.Здесь будут описаны конкретный опыт за последние пару дней и несколько экспериментов с отладочными платами. Это не опыт и эксперименты профессионального разработчика под ПЛИС и микроконтроллеры. Может быть, это прочитают специалисты, которые хорошо разбираются в затронутых темах и дадут конструктивную критику.
При возникновении вопросов по запуску ARM ядра в ПЛИС GOWIN изучение официального сайта GOWIN приведёт на страницу GITHUB. В файле README достаточно подробно описан процесс запуска ARM ядра в ПЛИС GW1NSR-LV4CQN48PC6/I5 и несколько программ для примера.
Для управления генератором синуса в ПЛИС и формирования модуляции psk можно использовать встроенное ARM ядро Cortex-m3. Чтобы связать логику процессора и логику ПЛИС можно использовать SPI интерфейс. При добавлении в дизайн IP MCU необходимо дополнительно включить SPI шину:
В качестве проверки работы можно использовать пример от LiLyGO, в котором управление миганием светодиода, подключённого к FPGA, осуществляется из микроконтроллера. В оригинальном примере в качестве микроконтроллера выступает ESP32-S3, размещённый на отладочной плате. Для текущего эксперимента предлагается использовать встроенный в ПЛИС Cortex-M3.
На самом деле автором было проделано больше промежуточных экспериментов. Например, при первом запуске Cortex-M3 в ПЛИС было запрограммировано классическое мигание светодиодом прямо из микроконтроллера. Для этого сигнал
led
был включён прямо в экземпляр Gowin_EMPU_Top.gpio(led)
(не выходной регистрoutput reg led
как в примере, а именноoutput led
. Иначе не пройдёт синтез, потому что экземпляр Cortex-M3 не допускает включение регистров в качестве GPIO. — прим. Авт.) .
Промежуточный эксперимент. Cortex-M3 SPI blink
После добавления экземпляра MCU в файл дизайна верхнего уровня необходимо подключить сигналы MCU к соответствующим портам верхнего уровня.
wirecs; wiresck; wireMOSI; wireMISO; wire [7:0]gpio; //--------Copy here to design-------- Gowin_EMPU_Top cortexM3_inst( .sys_clk(clk_60M), //input sys_clk .gpio(gpio[7:0]), //inout [15:0] gpio .uart0_rxd(1'b1), //input uart0_rxd .uart0_txd(UART_TX), //output uart0_txd .mosi(MOSI), //output mosi .miso(MISO), //input miso .sclk(sck), //output sclk .nss(cs), //output nss .reset_n(1'b1) //input reset_n );
Здесь тактовый сигнал — это выход из экземпляра PLL. Работа с этими IP описана и в туториале про использование Cortex-M3 в ПЛИС, и в блоге Marsohod.org. GPIO объявлены как wire, потому что в проекте они не используются. Сигналы SPI объявлены как wire. Микроконтроллер встроен внутрь ПЛИС, поэтому выводить сигналы для связи процессорной логики и логики ПЛИС наружу совсем необязательно.Всё остальное подключено как в оригинальной статье. После объявления constraints и проверки синтеза, имплементации и генерации bitstream на наличие ошибок можно приступить к программированию микроконтроллера. Как и в оригинальной статье будет использована Gowin’s MCU Designer.
Под спойлером полный исходный код для SPI blink со стороны ПЛИС.
Скрытый текст
module led( input clk, inputrst, output reg led, output UART_TX, outputrxd_flag ); wirecs; wiresck; wireMOSI; wireMISO; wire[7:0]rxd_out; //-------------------------------------------- reg [7:0] txd_dat; wire clk_60M; //-------------------------------------------- wire pll_out_clk; //------------------------------------------- wire [7:0]gpio; //--------Copy here to design-------- Gowin_EMPU_Top cortexM3_inst( .sys_clk(clk_60M), //input sys_clk .gpio(gpio[7:0]), //inout [15:0] gpio .uart0_rxd(1'b1), //input uart0_rxd .uart0_txd(UART_TX), //output uart0_txd .mosi(MOSI), //output mosi .miso(MISO), //input miso .sclk(sck), //output sclk .nss(cs), //output nss .reset_n(1'b1) //input reset_n ); //--------Copy end------------------- always@(posedge rxd_flag or negedge rst)begin if(!rst) txd_dat <= 8'b11000011; else begin txd_dat <= rxd_out + 1'b1; //отправить данные +1 отправителю end end always@(posedge rxd_flag or negedge rst)begin if(!rst) led<=1'b0; else if(rxd_out<8'h80) begin led<=1'b1; end else begin led<=1'b0; end end //--------Copy here to design-------- Gowin_PLLVR1 your_instance_name( .clkout(clk_60M), //output clkout .clkin(clk) //input clkin ); //--------Copy end------------------- spi_slaver spi_slaver1( .clk(clk_60M), // clk_30M .rst(rst), .cs(cs), .sck(sck), .MOSI(MOSI), .MISO(MISO), .rxd_out(rxd_out), .txd_data(txd_dat), .rxd_flag(rxd_flag) ); endmodule
Под этим спойлером файл .cst, который можно использовать для SPI blink.
Скрытый текст
//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 "led" 15; IO_PORT "led" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8; IO_LOC "clk" 45; IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP; IO_LOC "rst" 46; IO_PORT "rst" IO_TYPE=LVCMOS33 PULL_MODE=UP; IO_LOC "UART_TX" 22; IO_PORT "UART_TX" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 OPEN_DRAIN=ON; //------------------------------------------------------------- IO_LOC "rxd_flag" 42; IO_PORT "rxd_flag" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8;
Прошивка на языке С в IDE GMD
В оригинальном туториале предлагается ссылка для скачивания Gowin’s MCU Designer. Но у автора не получилось скачать по данной ссылке программу по неизвестной причине. На сайте GOWIN необходимое ПО скачивается без каких-либо проблем.
По аналогии с оригинальным туториалом в комплекте SDK от Gowin для GW1NS(R)-4C можно обнаружить пример для работы с SPI. Отличием от оригинала является другая тактовая частота работы микроконтроллера. В настоящей статье предлагается использовать 60 МГц. Эта частота была настроена в экземпляре PLL (.clkout(clk_60M), //output clkout
. — прим. Авт.) и подключена в экземпляр MCU через wire clk_60M;
. Именно эту частоту необходимо будет указать в файле lib/CMSIS/DeviceSupport/system/system_gw1ns4c.c
.
#define __SYSTEM_CLOCK (60000000UL) /* 60MHz */
В туториале указано, что ядро микроконтроллера может тактироваться и от 80 МГц, но в качестве опорного тактового генератора на отладочной плате T-FPGA выбран кварц с частотой 27 МГц. В примерах для работы с SPI есть делители на 8, 6, 4, 3 и 2, но IP generator PLL не может подобрать делители для 80 МГц. Зато может подобрать для 60 МГц из 27 МГц и тогда для SPI можно использовать делитель 6.
Теперь, если скопировать код под спойлером в main.c, светодиод, который подключён к логике ПЛИС, начнёт мигать под управлением Cortex-M3. Как и в оригинальном примере через SPI поочерёдно отправляется значение 0x01 или 0x81 и в зависимости от этого логика FPGA переключает состояние светодиода.
Скрытый текст
/* * ***************************************************************************************** * * Copyright (C) 2014-2021 Gowin Semiconductor Technology Co.,Ltd. * * @filemain.c * @authorEmbedded Development Team * @versionV1.x.x * @date2021-01-01 09:00:00 * @briefMain program body. ****************************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "gw1ns4c.h" #include <stdio.h> /* Includes ------------------------------------------------------------------*/ void SPIInit(void); void initializeUART(); void initializeTimer(); void delayMillis(uint32_t ms); /* Functions ------------------------------------------------------------------*/ int main(void) { SystemInit();//Initializes system SPIInit();//Initializes SPI initializeUART(); initializeTimer(); SPI_Select_Slave(0x01) ;//Select The SPI Slave SPI_WriteData(0x01);//Send Jedec printf("init complete\r\n"); uint32_t counter = 0; while(1) { counter++; printf("GowinFPGA says hi! Count: %d\r\n", counter); if(~SPI_GetToeStatus() && SPI_GetTrdyStatus() == 1) { SPI_WriteData(0x81); } delayMillis(500); if(~SPI_GetToeStatus() && SPI_GetTrdyStatus() == 1) { SPI_WriteData(0x01); } delayMillis(500); } } //Initializes SPI void SPIInit(void) { SPI_InitTypeDef init_spi; init_spi.CLKSEL= CLKSEL_CLK_DIV_6;//60MHZ / 6 init_spi.DIRECTION = DISABLE;//MSB First init_spi.PHASE =DISABLE;//ENABLE;//posedge init_spi.POLARITY =DISABLE;//polarity 0 SPI_Init(&init_spi); } //Initializes UART0 void initializeUART() { UART_InitTypeDef uartInitStruct; //Enable transmission uartInitStruct.UART_Mode.UARTMode_Tx = ENABLE; //Disable reception uartInitStruct.UART_Mode.UARTMode_Rx = DISABLE; //9600 baud rate typical of Arduinos uartInitStruct.UART_BaudRate = 9600; //Initialize UART0 using the struct configs UART_Init(UART0, &uartInitStruct); } void initializeTimer() { TIMER_InitTypeDef timerInitStruct; timerInitStruct.Reload = 0; //Disable interrupt requests from timer for now timerInitStruct.TIMER_Int = DISABLE; //Disable timer enabling/clocking from external pins (GPIO) timerInitStruct.TIMER_Exti = TIMER_DISABLE; TIMER_Init(TIMER0, &timerInitStruct); TIMER_StopTimer(TIMER0); } #define CYCLES_PER_MILLISEC (SystemCoreClock / 1000) void delayMillis(uint32_t ms) { TIMER_StopTimer(TIMER0); TIMER_SetValue(TIMER0, 0); //Reset timer just in case it was modified elsewhere TIMER_EnableIRQ(TIMER0); uint32_t reloadVal = CYCLES_PER_MILLISEC * ms; //Timer interrupt will trigger when it reaches the reload value TIMER_SetReload(TIMER0, reloadVal); TIMER_StartTimer(TIMER0); //Block execution until timer wastes the calculated amount of cycles while (TIMER_GetIRQStatus(TIMER0) != SET); TIMER_StopTimer(TIMER0); TIMER_ClearIRQ(TIMER0); TIMER_SetValue(TIMER0, 0); }
Теперь, когда микропроцессор инициализируется и данные передаются в ПЛИС, можно приступить к проектированию передатчика PSK31.
Коротковолновый радиопередатчик PSK31
В проекте используется DAC904 на отладочной плате (как и в прошлой статье. — прим. Авт.). При программировании PSK был использован исходный код из репозитория GitHub. Для формирования PSK модуляции необходимо изменять фазу. Для используемого генератора это возможно. Чтобы управлять фазой, необходимо в модуле dds_addr
сделать PWORD
входным сигналом:
module dds_addr (clk, rst_n, addr_out, strobe, FWORD, PWORD); input clk, rst_n; // Resetting the system clock input [31:0] FWORD; input [15:0] PWORD; 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 // parameter FWORD = 3316669189; // слово управления частотой F_out = B * (F_clk / 2 ** 32), fword = B 5KHZ // 858994 reg [N-1: 0] addr; // 32-bit battery // reg [11:0] addr; reg [15:0] pword; always @ (posedge clk)begin pword <= PWORD; end reg [31:0] fword; always @ (posedge clk)begin fword <= FWORD; end 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 // addr <= addr + 1; 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
При подключении экземпляра dds_addr
в модуль верхнего уровня необходимо передавать значение фазы. Фаза может быть задана значением 2^12
. Это значит, что единичная окружность разбита на 4096 значений и чтобы задать фазу сигнала, необходимо рассчитать значение PWORD
. PWORD = (x/360) * 4096. Где это может быть использовано? Известно, что SIN и COS различаются по фазе сигнала на 90 градусов. Тогда, воспользовавшись формулой (90/360)*4096=1024. То есть если в модуле верхнего уровня использовать два экземпляра dds_addr и в одном задать значение PWORD=0
, а в другом PWORD=1024
, эти два сигнала будут различаться по фазе на 90 градусов.
// --------------Phase-based module------------------------ dds_addr dds_addr_inst ( .clk(clk), // input wire clk .rst_n(1'b1), // input wire rst_n // 1 enable .addr_out(addr_out), // output wire [11 : 0] addr_out .strobe(strobe_sin), .FWORD(32'd1613094272), // F_out = B * (F_clk / 2 ** 32) частота равна 10.1406 КГц .PWORD(16'd2048) // (x/360) * 4096 фаза равна Pi ); //----------------------------------------------------------
В случае коротковолнового передатчика частота и фаза рассчитываются в микроконтроллере и передаются по SPI. Для приёма значения частоты на стороне FPGA можно использовать немного переписанный конечный автомат из прошлой статьи:
reg [31:0] fword; reg [31:0] oneBytes_f; reg [31:0] twoBytes_f; reg [31:0] thrBytes_f; reg [ 3:0] state_reg_f; always@(posedge rxd_flag or negedge rst)begin if(!rst)begin fword <= 1'b0; end else if(rxd_out==8'h01) begin state_reg_f <= 0; oneBytes_f <= 0; end else begin case(state_reg_f) 4'd0: begin oneBytes_f <= oneBytes_f + rxd_out; state_reg_f <= 1; end 4'd1: begin twoBytes_f <= oneBytes_f + (rxd_out << 8); // +tmp state_reg_f <= 2; end 4'd2: begin thrBytes_f <= twoBytes_f + (rxd_out << 16); // +tmp state_reg_f <= 3; end 4'd3: begin fword <= thrBytes_f + (rxd_out << 24); // +tmp state_reg_f <= 4; // led <= !led; end default: begin state_reg_f <= 4; end endcase end end
А для приёма значения фазы использовать вот такой:
reg [15:0] oneBytes_p; reg [15:0] pword_reg; reg [ 3:0] state_reg_p = 4; always@(posedge rxd_flag or negedge rst)begin if(!rst)begin pword_reg <= 0; end else if(rxd_out==8'h02) begin state_reg_p <= 0; end else begin case(state_reg_p) 4'd0: begin oneBytes_p <= rxd_out; state_reg_p <= 1; end 4'd1: begin pword_reg <= oneBytes_p + (rxd_out << 8); state_reg_p <= 4; // "другое" состояние, чтобы частота не мешалась led <= !led; end default: begin state_reg_p <= 4; // "другое" состояние, чтобы частота не мешалась end endcase end end
Весь модуль верхнего уровня под спойлером.
Скрытый текст
module led( input clk, inputrst, output reg led, output [11: 0] sin, output UART_TX, output clk_o, outputrxd_flag ); wirecs; wiresck; wireMOSI; wireMISO; wire[7:0]rxd_out; //-------------------------------------------- reg [7:0] txd_dat; wire clk_60M; //-------------------------------------------- wire [11: 0] addr_out; wire pll_out_clk; //------------------------------------------- wire [7:0]gpio; //--------Copy here to design-------- Gowin_EMPU_Top cortexM3_inst( .sys_clk(clk_60M), //input sys_clk .gpio(gpio[7:0]), //inout [15:0] gpio .uart0_rxd(1'b1), //input uart0_rxd .uart0_txd(UART_TX), //output uart0_txd .mosi(MOSI), //output mosi .miso(MISO), //input miso .sclk(sck), //output sclk .nss(cs), //output nss .reset_n(1'b1) //input reset_n ); //--------Copy end------------------- //assign led = gpio[7]; always@(posedge rxd_flag or negedge rst)begin if(!rst) txd_dat <= 8'b11000011; else begin txd_dat <= rxd_out + 1'b1; //отправить данные +1 отправителю end end reg [31:0] fword; reg [31:0] oneBytes_f; reg [31:0] twoBytes_f; reg [31:0] thrBytes_f; reg [ 3:0] state_reg_f; always@(posedge rxd_flag or negedge rst)begin if(!rst)begin fword <= 1'b0; end else if(rxd_out==8'h01) begin state_reg_f <= 0; oneBytes_f <= 0; end else begin case(state_reg_f) 4'd0: begin oneBytes_f <= oneBytes_f + rxd_out; state_reg_f <= 1; end 4'd1: begin twoBytes_f <= oneBytes_f + (rxd_out << 8); // +tmp state_reg_f <= 2; end 4'd2: begin thrBytes_f <= twoBytes_f + (rxd_out << 16); // +tmp state_reg_f <= 3; end 4'd3: begin fword <= thrBytes_f + (rxd_out << 24); // +tmp state_reg_f <= 4; // led <= !led; end default: begin state_reg_f <= 4; end endcase end end reg [15:0] oneBytes_p; reg [15:0] pword_reg; reg [ 3:0] state_reg_p = 4; always@(posedge rxd_flag or negedge rst)begin if(!rst)begin pword_reg <= 0; end else if(rxd_out==8'h02) begin state_reg_p <= 0; end else begin case(state_reg_p) 4'd0: begin oneBytes_p <= rxd_out; state_reg_p <= 1; end 4'd1: begin pword_reg <= oneBytes_p + (rxd_out << 8); state_reg_p <= 4; // "другое" состояние, чтобы частота не мешалась led <= !led; end default: begin state_reg_p <= 4; // "другое" состояние, чтобы частота не мешалась end endcase end end //--------Copy here to design-------- Gowin_PLLVR1 your_instance_name( .clkout(clk_60M), //output clkout .clkin(clk) //input clkin ); spi_slaver spi_slaver1( .clk(clk_60M), // clk_30M .rst(rst), .cs(cs), .sck(sck), .MOSI(MOSI), .MISO(MISO), .rxd_out(rxd_out), .txd_data(txd_dat), .rxd_flag(rxd_flag) ); //------------------------------------------- // --------------Phase-based module------------------------ dds_addr dds_addr_inst ( .clk(clk), // input wire clk .rst_n(1'b1), // input wire rst_n // 1 enable .addr_out(addr_out), // output wire [7 : 0] addr_out .strobe(strobe_sin), .FWORD(fword), // fword // fword_valid .PWORD(pword_reg) // tmp5 // 16'd2048 ); //---------------------------------------------------------- // 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 // 0 enable .ad(addr_out) //input [11:0] ad ); assign clk_o = clk; endmodule
Теперь программирование микроконтроллера. В первую очередь в проект для Cortex-M3 необходимо включить файлы hfbeacon.c
и hfbeacon.h
. Файлы отличаются от оригинальных отсутствием классов и способом изменения частоты и фазы генератора сигнала. В структурах языка C отсутствует понятие методов, поэтому для отправки значений частоты и фазы в генератор реализована простая функция checkGen(struct Gen *gen)
. Когда функция вызывается из hfbeacon.c
, проверяется какое значение (частоты и/или фазы. — прим. Авт.) изменилось, и именно оно передаётся по SPI в логику ПЛИС.
extern uint8_t checkGen(struct Gen *gen){ uint8_t res = 0; if(old_freq != gen->freq){ res = 1; send_frequency(&gen->freq); old_freq = gen->freq; } if(old_phase != gen->phase){ res = 2; //printf("old_phase\r\n"); send_phase(&gen->phase); old_phase = gen->phase; } return res; }
Значение частоты передаётся в функцию void send_frequency(uint32_t *freq)
из прошлой статьи. Функция такая же, но с учётом работы с SPI в Cortex-M3 в ПЛИС GW1NSR-4C. Значение фазы передаётся в свою аналогичную функцию подобным образом только с тем отличием, что это значение 2-байтовое.
Перед экспериментами с T-FPGA был проведён промежуточный эксперимент с Arduino Nano и AD9833 на фиолетовой отладочной плате. Исходный код этого эксперимента доступен по ссылке. Если обратить внимание на исходный код в файле
hfbeacon.c
, то там можно найти закомментированные строчки работы и с ad9850, и ad9833. Там очень хорошо видны особенности формирования значения фазы вhfbeacon.c
и передачи этого значения в ad9833 в формате угла в градусах на окружности.
В файле hfbeacon.c
значение фазы формируется в формате 5-битного значения. Это значит, что окружность разбита на 32 части по 11,25 градуса (2^5=32, 360/32=11,25. — прим. Авт.). То есть чтобы перевести значение фазы из hfbeacon.c
в градусы, необходимо умножить его на 11.25. Тогда, учитывая особенности настройки фазы, полученное значение угла в градусах делится на 360, а результат умножается на 4096. И именно это значение передаётся по SPI в логику ПЛИС. Перед отправкой значения фазы передаётся признак 0x02
того, что сейчас будет передаваться значение фазы.
void send_phase(uint32_t *phase){ //printf("send_phase\r\n"); uint32_t pword; pword=(((float)(*phase)*11.25)/360)*4096; uint8_t buff[2]; buff[0] = pword & 0xff; buff[1] = pword >> 8 & 0xff; if(~SPI_GetToeStatus() && SPI_GetTrdyStatus() == 1) { SPI_WriteData(0x02); // признак передачи значения фазы } for(uint8_t i=0;i<2;++i){ if(~SPI_GetToeStatus() && SPI_GetTrdyStatus() == 1) { SPI_WriteData(buff[i]); } } }
Под спойлером файл hfbeacon.c
Скрытый текст
#include <hfbeacon.h> /*************************************************************************** * CW ***************************************************************************/ void cwTx(long freqCw, char * stringCw, int cwWpm, struct Gen *gen){ // AD9833 *genPtr static int const morseVaricode[2][59] = { // PROGMEM {0,212,72,0,144,0,128,120,176,180,0,80,204,132,84,144,248,120,56,24,8,0,128,192,224,240,224,168,0,136,0,48,104,64,128,160,128,0,32,192,0,0,112,160,64,192,128,224,96,208,64,0,128,32,16,96,144,176,192}, {7,6,5,0,4,0,4,6,5,6,0,5,6,6,6,5,5,5,5,5,5,5,5,5,5,5,6,6,0,5,0,6,6,2,4,4,3,1,4,3,4,2,4,3,4,2,2,3,4,4,3,3,1,3,4,3,4,4,4} }; int tempo = 1200 / cwWpm; // Duration of 1 dot uint8_t nb_bits,val; int d; int c = *stringCw++; while(c != '\0'){ c = toupper(c); // Uppercase if(c == 32){ // Space character between words in string //DDS.setfreq(0,0); // 7 dots length spacing //genPtr->ApplySignal(SQUARE_WAVE,REG0,0); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = 0; checkGen(gen); delayMillis(tempo * 7); // between words } else if (c > 32 && c < 91) { c = c - 32; d = morseVaricode[0][c]; // Get CW varicode // int(pgm_read_word(& )) nb_bits = morseVaricode[1][c]; // Get CW varicode length // int(pgm_read_word(& )) if(nb_bits != 0){ // Number of bits = 0 -> invalid character #%<> for(int b = 7; b > 7 - nb_bits; b--){ // Send CW character, each bit represents a symbol (0 for dot, 1 for dash) MSB first val=bitRead(d,b); //look varicode //DDS.setfreq(freqCw,0); // Let's transmit //gen.ApplySignal(SQUARE_WAVE,REG0,((freqCw)*1000ul)); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE //genPtr->ApplySignal(SQUARE_WAVE,REG0,(freqCw)); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = freqCw; checkGen(gen); delayMillis(tempo + 2 * tempo * val); // A dot length or a dash length (3 times the dot) //DDS.setfreq(0,0); // 1 dot length spacing //genPtr->ApplySignal(SQUARE_WAVE,REG0,0); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = 0; checkGen(gen); delayMillis(tempo); // between symbols in a character } } //DDS.setfreq(0,0); // 3 dots length spacing //genPtr->ApplySignal(SQUARE_WAVE,REG0,0); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = 0; checkGen(gen); delayMillis(tempo * 3); // between characters in a word } c = *stringCw++; // Next caracter in string } //DDS.setfreq(0, 0); // No more transmission // genPtr->ApplySignal(SQUARE_WAVE,REG0,0); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = 0; checkGen(gen); } /******************************************************** * PSK ********************************************************/ #if 1 void pskTx(long freqPsk, char * stringPsk, int modePsk, int baudsPsk, struct Gen *gen) // AD9833 *genPtr { static int const PskVaricode[2][128] = { // PROGMEM {683,731,749,887,747,863,751,765,767,239,29,879,733,31,885,939,759,757,941,943,859,875,877, 855,891,893,951,853,861,955,763,895,1,511,351,501,475,725,699,383,251,247,367,479,117,53, 87,431,183,189,237,255,375,347,363,429,427,439,245,445,493,85,471,687,701,125,235,173,181, 119,219,253,341,127,509,381,215,187,221,171,213,477,175,111,109,343,437,349,373,379,685,503, 495,507,703,365,735,11,95,47,45,3,61,91,43,13,491,191,27,59,15,7,63,447,21,23,5,55,123,107, 223,93,469,695,443,693,727,949}, {10,10,10,10,10,10,10,10,10,8,5,10,10,5,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 10,1,9,9,9,9,10,10,9,8,8,9,9,7,6,7,9,8,8,8,8,9,9,9,9,9,9,8,9,9,7,9,10,10,7,8,8,8,7,8,8,9,7, 9,9,8,8,8,8,8,9,8,7,7,9,9,9,9,9,10,9,9,9,10,9,10,4,7,6,6,2,6,7,6,4,9,8,5,6,4,3,6,9,5,5,3,6, 7,7,8,7,9,10,9,10,10,10} }; static int const QpskConvol[32] = {16,8,-8,0,-8,0,16,8,0,-8,8,16,8,16,0,-8,8,16,0,-8,0,-8,8,16,-8,0,16,8,16,8,-8,0}; // PROGMEM int shreg = 0; // Shift register qpsk int phase = 0; if(0) // rsidTxEnable == 1 { // 0 bpsk31 // 1 qpsk31 // 2 bpsk63 rsidTx(freqPsk, (baudsPsk >> 4) - (modePsk == 'B'), gen); // 3 qpsk63 // genPtr // 6 bpsk125 // 7 qpsk125 } pskIdle(freqPsk, baudsPsk, gen); // A little idle on start of transmission for AFC capture // uint8_t nb_bits,val; int d,e; int c = *stringPsk++; while (c != '\0') { d = PskVaricode[0][c]; // Get PSK varicode // int(pgm_read_word(& )) nb_bits = PskVaricode[1][c]; // Get PSK varicode length // int(pgm_read_word(& )) d <<= 2; //add 00 on lsb for spacing between caracters e = d; for(int b = nb_bits + 2; b >= 0; b--) //send car in psk { val=bitRead(e,b); //look varicode if(modePsk == 'B') // BPSK mode { if (val == 0) { phase = (phase ^ 16) &16; // Phase reverted on 0 bit } } else if(modePsk == 'Q'){ // QPSK mode shreg = (shreg << 1) | val; // Loading shift register with next bit d=(int)QpskConvol[shreg & 31]; // Get the phase shift from convolution code of 5 bits in shit register // int(pgm_read_word(& )) phase = (phase + d) & 31; // Phase shifting } //DDS.setfreq(freqPsk, phase); // Let's transmit //gen.ApplySignal(SQUARE_WAVE,REG0,((freqPsk)*1000ul), REG0,phase); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE //genPtr->ApplySignal(SQUARE_WAVE,REG0,freqPsk, REG0,(float)phase*11.25); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = freqPsk; gen->phase = phase; // (float)phase*11.25; checkGen(gen); delayMillis((961 + baudsPsk) / baudsPsk); // Gives the baud rate } c = *stringPsk++; // Next caracter in string } pskIdle(freqPsk, baudsPsk, gen); // A little idle to end the transmission // genPtr // DDS.setfreq(0, 0); // No more transmission // genPtr->ApplySignal(SQUARE_WAVE,REG0,0); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = freqPsk; checkGen(gen); } #endif #if 1 void pskIdle(long freqIdle, int baudsIdle, struct Gen *gen) // AD9833 *genPtr { int phaseIdle = 0; for(int n = 0; n < baudsIdle; n++) { phaseIdle = (phaseIdle ^ 16) & 16; // Idle is a flow of zeroes so only phase inversion //DDS.setfreq(freqIdle, phaseIdle); // Let's transmit //gen.ApplySignal(SQUARE_WAVE,REG0,((freqIdle)*1000ul), REG0,phaseIdle); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE //genPtr->ApplySignal(SQUARE_WAVE,REG0,freqIdle, REG0,(float)phaseIdle*11.25); // SINE_WAVE // SQUARE_WAVE // HALF_SQUARE_WAVE gen->freq = freqIdle; gen->phase = phaseIdle; // (float)phase*11.25; checkGen(gen); delayMillis((961 + baudsIdle) / baudsIdle); // Gives the baud rate } } #endif
Под спойлером файл hfbeacon.h
Скрытый текст
#ifndef HFBEACON_H #define HFBEACON_H #define bitRead(value, bit) (((value) >> (bit)) & 0x01) #include <stdbool.h> #include <ctype.h> #include <stdio.h> #if 1 struct Gen{ uint32_t freq; uint32_t phase; }; #endif void rsidToggle(bool rsidEnable); void cwTx(long freqCw, char * stringCw, int cwWpm, struct Gen*); // , AD9833* void pskTx(long freqPsk, char * stringPsk, int modePsk, int baudsPsk, struct Gen*); // , AD9833* void rttyTx(long freqRtty, char * stringRtty); void hellTx(long freqHell, char * stringHell); void wsprTx(long freqWspr, char * callWsprTx, char * locWsprTx, char * powWsprTx); void wsprEncode(char * callWsprProc, char * locWsprProc, char * powWsprProc); void ddsPower(int powDds, struct Gen*); // , AD9833* uint8_t wsprSymb[162]; int wsprSymbGen; void rsidTx(long freqRsid, int modeRsid, struct Gen*); // , AD9833* void pskIdle(long freqIdle, int baudsIdle, struct Gen*); // , AD9833* void rttyTxByte (long freqRttyTxbyte, char c); uint8_t parity(unsigned long tempo); //uint8_t rsidTxEnable = 0; //extern HFBEACON Beacon; #endif
Перед psk модуляцией запускалась обычная морзянка для отладки. На этом этапе и были пойманы баги, после которых конечный автомат стал переводиться в «4» состояние. В оригинальном файле реализовано ещё несколько модуляций. Используя описанные примеры, не составит труда, применить их в своих проектах.
Исходный код проекта для ПЛИС доступен на GItHub в ветке feature-articleCortexM3
, а для Cortex-M3 в ветке feature-articleBpsk31
. Теперь, вызывая в main.c
функцию pskTx(freq, txString, 'B', 31, &gen);
из приёмного динамика станет доноситься необходимый звук:
Для декодирования снова можно использовать MyltiPSK
Сообщения декодируются, а это значит, что Cortex-M3 в ПЛИС GW1NSR-LV4CQN48PC6/I5 инициализируется и передаёт посчитанные значения через SPI в генератор сигналов в ПЛИС. Встроенные в FPGA микроконтроллеры открывают большие возможности для использования в различных проектах. Например, при использовании радиочастотной микросхемы-трансивера AD9361 очень удобно инициализировать её из встроенного в ZYNQ-7000 ARM ядра или софт-процессора Microblaze на языке С, тогда как приём, передача и/или обработка IQ сигнала ведётся в логике FPGA. А как Вы используете встроенные в ПЛИС микропроцессоры?
Спасибо.
С. Н.
ссылка на оригинал статьи https://habr.com/ru/articles/865480/
Добавить комментарий