FPGA(五)UART串口通信

5.1 串行通信基础

  • 同步与异步通信

    • 同步通信:带时钟同步信号的数据传输,发送方和接收方在同一时钟下工作。

    • 异步通信:不带时钟同步信号的数据传输,发送方和接收方使用各自的时钟。

  • 通信方向:单工、半双工、全双工。

  • 常见串行通信接口:

    通信标准 引脚 通信方式 通信方向
    UART TXD:发送端
    RXD:接收端
    GND:公共接地
    异步 全双工
    单总线(1-wire) DQ:发送/接收端 异步 半双工
    SPI SCK:同步时钟
    MISO:主机输入从机输出
    MOSI:主机输出从机输入
    同步 全双工
    I2C SCL:同步时钟
    SDA:数据输入/输出
    同步 半双工

5.2 UART基础

UART(Universal Asynchronous Receiver/Transmitter):通用异步接收器/发送器。

5.2.1 协议层

数据格式

  • 空闲:拉高。
  • 起始位:拉低。
  • 传输固定bit数据(上图是7bit)。
  • 校验位:奇偶校验(比如奇校验就是这8bit中1的个数为奇数)。
  • 停止位:拉高(长度不一定是一个波特率)。

传输速率

波特率:每秒传输的二进制位数,单位bps。常用波特率有9600、19200、38400、57600、115200。

比如波特率为115200,时钟频率为50MHz。那么每发送1位的时钟周期是:50M/115200=434.028≈434。

5.2.2 物理层

接口标准

RS422和RS485是差分传输。差分传输:两组信号相位相反,接收时用它们的差值来判断01,对共模噪声(两根线分别对地的噪声)抑制好。

DB9接口

常见的RS232接口。主要关注RXD、TXD、GND引脚。

拓展一下USB接口的定义。下图从左到右分别是USB-A、USB-B、USB-micro、USB-mini。

USB-AUSB-B

引脚 名称 说明
1 VCC +5V电源
2 Data- 数据-
3 Data+ 数据+
4 GND

USB-microUSB-mini

引脚 名称 说明
1 VCC +5V电源
2 Data- 数据-
3 Data+ 数据+
4 ID A型:与地相连
B型:不接地(空)
5 GND

5.3 UART通信实验代码

整个代码由以下3部分构成:

  • uart_recv:接收主机的uart数据;
  • uart_loop:环回模块,即简单的将接收到的数据发送出去,以及一些控制逻辑;
  • uart_send:发送数据给主机。

下图是接收的时序示意图(发送也是类似的,只不过rx变为tx),对其中的信号/变量作简单说明:

本实验接收的停止位长度设为1/2数据位的长度(434/2=217),实际上设置比较自由,1倍、2倍数据位长度都可以。

  • start_flag:检测到uart_rxd的下降沿时拉高;
  • rx_flag:拉高表示正在接收数据,start_flag拉高时拉高,接收完停止位时拉低;
  • clk_cnt:计数发送一位的时钟周期,本实验为434;
  • uart_done:接收完数据位,此时uart_data有效。

5.3.1 接收模块

module uart_recv #(
    parameter CLK_FREQ = 50000000,      // 系统时钟频率
    parameter UART_BPS = 115200         // 串口波特率
)(
    input			    sys_clk,        // 系统时钟
    input               sys_rst_n,      // 系统复位
    
    input               uart_rxd,       // UART接收端口
    output logic        recv_done,      // 接收一帧数据完成标志
    output logic [7:0]  recv_data       // 接收的数据
);

    // 传输一位数据需要的时钟周期数(434)
    localparam BPS_CNT = CLK_FREQ / UART_BPS;

    wire            start_flag;    // 检测下降沿,说明起始位来了
    logic           rx_flag;       // 接收过程标志信号
    logic [15:0]    clk_cnt;       // 系统时钟计数器
    logic [ 3:0]    rx_cnt;        // 接收数据计数器
    logic [ 7:0]    rx_data;       // 暂存接收的数据


    // 1. 打两拍捕捉下降沿
    logic           uart_rxd_d0;   
    logic           uart_rxd_d1;

    assign start_flag = (~uart_rxd_d0) & uart_rxd_d1;    

    always_ff @(posedge sys_clk, negedge sys_rst_n) begin 
        if (!sys_rst_n) begin 
            uart_rxd_d0 <= 1'b0;
            uart_rxd_d1 <= 1'b0;          
        end
        else begin
            uart_rxd_d0 <= uart_rxd;                   
            uart_rxd_d1 <= uart_rxd_d0;
        end   
    end

    // 2. 控制接收过程:为rx_flag赋值
    always_ff @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n)         rx_flag <= 1'b0;
        // 检测到起始位时拉高
        else if (start_flag)     rx_flag <= 1'b1;
        // 停止位结束时拉低(本实验停止位只有1/2数据位的长度)
        else if (rx_cnt == 4'd9 && clk_cnt == BPS_CNT / 2)  rx_flag <= 1'b0;
    end

    // 3. 计数接收数据
    always_ff @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin
            clk_cnt <= 16'd0;
            rx_cnt  <= 4'd0;
        end                          
        else if (rx_flag) begin     // 接收过程中计数
            if (clk_cnt == BPS_CNT - 1) begin
                clk_cnt <= 16'd0;
                rx_cnt  <= rx_cnt + 1'b1;
            end
            else begin
                clk_cnt <= clk_cnt + 1'b1;
                rx_cnt  <= rx_cnt;
            end
        end
        else begin
            clk_cnt <= 16'd0;
            rx_cnt  <= 4'd0;
        end
    end

    // 4. 根据计数值接收数据
    always_ff @(posedge sys_clk, negedge sys_rst_n) begin 
        if (!sys_rst_n)     rx_data <= 8'd0;
        else if (rx_flag) begin
            if (clk_cnt == BPS_CNT / 2) begin         // 计数到一半时认为数据稳定,赋值
                case (rx_cnt)
                    4'd1 : rx_data[0] <= uart_rxd_d1;
                    4'd2 : rx_data[1] <= uart_rxd_d1;
                    4'd3 : rx_data[2] <= uart_rxd_d1;
                    4'd4 : rx_data[3] <= uart_rxd_d1;
                    4'd5 : rx_data[4] <= uart_rxd_d1;
                    4'd6 : rx_data[5] <= uart_rxd_d1;
                    4'd7 : rx_data[6] <= uart_rxd_d1;
                    4'd8 : rx_data[7] <= uart_rxd_d1;
                endcase
            end
        end
        else    rx_data <= 8'd0;
    end

    // 5. 数据接收完毕后给出标志信号并寄存输出接收到的数据
    always_ff @(posedge sys_clk, negedge sys_rst_n) begin
        if (!sys_rst_n) begin
            recv_data <= 8'd0;
            recv_done <= 1'b0;
        end
        else if (rx_cnt == 4'd9) begin      // 接收数据计数器计数到停止位时           
            recv_data <= rx_data;           // 寄存输出接收到的数据
            recv_done <= 1'b1;              // 将接收完成标志位拉高
        end
        else begin
            recv_data <= 8'd0;                                   
            recv_done <= 1'b0; 
        end    
    end

endmodule

5.3.2 发送模块

module uart_send #(
    parameter CLK_FREQ = 50000000,      // 系统时钟频率
    parameter UART_BPS = 115200         // 串口波特率
)(
    input	        sys_clk,
    input           sys_rst_n,
    
    input           send_en,            // 发送使能信号
    input  [7:0]    send_data,          // 待发送数据
    output          send_busy,          // 发送忙状态标志      
    output logic    uart_txd            // UART发送端口
);
    
    localparam  BPS_CNT  = CLK_FREQ / UART_BPS;

    wire            en_flag;            // 捕获发送使能信号
    logic           tx_flag;            // 发送过程标志信号
    logic [15:0]    clk_cnt;            // 系统时钟计数器
    logic [ 3:0]    tx_cnt;             // 发送数据计数器
    logic [ 7:0]    tx_data;            // 寄存发送数据

    // 在串口发送过程中给出忙状态标志
    assign send_busy = tx_flag;

    // 1. 打两拍,捕获uart_en上升沿,得到一个时钟周期的脉冲信号
    logic           uart_en_d0; 
    logic           uart_en_d1;  
    assign en_flag = uart_en_d0 & (~uart_en_d1);

    always @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin
            uart_en_d0 <= 1'b0;                                  
            uart_en_d1 <= 1'b0;
        end                                                      
        else begin                                               
            uart_en_d0 <= send_en;                               
            uart_en_d1 <= uart_en_d0;                            
        end
    end

    // 2. 当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
    always @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin                                  
            tx_flag <= 1'b0;
            tx_data <= 8'd0;
        end
        // 检测到发送使能上升沿时,tx_flag拉高,寄存待发送数据
        else if (en_flag) begin
            tx_flag <= 1'b1;
            tx_data <= send_data;
        end
        // 计数到停止位结束时,停止发送过程(这里停止位的长度设置为15/16数据位的长度)
        else if (tx_cnt == 4'd9 && clk_cnt == BPS_CNT - BPS_CNT / 16) begin
            tx_flag <= 1'b0;
            tx_data <= 8'd0;
        end
    end

    // 3. 进入发送过程后,启动系统时钟计数器
    always @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin
            clk_cnt <= 16'd0;
            tx_cnt  <= 4'd0;
        end
        else if (tx_flag) begin         // 发送过程中计数
            if (clk_cnt == BPS_CNT - 1) begin
                clk_cnt <= 16'd0;
                tx_cnt  <= tx_cnt + 1'b1;
            end
            else begin
                clk_cnt <= clk_cnt + 1'b1;
                tx_cnt  <= tx_cnt;
            end
        end
        else begin
            clk_cnt <= 16'd0;
            tx_cnt  <= 4'd0;
        end
    end

    // 4. 根据发送数据计数器来给uart发送端口赋值
    always @(posedge sys_clk, negedge sys_rst_n) begin        
        if (!sys_rst_n)     uart_txd <= 1'b1;   // 默认拉高
        else if (tx_flag)
            case(tx_cnt)
                4'd0: uart_txd <= 1'b0;         // 起始位
                4'd1: uart_txd <= tx_data[0];
                4'd2: uart_txd <= tx_data[1];
                4'd3: uart_txd <= tx_data[2];
                4'd4: uart_txd <= tx_data[3];
                4'd5: uart_txd <= tx_data[4];
                4'd6: uart_txd <= tx_data[5];
                4'd7: uart_txd <= tx_data[6];
                4'd8: uart_txd <= tx_data[7];
                4'd9: uart_txd <= 1'b1;         // 停止位
            endcase
        else    uart_txd <= 1'b1;
    end

endmodule

5.3.3 环回模块

module uart_loop(
    input	            sys_clk,           // 系统时钟
    input               sys_rst_n,         // 系统复位,低电平有效
     
    input               recv_done,         // 接收一帧数据完成标志
    input        [7:0]  recv_data,         // 接收的数据
     
    input               send_busy,         // 发送忙状态标志
    output logic        send_en,           // 发送使能信号
    output logic [7:0]  send_data          // 待发送数据
);

    logic recv_done_d0;
    logic recv_done_d1;
    logic tx_ready;

    wire recv_done_flag;

    // 捕获recv_done上升沿,得到一个时钟周期的脉冲信号
    assign recv_done_flag = recv_done_d0 & (~recv_done_d1);
                                                    
    // 对发送使能信号recv_done延迟两个时钟周期
    always @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin
            recv_done_d0 <= 1'b0;                                  
            recv_done_d1 <= 1'b0;
        end                                                      
        else begin                                               
            recv_done_d0 <= recv_done;                               
            recv_done_d1 <= recv_done_d0;                            
        end
    end

    //判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
    always @(posedge sys_clk, negedge sys_rst_n) begin         
        if (!sys_rst_n) begin
            tx_ready  <= 1'b0; 
            send_en   <= 1'b0;
            send_data <= 8'd0;
        end                                                      
        else begin                                               
            if (recv_done_flag) begin               // 检测串口接收到数据
                tx_ready  <= 1'b1;                  // 准备启动发送过程
                send_en   <= 1'b0;
                send_data <= recv_data;             // 寄存串口接收的数据
            end
            else if (tx_ready && (~send_busy)) begin  // 检测串口发送模块空闲
                tx_ready <= 1'b0;                   // 准备过程结束
                send_en  <= 1'b1;                   // 拉高发送使能信号
            end
        end
    end

endmodule

5.3.4 顶层模块

module uart_loopback_top #(
    parameter CLK_FREQ = 50000000,      // 定义系统时钟频率
    parameter UART_BPS = 115200         // 定义串口波特率
)(
    input           sys_clk,            // 外部50M时钟
    input           sys_rst_n,          // 外部复位信号,低有效

    input           uart_rxd,           // UART接收端口
    output          uart_txd            // UART发送端口
);
    
    wire       recv_done;              // UART接收完成
    wire [7:0] recv_data;              // UART接收数据
    wire       send_en;                // UART发送使能
    wire [7:0] send_data;              // UART发送数据
    wire       send_busy;              // UART发送忙状态标志

    //串口接收模块     
    uart_recv #(.CLK_FREQ(CLK_FREQ), .UART_BPS(UART_BPS)) u_uart_recv (.*);

    //串口发送模块    
    uart_send #(.CLK_FREQ(CLK_FREQ), .UART_BPS(UART_BPS)) u_uart_send (.*);
    
    //串口环回模块    
    uart_loop u_uart_loop (.*);
    
endmodule

5.3.5 约束文件

由于alinx7010的uart连到了PS端,因此没法在FPGA上测。下面的约束文件只是示意。

create_clock -period 20.000 -name clk [get_ports sys_clk]

set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN K14 IOSTANDARD LVCMOS33} [get_ports uart_rxd]
set_property -dict {PACKAGE_PIN M15 IOSTANDARD LVCMOS33} [get_ports uart_txd]

FPGA(五)UART串口通信
https://shuusui.site/blog/2025/12/22/fpga-5/
作者
Shuusui
发布于
2025年12月22日
更新于
2026年1月5日
许可协议