UART串口环回测试中的校验位实战:从原理到FPGA实现
1. UART串口校验位的前世今生
第一次接触UART串口通信时,我被这个看似简单的协议难住了——为什么要有起始位、停止位,还要加个莫名其妙的校验位?直到在项目中遇到数据错乱的问题,才真正理解校验位的重要性。校验位就像快递包裹上的防拆封条,虽然增加了少许开销,但能确保数据在传输过程中没有被"调包"。
UART通信中最常用的校验方式有三种:
- 无校验(None):简单粗暴,适合对可靠性要求不高的场景
- 奇校验(Odd):确保数据位+校验位中"1"的总数为奇数
- 偶校验(Even):确保数据位+校验位中"1"的总数为偶数
举个生活中的例子:假设我们要传输数字"1011001":
- 奇校验:原始数据有4个"1"(偶数),所以校验位补"1"使总数变为奇数
- 偶校验:原始数据已有偶数个"1",校验位补"0"保持偶数
- 无校验:直接忽略校验位
在FPGA实现时,奇偶校验的计算可以用一个巧妙的技巧——按位异或。Verilog中只需一行代码:
// 奇校验计算 assign odd_parity = ~^data; // 偶校验计算 assign even_parity = ^data;这个设计妙在哪儿呢?异或运算本质上是在统计"1"的奇偶性——偶数个"1"异或结果为0,奇数个则为1。所以偶校验直接取异或结果,奇校验则取反。
2. FPGA环回测试系统设计
去年给某工业设备设计通信模块时,我搭建了一个完整的UART环回测试系统。这个系统就像个"回声测试仪"——FPGA收到什么数据就原样发回去,同时加入校验位验证机制。这种设计特别适合调试,能快速定位是发送端、接收端还是传输线路的问题。
2.1 系统架构设计
整个系统包含三个关键模块:
- UART发送模块(uart_tx):将并行数据转为串行波形,自动添加校验位
- UART接收模块(uart_rx):解析串行数据,校验位检查
- 环回控制逻辑:将接收数据实时转发给发送模块
状态机设计是核心难点。以发送模块为例,我采用了五段式状态机:
localparam IDLE = 5'b00001, // 空闲 START = 5'b00010, // 起始位 DATA = 5'b00100, // 数据位 CHECK = 5'b01000, // 校验位 STOP = 5'b10000; // 停止位每个状态对应特定的比特位数:
- 起始位:固定1bit(低电平)
- 数据位:8bit(可配置)
- 校验位:1bit
- 停止位:固定1bit(高电平)
2.2 波特率生成技巧
在50MHz时钟下实现115200波特率,需要计算分频系数:
parameter CLOCK = 50_000_000; parameter MAX_BPS = 115200; parameter MAX_1bit = CLOCK/MAX_BPS; // 434个时钟周期实际调试中发现,采样点设置在比特周期中间最稳定。我采用计数器过半触发的方式:
// 在接收模块中 always @(posedge clk) begin if(cnt_baud == (MAX_1bit >> 1)) // 计数器达到217时采样 rx_sample <= rx_input; end3. Verilog实现细节
3.1 发送模块关键代码
发送状态机的第三段处理输出逻辑特别重要:
always @(*) begin case(cstate) IDLE : tx = 1'b1; START : tx = 1'b0; // 起始位 DATA : tx = tx_data[cnt_bit]; CHECK : tx = check_val; // 动态校验位 STOP : tx = 1'b1; // 停止位 default: tx = 1'b1; endcase end校验位生成逻辑支持三种模式:
assign check_val = (CHECK_BIT == "Odd") ? ~^tx_data : (CHECK_BIT == "Even") ? ^tx_data : 1'b1; // 无校验时固定高电平3.2 接收模块的智能校验
接收端的数据有效性判断是难点。我的设计是:
assign rx_data_vld = (CHECK_BIT == "None") ? data_done : (check_done && (check_val == rx_check)) ? 1 : 0;这里有个坑要注意:校验比较必须等校验位采样完成(check_done)后才能进行。早期版本我漏了这个条件,导致偶尔出现误判。
4. 仿真与实测对比
4.1 Modelsim仿真技巧
搭建测试平台时,我设计了三种测试场景:
- 正常传输:校验位与数据匹配
- 人为错误:故意翻转校验位
- 边界情况:全0、全1数据测试
以奇校验为例,仿真波形应该看到:
- 数据"01010101"(奇数个1)→ 校验位0
- 数据"11010101"(偶数个1)→ 校验位1
// 测试用例片段 initial begin // 正常奇校验测试 tx_data = 8'b01010101; #100 tx_data_vld = 1; #20 tx_data_vld = 0; // 错误校验测试 #1000 force uart_rx.rx_check = ~uart_rx.rx_check; // 人为制造错误 end4.2 串口助手实测
用串口助手测试时要注意:
- 波特率必须严格匹配(误差<3%)
- 校验位设置要与FPGA代码一致
- 建议先测试环回再对接真实设备
常见问题排查表:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 接收乱码 | 波特率不匹配 | 检查时钟分频计算 |
| 校验错误 | 模式设置不一致 | 确认双方校验模式 |
| 数据截断 | 停止位不足 | 增加停止位周期 |
5. 工程优化经验
在实际项目中,我总结了几个优化点:
- 参数化设计:将波特率、校验模式设为参数,方便重用
module uart_tx #( parameter CHECK_BIT = "None", parameter MAX_BPS = 115200 )( // 端口声明 );- 亚稳态处理:对异步的RX信号双寄存器同步
always @(posedge clk) begin rx_r1 <= rx; rx_r2 <= rx_r1; end assign rx_nege = ~rx_r1 & rx_r2; // 下降沿检测- 资源优化:状态机采用独热码(one-hot)编码,虽然多用触发器但解码简单
最近一次升级中,我增加了自动波特率检测功能。通过测量起始位宽度动态调整分频系数,使模块能自适应不同设备。这个改进让我们的产品兼容性提升了40%。
