1. FPGA与UART串口通信入门指南
第一次接触FPGA开发板时,看到那些密密麻麻的引脚和复杂的开发环境,相信很多朋友和我当初一样感到无从下手。UART串口通信作为嵌入式系统中最基础也最实用的通信方式,是FPGA初学者理想的第一个实战项目。这个项目不仅能让你快速理解FPGA的并行处理特性,还能掌握数字系统设计中关键的时序控制技巧。
我选择UART作为FPGA入门项目有三个原因:首先,它协议简单,不需要复杂的IP核支持;其次,调试方便,用普通USB转串口模块就能验证;最重要的是,几乎所有嵌入式系统都会用到串口通信,这个技能会伴随你的整个开发生涯。记得我第一次成功用FPGA通过串口发送"Hello World"时的兴奋感,这种即时反馈对保持学习动力非常重要。
2. UART协议深度解析
2.1 帧结构详解
UART采用异步串行通信,其标准帧结构包含:
- 起始位(1位,低电平)
- 数据位(5-9位,通常8位)
- 校验位(可选,奇/偶/无校验)
- 停止位(1-2位,高电平)
以最常见的"8-N-1"格式为例,传输字符'A'(ASCII 0x41)的波形如下:
code复制[起始位0][1][0][0][0][0][0][1][0][停止位1]
│ └───────────────┘ │
│ 数据位LSB优先 │
└─────────────────────────┘
注意:数据发送采用LSB(最低有效位优先)方式,这与我们日常书写习惯相反,初学者特别容易混淆。
2.2 波特率时序控制
波特率决定了每位数据的持续时间。以115200bps为例:
每位时间 = 1/115200 ≈ 8.68μs
FPGA通常使用系统时钟分频实现:
分频系数 = 系统时钟频率/(16×波特率)
例如50MHz时钟下:
115200bps的分频系数 = 50,000,000/(16×115200) ≈ 27
实际实现时,我们采用16倍过采样技术提高抗干扰能力:
verilog复制reg [15:0] baud_counter;
always @(posedge clk) begin
if(baud_counter == 0) begin
baud_counter <= BAUD_DIVIDER - 1;
baud_tick <= 1;
end else begin
baud_counter <= baud_counter - 1;
baud_tick <= 0;
end
end
3. FPGA实现方案设计
3.1 整体架构设计
我们的UART模块包含以下关键组件:
- 波特率发生器(Baud Rate Generator)
- 发送状态机(TX Finite State Machine)
- 接收状态机(RX Finite State Machine)
- 数据缓冲区(FIFO)
- 校验位生成器(Parity Generator)
模块接口设计:
verilog复制module uart_core (
input wire clk,
input wire rst,
input wire [7:0] tx_data,
input wire tx_valid,
output wire tx_ready,
output wire txd,
input wire rxd,
output wire [7:0] rx_data,
output wire rx_valid
);
3.2 发送状态机实现
发送过程分为5个状态:
verilog复制localparam [2:0]
IDLE = 3'b000,
START = 3'b001,
DATA = 3'b010,
PARITY= 3'b011,
STOP = 3'b100;
always @(posedge clk) begin
case(state)
IDLE: if(tx_valid) begin
shift_reg <= {1'b1, tx_data, 1'b0};
bit_cnt <= 0;
state <= START;
end
START: if(baud_tick) state <= DATA;
DATA: if(baud_tick) begin
if(bit_cnt == 7) state <= PARITY;
bit_cnt <= bit_cnt + 1;
end
// 其他状态转换...
endcase
end
4. 关键技术与调试技巧
4.1 跨时钟域处理
当FPGA逻辑时钟与UART波特率不同源时,需要特别注意跨时钟域同步。推荐采用双触发器同步技术:
verilog复制reg rxd_sync1, rxd_sync2;
always @(posedge clk) begin
rxd_sync1 <= rxd;
rxd_sync2 <= rxd_sync1;
end
4.2 调试技巧实录
- 环路测试验证:将FPGA的TXD与RXD短接,自发自收验证基本功能
- 逻辑分析仪捕获:使用Saleae等工具直接观察信号波形
- 虚拟串口工具:Windows下可用COMx虚拟配对,Linux下使用socat
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 接收乱码 | 波特率不匹配 | 检查双方波特率设置 |
| 只能接收单字节 | FIFO未正确复位 | 验证复位信号时序 |
| 偶发数据错误 | 信号干扰 | 添加施密特触发器 |
5. 性能优化进阶
5.1 硬件流控实现
对于高速通信(>1Mbps),建议添加RTS/CTS流控信号:
verilog复制input wire cts_n,
output wire rts_n,
assign rts_n = (rx_fifo_count > RX_FIFO_HALF) ? 1'b0 : 1'b1;
assign tx_ready = (tx_fifo_count < TX_FIFO_SIZE-1) && !cts_n;
5.2 自适应波特率检测
通过测量起始位宽度自动检测波特率:
verilog复制reg [15:0] start_bit_width;
always @(negedge rxd_sync2) begin
start_bit_width <= 0;
while(!rxd_sync2) begin
start_bit_width <= start_bit_width + 1;
@(posedge clk);
end
baud_divider <= start_bit_width / 16;
end
6. 完整工程示例
6.1 顶层模块设计
verilog复制module top (
input wire clk_50m,
input wire rst_n,
input wire rxd,
output wire txd,
output wire [7:0] leds
);
wire [7:0] rx_data;
wire rx_valid;
uart_core u_uart(
.clk(clk_50m),
.rst(~rst_n),
.tx_data(rx_data), // 回环测试
.tx_valid(rx_valid),
.txd(txd),
.rxd(rxd),
.rx_data(rx_data),
.rx_valid(rx_valid)
);
assign leds = rx_data; // 用LED显示接收数据
endmodule
6.2 约束文件示例(XDC格式)
code复制set_property PACKAGE_PIN E3 [get_ports clk_50m]
set_property IOSTANDARD LVCMOS33 [get_ports clk_50m]
set_property PACKAGE_PIN A10 [get_ports rxd]
set_property PACKAGE_PIN A9 [get_ports txd]
create_clock -period 20.000 -name clk [get_ports clk_50m]
7. 实测经验分享
在实际项目部署时,我总结了几个关键要点:
- PCB布局时,UART信号线尽量短,避免与高频信号平行走线
- 工业环境建议添加TVS二极管保护,如SMBJ3.3A
- 长时间通信测试时,注意FIFO深度设置,一般建议至少16字节
- 多设备通信时,RS485转换芯片(如MAX3485)比直接UART更可靠
一个容易忽略的细节是上电时序。某次项目中,FPGA已完成配置但MCU尚未启动,导致起始位被误判为帧错误。解决方法是在Verilog中添加上电延时:
verilog复制reg [23:0] power_on_delay;
always @(posedge clk) begin
if(!rst_n) power_on_delay <= 0;
else if(power_on_delay != 24'hFFFFFF)
power_on_delay <= power_on_delay + 1;
end
wire uart_enable = (power_on_delay == 24'hFFFFFF);
通过这个完整的UART实现过程,你不仅掌握了串口通信技术,更重要的是理解了FPGA开发的核心思想——用硬件描述语言构建精准的时序逻辑。这种思维方式将为你后续学习更复杂的FPGA应用打下坚实基础。
