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

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

综述由AI生成2023 年电赛 H 题信号分离装置的 FPGA 与 STM32 联合解决方案。系统通过高速 ADC 采集混合信号 C,利用 FPGA 进行 FIFO 缓存及串口通信,将数据发送至 STM32。STM32 端运行 FFT 算法识别输入信号的频率及波形类型(正弦波或三角波),并将结果回传至 FPGA。FPGA 端基于 DDS 技术重构波形,并结合锁相环(PLL)消除时钟差异导致的相位漂移,最终通过 DAC 输出分离后的 A'和 B'信号。此外,系统支持通过按键调整相位差并在数码管显示。该方案有效解决了多信号分离与同步问题。

灰度发布发布于 2026/4/6更新于 2026/5/2024 浏览
2023 年电赛 H 题信号分离装置 FPGA 与 STM32 实现方案

前言

本文章除开要求一使用的增益为一的加法器以外,其余皆由 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 为参考例程,其中写入速率为 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_vilid;
always@(posedge clk_1M or negedge rst) begin 
    if(rst == 1'b0) write_state <= W_IDLE; 
    else write_state <= next_write_state; 
end
// ... (FIFO 状态机逻辑)
2.(FPGA 串口发送)

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

uart_tx#( .CLK_FRE(50), .BAUD_RATE(115200) )uart_tx(
    .clk(clk), 
    .rst_n(rst), 
    .tx_data(tx_data), 
    .tx_data_valid(tx_vilid), 
    .tx_pin(tx_pin)
);
3.FPGA 串口接收

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

module uart_rx #( parameter CLK_FRE = 50, parameter BAUD_RATE = 115200 ) (
    input clk, 
    input rst_n, 
    output reg[7:0] rx_data, 
    output reg rx_data_valid, 
    input rx_data_ready, 
    input rx_pin
);
// ... (UART 接收逻辑)
4.总结

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

module stm32_communication( 
    input clk, input rst, input clk_1M, 
    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 
); 
// ... (通信模块逻辑)

第二部分(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 主要的处理逻辑代码。

/* 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 */
// ... (变量定义)
int main(void) {
    /* MCU Configuration--------------------------------------------------------*/
    HAL_Init();
    SystemClock_Config();
    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();
    /* USER CODE END 2 */
    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; }
        // ... (FFT 处理与波形识别逻辑)
        HAL_UART_Transmit(&huart2,tx_data,6,HAL_MAX_DELAY);
    }
}
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 ); 
// ... (DDS 逻辑)

第四部分(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) );
// ... (B 路类似)
2.环路滤波

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

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 输出)

.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 ); 
// ... (消抖逻辑)
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 ); 
// ... (数码管驱动逻辑)

第七部分(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 ); 
// ... (顶层模块实例化与连接)
endmodule

目录

  1. 前言
  2. 题目
  3. 解题思路
  4. 基本框架
  5. 代码思路
  6. 第一部分(FPGA 的 FIFO 以及串口发送接收)
  7. 1.FIFO
  8. 2.(FPGA 串口发送)
  9. 3.FPGA 串口接收
  10. 4.总结
  11. 第二部分(STM32 接收数据进行 FFT 识别波形以及频率并发送)
  12. 1.STM32 串口接收
  13. 2.STM32 进行 FFT
  14. 3.STM32 串口发送
  15. 第三部分(FPGA 得到波形与频率后生成波形)
  16. 第四部分(FPGA 锁相)
  17. 1.鉴相
  18. 2.环路滤波
  19. 3.反馈
  20. 第五部分(DAC 输出)
  21. 第六部分(移相)
  22. 1.按键消抖
  23. 2.按键设置相位差
  24. 3.数码管显示相位
  25. 第七部分(FPGA 代码总结)
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Web 打印最简方案:HttpPrinter 实现跨浏览器打印
  • 基于 Django-Flask 的关爱空巢老人与留守儿童管理系统设计与实现
  • Vivado 2023.2 安装与 FPGA 开发环境搭建指南
  • C++ STL 标准库算法详解与实战
  • Git 如何将特定提交合并到另一个分支?
  • 医疗 AI 新范式:数理模型重构传统大模型
  • Python 办公自动化:Word 文档操作与报告生成
  • AI 时代产品经理的进化方向与核心能力
  • Linux 多路转接 IO 模型详解:select
  • JiuwenClaw AI 智能体实战:从安装到多端协同应用
  • 二分算法实战:A-B 数对与烦恼的高考志愿
  • Java AI 编程实测:从自然语言生成完整工程,初中级开发者效率分析
  • 前缀和算法详解:原理、模板与经典应用
  • 高鋒集團合夥人黃俊瑯:資本與生態賦能傳統企業 Web3 轉型
  • Polar CTF Web 简单题目解题思路总结
  • 网络安全从零开始入门学习路线与基础指南
  • 使用 Bright Data Web Scraper API + Python 高效抓取 Glassdoor 数据:从配置到结构化输出全流程实战
  • EasyOCR Python 开源 OCR 工具实战指南
  • 无人机远程路径规划:A*算法与GPS精准定位
  • C++ 高精度时间库 chrono 详解

相关免费在线工具

  • 加密/解密文本

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