找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2137

积分

0

好友

299

主题
发表于 昨天 04:31 | 查看: 4| 回复: 0

在 FPGA 上实现 50Hz 陷波器(Notch Filter)是一项经典且实用的数字信号处理任务,其核心目的是滤除无处不在的工频干扰。由于 50Hz 相对于 FPGA 常见的采样频率(如几 kHz 到几 MHz)而言属于低频信号,因此在实现时需要特别注意系数的精度与硬件资源的平衡。

陷波器的数学模型与参数计算

数字陷波器通常采用 IIR(无限脉冲响应)结构来实现,相较于 FIR 滤波器,IIR 能够以极少的阶数(计算量)实现非常窄的阻带带宽,这对于精准滤除单一频率干扰非常有利。

1. 传递函数

标准的二阶 IIR 陷波器在 Z 域的传递函数为:

二阶IIR陷波器Z域传递函数公式

其中,参数 r 控制陷波的带宽和深度。r 的值越接近 1,陷波器的带宽就越窄,频率选择性越好,但同时也会对计算的数值稳定性提出更高要求。

2. 系数计算示例

假设我们的目标采样率 fs = 1000Hz,需要滤除的频率 f0 = 50Hz

50Hz陷波器系数计算过程

3. 差分方程(用于 FPGA 编程)

将传递函数转换到时域,得到其差分方程,这将是我们在 FPGA 中进行逻辑实现的直接依据:

二阶IIR陷波器差分方程

FPGA 逻辑实现

在 FPGA 的硬件逻辑中,我们无法直接处理浮点数,因此必须将计算好的浮点系数转换为定点数(Fixed-point)格式。

1. 定点化处理

我们需要将上述浮点系数放大 2^k 倍并取整。例如,当 k=16 时,放大倍数为 65536。

  • b1_fixed = round(-1.9021 × 65536)
  • 计算完成后,最终结果需要再右移 16 位来还原真实值。
  • 重要提示:对于 50Hz 这样的低频信号,如果系统采样率很高(例如 100kHz),计算出的系数会非常接近 2 或 -2。此时必须使用足够高的位宽(如 32 位或更高)来进行定点化,否则滤波器可能失效或产生振荡。

2. 定点系数(Q16.16 格式)

这里我们采用 Q16.16 格式(16位整数,16位小数)对系数进行定点化,得到用于硬件计算的整型系数:

50Hz陷波器定点化系数结果

3. Verilog 代码实现

以下模块采用直接 I 型(Direct Form I)结构,并加入了分时处理与饱和截断(Saturation)逻辑,有效防止运算溢出导致信号畸变。理解这类硬件描述语言是实现高效数字信号处理的基础,更多关于计算机体系结构和底层逻辑设计的知识可以在 云栈社区 的计算机基础板块找到深入讨论。

module notch_filter_50hz (
    input  wire                 clk,      // 高速系统时钟 (如 50MHz)
    input  wire                 rst_n,    // 异步复位
    input  wire                 vld_in,   // 采样脉冲 (每当有新数据时拉高一个clk)
    input  wire signed [15:0]   data_in,  // 输入信号 (16位)
    output reg  signed [15:0]   data_out, // 输出信号 (16位)
    output reg                  vld_out   // 输出有效标志
);

    // --- 定点化系数 (Q16.16) ---
    localparam signed [31:0] B0 =  32'sd65536;
    localparam signed [31:0] B1 = -32'sd124657;
    localparam signed [31:0] B2 =  32'sd65536;
    localparam signed [31:0] A1 = -32'sd118423;
    localparam signed [31:0] A2 =  32'sd59146;

    // --- 状态寄存器 (延迟链) ---
    reg signed [15:0] x_n1, x_n2; // 输入延迟
    reg signed [15:0] y_n1, y_n2; // 输出延迟

    // --- 乘累加中间变量 ---
    // 16位数据 * 32位系数 = 48位结果
    reg signed [47:0] sum;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            x_n1 <= 0; x_n2 <= 0;
            y_n1 <= 0; y_n2 <= 0;
            data_out <= 0;
            vld_out <= 0;
        end else if (vld_in) begin
            // 核心差分方程计算:
            // y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
            // 注意: 此处 A1, A2 已经在计算时带了负号,所以代码中用加法
            sum <= (B0 * data_in) + (B1 * x_n1) + (B2 * x_n2)
                   - (A1 * y_n1) - (A2 * y_n2);

            // 延迟链更新
            x_n2 <= x_n1;
            x_n1 <= data_in;
            y_n2 <= y_n1;

            // 结果截断与饱和处理
            // 我们使用了 Q16.16,所以结果需要右移 16 位
            // 如果结果超过 16 位有符号数的范围,则强行置为最大值/最小值
            if (sum[47:16] > 32'sd32767)
                y_n1 <= 16'sd32767;
            else if (sum[47:16] < -32'sd32768)
                y_n1 <= -16'sd32768;
            else
                y_n1 <= sum[31:16];

            data_out <= y_n1;
            vld_out <= 1'b1;
        end else begin
            vld_out <= 1'b0;
        end
    end

endmodule

实时采样与实现要点

  • 时序同步:代码中的 vld_in 信号至关重要。滤波器不应在每个系统时钟周期都进行计算,而必须与 ADC 的采样率同步。例如,若 ADC 采样率为 1kHz,则 vld_in 应每 1ms 产生一个时钟周期的高电平脉冲,触发滤波器计算一次。
  • 实时性:从 vld_in 有效到 vld_out 输出结果,仅有一个系统时钟周期的延迟(纳秒级)。这对于处理周期为 20ms 的 50Hz 信号而言,完全可以视为“零延迟”的实时处理。



上一篇:C++ string_view 返回值避坑指南:生命周期与线上崩溃风险解析
下一篇:嵌入式AI框架uTensor实测:仅2KB内存的轻量化模型如何跑在单片机上
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 16:30 , Processed in 0.289344 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表