实验 14:UART 串口发送
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/10 (Arora V 系列)
一、实验目的
- 理解 UART(通用异步收发器)的物理层协议:起始位 + 8 数据位 + 停止位 的帧格式,以及空闲高电平的线状态。
- 掌握 FPGA 实现 UART 发送的核心技术——波特率分频。学会将 50MHz 系统时钟分频为 115200bps 的位周期,并计算分频系数误差。
- 理解"忙标志(busy flag)"设计模式:在发送过程中拉高
tx_active,防止新数据覆盖正在发送的帧。 - 学习使用 case 语句 实现简单的多路选择器,根据位索引
bit_idx逐位输出起始位/数据位/停止位。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | Bank | IO 标准 | 说明 |
|---|---|---|---|---|---|
| CLK | 输入 | T9 | Bank 4 | LVCMOS33 | 50MHz 有源晶振 |
| UART_TXD | 输出 | V13 | Bank 2 | LVCMOS33 | CH340 USB 转串口,连 PC |
2.2 设计思路
核心任务:将 8-bit 并行数据按照 UART 8N1 格式逐位串行发送出去。
UART 帧格式(8N1):
空闲(高) ─┐ 起始位(0) │ D0 │ D1 │ ... │ D7 │ 停止位(1) ─¬ 空闲(高)
└───────────┴────────────────────┴──────────┘
1 bit 8 data bits (LSB first) 1 bit
波特率计算:50MHz / 115200 = 434.0278 ≈ 434 个时钟周期
实际波特率 = 50,000,000 / 434 = 115,207 bps,误差仅为 0.006%,远在 ±2% 容差范围内。
设计架构:
50MHz clk ─→ sec_cnt (1秒定时器) ─→ tx_trigger ─→ tx_active=1
↓
bit_cnt (0~433)
bit_idx (0~9)
↓
uart_txd 输出选择
2.3 模块设计
模块名:uart_tx
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
uart_txd | output | 1 | UART 发送引脚(空闲高电平) |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
sec_cnt | [25:0] | 1 秒定时计数器,0→49,999,999 |
tx_trigger | 1 | 每秒一次发送触发脉冲 |
tx_active | 1 | 发送忙标志,高电平表示正在发送 |
bit_cnt | [9:0] | 位周期计数器,0→433 |
bit_idx | [3:0] | 当前发送位索引:0=起始位,1~8=数据位,9=停止位 |
tx_data | [7:0] | 待发送字节,自动递增 0x00→0xFF 循环 |
三、代码逐段讲解
3.1 参数定义
localparam BIT_PERIOD = 434 - 1; // 位周期计数器最大值 (0~433)
localparam ONE_SECOND = 50_000_000 - 1; // 1 秒计数器最大值
BIT_PERIOD = 433:115200bps 每位需要 434 个 50MHz 时钟周期(0~433 = 434 个计数值)。ONE_SECOND = 49,999,999:50MHz 时钟计数 5000 万次 = 1 秒。
3.2 1 秒定时器与发送触发
wire tx_trigger = (sec_cnt == ONE_SECOND);
always @(posedge clk) begin
if (sec_cnt == ONE_SECOND)
sec_cnt <= 0;
else
sec_cnt <= sec_cnt + 1'b1;
end
每秒 tx_trigger 出现一个单周期脉冲,触发一次 UART 发送。
3.3 发送数据寄存器(忙标志核心)
always @(posedge clk) begin
if (tx_trigger && !tx_active) begin
tx_data <= tx_data + 1'b1; // 递增发送字节
tx_active <= 1'b1; // 设置忙标志
end else if (tx_active && bit_idx == 4'd9 && bit_cnt == BIT_PERIOD) begin
tx_active <= 1'b0; // 一帧发送完毕,清除忙标志
end
end
关键设计:
tx_trigger && !tx_active:仅在空闲时响应触发,避免打断当前帧。bit_idx==9 && bit_cnt==BIT_PERIOD:停止位最后一个周期结束后才清除tx_active。
3.4 位周期计数器与位索引
always @(posedge clk) begin
if (!tx_active) begin
bit_cnt <= 0;
bit_idx <= 0;
end else begin
if (bit_cnt == BIT_PERIOD) begin
bit_cnt <= 0;
if (bit_idx == 4'd9)
bit_idx <= 0;
else
bit_idx <= bit_idx + 1'b1;
end else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end
- 空闲时 (
!tx_active) 两个计数器归零。 - 每 434 个时钟周期
bit_idx加 1,完成一帧后bit_idx回零。
3.5 UART TX 输出(case 选择器)
always @(posedge clk) begin
if (!tx_active)
uart_txd <= 1'b1; // 空闲:高电平
else begin
case (bit_idx)
4'd0: uart_txd <= 1'b0; // 起始位(低电平)
4'd1: uart_txd <= tx_data[0]; // D0 (LSB)
4'd2: uart_txd <= tx_data[1]; // D1
// ... D2~D7 ...
4'd8: uart_txd <= tx_data[7]; // D7 (MSB)
4'd9: uart_txd <= 1'b1; // 停止位(高电平)
default: uart_txd <= 1'b1;
endcase
end
end
LSB first 发送顺序:先发 D0(最低位),最后发 D7(最高位)。这是 UART 协议规定的。
四、实验现象
| 步骤 | 预期结果 | 验证方法 |
|---|---|---|
| 上电下载 | 无 LED 变化(本实验只用 UART) | — |
| 打开串口助手 | 每秒收到 1 个字节,从 0x00 递增到 0xFF | 串口助手 115200,8,N,1 |
| 观察递增 | 0x00 → 0x01 → ... → 0xFF → 0x00 循环 | 十六进制显示模式 |
| 示波器观察 | T13 引脚可见标准 UART 帧波形 | 起始位低 + 8 数据 + 停止位高 |
验证要点:
- ✅ 串口助手能正确接收数据,无乱码(波特率匹配)。
- ✅ 数据每秒钟更新一次,递增顺序正确。
- ✅ 停止位后有空闲高电平,帧间无重叠。
五、掌握知识点
| 知识点 | 说明 | 在本实验中的体现 |
|---|---|---|
| UART 协议 | 异步串行通信,起始位+数据+停止位 | 8N1 帧格式,LSB first |
| 波特率分频 | F_clk / baud_rate 计算位周期 | 50M/115200≈434 个时钟 |
| 忙标志(busy) | 防止数据竞争的多周期操作保护 | tx_active 控制发送流程 |
| case 选择器 | 多路输出的简洁表达 | case(bit_idx) 逐位输出 |
| 参数化设计 | localparam 定义可调常数 | BIT_PERIOD、ONE_SECOND |
附录:源码
top.v
//////////////////////////////////////////////////////////////////////////////////
// Company: 武汉芯路恒科技有限公司
// Engineer: 小梅哥团队
// Web: www.corecourse.cn
//
// Create Date: 2020/07/20 00:00:00
// Design Name: uart_tx
// Module Name: uart_tx
// Project Name: EK301 实验12
// Description: UART串口发送顶层测试
// 115200bps 8N1, 周期性发送递增字节(0x00-0xFF循环), 每秒1字节
//
// Dependencies: uart_byte_tx.v
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 芯片: GW5A-LV25UG324C1/I0, 主时钟50MHz
// 引脚: clk→T9, uart_txd→V13, reset_n→A11
//////////////////////////////////////////////////////////////////////////////////
module uart_tx (
input wire clk, // 50MHz 系统时钟
input wire reset_n, // 复位信号,低有效
output wire uart_txd // UART发送引脚 (空闲=高电平)
);
wire reset = ~reset_n;
// ---- 内部信号 ----
reg [25:0] sec_cnt; // 1秒计数器
reg [7:0] tx_data; // 待发送字节 (0x00-0xFF 递增)
wire send_en; // 发送触发
wire tx_done; // 发送完成
wire uart_state; // 发送忙标志
// ---- 1秒定时器: 每秒触发一次发送 ----
assign send_en = (sec_cnt == 50_000_000 - 1);
always@(posedge clk or posedge reset)
if(reset)
sec_cnt <= 26'd0;
else if(sec_cnt == 50_000_000 - 1)
sec_cnt <= 26'd0;
else
sec_cnt <= sec_cnt + 1'b1;
// ---- 发送数据递增 ----
always@(posedge clk or posedge reset)
if(reset)
tx_data <= 8'd0;
else if(send_en && !uart_state)
tx_data <= tx_data + 1'b1; // 递增, 自然溢出0x00-0xFF循环
// ---- UART发送模块例化 ----
// baud_set=4 → 115200bps
uart_byte_tx u_uart_tx (
.clk (clk),
.reset_n (reset_n),
.data_byte (tx_data),
.send_en (send_en && !uart_state),
.baud_set (3'd4),
.uart_tx (uart_txd),
.tx_done (tx_done),
.uart_state (uart_state)
);
endmodule