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

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

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

灰度发布发布于 2026/4/6更新于 2026/7/2547 浏览
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 代码总结)
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Python 在 CentOS 系统环境部署与配置实战指南
  • Mastering GitHub Copilot 课程点评:免费版与 Pro 版差异解析
  • 人工智能大模型的多模态融合与跨领域应用实战
  • 转行网络安全两年:行业现状分析与学习路径建议
  • GLM-4.6V-Flash-WEB API 额度管理与本地部署指南
  • Linux iperf3 网络带宽与质量测试工具详解
  • VSCode 插件 Git Graph 可视化 Git 提交记录教程
  • Qwen3-32B 本地部署:Clawdbot 网关与企业微信/钉钉集成实战
  • Stable Diffusion 秋叶绘世整合包与 ComfyUI 工具介绍
  • 飞书机器人联动 Claude Code:打造移动端 AI 编程助手
  • DeerFlow 基础教程:控制台 UI 与 Web UI 双模式使用详解
  • HDFS 读写究竟做了什么?从 Java 代码看数据流内幕
  • OpenClaw 自动化与定时任务实战指南
  • GitHub Copilot 学生身份认证与激活指南
  • 学术论文降低 AI 检测率的实操方法与工具推荐
  • GitHub Copilot 学生认证流程详解与 Pro 版激活指南
  • Python 数据分析:学生画像匹配与相似度计算
  • Midjourney V8 Alpha 正式发布:核心更新与行业影响分析
  • DaxPay 开源支付系统:支持多渠道接入与独立部署
  • VSCode 使用 Copilot 与 Cline 插件实现 AI 辅助编程

相关免费在线工具

  • 加密/解密文本

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