- PVSM.RU - https://www.pvsm.ru -
В статье описывается опыт использования ARM ядра, встроенного в ПЛИС GOWIN GW1NSR-4C, в качестве процессора общего назначения для формирования PSK31 [1] сигнала. Сигнал формируется с помощью генератора синуса, который был описан в предыдущей статье [2]. Используются отладочная плата LilyGO T-FPGA, в составе которой ПЛИС GW1NSR-LV4CQN48PC6/I5, ЦАП на основе DAC904, ide GOWIN FPGA Designer и образовательная версия GMD [3].
Дисклеймер
Туториал описывает процесс радиопередачи, поэтому прежде чем шалить, ознакомьтесь с нормативно-правовой базой государства, на территории которого предполагается шалость, получите радиопозывной и избавьтесь от паразитных гармоник.Здесь будут описаны конкретный опыт за последние пару дней и несколько экспериментов с отладочными платами. Это не опыт и эксперименты профессионального разработчика под ПЛИС и микроконтроллеры. Может быть, это прочитают специалисты, которые хорошо разбираются в затронутых темах и дадут конструктивную критику.
При возникновении вопросов по запуску ARM ядра в ПЛИС GOWIN изучение официального сайта GOWIN [4] приведёт на страницу GITHUB [5]. В файле README достаточно подробно описан процесс запуска ARM ядра в ПЛИС GW1NSR-LV4CQN48PC6/I5 и несколько программ для примера.
Для управления генератором синуса в ПЛИС и формирования модуляции psk можно использовать встроенное ARM ядро Cortex-m3. Чтобы связать логику процессора и логику ПЛИС можно использовать SPI интерфейс. При добавлении в дизайн IP MCU необходимо дополнительно включить SPI шину:
В качестве проверки работы можно использовать пример [6]от LiLyGO, в котором управление миганием светодиода, подключённого к FPGA, осуществляется из микроконтроллера. В оригинальном примере в качестве микроконтроллера выступает ESP32-S3, размещённый на отладочной плате. Для текущего эксперимента предлагается использовать встроенный в ПЛИС Cortex-M3.
На самом деле автором было проделано больше промежуточных экспериментов. Например, при первом запуске Cortex-M3 в ПЛИС было запрограммировано классическое мигание светодиодом прямо из микроконтроллера. Для этого сигнал
ledбыл включён прямо в экземпляр Gowin_EMPU_Top.gpio(led)(не выходной регистрoutput reg ledкак в примере [7], а именноoutput led. Иначе не пройдёт синтез, потому что экземпляр Cortex-M3 не допускает включение регистров в качестве GPIO. - прим. Авт.) .
После добавления экземпляра MCU в файл дизайна верхнего уровня необходимо подключить сигналы MCU к соответствующим портам верхнего уровня.
wire cs;
wire sck;
wire MOSI;
wire MISO;
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 в ПЛИС [5], и в блоге Marsohod.org [8]. GPIO объявлены как wire, потому что в проекте они не используются. Сигналы SPI объявлены как wire. Микроконтроллер встроен внутрь ПЛИС, поэтому выводить сигналы для связи процессорной логики и логики ПЛИС наружу совсем необязательно.Всё остальное подключено как в оригинальной статье. После объявления constraints и проверки синтеза, имплементации и генерации bitstream на наличие ошибок можно приступить к программированию микроконтроллера. Как и в оригинальной статье будет использована Gowin's MCU Designer.
Под спойлером полный исходный код для SPI blink со стороны ПЛИС.
module led(
input clk,
input rst,
output reg led,
output UART_TX,
output rxd_flag
);
wire cs;
wire sck;
wire MOSI;
wire MISO;
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;
В оригинальном туториале предлагается ссылка для скачивания Gowin's MCU Designer. Но у автора не получилось скачать по данной ссылке программу по неизвестной причине. На сайте GOWIN [9]необходимое ПО скачивается без каких-либо проблем.
По аналогии с оригинальным туториалом в комплекте 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. Как и в оригинальном примере [10]через SPI поочерёдно отправляется значение 0x01 или 0x81 и в зависимости от этого логика FPGA переключает состояние светодиода.
/*
* *****************************************************************************************
*
* Copyright (C) 2014-2021 Gowin Semiconductor Technology Co.,Ltd.
*
* @file main.c
* @author Embedded Development Team
* @version V1.x.x
* @date 2021-01-01 09:00:00
* @brief Main 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 completern");
uint32_t counter = 0;
while(1)
{
counter++;
printf("GowinFPGA says hi! Count: %drn", 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.
В проекте используется DAC904 на отладочной плате (как и в прошлой статье [2]. - прим. Авт.). При программировании PSK был использован исходный код из репозитория GitHub [11]. Для формирования 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,
input rst,
output reg led,
output [11: 0] sin,
output UART_TX,
output clk_o,
output rxd_flag
);
wire cs;
wire sck;
wire MOSI;
wire MISO;
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_phasern");
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 на фиолетовой отладочной плате. Исходный код этого эксперимента доступен по ссылке [12]. Если обратить внимание на исходный код в файле
hfbeacon.c, то там можно найти закомментированные строчки работы и с ad9850, и ad9833. Там очень хорошо видны особенности формирования значения фазы вhfbeacon.cи передачи этого значения в ad9833 в формате угла в градусах на окружности.
В файле hfbeacon.c значение фазы формируется в формате 5-битного значения. Это значит, что окружность разбита на 32 части по 11,25 градуса (2^5=32, 360/32=11,25. - прим. Авт.). То есть чтобы перевести значение фазы из hfbeacon.c в градусы, необходимо умножить его на 11.25. Тогда, учитывая особенности настройки фазы [13], полученное значение угла в градусах делится на 360, а результат умножается на 4096. И именно это значение передаётся по SPI в логику ПЛИС. Перед отправкой значения фазы передаётся признак 0x02 того, что сейчас будет передаваться значение фазы.
void send_phase(uint32_t *phase){
// printf("send_phasern");
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 != ''){
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 != '')
{
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" состояние. В оригинальном файле реализовано ещё несколько модуляций. Используя описанные примеры, не составит труда, применить их в своих проектах.
Исходный код проекта для ПЛИС [14]доступен на GItHub в ветке feature-articleCortexM3 , а для Cortex-M3 [15] в ветке 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. А как Вы используете встроенные в ПЛИС микропроцессоры?
Спасибо.
С. Н.
Автор: Pisikak
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/psk/405466
Ссылки в тексте:
[1] PSK31: https://ru.ruwiki.ru/wiki/%D0%A4%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%8F_%D0%BC%D0%B0%D0%BD%D0%B8%D0%BF%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F
[2] статье: https://habr.com/p/862842/
[3] образовательная версия GMD: https://www.gowinsemi.com/en/support/database/1331/
[4] официального сайта GOWIN: https://www.gowinsemi.com/en/support/arm/
[5] страницу GITHUB: https://github.com/verilog-indeed/gowin_fpga_tutorials/tree/main/gowin_empu
[6] пример : https://github.com/Xinyuan-LilyGO/T-FPGA/tree/main/example
[7] примере: https://github.com/Xinyuan-LilyGO/T-FPGA/blob/main/example/FPGA/led/src/led.v
[8] Marsohod.org: https://marsohod.org/projects/marsohod3gw-prj/432-gowin-rpll
[9] GOWIN : https://www.gowinsemi.com/en/support/database/569/?support_search=GOWIN+MCU+Designer
[10] примере : https://github.com/Xinyuan-LilyGO/T-FPGA/blob/main/example/ESP32-S3/spi_blink/spi_blink.ino
[11] исходный код из репозитория GitHub: https://github.com/F4GOJ/HFBEACON
[12] ссылке: https://github.com/NSV47/ad9833_beacon_bpsk31
[13] учитывая особенности настройки фазы: #phase_calc
[14] ПЛИС : https://github.com/NSV47/T-FPGA-transmitter/tree/feature-articleCortexM3
[15] Cortex-M3: https://github.com/NSV47/GMD_workspace/blob/feature-articleBpsk31/test1/test1/template/main.c
[16] disk.yandex.ru: https://disk.yandex.ru/showcaptcha?cc=1&mt=F67AD92BB9A87CADB2670C2BEAFC4C6E8ECAA55F2FB7A5EB83DD4E3C91B1BFA15B3E34CB02A9E2FFC9CA9D888AC704A60C1F1B60B9333F2A747687FC25F4973D646E014170197F359A2D2C56FAC9DD93BCFBE5D21BF805E7B859073242439288249FF4FDE8E79F4F744A2C6CA9BB2E455CA92C8523C287AE28686A60C5D2305090AEBF80D1D24FFD918400946F85FB8A809B5A199E0688A6170D5B6E6817E0A4B2AB1D0703C4404155DC4680E39A35C06103EDB9FD7DA4851B117690337D7133696B27607D7075DF75C19D61300ADA18E4ECF656649F48E9F3178E7F79&retpath=aHR0cHM6Ly9kaXNrLnlhbmRleC5ydS9kL21Mdml0dkxfTi1CZl9BPw%2C%2C_c3d4f583947ccf822cd5482e4c5823e7&t=2/1734348720/de75ef54bd36bb68e450d4d7eaad0e68&u=5709707970653253158&s=8a7854a157ecbf2c5d275af88f0dd2c2
[17] Источник: https://habr.com/ru/articles/865480/?utm_campaign=865480&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.