1. 安路FPGA UART发送模块设计解析
作为一名FPGA开发者,UART通信模块的设计是基本功中的基本功。但就是这个看似简单的串口发送模块,在实际开发中却暗藏玄机。今天我就来详细拆解这个项目中遇到的典型问题和技术要点,希望能帮助大家少走弯路。
1.1 核心变量协作机制
在UART发送模块中,有五个关键变量构成了整个系统的"交响乐团":
-
div_cnt(节拍器):这个16位计数器负责对系统时钟进行分频。它就像乐队的指挥,决定了整个通信的节奏快慢。计算公式为:
分频系数 = 系统时钟频率 / 波特率 - 1。例如50MHz时钟下,9600波特率对应的分频系数就是50_000_000/9600-1≈5207。 -
bps_clk(拍子):当div_cnt计数到预设值时,会产生一个单周期脉冲。这个脉冲相当于节拍器的"咔嗒"声,标志着当前bit的持续时间已经结束,需要切换到下一个bit。
-
bps_cnt(进度条):这个计数器记录发送进度,从起始位(1)到停止位(10),共11个状态。每个bps_clk脉冲会使其加1,相当于乐谱上的小节线。
-
uart_state(总开关):这个状态机只有两个状态——空闲(IDLE)和发送中(TXING)。它像乐队经理,决定何时开始演奏(检测到send_en信号),何时结束(bps_cnt==11)。
-
data_byte_reg(保险柜):在send_en信号有效时锁存待发送数据,确保在长达11个波特率周期的发送过程中,原始数据不会变化导致发送错误。
重要提示:这些变量的协作关系是理解整个模块的关键。div_cnt产生bps_clk,bps_clk驱动bps_cnt,bps_cnt反馈给uart_state,uart_state协调data_byte_reg,最终通过多路选择器输出串行数据。
1.2 波特率生成实现细节
波特率选择模块采用了查表法,支持5种常用波特率配置:
verilog复制case(baud_set)
0: bps_DR <= 16'd5207; // 9600bps @50MHz
1: bps_DR <= 16'd2603; // 19200bps
2: bps_DR <= 16'd1301; // 38400bps
3: bps_DR <= 16'd867; // 57600bps
4: bps_DR <= 16'd433; // 115200bps
default bps_DR <= 16'd5207;
endcase
这里有个设计技巧:bps_DR寄存器只在复位或baud_set变化时更新,避免了在发送过程中波特率被意外修改。同时default case保证了即使baud_set输入异常,模块也能以最低波特率正常工作。
2. 关键调试问题与解决方案
2.1 复位电平配置错误
问题现象:模块在仿真时工作正常,但下载到板卡后完全无反应。
根本原因:开发板使用低电平复位(reset_n),而代码中错误地使用了posedge reset。这导致实际上复位信号从未生效,寄存器处于未定义状态。
解决方案:
verilog复制// 错误写法
always@(posedge clk or posedge reset)
// 正确写法
always@(posedge clk or negedge reset_n)
经验之谈:拿到新开发板时,第一件事就是查阅手册确认复位极性。有些厂商习惯用"_n"表示低有效,有些则用注释说明。建议在顶层模块定义时就明确标注类似
input reset_n, //低电平有效。
2.2 状态机与计数器冲突
问题现象:综合时报错"[Synth 8-2715] syntax error near if"。
根本原因:试图在同一个always块中驱动多个reg变量(uart_state和bps_cnt),违反了Verilog语法规则。
正确实现方案:
verilog复制// 状态机单独一个always块
always@(posedge clk or negedge reset_n)
if(!reset_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 16'd11)
uart_state <= 1'b0;
// 计数器单独一个always块
always@(posedge clk or negedge reset_n)
if(!reset_n)
bps_cnt <= 16'd0;
else if(bps_cnt == 16'd11)
bps_cnt <= 16'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
2.3 串口调试技巧
快速验证技巧:将data_byte_reg默认值设为8'h55(ASCII字符'U')。这样如果看到:
- 连续收到'U':说明send_en信号未正确连接或持续有效
- 收到随机字符:可能是波特率设置错误
- 无任何输出:检查硬件连接或复位信号
乱码问题排查清单:
- 确认串口助手的波特率与FPGA设置一致
- 检查数据格式(8位数据位、1位停止位、无校验)
- 尝试切换ASCII/Hex显示模式
- 用示波器测量TX引脚波形,确认电平是否符合标准(RS232为±12V,TTL为0/3.3V)
3. 完整代码实现解析
3.1 模块接口定义
verilog复制module uart_tx (
input send_en, // 发送使能脉冲
input [7:0] data_byte, // 待发送字节
input [2:0] baud_set, // 波特率选择(0-4)
input clk, // 系统时钟(50MHz)
input reset_n, // 低电平复位
output reg uart_tx, // 串行输出
output reg tx_done, // 发送完成标志
output reg uart_state // 状态指示(可选)
);
接口设计考虑:
- 采用独立的send_en脉冲触发,避免持续信号导致重复发送
- baud_set用3位宽支持最多8种波特率预设
- tx_done信号可用于流控制或中断触发
3.2 数据发送状态机
发送过程分为11个状态(1起始位 + 8数据位 + 2停止位):
verilog复制always@(posedge clk or negedge reset_n)
if(!reset_n)
uart_tx <= 1'b1; // 空闲状态保持高电平
else begin
case(bps_cnt)
0: uart_tx <= 1'b1; // 空闲
1: uart_tx <= START_BIT; // 起始位(0)
2: uart_tx <= data_byte_reg[0]; // LSB first
3: uart_tx <= data_byte_reg[1];
...
9: uart_tx <= data_byte_reg[7]; // MSB
10: uart_tx <= STOP_BIT; // 停止位(1)
default: uart_tx <= 1'b1; // 异常保护
endcase
end
注意细节:UART协议规定先发送最低位(LSB first),这与我们的直觉相反。有些串口设备允许配置发送顺序,但大多数情况下都遵循这个传统。
4. 实测波形与性能分析
4.1 仿真波形关键点
通过Modelsim仿真可以看到:
- send_en脉冲到来后,uart_state立即变高
- 每个bps_clk脉冲间隔5208个时钟周期(对应9600bps)
- bps_cnt在每个bps_clk时递增
- uart_tx依次输出起始位(0)、数据位(D0-D7)、停止位(1)
4.2 资源占用评估
在安路EG4S20 FPGA上综合结果:
- 逻辑单元:78/18,752(0.4%)
- 寄存器:5
- 最大时钟频率:150MHz(远高于需求)
优化建议:如果资源紧张,可以将16位计数器改为12位(最大计数5207只需13位),节省3个寄存器。
4.3 时序约束建议
添加以下约束保证稳定性:
tcl复制create_clock -period 20.000 -name clk [get_ports clk]
set_input_delay -clock clk 2.000 [get_ports {send_en data_byte baud_set}]
set_output_delay -clock clk 2.000 [get_ports {uart_tx tx_done}]
5. 扩展应用场景
本模块稍作修改即可用于:
- 无线模块通信:配合HC-05蓝牙模块实现无线数据传输
- 传感器读取:对接GPS模块、温湿度传感器等串口设备
- 调试接口:替代JTAG实现轻量级调试信息输出
- 多设备通信:通过增加片选信号扩展为多节点UART网络
进阶改进方向:
- 添加FIFO缓冲实现连续发送
- 支持硬件流控制(RTS/CTS)
- 增加奇偶校验位生成
- 实现自适应波特率检测
这个UART发送模块虽然代码量不大,但涵盖了FPGA开发的多个核心概念:时钟分频、状态机设计、同步时序等。建议初学者可以以此为模板,逐步添加新功能来加深理解。下次我将分享接收模块的设计要点,包括如何实现可靠的起始位检测和采样时钟同步。