跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具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/5/2319 浏览
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 代码总结)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Linux 下 libwebkit2gtk-4.1-0 安装与使用指南
  • 大模型书单指南:如何快速选择适合你的书籍
  • VRCT 智能翻译工具:解决 VRChat 跨语言交流问题
  • LangChain 消息处理实战:缓存、过滤、合并与流式输出
  • 基于 Gemini 3 Pro Image 模型的 AI 科研绘图实测:机制图与技术路线图生成
  • 基于 Claude MCP 协议的智能体落地示例
  • 通义万相 2.1 开源视频生成模型功能解析
  • 前端通用 AI Rules 规范,适用于 Cursor、Trae、Qoder 等主流 AI 代码助手
  • 现代 CMOS 工艺设计套件(PDK)架构、演进与实战核心
  • 利用 AI 与 Apache ECharts 快速生成专业数据可视化图表
  • Visual C++ 运行库安装与故障排查指南
  • OpenClaw macOS 安装配置教程
  • macOS Web:基于 Web 技术的 macOS 桌面模拟器
  • faster-whisper 高性能语音转文字实现与优化指南
  • Ubuntu 22.04, Isaac Sim 5.1.0 + Isaac Lab 2.3.0 Conda 环境安装指南
  • AI 安全:基于 PGD 的 Stable Diffusion 视觉提示词注入攻击
  • 微信小程序通过阿里云 IoT 实现设备互联
  • Java 集合框架进阶:Map 接口深度解析与实战
  • Java volatile 关键字:底层原理、应用场景与最佳实践
  • YOLOv8 开发环境配置实战:Python、PyTorch 与 CUDA 搭建

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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