1. 项目概述
UART串口通信作为嵌入式系统中最基础也最常用的通信方式之一,其FPGA实现方案一直是数字电路设计的重要教学内容。这个例程聚焦于UART接收端状态机的设计与实现,通过Verilog HDL语言构建一个可靠的数据接收通道。不同于简单的轮询式接收,状态机架构能有效处理异步串行通信中的时钟域交叉、起始位检测、数据采样等关键问题。
在实际工程中,我曾用类似方案为工业传感器设计数据采集模块,需要稳定接收115200bps的传感器数据。当时发现许多开源UART接收代码存在采样点偏移导致的误码问题,最终通过优化状态机转换逻辑将误码率从10^-4降低到10^-7以下。这个案例让我深刻认识到状态机设计对通信可靠性的决定性影响。
2. 核心需求解析
2.1 UART协议要点
UART采用异步传输格式,每个字符帧包含:
- 1位起始位(低电平)
- 5-9位数据位(LSB先发)
- 可选的奇偶校验位
- 1-2位停止位(高电平)
以常见的8N1配置(8数据位、无校验、1停止位)为例,其帧结构如下:
code复制[START(0)][D0][D1][D2][D3][D4][D5][D6][D7][STOP(1)]
2.2 接收状态机核心挑战
- 时钟同步:接收时钟(通常16倍于波特率)与发送端时钟不同源
- 亚稳态风险:异步信号进入FPGA时钟域时的建立/保持时间违例
- 采样精度:必须在数据位中央采样以避免边沿抖动影响
- 错误恢复:遇到帧错误时应能快速回到空闲状态
3. 状态机设计与实现
3.1 状态划分
采用Moore型状态机,定义五个状态:
verilog复制typedef enum {
IDLE, // 等待起始位
START_DET, // 确认起始位
DATA_SAMP, // 数据位采样
STOP_DET, // 停止位检测
CLEANUP // 错误恢复
} uart_state_t;
3.2 关键参数计算
对于50MHz系统时钟和115200bps波特率:
- 波特率分频系数 = 50,000,000 / (16 × 115200) ≈ 27
- 中点采样位置 = 分频系数 / 2 = 13(第13个计数周期)
3.3 Verilog核心代码
verilog复制always @(posedge clk or posedge rst) begin
if (rst) begin
state <= IDLE;
baud_cnt <= 0;
bit_idx <= 0;
rx_data <= 8'h00;
end else begin
case (state)
IDLE:
if (!rx_pin) begin // 检测起始位下降沿
state <= START_DET;
baud_cnt <= 0;
end
START_DET:
if (baud_cnt == 13) begin // 起始位中点验证
if (!rx_pin) begin
state <= DATA_SAMP;
bit_idx <= 0;
baud_cnt <= 0;
end else
state <= CLEANUP;
end else
baud_cnt <= baud_cnt + 1;
DATA_SAMP:
if (baud_cnt == 26) begin // 每个bit周期16clk×27=432clk
rx_data[bit_idx] <= rx_pin;
bit_idx <= bit_idx + 1;
baud_cnt <= 0;
if (bit_idx == 7)
state <= STOP_DET;
end else
baud_cnt <= baud_cnt + 1;
STOP_DET:
if (baud_cnt == 26) begin
if (rx_pin) // 验证停止位
data_valid <= 1'b1;
state <= CLEANUP;
end else
baud_cnt <= baud_cnt + 1;
CLEANUP:
begin
data_valid <= 1'b0;
state <= IDLE;
end
endcase
end
end
4. 工程实践要点
4.1 亚稳态处理
在异步信号进入时钟域前必须进行同步化:
verilog复制reg [2:0] rx_sync;
always @(posedge clk) begin
rx_sync <= {rx_sync[1:0], rx_pin};
end
assign rx_filtered = rx_sync[2]; // 使用同步后的信号
4.2 采样点优化技巧
- 使用16倍过采样提高抗干扰能力
- 动态调整采样点位置(通过校准模式)
- 多数表决法:连续采样3次取众数
4.3 错误检测机制
- 起始位验证失败
- 停止位验证失败
- 帧超时(超过11bit时间未完成接收)
- 奇偶校验错误(如果启用)
5. 实测问题与解决方案
5.1 典型问题记录表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 首字节丢失 | 复位后状态机未正确初始化 | 确保所有寄存器在复位时有确定值 |
| 偶发误码 | 采样点靠近跳变沿 | 调整采样点位置至bit中央 |
| 连续帧错误 | 波特率偏差超过3% | 使用精确的时钟源或自动波特率检测 |
| 死锁状态 | 异常输入导致状态机卡死 | 添加看门狗定时器强制复位 |
5.2 调试技巧
- ILA抓取状态机轨迹:通过Vivado ILA观察state寄存器变化
- 模拟异常输入:故意发送错误帧测试恢复能力
- 眼图分析:用示波器观察RX信号质量
关键提示:状态机所有分支必须完备,特别是错误处理路径。我曾遇到因漏处理CLEANUP状态导致系统锁死的事故。
6. 性能优化方向
6.1 多级缓冲设计
添加FIFO缓冲层解决数据速率不匹配问题:
verilog复制uart_fifo #(
.WIDTH(8),
.DEPTH(16)
) rx_fifo (
.clk(clk),
.rst(rst),
.wr_en(data_valid),
.din(rx_data),
.full(full),
.empty(empty),
.dout(uart_rx_data)
);
6.2 自动波特率检测
通过测量起始位宽度动态计算分频系数:
verilog复制always @(negedge rx_pin) begin // 捕获起始位下降沿
if (state == IDLE) begin
edge_cnt <= 0;
baud_calc <= 1;
end
end
always @(posedge clk) begin
if (baud_calc) begin
edge_cnt <= edge_cnt + 1;
if (rx_pin) begin // 上升沿结束测量
baud_div <= edge_cnt >> 4; // edge_cnt/16
baud_calc <= 0;
end
end
end
6.3 容错增强策略
- 帧间隔检测(大于1.5个字符时间)
- 噪声滤波(连续3次采样一致才确认状态变化)
- 热插拔检测(监测线路空闲时间)
在实际部署中发现,增加这些机制后,系统在工业电磁干扰环境下的稳定性提升显著。某电机控制项目中,原始方案的误码率在变频器工作时高达5%,经过上述优化后降至可忽略水平。