1. FPGA串口接收模块设计概述
串口通信作为嵌入式系统中最基础的外设接口之一,在FPGA开发中有着广泛的应用场景。与单片机不同,FPGA实现串口需要从底层开始构建所有功能模块,这既带来了灵活性,也对设计者的数字电路功底提出了更高要求。
在实际工程中,一个可靠的UART接收模块需要解决三个核心问题:精确的波特率同步、稳定的数据采样和健壮的错误处理机制。基于50MHz系统时钟实现115200bps通信时,每个比特位仅持续约434个时钟周期,这对时序控制提出了严苛要求。传统方案采用16倍过采样技术,通过多数表决机制提高抗干扰能力,这也是工业级设备普遍采用的设计方法。
2. 波特率生成器设计
2.1 分频系数计算
对于50MHz时钟和115200bps目标波特率,理论分频系数计算如下:
code复制50,000,000 / (115200 × 16) ≈ 27.1267
取整后得到CLK_DIV=27,这意味着实际波特率为:
code复制50,000,000 / (27 × 16) ≈ 115740.7bps
误差率为0.47%,远低于RS-232标准允许的3%容差范围。在Verilog中实现时,需要特别注意计数器位宽选择:
verilog复制parameter CLK_DIV = 27;
reg [4:0] div_cnt; // 足够存储0-31的计数值
always @(posedge clk) begin
if(div_cnt == CLK_DIV) begin
sample_pulse <= 1'b1;
div_cnt <= 0;
end else begin
sample_pulse <= 1'b0;
div_cnt <= div_cnt + 1;
end
end
2.2 采样时钟生成技巧
实际工程中建议添加时钟使能信号控制,避免在非通信时段消耗功耗:
verilog复制always @(posedge clk) begin
if(enable) begin
// 原有计数逻辑
end else begin
div_cnt <= 0;
sample_pulse <= 0;
end
end
3. 接收状态机实现
3.1 三状态转换设计
核心状态机采用IDLE-START-DATA三状态模型,每个状态转换都基于精确的采样点计数:
verilog复制localparam IDLE = 2'b00;
localparam START = 2'b01;
localparam DATA = 2'b10;
reg [1:0] state;
reg [3:0] sample_cnt;
reg [2:0] bit_cnt;
always @(posedge clk) begin
case(state)
IDLE:
if(rx_sync == 1'b0 && start_detect) // 防抖确认
state <= START;
START:
if(sample_cnt == 7) begin
state <= DATA;
bit_cnt <= 0;
end
DATA:
if(bit_cnt == 8 && sample_cnt == 15)
state <= IDLE;
endcase
end
3.2 防抖机制实现
起始位检测采用三次采样表决机制,有效避免毛刺干扰:
verilog复制reg [1:0] start_samples;
wire start_detect = &start_samples; // 连续三次低电平
always @(posedge clk) begin
if(sample_pulse) begin
start_samples <= {start_samples[0], rx_sync};
end
end
4. 数据采样策略
4.1 中点采样技术
在每个比特位的中间位置(第8个采样点)进行数据锁存:
verilog复制always @(posedge clk) begin
if(state == DATA && sample_cnt[3]) begin
rx_data[bit_cnt] <= rx_sync;
bit_cnt <= bit_cnt + 1;
end
end
4.2 多数表决采样
对于高干扰环境,可采用7次采样表决机制:
verilog复制reg [6:0] sample_window;
always @(posedge clk) begin
if(sample_pulse) begin
sample_window <= {sample_window[5:0], rx_sync};
if(sample_window[6:4] == 3'b111)
vote_result <= 1'b1;
else if(sample_window[6:4] == 3'b000)
vote_result <= 1'b0;
end
end
5. ModelSim仿真验证
5.1 测试激励生成
构建符合UART协议的测试序列,注意起始位为低电平、停止位为高电平:
verilog复制parameter BIT_TIME = 8680; // 50MHz时钟下115200bps的位周期
initial begin
rx = 1; // 空闲状态
#1000;
// 发送0x55 (01010101)
rx = 0; // 起始位
#BIT_TIME;
for(i=0;i<8;i++) begin
rx = (i%2 == 0); // 交替数据位
#BIT_TIME;
end
rx = 1; // 停止位
#(2*BIT_TIME);
$stop;
end
5.2 关键信号观测
仿真时应重点关注以下信号时序关系:
- sample_pulse与rx信号的同步情况
- 状态机转换时机是否正确
- bit_cnt在数据位的递增过程
- 最终rx_data寄存器的值是否为0x55
6. 工程实践中的优化技巧
6.1 数据就绪信号设计
为避免连续接收时的数据覆盖问题,建议添加数据有效指示信号:
verilog复制reg [7:0] data_reg;
reg data_valid;
always @(posedge clk) begin
if(state == DATA && bit_cnt == 8 && sample_cnt == 15) begin
data_reg <= rx_data;
data_valid <= 1'b1;
end else begin
data_valid <= 1'b0;
end
end
6.2 时钟域同步处理
当接收信号来自异步时钟域时,必须添加双级同步器:
verilog复制reg rx_sync, rx_meta;
always @(posedge clk) begin
rx_meta <= rx;
rx_sync <= rx_meta;
end
7. 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 接收数据错位 | 波特率分频系数错误 | 重新计算CLK_DIV参数 |
| 只能接收第一个字节 | 状态机未正确复位 | 检查IDLE状态转换条件 |
| 数据位出现毛刺 | 未进行时钟域同步 | 添加双级触发器同步 |
| 仿真波形无反应 | 测试激励电平错误 | 确认起始位为低电平 |
在实际项目中,我曾遇到过一个隐蔽的BUG:当连续发送数据时,偶尔会出现字节丢失。最终发现是sample_cnt计数器在状态转换时没有及时清零,导致后续比特采样点偏移。解决方法是在状态机每个状态转换处都重置相关计数器:
verilog复制always @(posedge clk) begin
case(state)
IDLE: begin
sample_cnt <= 0;
if(start_detect) state <= START;
end
START: begin
if(sample_cnt == 7) begin
state <= DATA;
sample_cnt <= 0; // 关键重置点
end
end
endcase
end
