跳到主要内容

实验 14:UART 串口发送

🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/10 (Arora V 系列)


一、实验目的

  1. 理解 UART(通用异步收发器)的物理层协议:起始位 + 8 数据位 + 停止位 的帧格式,以及空闲高电平的线状态。
  2. 掌握 FPGA 实现 UART 发送的核心技术——波特率分频。学会将 50MHz 系统时钟分频为 115200bps 的位周期,并计算分频系数误差。
  3. 理解"忙标志(busy flag)"设计模式:在发送过程中拉高 tx_active,防止新数据覆盖正在发送的帧。
  4. 学习使用 case 语句 实现简单的多路选择器,根据位索引 bit_idx 逐位输出起始位/数据位/停止位。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚BankIO 标准说明
CLK输入T9Bank 4LVCMOS3350MHz 有源晶振
UART_TXD输出V13Bank 2LVCMOS33CH340 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

端口方向位宽说明
clkinput150MHz 系统时钟
uart_txdoutput1UART 发送引脚(空闲高电平)

内部信号

信号位宽说明
sec_cnt[25:0]1 秒定时计数器,0→49,999,999
tx_trigger1每秒一次发送触发脉冲
tx_active1发送忙标志,高电平表示正在发送
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_PERIODONE_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