4、数字信号基础:电平与逻辑状态、时序图解读(Setup/Hold Time)、边沿检测与毛刺处理

好,咱们进入第四讲。这一讲的内容,说白了就是数字世界的「语法」和「标点符号」。你想想看,逻辑分析仪抓到的是一堆 0 和 1,但如果你看不懂电平背后的逻辑,读不懂时序图里的时间约束,那抓回来的数据就是一堆乱码。

我个人习惯,在开始写任何协议解析代码之前,先把这三件事搞清楚:电平怎么映射到逻辑、时序窗口有多宽、以及怎么对付那些讨厌的毛刺。咱们一个一个来。

4.1 电平与逻辑状态:从电压到二进制

数字信号,本质上就是电压。但电压是连续的,逻辑状态是离散的。怎么把连续的电压变成 0 和 1?靠的是阈值。

拿最常见的 3.3V 的 CMOS 电平来说:

参数 最小值 最大值 说明
VIL (输入低电平) 0V 0.8V 低于这个值,芯片认为是逻辑 0
VIH (输入高电平) 2.0V 3.3V 高于这个值,芯片认为是逻辑 1
VOL (输出低电平) 0V 0.4V 芯片输出 0 时,保证电压不超过这个值
VOH (输出高电平) 2.4V 3.3V 芯片输出 1 时,保证电压不低于这个值

这里有个关键点:0.8V 到 2.0V 之间是「灰色地带」。如果信号落在这个区间,逻辑分析仪采到的值是不确定的。我在项目中遇到过,一根长走线上的信号因为反射,电压刚好卡在 1.2V 左右,结果逻辑分析仪一会儿读到 0,一会儿读到 1,折腾了我整整一个下午。

注意: 不同电平标准(TTL、CMOS、LVDS)的阈值完全不同。做逻辑分析仪时,一定要先确认被测信号的电平标准,否则采样结果毫无意义。

4.2 时序图解读:Setup/Hold Time 是硬约束

时序图,就是数字信号的「时间轴照片」。你拿到一张时序图,第一件事不是看数据,而是看时钟沿和数据变化之间的时间关系。

这里有两个最重要的概念:建立时间(Setup Time)保持时间(Hold Time)

  • 建立时间(tsu:在时钟有效沿到来之前,数据必须保持稳定的最短时间。
  • 保持时间(th:在时钟有效沿到来之后,数据必须保持稳定的最短时间。

说白了,就是时钟沿前后各有一个「禁入区」。数据在这两个窗口内不能变化,否则寄存器会进入亚稳态——输出既不是 0 也不是 1,而是一个中间值,甚至可能振荡。

我曾经调试过一个 SPI 接口,总是不定时地读错数据。用逻辑分析仪抓了波形,发现从机的 MISO 数据变化刚好落在主时钟的上升沿上。一查数据手册,保持时间要求是 5ns,实际只有 2ns。嗯,这就是典型的保持时间违例。

核心要点: 在逻辑分析仪中,采样时钟的边沿必须避开数据的跳变沿。一般建议采样点选在数据窗口的中间位置,这样能最大程度容忍 Setup/Hold 的偏差。

4.3 边沿检测:上升沿、下降沿与双沿

协议解析的核心,就是检测边沿。因为大多数协议(I2C、SPI、UART)都是以边沿作为同步信号的。

边沿检测在 FPGA 里实现起来很简单,就是一个寄存器加一个异或门:

// Verilog 边沿检测
reg signal_d1;
reg signal_d2;

always @(posedge clk) begin
    signal_d1 <= signal_in;   // 当前值
    signal_d2 <= signal_d1;   // 前一拍的值
end

assign rising_edge  = signal_d1 & ~signal_d2;  // 上升沿
assign falling_edge = ~signal_d1 & signal_d2;  // 下降沿
assign both_edge    = signal_d1 ^ signal_d2;   // 双沿

这段代码的逻辑很简单:signal_d1 是当前采样值,signal_d2 是上一拍的采样值。如果当前是 1 且上一拍是 0,那就是上升沿。反过来就是下降沿。

你想想看,这个电路其实就是一个「差分器」。它把连续的时间信号,变成了一个脉冲。这个脉冲的宽度,正好是一个时钟周期。

小技巧: 如果被测信号频率很高,而你的采样时钟不够快,可以用「双沿采样」来提升分辨率。但要注意,双沿采样对时钟占空比有要求,占空比偏差太大会引入误差。

4.4 毛刺处理:数字世界的「噪声」

毛刺,就是信号上那些不该出现的窄脉冲。它们可能是反射、串扰、或者电源噪声引起的。在逻辑分析仪里,毛刺是最常见的「假信号」来源。

怎么处理毛刺?我常用的方法有三种:

  1. 多次采样取多数:对同一个信号连续采样 3 次或 5 次,取出现次数最多的值作为最终结果。这叫「中值滤波」或「多数判决」。
  2. 延迟消抖:检测到边沿后,延迟一段时间再确认。如果延迟后信号状态不变,才认为是有效边沿。这其实就是数字滤波。
  3. 设置最小脉宽:在协议解析时,忽略所有宽度小于某个阈值的脉冲。比如 SPI 的时钟周期是 100ns,那所有宽度小于 10ns 的脉冲都视为毛刺。

我曾经在一个项目中,用逻辑分析仪抓 I2C 总线。总线上有毛刺,导致每次解析都多出一个 Start 条件。后来我用了一个简单的 3 次采样滤波,毛刺就被滤掉了。嗯,有时候最简单的办法反而最有效。

注意: 滤波会引入延迟。如果信号频率很高,滤波可能会把有效信号也滤掉。这时候需要权衡:是容忍毛刺,还是牺牲一点时序精度。

4.5 实战:在逻辑分析仪中实现边沿检测与毛刺滤波

咱们把前面讲的东西串起来,写一个简单的模块。这个模块接收一个外部信号,输出它的上升沿和下降沿,同时带一个可配置的毛刺滤波。

// 带毛刺滤波的边沿检测模块
module edge_detector #(
    parameter FILTER_LEN = 3  // 滤波长度,建议 3 或 5
)(
    input  wire       clk,
    input  wire       rst_n,
    input  wire       signal_in,
    output reg        rising_edge,
    output reg        falling_edge
);

    reg [FILTER_LEN-1:0] shift_reg;
    reg                  signal_filtered;
    reg                  signal_d1;
    wire                 all_high, all_low;

    // 移位寄存器实现多数判决
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            shift_reg <= {FILTER_LEN{1'b0}};
        else
            shift_reg <= {shift_reg[FILTER_LEN-2:0], signal_in};
    end

    assign all_high = &shift_reg;  // 全 1
    assign all_low  = ~|shift_reg; // 全 0

    // 只有连续 FILTER_LEN 个采样一致,才更新输出
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            signal_filtered <= 1'b0;
            signal_d1       <= 1'b0;
            rising_edge     <= 1'b0;
            falling_edge    <= 1'b0;
        end else begin
            if (all_high)
                signal_filtered <= 1'b1;
            else if (all_low)
                signal_filtered <= 1'b0;
            // 否则保持原值,不更新

            signal_d1 <= signal_filtered;

            // 边沿检测
            rising_edge  <= signal_filtered & ~signal_d1;
            falling_edge <= ~signal_filtered & signal_d1;
        end
    end

endmodule

这段代码的核心思想是:不轻易相信一次采样。只有连续 3 次(或 5 次)采样结果一致,我才认为信号真的变了。这能滤掉大部分毛刺。

你想想看,如果毛刺宽度只有 1 个时钟周期,那它最多在移位寄存器里出现一次,永远达不到「全 1」或「全 0」的条件,所以输出不会变化。这就是数字滤波的精髓。

总结一下: 电平决定逻辑,时序决定窗口,边沿决定事件,毛刺决定可靠性。这四个概念,是数字信号分析的基石。搞懂了它们,你就能看懂任何一张时序图,也能写出健壮的协议解析代码。

下一讲,咱们会把这些基础用到实际的协议解析中。到时候你会发现,今天讲的这些东西,全都会用上。