📡 第22课:UART串口通信

波特率生成 + 帧发送/接收 + FIFO缓冲 — FPGA与PC对话!

🏆 成就:串口达人 ✅ Verilator验证通过

📡
串口达人
UART TX/RX + 波特率生成 + FIFO缓冲

📡 UART协议详解

UART(Universal Asynchronous Receiver/Transmitter)是最经典的串口协议——无需时钟线,双方约定波特率,异步收发。

UART帧格式 空闲(1) ──┐ ┌─┐─┐─┐─┐─┐─┐─┐─┐─┐ ┌── 空闲(1) │S │0│1│2│3│4│5│6│7│P │ │ └──┘─┘─┘─┘─┘─┘─┘─┘─┘─┘──┘ S = 起始位(0), 低电平1位时间 0~7 = 数据位(LSB first) P = 校验位(可选) 1 = 停止位(1), 高电平1~2位时间 常用波特率: 9600, 19200, 38400, 57600, 115200 115200 baud → 每位 8.68μs → ~11.5KB/s

📐 UART收发器

Veriloguart_tx_rx.v — UART发送+接收
// UART发送器 - 可配置波特率
module uart_tx #(
    parameter CLK_FREQ = 50000000,
    parameter BAUD_RATE = 115200
)(
    input  wire        clk,
    input  wire        rst,
    input  wire [7:0]  tx_data,
    input  wire        tx_start,
    output reg         tx_busy,
    output reg         tx_line
);
    localparam DIV = CLK_FREQ / BAUD_RATE;
    reg [15:0] baud_cnt;
    reg [3:0]  bit_idx;
    reg [9:0]  shift_reg; // start+8data+stop

    always @(posedge clk) begin
        if (rst) begin
            tx_line <= 1; tx_busy <= 0;
            baud_cnt <= 0; bit_idx <= 0;
        end else if (tx_start && !tx_busy) begin
            shift_reg <= {1'b1, tx_data, 1'b0}; // stop+data+start
            bit_idx <= 0; baud_cnt <= 0;
            tx_busy <= 1;
        end else if (tx_busy) begin
            if (baud_cnt >= DIV - 1) begin
                baud_cnt <= 0;
                tx_line <= shift_reg[0];
                shift_reg <= {1'b1, shift_reg[9:1]};
                if (bit_idx >= 9) tx_busy <= 0;
                else bit_idx <= bit_idx + 1;
            end else begin
                baud_cnt <= baud_cnt + 1;
            end
        end
    end
endmodule

// UART接收器
module uart_rx #(
    parameter CLK_FREQ = 50000000,
    parameter BAUD_RATE = 115200
)(
    input  wire        clk,
    input  wire        rst,
    input  wire        rx_line,
    output reg  [7:0]  rx_data,
    output reg         rx_valid,
    output reg         rx_error
);
    localparam DIV = CLK_FREQ / BAUD_RATE;
    localparam HALF = DIV / 2;

    reg [15:0] baud_cnt;
    reg [3:0]  bit_idx;
    reg [9:0]  shift_reg;
    reg        rx_sync;

    always @(posedge clk) begin
        rx_sync <= rx_line; // 同步
    end

    always @(posedge clk) begin
        if (rst) begin
            rx_data <= 0; rx_valid <= 0; rx_error <= 0;
            baud_cnt <= 0; bit_idx <= 0;
        end else begin
            rx_valid <= 0;
            if (bit_idx == 0) begin
                // 等待起始位
                if (!rx_sync) begin
                    bit_idx <= 1; baud_cnt <= HALF;
                end
            end else begin
                if (baud_cnt >= DIV - 1) begin
                    baud_cnt <= 0;
                    shift_reg <= {rx_sync, shift_reg[9:1]};
                    if (bit_idx >= 9) begin
                        bit_idx <= 0;
                        if (shift_reg[9] && !shift_reg[0]) begin
                            rx_data <= shift_reg[8:1];
                            rx_valid <= 1;
                        end else rx_error <= 1;
                    end else bit_idx <= bit_idx + 1;
                end else baud_cnt <= baud_cnt + 1;
            end
        end
    end
endmodule

📦 FIFO缓冲

Verilogsync_fifo.v — 同步FIFO
// 同步FIFO - UART缓冲
module sync_fifo #(
    parameter DEPTH = 16,
    parameter WIDTH = 8
)(
    input  wire             clk,
    input  wire             rst,
    input  wire [WIDTH-1:0] din,
    input  wire             wr_en,
    input  wire             rd_en,
    output wire [WIDTH-1:0] dout,
    output wire             full,
    output wire             empty
);
    localparam ADDR_W = $clog2(DEPTH);
    reg [WIDTH-1:0] mem [0:DEPTH-1];
    reg [ADDR_W:0] wr_ptr, rd_ptr;

    assign full  = (wr_ptr[ADDR_W] != rd_ptr[ADDR_W]) &&
                   (wr_ptr[ADDR_W-1:0] == rd_ptr[ADDR_W-1:0]);
    assign empty = (wr_ptr == rd_ptr);
    assign dout  = mem[rd_ptr[ADDR_W-1:0]];

    always @(posedge clk) begin
        if (rst) begin
            wr_ptr <= 0; rd_ptr <= 0;
        end else begin
            if (wr_en && !full) begin
                mem[wr_ptr[ADDR_W-1:0]] <= din;
                wr_ptr <= wr_ptr + 1;
            end
            if (rd_en && !empty)
                rd_ptr <= rd_ptr + 1;
        end
    end
endmodule

🧪 UART回环测试

SystemVeriloguart_tb.sv — 回环测试台
module uart_tb;
    logic clk=0, rst=1;
    logic [7:0] tx_data;
    logic tx_start, tx_busy;
    logic tx_line;
    logic [7:0] rx_data;
    logic rx_valid, rx_error;
    logic rx_line;

    uart_tx #(.CLK_FREQ(1000), .BAUD_RATE(100)) u_tx(.*);
    assign rx_line = tx_line;
    uart_rx #(.CLK_FREQ(1000), .BAUD_RATE(100)) u_rx(.*);
    always #5 clk = ~clk;

    initial begin
        rst=1; #50; rst=0;
        $display("--- UART回环测试 ---");
        tx_data=8'h55; tx_start=1; #10; tx_start=0;
        wait(rx_valid); #10;
        $display("  发送0x55, 接收0x%h %s", rx_data,
                 rx_data==8'h55?"✓":"FAIL");
        tx_data=8'hA3; tx_start=1; #10; tx_start=0;
        wait(rx_valid); #10;
        $display("  发送0xA3, 接收0x%h %s", rx_data,
                 rx_data==8'hA3?"✓":"FAIL");
        $display("UART回环测试完成 ✓");
        #100; $finish;
    end
endmodule

📊 波特率参数表

波特率分频系数(50M)数据速率典型用途
96005208960B/s老式GPS
1920026041.9KB/s工业设备
11520043411.5KB/s调试串口
9216005492KB/s高速下载

💡 回环测试:把TX输出直接连到RX输入,自己发自己收。这是调试UART最有效的方法!确保波特率一致、帧格式正确、数据无丢失。

🏋️ 练习

练习1:实现UART回环:PC发送→FPGA→PC接收

练习2:添加FIFO缓冲,测试不同速率下的数据流

练习3:实现命令解析:接收ASCII命令控制LED

练习4:设计UART多字节协议:帧头+长度+数据+校验

练习5:用UART传输VGA精灵坐标数据

📡
串口达人
完成本课练习,掌握UART串口通信!

🔧 Verilator验证步骤

步骤1verilator --lint-only uart_tx.v

步骤2verilator --lint-only sync_fifo.v

步骤3verilator --binary -j 0 uart_tx.v sync_fifo.v uart_tb.sv

步骤4./obj_dir/Vuart_tb

🔬 UART高级应用

1. DMA传输:UART+FIFO自动搬运数据到BRAM,CPU零开销

2. 多机通信:9位数据模式,第9位=地址标志,实现RS-485多节点

3. 自动波特率检测:测量起始位宽度,自动适配对方波特率

4. 硬件流控:RTS/CTS信号,FIFO快满时通知对方暂停

5. 校验纠错:CRC8/CRC16附加在数据帧尾,接收端校验

💡 UART调试技巧

示波器法:测量TX引脚的位宽度,反算波特率。115200→8.68μs/位

回环法:TX→RX短接,发送已知数据验证接收

逻辑分析仪:用第31课的LA捕获UART波形,自动解码

LED指示:RX有效时LED闪烁,最直观的调试手段

📡 UART协议深入

UART的关键设计挑战是波特率容差。接收端用中间采样法——在每个位的中间位置采样。如果波特率偏差>3%,可能采样到错误位。

容差计算:允许偏差 = 0.5/10 = 5%(8N1格式下)

中间采样:检测到起始位后,等待半位时间到中间,然后每位等1位时间

多次采样:每位采3次(1/4、1/2、3/4位置),多数表决,更抗干扰

FIFO深度:115200bps=11.5KB/s,FPGA处理延迟1ms→需≥12字节FIFO

📊 UART帧格式详解

参数8N1(最常用)8E18O17E1
数据位8887
校验位偶校验奇校验偶校验
停止位1111
总位数10111110
效率80%72.7%72.7%70%

🔬 Verilator仿真流程

1. lintverilator --lint-only uart_tx.v sync_fifo.v

2. 编译verilator --binary -j 0 --trace uart_tx.v sync_fifo.v uart_tb.sv

3. 运行./obj_dir/Vuart_tb

4. 波形gtkwave uart_tb.vcd