3. UART发送模块

UART发送模块 学习卡片

任务目标

实现一个可综合的UART异步发送模块,将8位并行数据按指定波特率转换为串行帧格式(1起始位 + 8数据位 + 1停止位,无校验位)逐位发送。模块需支持外部时钟输入与发送使能控制,并提供发送完成指示信号。

实现方案

采用计数器分频法产生波特率时钟。系统时钟经分频得到与波特率对应的位周期,状态机控制帧的逐位移位输出。核心参数:系统时钟频率 CLK_FREQ 与目标波特率 BAUD_RATE 以参数形式定义,便于灵活配置。

完整代码

module uart_tx #(
    parameter CLK_FREQ = 50_000_000,  // 系统时钟频率(Hz)
    parameter BAUD_RATE = 115_200     // 目标波特率(bps)
)(
    input  wire       clk,            // 系统时钟
    input  wire       rst_n,          // 异步复位,低有效
    input  wire [7:0] tx_data,        // 待发送的8位数据
    input  wire       tx_start,       // 发送启动脉冲(高有效)
    output reg        txd,            // 串行数据输出
    output reg        tx_busy         // 发送忙标志(高电平表示正在发送)
);

    // 波特率分频计数最大值
    localparam integer DIV_CNT_MAX = CLK_FREQ / BAUD_RATE - 1;

    // 状态定义
    localparam IDLE  = 3'd0;
    localparam START = 3'd1;
    localparam DATA  = 3'd2;
    localparam STOP  = 3'd3;

    reg [2:0]  state;
    reg [15:0] clk_cnt;      // 分频计数器
    reg [2:0]  bit_cnt;      // 数据位索引(0~7)
    reg [7:0]  tx_shift_reg; // 发送移位寄存器

    // 发送完成标志(非忙状态且刚结束停止位)
    reg tx_done;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state       <= IDLE;
            clk_cnt     <= 16'd0;
            bit_cnt     <= 3'd0;
            tx_shift_reg <= 8'd0;
            txd         <= 1'b1;   // 空闲时保持高电平
            tx_busy     <= 1'b0;
            tx_done     <= 1'b0;
        end
        else begin
            // 默认清除完成标志
            tx_done <= 1'b0;

            case (state)
                IDLE: begin
                    txd <= 1'b1;
                    tx_busy <= 1'b0;
                    if (tx_start) begin
                        tx_shift_reg <= tx_data;
                        state       <= START;
                        tx_busy     <= 1'b1;
                        clk_cnt     <= 16'd0;
                    end
                end

                START: begin
                    txd <= 1'b0;  // 起始位
                    if (clk_cnt == DIV_CNT_MAX) begin
                        clk_cnt <= 16'd0;
                        state   <= DATA;
                        bit_cnt <= 3'd0;
                    end
                    else begin
                        clk_cnt <= clk_cnt + 1'b1;
                    end
                end

                DATA: begin
                    txd <= tx_shift_reg[0];  // LSB先发送
                    if (clk_cnt == DIV_CNT_MAX) begin
                        clk_cnt <= 16'd0;
                        if (bit_cnt == 3'd7) begin
                            state <= STOP;
                        end
                        else begin
                            bit_cnt <= bit_cnt + 1'b1;
                            tx_shift_reg <= {1'b0, tx_shift_reg[7:1]}; // 右移
                        end
                    end
                    else begin
                        clk_cnt <= clk_cnt + 1'b1;
                    end
                end

                STOP: begin
                    txd <= 1'b1;  // 停止位
                    if (clk_cnt == DIV_CNT_MAX) begin
                        clk_cnt <= 16'd0;
                        state   <= IDLE;
                        tx_busy <= 1'b0;
                        tx_done <= 1'b1;  // 发送完成脉冲
                    end
                    else begin
                        clk_cnt <= clk_cnt + 1'b1;
                    end
                end

                default: state <= IDLE;
            endcase
        end
    end

endmodule

测试平台(Testbench)

module uart_tx_tb;
    reg clk = 0;
    reg rst_n = 0;
    reg [7:0] data = 8'hA5;
    reg start = 0;
    wire txd;
    wire busy;

    // 实例化被测模块
    uart_tx #(
        .CLK_FREQ(50_000_000),
        .BAUD_RATE(115_200)
    ) dut (
        .clk(clk), .rst_n(rst_n),
        .tx_data(data), .tx_start(start),
        .txd(txd), .tx_busy(busy)
    );

    // 50MHz时钟
    always #10 clk = ~clk;

    initial begin
        // 复位
        #30 rst_n = 1;
        #20;
        // 启动发送
        @(posedge clk);
        start = 1;
        @(posedge clk);
        start = 0;

        // 等待发送完成
        wait (busy == 0);
        #100;
        $finish;
    end

    // 监控输出
    initial begin
        $monitor("Time=%0t | txd=%b | busy=%b", $time, txd, busy);
    end

endmodule

运行与验证

仿真步骤:

  1. 将上述两个文件保存为 uart_tx.v 和 uart_tx_tb.v。
  2. 使用Vivado/ModelSim/Verilator等工具进行仿真。
  3. 观察 txd 波形,验证帧格式。

通过条件:

  • 发送数据 0xA5(二进制 1010_0101)时,txd 输出应为:起始位 0 → 数据位 LSB先发 1,0,1,0,0,1,0,1 → 停止位 1,即完整序列 0 1 0 1 0 0 1 0 1 1。
  • 每个位持续时间为 1/115200 ≈ 8.68μs(50MHz时钟下约434个时钟周期)。
  • tx_busy 在发送期间保持高电平,发送结束后拉低。

预期输出序列(仿真可观察):

txd: 1(空闲) → 0(起始) → 1 → 0 → 1 → 0 → 0 → 1 → 0 → 1 → 1(停止) → 1(空闲)

该序列与 0xA5 的LSB-first发送顺序完全一致,满足UART标准帧格式。