1. 项目背景与核心需求
在嵌入式系统开发中,UART(通用异步收发传输器)作为最基础的通信接口之一,其稳定性和可靠性直接影响整个系统的通信质量。这次基于安路FPGA的UART发送模块开发,源于一个工业控制器项目中对多设备串行通信的需求。传统方案采用现成的UART IP核,但在特定场景下(如需要自定义波特率、特殊校验方式等)往往显得不够灵活。
安路FPGA以其高性价比和丰富的外设资源在工业控制领域应用广泛。我们选择Tang Primer 20K开发板作为硬件平台,其内置的PLL模块和灵活的IO配置非常适合实现定制化串口通信。这个UART发送模块需要满足以下核心指标:
- 支持115200bps及以下常用波特率(误差<2%)
- 可配置数据位宽(5-8位)
- 可选奇偶校验模式
- 至少2级FIFO缓冲
- 状态指示信号完备
2. 硬件设计与接口定义
2.1 安路FPGA资源分析
Tang Primer 20K搭载的安路EG4S20BG256芯片具有以下关键特性:
- 20K LUT4逻辑单元
- 72个18x18乘法器
- 4个PLL模块
- 最大200MHz主频
- 支持LVCMOS 3.3V IO标准
针对UART发送模块,我们主要利用其可编程逻辑资源和PLL时钟管理功能。实测显示,使用内部PLL生成57.6MHz时钟(1152001631)时,时钟抖动小于50ps,完全满足UART时序要求。
2.2 模块接口设计
采用标准的AMBA APB总线接口便于系统集成,同时保留直接寄存器访问模式用于调试。关键信号定义如下:
verilog复制module uart_tx (
input wire clk, // 57.6MHz主时钟
input wire rst_n, // 低电平复位
input wire apb_psel, // APB选择信号
input wire apb_penable,
input wire [31:0] apb_paddr,
input wire apb_pwrite,
input wire [31:0] apb_pwdata,
output reg [31:0] apb_prdata,
output wire txd, // 串行输出
output wire tx_busy, // 发送状态指示
output wire fifo_full // FIFO满标志
);
特别需要注意APB总线时序约束:
- 建立时间(setup time):≥2ns
- 保持时间(hold time):≥1ns
- 时钟到输出延迟:≤5ns
3. 核心逻辑实现
3.1 波特率生成方案
采用过采样技术实现精准的波特率控制,核心代码如下:
verilog复制// 波特率分频计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_cnt <= 0;
sample_en <= 0;
end else begin
if (baud_cnt == BAUD_DIV - 1) begin
baud_cnt <= 0;
sample_en <= 1;
end else begin
baud_cnt <= baud_cnt + 1;
sample_en <= 0;
end
end
end
其中BAUD_DIV计算公式为:
code复制BAUD_DIV = (系统时钟频率) / (波特率 × 过采样倍数) - 1
例如对于115200bps,57.6MHz时钟,16倍过采样:
code复制BAUD_DIV = 57,600,000 / (115200 × 16) - 1 = 31 - 1 = 30
3.2 发送状态机设计
采用经典的三段式状态机实现发送控制:
verilog复制// 状态定义
localparam IDLE = 3'b000;
localparam START = 3'b001;
localparam DATA = 3'b010;
localparam PARITY = 3'b011;
localparam STOP = 3'b100;
// 状态转移逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
end else if (sample_en) begin
case (state)
IDLE: if (tx_start) state <= START;
START: state <= DATA;
DATA: if (bit_cnt == DATA_BITS-1) state <= PARITY;
PARITY: state <= STOP;
STOP: state <= IDLE;
endcase
end
end
关键技巧:在状态机设计中添加"看门狗"计数器,防止异常情况下状态机卡死。例如在DATA状态,如果bit_cnt超过最大数据位宽+2个周期仍未跳转,则强制复位状态机。
4. FIFO缓冲实现
4.1 双时钟域异步FIFO
为适应不同速率的写入和发送,采用经典的异步FIFO设计:
verilog复制// 写指针同步链
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
wptr <= 0;
wptr_gray <= 0;
end else if (wr_en && !fifo_full) begin
wptr <= wptr + 1;
wptr_gray <= (wptr + 1) ^ ((wptr + 1) >> 1);
end
end
// 读指针同步链
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
rptr_sync1 <= 0;
rptr_sync2 <= 0;
end else begin
rptr_sync1 <= rptr_gray;
rptr_sync2 <= rptr_sync1;
end
end
4.2 FIFO深度优化
通过实测数据流量特征,确定最优FIFO深度:
- 最大突发写入:32字节
- 发送耗时:每字节约87μs (115200bps)
- 系统响应延迟:≤100μs
根据Little's定律:
code复制FIFO深度 = 最大突发量 × (发送时间/响应时间) = 32 × (87/100) ≈ 28
最终选择32级深度的FIFO,留有足够余量。
5. 调试问题与解决方案
5.1 波特率误差问题
初期测试发现实际波特率存在约1.8%的误差,通过以下措施解决:
- 改用PLL直接生成目标频率时钟(而非分频)
- 添加动态校准逻辑:
verilog复制// 动态校准模块
always @(posedge clk) begin
if (calib_en) begin
if (edge_cnt < 16) begin
baud_adj <= baud_adj + 1;
end else if (edge_cnt > 16) begin
baud_adj <= baud_adj - 1;
end
end
end
5.2 亚稳态问题
在跨时钟域信号传输时偶尔出现数据错误,通过以下方法改善:
- 采用三级寄存器同步链
- 对关键信号添加ASYNC_REG属性:
verilog复制(* ASYNC_REG = "TRUE" *) reg [2:0] sync_chain;
- 在布局约束中添加时序例外:
code复制set_false_path -from [get_clocks wclk] -to [get_clocks rclk]
6. 实测性能数据
使用Saleae Logic Pro 16进行波形捕获和分析,关键指标如下:
| 测试项 | 指标要求 | 实测结果 |
|---|---|---|
| 波特率精度 | <±2% | ±0.3% |
| 上升时间(10-90%) | <1μs | 0.8μs |
| 帧错误率 | <1e-6 | 0 |
| FIFO吞吐量 | ≥1MB/s | 1.2MB/s |
功耗测试结果:
- 静态功耗:12mW
- 115200bps工作功耗:18mW
- 峰值功耗:22mW
7. 工程优化建议
-
时钟方案优化:
- 对于多UART实例场景,建议共享PLL输出时钟
- 低频应用可考虑使用内部RC振荡器+软件校准
-
功耗控制技巧:
verilog复制// 动态时钟门控 always @(*) begin if (idle_cnt > 1000) begin clk_gate = 0; end else begin clk_gate = 1; end end -
EMI抑制措施:
- 在TXD输出端串联22Ω电阻
- PCB布局时保持至少3mm远离高频信号线
- 必要时添加π型滤波电路
这个UART发送模块最终稳定运行在多个工业现场设备中,其可靠性和灵活性得到了验证。特别在需要自定义通信协议的场景下,FPGA实现的优势尤为明显。通过这个项目积累的调试经验表明,信号完整性和时序约束是数字接口设计中最需要关注的重点。