状态机设计第一步:空闲状态(IDLE)的设计与实现,如何检测起始位

各位同学,咱们今天聊点实在的。

串口接收,说白了就是一根线上串着传数据。没有时钟线,全靠约定好的波特率来采样。那问题来了——接收端怎么知道什么时候开始收?

答案就是:起始位。

而检测起始位,正是状态机里空闲状态(IDLE)的核心任务。

1. 空闲状态在干什么?

先想想看,串口总线空闲时是什么电平?

对,高电平。这是RS-232标准定的,UART协议也沿用了这个习惯。

所以空闲状态(IDLE)要做的就两件事:

  • 盯着RX线,看它什么时候从高电平跳变到低电平
  • 确认这个低电平是真正的起始位,而不是毛刺干扰

嗯,这里要注意——起始位是一个位宽的低电平。在约定的波特率下,它持续整整一个位时间。

核心逻辑:检测到下降沿 → 等待半个位时间 → 再次采样确认低电平 → 进入接收状态

2. 为什么不能直接检测下降沿就收?

我刚开始做串口的时候,也犯过这个错。

直接写个边沿检测,下降沿一到就启动接收。结果呢?

  • 线上有毛刺,误触发
  • 起始位中间有抖动,采样点偏移
  • 波特率稍微不准,整个数据全错

所以,必须做“确认”这一步。

我个人习惯的做法是:

  1. 用系统时钟(比如50MHz)对RX线做同步打拍,消除亚稳态
  2. 检测同步后的下降沿
  3. 启动一个半位定时器,等待半个位时间
  4. 在半个位时间点再次采样RX线
  5. 如果还是低电平 → 确认是起始位 → 状态机跳转
  6. 如果是高电平 → 认为是毛刺 → 回到IDLE继续等待

小技巧:半位采样点正好落在起始位的正中间,能最大程度避开边沿抖动。这个位置最稳。

3. 代码怎么写?我给你们一个模板

下面是我在实际项目中用过的代码框架,你们可以直接参考。

// 同步打拍,消除亚稳态
reg rx_sync1, rx_sync2;
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    rx_sync1 <= 1'b1;
    rx_sync2 <= 1'b1;
  end else begin
    rx_sync1 <= rx;
    rx_sync2 <= rx_sync1;
  end
end

// 下降沿检测
wire rx_negedge = rx_sync2 & ~rx_sync1;

// 状态机:IDLE部分
localparam IDLE = 3'd0;
localparam HALF_WAIT = 3'd1;
localparam RECV = 3'd2;

reg [2:0] state, next_state;
reg [7:0] half_timer;  // 半位定时器

always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    state <= IDLE;
  else
    state <= next_state;
end

always @(*) begin
  next_state = state;
  case (state)
    IDLE: begin
      if (rx_negedge)
        next_state = HALF_WAIT;
    end
    HALF_WAIT: begin
      if (half_timer == HALF_BIT_CNT - 1) begin
        if (!rx_sync2)          // 再次确认是低电平
          next_state = RECV;
        else
          next_state = IDLE;    // 毛刺,回到空闲
      end
    end
    // ... 其他状态
  endcase
end

注意:HALF_BIT_CNT 要根据你的系统时钟和波特率计算。比如50MHz时钟,9600波特率,一个位时间约5208个时钟周期,半位就是2604。

千万别算错,算错了整个串口都收不到数据。

4. 我曾经踩过的坑

有一次做项目,板子串口死活收不到数据。示波器一看,RX线上波形完全正确。

查了两天,最后发现——同步打拍只打了一级。

一级同步根本消除不了亚稳态,尤其是在时钟频率高、线缆长的情况下。从那以后,我所有跨时钟域信号都至少打两拍,有些关键信号打三拍。

还有一个坑:起始位检测的定时器启动时机。

我建议在检测到下降沿的下一个时钟周期启动定时器,而不是立即启动。为什么?因为边沿检测信号只持续一个周期,你立即启动的话,定时器可能少计一个周期,导致半位采样点偏移。

经验总结:

  • 同步打拍至少两级
  • 半位采样点确认起始位
  • 定时器启动要滞后一个周期
  • 毛刺检测不能省

5. 知识体系:一张图看懂

下面我用SVG画了一张流程图,把IDLE状态的核心逻辑串起来。你们可以保存下来,写代码时对照着看。

空闲状态(IDLE)起始位检测流程 IDLE 状态 RX 信号同步打拍(两级) 检测下降沿(rx_negedge) 下降沿? 否 是 等待半位时间 再次采样RX 低电平? 否(毛刺) 是 进入RECV

6. 总结一下

空闲状态(IDLE)的设计,说白了就一句话:用半位采样点确认起始位的真实性。

别小看这一步。我见过太多新手直接检测下降沿就跳转,结果被毛刺坑得死去活来。

记住:FPGA设计里,确认比检测更重要。

最后送大家一句话:状态机设计,IDLE是地基。地基没打好,后面全白搭。

可可精品资料库