Использование Cortex-M3 hard core processor в ПЛИС GOWIN

от автора

В статье описывается опыт использования 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 шину:

Рис. Настройка SPI в IP generator

Рис. Настройка SPI в IP generator

В качестве проверки работы можно использовать пример от 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

Рис. Интерфейс MyltiPSK с декодированными сообщениями

Рис. Интерфейс MyltiPSK с декодированными сообщениями

Сообщения декодируются, а это значит, что Cortex-M3 в ПЛИС GW1NSR-LV4CQN48PC6/I5 инициализируется и передаёт посчитанные значения через SPI в генератор сигналов в ПЛИС. Встроенные в FPGA микроконтроллеры открывают большие возможности для использования в различных проектах. Например, при использовании радиочастотной микросхемы-трансивера AD9361 очень удобно инициализировать её из встроенного в ZYNQ-7000 ARM ядра или софт-процессора Microblaze на языке С, тогда как приём, передача и/или обработка IQ сигнала ведётся в логике FPGA. А как Вы используете встроенные в ПЛИС микропроцессоры?

Спасибо.

С. Н.


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


Комментарии

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

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