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,折腾了我整整一个下午。
4.2 时序图解读:Setup/Hold Time 是硬约束
时序图,就是数字信号的「时间轴照片」。你拿到一张时序图,第一件事不是看数据,而是看时钟沿和数据变化之间的时间关系。
这里有两个最重要的概念:建立时间(Setup Time) 和 保持时间(Hold Time)。
- 建立时间(tsu):在时钟有效沿到来之前,数据必须保持稳定的最短时间。
- 保持时间(th):在时钟有效沿到来之后,数据必须保持稳定的最短时间。
说白了,就是时钟沿前后各有一个「禁入区」。数据在这两个窗口内不能变化,否则寄存器会进入亚稳态——输出既不是 0 也不是 1,而是一个中间值,甚至可能振荡。
我曾经调试过一个 SPI 接口,总是不定时地读错数据。用逻辑分析仪抓了波形,发现从机的 MISO 数据变化刚好落在主时钟的上升沿上。一查数据手册,保持时间要求是 5ns,实际只有 2ns。嗯,这就是典型的保持时间违例。
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 毛刺处理:数字世界的「噪声」
毛刺,就是信号上那些不该出现的窄脉冲。它们可能是反射、串扰、或者电源噪声引起的。在逻辑分析仪里,毛刺是最常见的「假信号」来源。
怎么处理毛刺?我常用的方法有三种:
- 多次采样取多数:对同一个信号连续采样 3 次或 5 次,取出现次数最多的值作为最终结果。这叫「中值滤波」或「多数判决」。
- 延迟消抖:检测到边沿后,延迟一段时间再确认。如果延迟后信号状态不变,才认为是有效边沿。这其实就是数字滤波。
- 设置最小脉宽:在协议解析时,忽略所有宽度小于某个阈值的脉冲。比如 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」的条件,所以输出不会变化。这就是数字滤波的精髓。
下一讲,咱们会把这些基础用到实际的协议解析中。到时候你会发现,今天讲的这些东西,全都会用上。