跳到主要内容
2023 年电赛 H 题:信号分离装置 FPGA 与 STM32 实现方案 | 极客日志
C 算法
2023 年电赛 H 题:信号分离装置 FPGA 与 STM32 实现方案 2023 年电赛 H 题信号分离装置的 FPGA 与 STM32 联合设计方案。系统通过高速 ADC 采集混合信号,利用 FPGA 进行数据缓冲与串口传输。STM32 端接收数据并执行 FFT 算法识别波形类型及频率,随后将结果回传至 FPGA。FPGA 基于 DDS 技术重构波形,并通过锁相环(PLL)消除时钟漂移,最终经 DAC 输出分离后的信号。此外,系统支持按键移相及数码管显示相位差功能。
战神 发布于 2026/4/6 更新于 2026/5/20 22 浏览解题思路
题目要求从信号发生器输出两路波形 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),
.wr_clk(clk_1M),
.rd_clk(clk_100K),
.din(w_data),
.wr_en(wr_en),
.rd_en(rd_en),
.dout(tx_data),
.full(full),
.empty(empty),
.rd_data_count(rd_data_count),
.wr_data_count(wr_data_count)
);
2.(FPGA 串口发送) 此处串口发送使用的也是黑金 AX7035B 的例程(在 FIFO 中每读一个数就通过串口发送出去)。
uart_tx#(
.CLK_FRE(50),
.BAUD_RATE(115200) //serial baud rate
)uart_tx(
.clk(clk),
.rst_n(rst),
.tx_data(tx_data),
.tx_data_valid(tx_valid),
.tx_pin(tx_pin)
);
module uart_tx #(
parameter CLK_FRE = 50,
parameter BAUD_RATE = 115200
) (
input clk,
input rst_n,
input[7:0] tx_data,
input tx_data_valid,
output reg tx_data_ready,
output tx_pin
);
localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;
localparam S_IDLE = 1;
localparam S_START = 2;
localparam S_SEND_BYTE = 3;
localparam S_STOP = 4;
reg[2:0] state;
reg[2:0] next_state;
reg[15:0] cycle_cnt;
reg[2:0] bit_cnt;
reg[7:0] tx_data_latch;
reg tx_reg;
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
// ... (omitted for brevity in summary, logic remains consistent)
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),
.BAUD_RATE(115200)
)uart_rx (
.clk(clk),
.rst_n(rst),
.rx_data(rx_data),
.rx_data_ready(rx_start),
.rx_data_valid(rx_done),
.rx_pin(rx_pin)
);
// ... (state machine logic for parsing frame header/footers and data)
4.总结 这是与 stm32 通信模块的所有代码,整合了 FIFO、UART Tx/Rx 逻辑。
第二部分(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++) {
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 主要的处理逻辑(已去除自动生成的版权头尾及调试打印):
#include "main.h"
#include "arm_math.h"
float max[2 ]; uint16_t idx[2 ]; float 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 rx_data[2048 ]; uint8_t wave_form[2048 ];
int main (void ) {
HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init();
HAL_UART_Receive_IT(&huart2,rx_data,2048 );
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 ;
}
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);
for (uint8_t i=0 ;i<2 ;i++) { max[i]=0 ; idx[i]=0 ; wave[i]=0 ; }
}
}
3.stm32 串口发送 由于 A 波的频率始终小于 B 波,所以我们要先判断那个是 A 波哪个是 B 波,然后通过串口发送给 FPGA。
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);
第三部分(FPGA 得到波形与频率后生成波形) 需要一个 DDS 模块,使用 ROM 存储正弦波和三角波的点。
module dds(
input clk, input rst,
input wave1, input wave2,
input [31:0] freq1, input [31:0] freq2,
input [9:0] phase1, input [9:0] phase2,
input [9:0] de_phase,
output [13:0]dataout1, output [13:0]dataout2, output [13:0]dataout2_delay
);
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) );
// ... similar instances for wave2 and delayed wave2 ...
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
assign dataout1 = wavedata1; assign dataout2 = wavedata2; assign dataout2_delay = wavedata3;
endmodule
第四部分(FPGA 锁相)
1.鉴相 对 AB 两路信号与输入的 C 信号进行鉴相处理(即相乘再过一个低通滤波器,乘法器和低通滤波器都是使用的 VIVADO 的 ip 核)。
mult_mix mult_mix_A ( .CLK(clk), .A(ADC_signed), .B(DAC_A), .P(mix_A) );
LPF LPF_A ( .aclk(clk), .s_axis_data_tvalid(1'b1), .s_axis_data_tdata(A_filter), .m_axis_data_tdata(A_filter_out) );
mult_mix mult_mix_B ( .CLK(clk), .A(ADC_signed), .B(DAC_B), .P(mix_B) );
LPF LPF_B ( .aclk(clk), .s_axis_data_tvalid(1'b1), .s_axis_data_tdata(B_filter), .m_axis_data_tdata(B_filter_out) );
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]};
assign pd_c2 = {{12{i_pd[23]}},i_pd[23:12]};
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 输出) .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 );
// ... scanning logic and segment mapping ...
endmodule
nixie nixie_inst( .clk(clk), .rst(rst), .data(phase), .SMG_Data(SMG_Data), .Scan_Sig(Scan_Sig) );
第七部分(FPGA 代码总结) 以下是 top 模块的所有代码,整合了通信、DDS、PLL、控制等模块。
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), .A(ADC_signed), .B(DAC_A), .P(mix_A) );
LPF LPF_A ( .aclk(clk), .s_axis_data_tvalid(1'b1), .s_axis_data_tdata(A_filter), .m_axis_data_tdata(A_filter_out) );
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), .A(ADC_signed), .B(DAC_B), .P(mix_B) );
LPF LPF_B ( .aclk(clk), .s_axis_data_tvalid(1'b1), .s_axis_data_tdata(B_filter), .m_axis_data_tdata(B_filter_out) );
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
endmodule
相关免费在线工具 加密/解密文本 使用加密算法(如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