跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

2023 电赛 H 题信号分离装置 FPGA 与 STM32 实现方案

针对 2023 年电赛 H 题信号分离装置,提出 FPGA 与 STM32 协同设计方案。系统采用高速 ADC 采集混合信号 C,FPGA 负责 FIFO 缓存及串口收发,STM32 基于 FFT 算法识别输入波形的频率与类型(正弦或三角)。识别结果回传至 FPGA 驱动 DDS 模块重构波形,结合锁相环技术解决时钟不同步导致的相位漂移问题。系统支持按键调节相位差并通过数码管显示,最终经 DAC 输出分离后的 A'与 B'信号。

1739658202发布于 2026/4/8更新于 2026/7/2452 浏览
2023 电赛 H 题信号分离装置 FPGA 与 STM32 实现方案

题目

题目描述

题目描述

题目描述

题目描述

解题思路

题目要求从信号发生器输出两路波形 A 和 B 并通过一个增益为 1 的加法器生成信号 C,我们需要做的就是从 C 中将 A'和 B'解出来。一种方法是将 C 进行傅里叶变换然后识别出两路波形分别是什么然后将频域分离出来随后进行傅里叶逆变换再输出就行。另一种方法是本文章所使用的方法,由于题目不考虑波形的相位和峰峰值与原波形的关系,所以我们可以将输入的 C 信号进行傅里叶变换识别出两路波形的频率,并且由于 AB 波的峰峰值为固定的 1V,而正弦波为单音信号频谱集中,三角波有三次谐波的存在导致基波信号的峰峰值会被削弱使得可以通过比较频谱中基波的幅值大小来识别输入的波形是三角波还是正弦波,然后就能得到两路波的波形以及频率,随后就可以使用 DDS 重构出 A'和 B'波形。但是这个方法有一个问题就是由于重构的波形与信号发生器所出的波形不在同一个时钟上,由于不同时钟的微小差异,会使得相位差在不停的累积就会产生信号漂移,所以我们需要设计一个锁相环将生成的信号与原信号的相位锁住就不会再产生漂移了,移相只需要改变 ROM 表的地址就可以实现。

基本框架

C 信号——>高速 ADC——>FPGA——>FIFO——>串口——>stm32——>FFT——>识别波形和频率——>串口——>DDS——>锁相环——>高速 DAC——>A',B'

代码思路

第一部分(FPGA 的 FIFO 以及串口发送接收)

1.FIFO

此处使用的 FIFO 为 AX7035B 参考例程,其中写入速率为 1MHz(与高速 ADC 的采样频率相同),读取速率为 12.5KHz(串口的发送速率)

assign ad9238_clk_ch0 = clk_1M;
wire wr_en,rd_en,full,empty,wr_rst_busy,rd_rst_busy; wire [10:0] rd_data_count,wr_data_count; reg [7:0] w_data;
localparam W_IDLE = 1; localparam W_FIFO = 2;
localparam R_IDLE = 1; localparam R_FIFO = 2;
reg[2:0] write_state; reg[2:0] next_write_state;
reg[2:0] read_state; reg[2:0] next_read_state;
wire [7:0] rx_data,tx_data; reg tx_valid;
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) write_state <= W_IDLE;
    else write_state <= next_write_state;
end
always@(*) begin
    case(write_state)
        W_IDLE: if(empty == 1'b1) //FIFO is empty, start writing FIFO
            next_write_state <= W_FIFO;
        else next_write_state <= W_IDLE;
        W_FIFO: if(full == 1'b1) //FIFO is full
            next_write_state <= W_IDLE;
        else next_write_state <= W_FIFO;
        default: next_write_state <= W_IDLE;
    endcase
end
assign wr_en = (next_write_state == W_FIFO) ? 1'b1 : 1'b0;
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) w_data <= 16'd0;
    else if (wr_en == 1'b1) w_data <= ADC;
    else w_data <= ADC;
end
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) read_state <= R_IDLE;
    else read_state <= next_read_state;
end
always@(*) begin
    case(read_state)
        R_IDLE: if(full == 1'b1) //FIFO is full, starting read FIFO
            next_read_state <= R_FIFO;
        else next_read_state <= R_IDLE;
        R_FIFO: if(empty == 1'b1) //FIFO is empty
            next_read_state <= R_IDLE;
        else next_read_state <= R_FIFO;
        default: next_read_state <= R_IDLE;
    endcase
end
reg [10:0] rd_data_count_last;
always@(posedge clk or negedge rst) begin
    if(!rst) rd_data_count_last<=0;
    else begin
        rd_data_count_last<=rd_data_count;
        if(rd_data_count_last!=rd_data_count && rd_en) tx_valid<=1;
        else tx_valid<=0;
    end
end
assign rd_en = (next_read_state == R_FIFO) ? 1'b1 : 1'b0;
fifo fifo (
    .rst(~rst), // input wire rst
    .wr_clk(clk_1M), // input wire wr_clk
    .rd_clk(clk_100K), // input wire rd_clk
    .din(w_data), // input wire [7 : 0] din
    .wr_en(wr_en), // input wire wr_en
    .rd_en(rd_en), // input wire rd_en
    .dout(tx_data), // output wire [7 : 0] dout
    .full(full), // output wire full
    .empty(empty), // output wire empty
    .rd_data_count(rd_data_count), // output wire [10 : 0] rd_data_count
    .wr_data_count(wr_data_count) // output wire [10 : 0] wr_data_count
);
2.(FPGA 串口发送)

此处串口发送使用的也是 AX7035B 的例程(在 FIFO 中每读一个数就通过串口发送出去)

uart_tx#( .CLK_FRE(50), .BAUD_RATE(115200) //serial baud rate )uart_tx(
    .clk(clk), //clock input
    .rst_n(rst), //asynchronous reset input, low active
    .tx_data(tx_data), //data to send
    .tx_data_valid(tx_valid), //data to be sent is valid
    .tx_pin(tx_pin) //serial data output
);
module uart_tx #( parameter CLK_FRE = 50, //clock frequency(Khz) parameter BAUD_RATE = 115200 //serial baud rate ) (
    input clk, //clock input
    input rst_n, //asynchronous reset input, low active
    input[7:0] tx_data, //data to send
    input tx_data_valid, //data to be sent is valid
    output reg tx_data_ready, //send ready
    output tx_pin //serial data output
);
//calculates the clock cycle for baud rate
localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam S_IDLE = 1; localparam S_START = 2;//start bit
localparam S_SEND_BYTE = 3;//data bits
localparam S_STOP = 4;//stop bit
reg[2:0] state; reg[2:0] next_state;
reg[15:0] cycle_cnt; //baud counter
reg[2:0] bit_cnt;//bit counter
reg[7:0] tx_data_latch; //latch data to send
reg tx_reg; //serial data output
assign tx_pin = tx_reg;
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) state <= S_IDLE;
    else state <= next_state;
end
always@(*) begin
    case(state)
        S_IDLE: if(tx_data_valid == 1'b1) next_state <= S_START;
        else next_state <= S_IDLE;
        S_START: if(cycle_cnt == CYCLE - 1) next_state <= S_SEND_BYTE;
        else next_state <= S_START;
        S_SEND_BYTE: if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) next_state <= S_STOP;
        else next_state <= S_SEND_BYTE;
        S_STOP: if(cycle_cnt == CYCLE - 1) next_state <= S_IDLE;
        else next_state <= S_STOP;
        default: next_state <= S_IDLE;
    endcase
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin tx_data_ready <= 1'b0; end
    else if(state == S_IDLE) if(tx_data_valid == 1'b1) tx_data_ready <= 1'b0;
    else tx_data_ready <= 1'b1;
    else if(state == S_STOP && cycle_cnt == CYCLE - 1) tx_data_ready <= 1'b1;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin tx_data_latch <= 8'd0; end
    else if(state == S_IDLE && tx_data_valid == 1'b1) tx_data_latch <= tx_data;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin bit_cnt <= 3'd0; end
    else if(state == S_SEND_BYTE) if(cycle_cnt == CYCLE - 1) bit_cnt <= bit_cnt + 3'd1;
    else bit_cnt <= bit_cnt;
    else bit_cnt <= 3'd0;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) cycle_cnt <= 16'd0;
    else if((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) cycle_cnt <= 16'd0;
    else cycle_cnt <= cycle_cnt + 16'd1;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) tx_reg <= 1'b1;
    else case(state)
        S_IDLE,S_STOP: tx_reg <= 1'b1;
        S_START: tx_reg <= 1'b0;
        S_SEND_BYTE: tx_reg <= tx_data_latch[bit_cnt];
        default: tx_reg <= 1'b1;
    endcase
end
endmodule
3.FPGA 串口接收

此处串口接收使用的也是 AX7035B 的例程(帧头为 FF 帧尾为 FE 第一个数据为第一个波的波形 第二个数据为第一个波的频率 第三个数据为第二个波的波形 第四个数据为第二个波的频率)

reg [7:0] data1,data2,data3,data4; reg rx_start; wire rx_done; reg [2:0] state;
uart_rx #( .CLK_FRE(50), //clock frequency(Mhz) .BAUD_RATE(115200) //serial baud rate )uart_rx (
    .clk(clk), //clock input
    .rst_n(rst), //asynchronous reset input, low active
    .rx_data(rx_data), //received serial data
    .rx_data_ready(rx_start), //data receiver module ready
    .rx_data_valid(rx_done), //received serial data is valid
    .rx_pin(rx_pin) //serial data input
);
always@(posedge clk or negedge rst) begin
    if(!rst) begin state<=0; data1<=0; data2<=0; data3<=0; data4<=0; end
    else begin
        case(state)
            3'b000: begin rx_start<=1; if(rx_done) begin if(rx_data==8'hFF) begin rx_start<=0; state<=state+1; data1<=0; data2<=0; data3<=0; data4<=0; end end end
            3'b001: begin rx_start<=1; if(rx_done) begin rx_start<=0; data1<=rx_data; state<=state+1; end end
            3'b010: begin rx_start<=1; if(rx_done) begin rx_start<=0; data2<=rx_data; state<=state+1; end end
            3'b011: begin rx_start<=1; if(rx_done) begin rx_start<=0; data3<=rx_data; state<=state+1; end end
            3'b100: begin rx_start<=1; if(rx_done) begin rx_start<=0; data4<=rx_data; state<=state+1; end end
            3'b101: begin rx_start<=1; if(rx_done) begin if(rx_data==8'hFE) begin rx_start<=0; state<=0; wave1<=data1; wave1_freq<=data2; wave2<=data3; wave2_freq<=data4; end else begin rx_start<=0; state<=0; data1<=0; data2<=0; data3<=0; data4<=0; end end end
        endcase
    end
end
module uart_rx #( parameter CLK_FRE = 50, //clock frequency(Mhz) parameter BAUD_RATE = 115200 //serial baud rate ) (
    input clk, //clock input
    input rst_n, //asynchronous reset input, low active
    output reg[7:0] rx_data, //received serial data
    output reg rx_data_valid, //received serial data is valid
    input rx_data_ready, //data receiver module ready
    input rx_pin //serial data input
);
//calculates the clock cycle for baud rate
localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
//state machine code
localparam S_IDLE = 1; localparam S_START = 2; //start bit
localparam S_REC_BYTE = 3; //data bits
localparam S_STOP = 4; //stop bit
localparam S_DATA = 5;
reg[2:0] state; reg[2:0] next_state;
reg rx_d0; //delay 1 clock for rx_pin
reg rx_d1; //delay 1 clock for rx_d0
wire rx_negedge; //negedge of rx_pin
reg[7:0] rx_bits; //temporary storage of received data
reg[15:0] cycle_cnt; //baud counter
reg[2:0] bit_cnt; //bit counter
assign rx_negedge = rx_d1 && ~rx_d0;
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin rx_d0 <= 1'b0; rx_d1 <= 1'b0; end
    else begin rx_d0 <= rx_pin; rx_d1 <= rx_d0; end
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) state <= S_IDLE;
    else state <= next_state;
end
always@(*) begin
    case(state)
        S_IDLE: if(rx_negedge) next_state <= S_START;
        else next_state <= S_IDLE;
        S_START: if(cycle_cnt == CYCLE - 1)//one data cycle
            next_state <= S_REC_BYTE;
        else next_state <= S_START;
        S_REC_BYTE: if(cycle_cnt == CYCLE - 1 && bit_cnt == 3'd7) //receive 8bit data
            next_state <= S_STOP;
        else next_state <= S_REC_BYTE;
        S_STOP: if(cycle_cnt == CYCLE/2 - 1)//half bit cycle,to avoid missing the next byte receiver
            next_state <= S_DATA;
        else next_state <= S_STOP;
        S_DATA: if(rx_data_ready) //data receive complete
            next_state <= S_IDLE;
        else next_state <= S_DATA;
        default: next_state <= S_IDLE;
    endcase
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) rx_data_valid <= 1'b0;
    else if(state == S_STOP && next_state != state) rx_data_valid <= 1'b1;
    else if(state == S_DATA && rx_data_ready) rx_data_valid <= 1'b0;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) rx_data <= 8'd0;
    else if(state == S_STOP && next_state != state) rx_data <= rx_bits;//latch received data
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin bit_cnt <= 3'd0; end
    else if(state == S_REC_BYTE) if(cycle_cnt == CYCLE - 1) bit_cnt <= bit_cnt + 3'd1;
    else bit_cnt <= bit_cnt;
    else bit_cnt <= 3'd0;
end
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) cycle_cnt <= 16'd0;
    else if((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state) cycle_cnt <= 16'd0;
    else cycle_cnt <= cycle_cnt + 16'd1;
end
//receive serial data bit data
always@(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) rx_bits <= 8'd0;
    else if(state == S_REC_BYTE && cycle_cnt == CYCLE/2 - 1) rx_bits[bit_cnt] <= rx_pin;
    else rx_bits <= rx_bits;
end
endmodule
4.总结

这是与 stm32 通信模块的所有代码

module stm32_communication(
    input clk,
    input rst,
    input clk_1M, //采样率 1MHz
    input [7:0] ADC,
    input rx_pin,
    output reg wave1,
    output reg [7:0] wave1_freq,
    output reg wave2,
    output reg [7:0] wave2_freq,
    output tx_pin
);
reg clk_100K_reg; //串口发送速率 12.5Khz
reg [11:0] clk_100K_cnt;
wire clk_100K = clk_100K_reg;
always@(posedge clk or negedge rst) begin
    if(!rst) begin clk_100K_cnt<=0; clk_100K_reg<=0; end
    else begin
        clk_100K_cnt<=clk_100K_cnt+1;
        if(clk_100K_cnt==1999) begin clk_100K_cnt<=0; clk_100K_reg<=~clk_100K_reg; end
    end
end
wire wr_en,rd_en,full,empty,wr_rst_busy,rd_rst_busy;
wire [10:0] rd_data_count,wr_data_count;
reg [7:0] w_data;
localparam W_IDLE = 1; localparam W_FIFO = 2;
localparam R_IDLE = 1; localparam R_FIFO = 2;
reg[2:0] write_state; reg[2:0] next_write_state;
reg[2:0] read_state; reg[2:0] next_read_state;
wire [7:0] rx_data,tx_data; reg tx_valid;
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) write_state <= W_IDLE;
    else write_state <= next_write_state;
end
always@(*) begin
    case(write_state)
        W_IDLE: if(empty == 1'b1) //FIFO is empty, start writing FIFO
            next_write_state <= W_FIFO;
        else next_write_state <= W_IDLE;
        W_FIFO: if(full == 1'b1) //FIFO is full
            next_write_state <= W_IDLE;
        else next_write_state <= W_FIFO;
        default: next_write_state <= W_IDLE;
    endcase
end
assign wr_en = (next_write_state == W_FIFO) ? 1'b1 : 1'b0;
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) w_data <= 16'd0;
    else if (wr_en == 1'b1) w_data <= ADC;
    else w_data <= ADC;
end
always@(posedge clk_1M or negedge rst) begin
    if(rst == 1'b0) read_state <= R_IDLE;
    else read_state <= next_read_state;
end
always@(*) begin
    case(read_state)
        R_IDLE: if(full == 1'b1) //FIFO is full, starting read FIFO
            next_read_state <= R_FIFO;
        else next_read_state <= R_IDLE;
        R_FIFO: if(empty == 1'b1) //FIFO is empty
            next_read_state <= R_IDLE;
        else next_read_state <= R_FIFO;
        default: next_read_state <= R_IDLE;
    endcase
end
reg [10:0] rd_data_count_last;
always@(posedge clk or negedge rst) begin
    if(!rst) rd_data_count_last<=0;
    else begin
        rd_data_count_last<=rd_data_count;
        if(rd_data_count_last!=rd_data_count && rd_en) tx_valid<=1;
        else tx_valid<=0;
    end
end
assign rd_en = (next_read_state == R_FIFO) ? 1'b1 : 1'b0;
fifo fifo (
    .rst(~rst), // input wire rst
    .wr_clk(clk_1M), // input wire wr_clk
    .rd_clk(clk_100K), // input wire rd_clk
    .din(w_data), // input wire [7 : 0] din
    .wr_en(wr_en), // input wire wr_en
    .rd_en(rd_en), // input wire rd_en
    .dout(tx_data), // output wire [7 : 0] dout
    .full(full), // output wire full
    .empty(empty), // output wire empty
    .rd_data_count(rd_data_count), // output wire [10 : 0] rd_data_count
    .wr_data_count(wr_data_count) // output wire [10 : 0] wr_data_count
);
reg [7:0] data1,data2,data3,data4; reg rx_start; wire rx_done; reg [2:0] state;
uart_rx #( .CLK_FRE(50), //clock frequency(Mhz) .BAUD_RATE(115200) //serial baud rate )uart_rx (
    .clk(clk), //clock input
    .rst_n(rst), //asynchronous reset input, low active
    .rx_data(rx_data), //received serial data
    .rx_data_ready(rx_start), //data receiver module ready
    .rx_data_valid(rx_done), //received serial data is valid
    .rx_pin(rx_pin) //serial data input
);
uart_tx#( .CLK_FRE(50), .BAUD_RATE(115200) //serial baud rate )uart_tx(
    .clk(clk), //clock input
    .rst_n(rst), //asynchronous reset input, low active
    .tx_data(tx_data), //data to send
    .tx_data_valid(tx_valid), //data to be sent is valid
    .tx_pin(tx_pin) //serial data output
);
always@(posedge clk or negedge rst) begin
    if(!rst) begin state<=0; data1<=0; data2<=0; data3<=0; data4<=0; end
    else begin
        case(state)
            3'b000: begin rx_start<=1; if(rx_done) begin if(rx_data==8'hFF) begin rx_start<=0; state<=state+1; data1<=0; data2<=0; data3<=0; data4<=0; end end end
            3'b001: begin rx_start<=1; if(rx_done) begin rx_start<=0; data1<=rx_data; state<=state+1; end end
            3'b010: begin rx_start<=1; if(rx_done) begin rx_start<=0; data2<=rx_data; state<=state+1; end end
            3'b011: begin rx_start<=1; if(rx_done) begin rx_start<=0; data3<=rx_data; state<=state+1; end end
            3'b100: begin rx_start<=1; if(rx_done) begin rx_start<=0; data4<=rx_data; state<=state+1; end end
            3'b101: begin rx_start<=1; if(rx_done) begin if(rx_data==8'hFE) begin rx_start<=0; state<=0; wave1<=data1; wave1_freq<=data2; wave2<=data3; wave2_freq<=data4; end else begin rx_start<=0; state<=0; data1<=0; data2<=0; data3<=0; data4<=0; end end end
        endcase
    end
end
endmodule

第二部分(stm32 接收数据进行 FFT 识别波形以及频率并发送)

1.stm32 串口接收

stm32 使用的是串口中断(使用 DMA 会更好)

HAL_UART_Receive_IT(&huart2,rx_data,2048); //打开串口中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART2) {
        for(int i=0;i<2048;i++) { wave_form[i]=rx_data[i]; //接收后转存出来 }
        HAL_UART_Receive_IT(&huart2,rx_data,2048);
    }
}
2.stm32 进行 FFT

stm32 导入 DSP 库并使用 FFT 的方法请参考官方文档。接下来我就直接介绍如何通过频域来识别两种波形以及他们的频率。

首先确定波的频率范围为 20KHz-100KHz,步进为 5KHz,所以我们可以只考虑 20KHz-100KHz 这个频率范围之间的能量。并且以 5KHz 的步进将能量聚集起来,得到能量最多的两个频段就是所需要的两个波形的频率,然后通过一个阈值比较,判断两个波的波形,较小的是三角波,较大的是正弦波

for (int i=35; i<1000;i++) //将 5KHz 以内的能量聚集起来
{
    freq = (int)(((float)i*1000000/2048/5000)+0.5);
    if(freq==freq_last) { v_max = FFT_output[i]*FFT_output[i]+v_max; }
    else { v_max = sqrt(v_max); freq_change[cnt] = v_max; fre[cnt] = freq; freq_last=freq; cnt++; }
}
v_max=0; cnt=0;
for(int i=0;i<100;i++) //计算出两个最大的能量的频率
{
    if(freq_change[i]>max[0]) { max[1]=max[0]; idx[1]=idx[0]; max[0]=freq_change[i]; idx[0]=2.5*(fre[i]-1); }
    else if(freq_change[i]>max[1] && freq_change[i]<max[0]) { max[1]=freq_change[i]; idx[1]=2.5*(fre[i]-1); }
}
if(max[0]>5.7) wave[0] = 0; //正弦波
else wave[0] = 1; //三角波
if(max[1]>5.7) wave[1] = 0;
else wave[1] = 1;

以下是 stm32 所有的代码

/* USER CODE BEGIN Header */
/** ******************************************************************************
 * @file : main.c
 * @brief : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2025 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "key.h"
#include "OLED.h"
#include "LED.h"
#include "math.h"
#include "arm_math.h"
#include "arm_const_structs.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
float max[2]; uint16_t idx[2]; uint16_t freq,freq_last;
float FFT_input[4096],FFT_output[2048]; float freq_change[2048];
uint16_t fre[2048]; uint16_t cnt; float v_max;
uint8_t wave[2]; uint8_t tx_data[6]; uint8_t flag;
uint8_t rx_data[2048]; uint8_t wave_form[2048];
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
char message[50];
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
 * @brief The application entry point.
 * @retval int
 */
int main(void) {
    /* USER CODE BEGIN 1 */
    /* USER CODE END 1 */
    /* MCU Configuration--------------------------------------------------------*/
    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();
    /* USER CODE BEGIN Init */
    /* USER CODE END Init */
    /* Configure the system clock */
    SystemClock_Config();
    /* USER CODE BEGIN SysInit */
    /* USER CODE END SysInit */
    /* Initialize all configured peripherals */
    MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM2_Init(); MX_TIM4_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init();
    /* USER CODE BEGIN 2 */
    HAL_UART_Receive_IT(&huart2,rx_data,2048); OLED_Init();
    OLED_PrintString(0,0,"Init Success",&font16x16,OLED_COLOR_NORMAL);
    OLED_ShowFrame(); HAL_Delay(500);
    /* USER CODE END 2 */
    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1) {
        for (int i = 0; i < 2048; i++) {
            FFT_input[i * 2] = wave_form[i]; FFT_input[i * 2 + 1] = 0;
        }
        arm_cfft_f32(&arm_cfft_sR_f32_len2048, FFT_input, 0, 1);
        arm_cmplx_mag_f32(FFT_input, FFT_output, 2048);
        FFT_output[0] /= 2048;
        for (int i = 1; i < 2048; i++) { FFT_output[i] /= 1024; }
        for (int i=35; i<1000;i++) {
            freq = (int)(((float)i*1000000/2048/5000)+0.5);
            if(freq==freq_last) { v_max = FFT_output[i]*FFT_output[i]+v_max; }
            else { v_max = sqrt(v_max); freq_change[cnt] = v_max; fre[cnt] = freq; freq_last=freq; cnt++; }
        }
        v_max=0; cnt=0;
        for (int i = 0; i < 25; i++) { printf("%d,%.2f\n",fre[i],freq_change[i]); }
        for(int i=0;i<100;i++) {
            if(freq_change[i]>max[0]) { max[1]=max[0]; idx[1]=idx[0]; max[0]=freq_change[i]; idx[0]=2.5*(fre[i]-1); }
            else if(freq_change[i]>max[1] && freq_change[i]<max[0]) { max[1]=freq_change[i]; idx[1]=2.5*(fre[i]-1); }
        }
        if(max[0]>5.7) wave[0] = 0; //正弦波
        else wave[0] = 1; //三角波
        if(max[1]>5.7) wave[1] = 0;
        else wave[1] = 1;
        if(idx[0]>idx[1]) {
            tx_data[0]=0xff; tx_data[1]=wave[1]; tx_data[2]=idx[1];
            tx_data[3]=wave[0]; tx_data[4]=idx[0]; tx_data[5]=0xfe;
        } else {
            tx_data[0]=0xff; tx_data[1]=wave[0]; tx_data[2]=idx[0];
            tx_data[3]=wave[1]; tx_data[4]=idx[1]; tx_data[5]=0xfe;
        }
        HAL_UART_Transmit(&huart2,tx_data,6,HAL_MAX_DELAY);
        OLED_NewFrame();
        sprintf(message,"%d : %d %.2f",wave[0],idx[0],max[0]);
        OLED_PrintString(0,0,message,&font16x16,OLED_COLOR_NORMAL);
        sprintf(message,"%d : %d %.2f",wave[1],idx[1],max[1]);
        OLED_PrintString(0,20,message,&font16x16,OLED_COLOR_NORMAL);
        OLED_ShowFrame();
        for(uint8_t i=0;i<2;i++) { max[i]=0; idx[i]=0; wave[i]=0; }
        /* USER CODE END WHILE */
        /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}
/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    /** Configure the main internal regulator output voltage */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 6; RCC_OscInitStruct.PLL.PLLN = 168;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); }
    /** Initializes the CPU, AHB and APB buses clocks */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); }
}
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART2) {
        for(int i=0;i<2048;i++) { wave_form[i]=rx_data[i]; }
        HAL_UART_Receive_IT(&huart2,rx_data,2048);
    }
}
/* USER CODE END 4 */
/**
 * @brief This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void) {
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1) { }
    /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
 * @brief Reports the name of the source file and the source line number
 * where the assert_param error has occurred.
 * @param file: pointer to the source file name
 * @param line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line) {
    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
3.stm32 串口发送

由于 A 波的频率始终小于 B 波,所以我们要先判断那个是 A 波哪个是 B 波,然后通过串口发送给 FPGA

if(idx[0]>idx[1]) {
    x_data[0]=0xff; tx_data[1]=wave[1]; tx_data[2]=idx[1];
    tx_data[3]=wave[0]; tx_data[4]=idx[0]; tx_data[5]=0xfe;
} else {
    tx_data[0]=0xff; tx_data[1]=wave[0]; tx_data[2]=idx[0];
    tx_data[3]=wave[1]; tx_data[4]=idx[1]; tx_data[5]=0xfe;
}
HAL_UART_Transmit(&huart2,tx_data,6,HAL_MAX_DELAY);

第三部分(FPGA 得到波形与频率后生成波形)

需要一个 DDS 模块,使用 ROM 存储正弦波和三角波的点

module dds( input clk, input rst, input wave1, //A 波波形
    input wave2, //B 波波形
    input [31:0] freq1, //A 波频率
    input [31:0] freq2, //B 波频率
    input [9:0] phase1, //A 波相位
    input [9:0] phase2, //B 波相位
    input [9:0] de_phase, //相位偏移
    output [13:0]dataout1, //A'波
    output [13:0]dataout2, //B'波
    output [13:0]dataout2_delay //B'波偏移
);
//波形数据:
reg [13:0] wavedata1,wavedata2,wavedata3;
//波形信号:
wire [13:0] sindata1,sindata2,sindata3;
wire [13:0] tridata1,tridata2,tridata3;
//相位寄存器:
reg [31:0]frechange1,frechange2;
always @(posedge clk or negedge rst) begin
    if(!rst) begin frechange1 <= 32'd0; frechange2 <= 32'd0; end
    else begin frechange1 <= frechange1 + freq1; frechange2 <= frechange2 + freq2; end
end
//相位累加器:
reg [9:0]romaddr1,romaddr2,romaddr3;
always @(posedge clk or negedge rst) begin
    if(!rst) begin romaddr1 <= 10'd0; romaddr2 <= 10'd0; end
    else begin romaddr1 <= frechange1[31:22] + phase1; romaddr2 <= frechange2[31:22] + phase2; romaddr3 <= romaddr2 + de_phase; end
end
ROM_sin romsin1 ( .clka(clk), .addra(romaddr1), .douta(sindata1) );
ROM_tri romtri1 ( .clka(clk), .addra(romaddr1), .douta(tridata1) );
ROM_sin romsin2 ( .clka(clk), .addra(romaddr2), .douta(sindata2) );
ROM_tri romtri2 ( .clka(clk), .addra(romaddr2), .douta(tridata2) );
ROM_sin romsin3 ( .clka(clk), .addra(romaddr3), .douta(sindata3) );
ROM_tri romtri3 ( .clka(clk), .addra(romaddr3), .douta(tridata3) );
always @(*) begin
    case(wave1)
        1'b0: wavedata1<= sindata1;
        1'b1: wavedata1<= tridata1;
        default: wavedata1<= sindata1;
    endcase
end
always @(*) begin
    case(wave2)
        1'b0: wavedata2<= sindata2;
        1'b1: wavedata2<= tridata2;
        default: wavedata2<= sindata2;
    endcase
end
always @(*) begin
    case(wave2)
        1'b0: wavedata3<= sindata3;
        1'b1: wavedata3<= tridata3;
        default: wavedata3<= sindata3;
    endcase
end
assign dataout1 = wavedata1;
assign dataout2 = wavedata2;
assign dataout2_delay = wavedata3;
endmodule
dds ddsA( .clk(clk), .rst(rst), .wave1(wave1), .wave2(wave2),
    .freq1(freq1*32'd85899), .freq2(freq2*32'd85899),
    .phase1(A_phase[21:12]), .phase2(B_phase[21:12]),
    .de_phase((((phase0+phase)*91)>>5)),
    .dataout1(da1_data), .dataout2(da2_data_start), .dataout2_delay(da2_data) );

第四部分(FPGA 锁相)

1.鉴相

对 AB 两路信号与输入的 C 信号进行鉴相处理(即相乘再过一个低通滤波器,乘法器和低通滤波器都是使用的 VIVADO 的 ip 核)

mult_mix mult_mix_A ( .CLK(clk), // input wire CLK
    .A(ADC_signed), // input wire [11 : 0] A
    .B(DAC_A), // input wire [13 : 0] B
    .P(mix_A) // output wire [25 : 0] P
);
LPF LPF_A ( .aclk(clk), // input wire aclk
    .s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
    .s_axis_data_tdata(A_filter), // input wire [23 : 0] s_axis_data_tdata
    .m_axis_data_tdata(A_filter_out) // output wire [23 : 0] m_axis_data_tdata
);
mult_mix mult_mix_B ( .CLK(clk), // input wire CLK
    .A(ADC_signed), // input wire [11 : 0] A
    .B(DAC_B), // input wire [13 : 0] B
    .P(mix_B) // output wire [25 : 0] P
);
LPF LPF_B ( .aclk(clk), // input wire aclk
    .s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
    .s_axis_data_tdata(B_filter), // input wire [23 : 0] s_axis_data_tdata
    .m_axis_data_tdata(B_filter_out) // output wire [23 : 0] m_axis_data_tdata
);
2.环路滤波

环路滤波器就相当于是一个 PI 电路(即差距和积分电路,只需要调节它的 Kp 和 Ki 参数使得波形不抖动就算调节成功)

Loop_filter Loop_filter_A( .clk(clk), .rst(rst), .i_pd(A_filter_out), .o_frequency_df(A_phase) );
Loop_filter Loop_filter_B( .clk(clk), .rst(rst), .i_pd(B_filter_out), .o_frequency_df(B_phase) );
module Loop_filter( input clk, input rst, input signed [23:0] i_pd, output signed [23:0] o_frequency_df );
reg signed [23:0] sum_d;
wire signed [23:0] pd_c2, pd_c1,sum;
assign pd_c1 = {{3{i_pd[23]}},i_pd[23:3]}; //c1
assign pd_c2 = {{12{i_pd[23]}},i_pd[23:12]}; //c2
always@(posedge clk or negedge rst) begin
    if(!rst) sum_d <= 0;
    else sum_d <= sum;
end
assign sum = pd_c2 + sum_d;
assign o_frequency_df = sum_d + pd_c1;
endmodule
3.反馈

将输出的 phase 信号反馈到 DDS 模块上

.phase1(A_phase[21:12]), .phase2(B_phase[21:12]),

第五部分(DAC 输出)

.dataout1(da1_data), .dataout2_delay(da2_data)
assign da1_clk = clk; assign da1_wrt = clk;
assign da2_clk = clk; assign da2_wrt = clk;

第六部分(移相)

通过前五个部分已经可以实现除开移相的所有功能

接下来我们要使用按键来控制 A'和 B'的相位差并且能在数码管上显示

1.按键消抖
module key ( input clk, input reset, input [3:0] key, output [3:0] key_num );
reg[31:0] timer; reg[3:0] key_first; reg[3:0] key_second; reg[3:0] key_now;
assign key_num=key_now;
always@(posedge clk or negedge reset) begin
    if(reset==0) begin key_now<=4'b0000; key_first<=4'b1111; key_second<=4'b1111; timer<=32'd0; end
    else begin
        if(key!=4'b1111) begin key_first<=key; timer<=timer+32'd1; end
        else begin timer<=32'd0; if(key_first==key_second) begin key_now<=~key_first; key_second<=0; end else key_now<=0; end
        if(timer==32'd999_999) key_second<=key;
    end
end
endmodule
2.按键设置相位差

需要用前三个按键将相位手动调零,然后按下第四个按键表示调零完毕,再通过前三个按键设置相位差

key key_inst ( .clk(clk), .reset(rst), .key(key), .key_num(key_num) );
always@(posedge clk or negedge rst) begin
    if(!rst) begin phase<=0; phase0<=0; end
    else begin
        if(key_num == 4'b0001) begin phase<=phase+1; end
        else if(key_num == 4'b0010) begin phase<=phase+5; end
        else if(key_num == 4'b0100) begin phase<=phase+30; end
        else if(key_num == 4'b1000) begin phase0<=phase; phase<=0; end
        if(phase0 == 0) begin if(phase>360) phase<=phase-360; end
        else begin if(phase>180) phase<=phase-180; end
    end
end
3.数码管显示相位
module nixie( input clk, input rst, input [7:0] data, output [7:0] SMG_Data, output [5:0] Scan_Sig );
reg [5:0] sig; assign Scan_Sig = sig;
reg [7:0] num; reg [31:0] timer; reg clk_low; reg [9:0] count;
always@(posedge clk or negedge rst) begin
    if(!rst) begin count<=0; clk_low<=0; end
    else begin count<=count+1; if(count==49) begin count<=0; clk_low <= ~clk_low; end
    end
end
always@(posedge clk_low or negedge rst) begin
    if(!rst) begin num<=8'd0; timer<=0; end
    else begin timer<=timer+1;
        if(timer==50) begin sig=6'b011111; num = 8'h11; end
        else if(timer==100) begin sig=6'b101111; num = 8'h11; end
        else if(timer==150) begin sig=6'b110111; num = 8'h11; end
        else if(timer==200) begin sig=6'b111011; num = 8'h11; end
        else if(timer==250) begin sig=6'b111101; num = 8'h11; end
        else if(timer==300) begin sig=6'b111110; num = 8'h11; timer<=0; end
        if(sig == 6'b011111) begin num <= ~(8'h73); end
        else if(sig == 6'b101111) begin num <= ~(8'h74); end
        else if(sig == 6'b110111) begin num <= ~(8'h40); end
        else if(sig == 6'b111011) begin case(data%1000/100)
                1: num<=~(8'h06); 2: num<=~(8'h5b); 3: num<=~(8'h4f); 4: num<=~(8'h66); 5: num<=~(8'h6d); 6: num<=~(8'h7d); 7: num<=~(8'h07); 8: num<=~(8'h7f); 9: num<=~(8'h6f); 0: num<=~(8'h3f); endcase
            end
        else if(sig == 6'b111101) begin case(data%100/10)
                1: num<=~(8'h06); 2: num<=~(8'h5b); 3: num<=~(8'h4f); 4: num<=~(8'h66); 5: num<=~(8'h6d); 6: num<=~(8'h7d); 7: num<=~(8'h07); 8: num<=~(8'h7f); 9: num<=~(8'h6f); 0: num<=~(8'h3f); endcase
            end
        else if(sig == 6'b111110) begin case(data%10)
                1: num<=~(8'h06); 2: num<=~(8'h5b); 3: num<=~(8'h4f); 4: num<=~(8'h66); 5: num<=~(8'h6d); 6: num<=~(8'h7d); 7: num<=~(8'h07); 8: num<=~(8'h7f); 9: num<=~(8'h6f); 0: num<=~(8'h3f); endcase
            end
    end
end
assign SMG_Data = num;
endmodule
nixie nixie_inst( .clk(clk), .rst(rst), .data(phase), .SMG_Data(SMG_Data), .Scan_Sig(Scan_Sig) );

第七部分(FPGA 代码总结)

以下是 top 模块的所有代码

module top( input clk, input rst, input [11:0] ad9238_data_ch0, input rx_pin, input [3:0] key,
    output [7:0] SMG_Data, output [5:0] Scan_Sig, output tx_pin,
    output ad9238_clk_ch0, output da1_clk, output da1_wrt, output [13:0] da1_data,
    output da2_clk, output da2_wrt, output [13:0] da2_data
);
reg [7:0] phase,phase0;
wire [13:0] da2_data_start;
wire signed [11:0] ADC_signed = ad9238_data_ch0 - 12'd2047;
wire signed [13:0] DAC_A = da1_data - 14'd8191;
wire signed [13:0] DAC_B = da2_data_start - 14'd8191;
wire signed [25:0] mix_A,mix_B;
wire signed [23:0] A_filter = mix_A>>2,B_filter = mix_B>>2;
wire signed [23:0] A_filter_out,B_filter_out,A_phase,B_phase;
reg clk_1M_reg; reg [7:0] clk_1M_cnt;
wire clk_1M = clk_1M_reg;
wire wave1,wave2;
wire [7:0] freq1,freq2;
wire [7:0] ADC_send = ad9238_data_ch0>>4;
wire [2:0] N = (freq1&&freq2) ? (freq2/freq1) : 1;
always@(posedge clk or negedge rst) begin
    if(!rst) begin clk_1M_cnt<=0; clk_1M_reg<=0; end
    else begin clk_1M_cnt<=clk_1M_cnt+1; if(clk_1M_cnt==24) begin clk_1M_cnt<=0; clk_1M_reg<=~clk_1M_reg; end
    end
end
assign ad9238_clk_ch0 = clk_1M;
assign da1_clk = clk; assign da1_wrt = clk;
assign da2_clk = clk; assign da2_wrt = clk;
stm32_communication stm32_communication(
    .clk(clk), .rst(rst), .clk_1M(clk_1M), .ADC(ADC_send), .rx_pin(rx_pin),
    .wave1(wave1), .wave1_freq(freq1), .wave2(wave2), .wave2_freq(freq2), .tx_pin(tx_pin)
);
dds ddsA( .clk(clk), .rst(rst), .wave1(wave1), .wave2(wave2),
    .freq1(freq1*32'd85899), .freq2(freq2*32'd85899),
    .phase1(A_phase[21:12]), .phase2(B_phase[21:12]),
    .de_phase((((phase0+phase)*91)>>5)),
    .dataout1(da1_data), .dataout2(da2_data_start), .dataout2_delay(da2_data)
);
mult_mix mult_mix_A ( .CLK(clk), // input wire CLK
    .A(ADC_signed), // input wire [11 : 0] A
    .B(DAC_A), // input wire [13 : 0] B
    .P(mix_A) // output wire [25 : 0] P
);
LPF LPF_A ( .aclk(clk), // input wire aclk
    .s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
    .s_axis_data_tdata(A_filter), // input wire [23 : 0] s_axis_data_tdata
    .m_axis_data_tdata(A_filter_out) // output wire [23 : 0] m_axis_data_tdata
);
Loop_filter Loop_filter_A( .clk(clk), .rst(rst), .i_pd(A_filter_out), .o_frequency_df(A_phase) );
mult_mix mult_mix_B ( .CLK(clk), // input wire CLK
    .A(ADC_signed), // input wire [11 : 0] A
    .B(DAC_B), // input wire [13 : 0] B
    .P(mix_B) // output wire [25 : 0] P
);
LPF LPF_B ( .aclk(clk), // input wire aclk
    .s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
    .s_axis_data_tdata(B_filter), // input wire [23 : 0] s_axis_data_tdata
    .m_axis_data_tdata(B_filter_out) // output wire [23 : 0] m_axis_data_tdata
);
Loop_filter Loop_filter_B( .clk(clk), .rst(rst), .i_pd(B_filter_out), .o_frequency_df(B_phase) );
wire [3:0] key_num;
key key_inst ( .clk(clk), .reset(rst), .key(key), .key_num(key_num) );
nixie nixie_inst( .clk(clk), .rst(rst), .data(phase), .SMG_Data(SMG_Data), .Scan_Sig(Scan_Sig) );
always@(posedge clk or negedge rst) begin
    if(!rst) begin phase<=0; phase0<=0; end
    else begin
        if(key_num == 4'b0001) begin phase<=phase+1; end
        else if(key_num == 4'b0010) begin phase<=phase+5; end
        else if(key_num == 4'b0100) begin phase<=phase+30; end
        else if(key_num == 4'b1000) begin phase0<=phase; phase<=0; end
        if(phase0 == 0) begin if(phase>360) phase<=phase-360; end
        else begin if(phase>180) phase<=phase-180; end
    end
end
//ila ila ( // .clk(clk), // input wire clk // .probe0(mix_B), // input wire [25:0] probe0 // .probe1(B_filter), // input wire [23:0] probe1 // .probe2(B_filter_out), // input wire [23:0] probe2 // .probe3(B_phase[23:15]) // input wire [8:0] probe3 //);
endmodule

目录

  1. 第一部分(FPGA 的 FIFO 以及串口发送接收)
  2. 1.FIFO
  3. 2.(FPGA 串口发送)
  4. 3.FPGA 串口接收
  5. 4.总结
  6. 第二部分(stm32 接收数据进行 FFT 识别波形以及频率并发送)
  7. 1.stm32 串口接收
  8. 2.stm32 进行 FFT
  9. 3.stm32 串口发送
  10. 第三部分(FPGA 得到波形与频率后生成波形)
  11. 第四部分(FPGA 锁相)
  12. 1.鉴相
  13. 2.环路滤波
  14. 3.反馈
  15. 第五部分(DAC 输出)
  16. 第六部分(移相)
  17. 1.按键消抖
  18. 2.按键设置相位差
  19. 3.数码管显示相位
  20. 第七部分(FPGA 代码总结)
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Vue 组件 Prop 验证与枚举值最佳实践
  • 声源定位算法 1:CBF 原理与时延补偿模型
  • GitHub Actions 集成 PyTorch-CUDA-v2.6 镜像实现 CI/CD 自动化
  • llama.cpp 高效部署与使用指南
  • 数组算法总结:二分查找、快慢指针、双指针与滑动窗口
  • Whisper 语音识别技术:本地部署与使用指南
  • Stable Diffusion v1.5 风格迁移实战:照片转油画/水彩/像素风
  • JeecgBoot 低代码平台 AI 功能与零代码开发指南
  • 利用 AI 自动生成符合 xxxxxl19d18–19 规范的 Python 项目结构
  • 自然语言处理在客户服务领域的实战应用
  • VS Code 中 GitHub Copilot 代理与 AI 编程实战指南
  • IntelliJ IDEA 打包 Web 项目 WAR 包及 Tomcat 部署指南
  • Claude Code 本地环境配置与使用指南
  • AWS Kiro 账号池管理系统:OpenAI 兼容代理服务,支持多账号与 OIDC 认证
  • Java 核心面试题与答案详解
  • SpringBoot 整合 LangChain4j 实现 RAG 检索实战
  • Python 核心数据结构:集合与冻结集合
  • 钉钉 Webhook 完全指南:@用户通知与多语言实现
  • Coze 打造专属 AI 应用:从智能体开发到 Web 部署
  • KingbaseES 内核级 SQL 防火墙:白名单机制实现精准防护

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online