FPGA总线学习之UART

一、UART简介

        串行通信分为两种方式:同步串行通信和异步串行通信,两者的区别在于通信双方是否采用统一的工作时钟。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。

        通 用 异 步 收 发 传 输 器 (Universal Asynchronous Receiver/Transmitter) , 通 常 称 作UART。UART 是一种通用的数据通信协议,也是异步、串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。它包括了 RS232、RS499、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。

单工通信:只需要一根数据线。在任何时刻都只能进行一个方向的通信,即一个固定为发送设备,另一个固定为接收设备通信。

半双工通信:需要两根数据线。可以进行两个方向的传输,但不能同时进行。

全双工通信:需要两根数据线。在同一时刻可以进行两个方向的传输,即两个设备可以同时收发数据。

         UART 通信主要由发送数据端口线TX(Transmitter)和接收数据端口线RX(Receiver)组成。在通信时,通信一方的TX与另一方的 RX进行连接,如图一所示(勿错接)

 图一 UART通信连接图

二、UART协议层

        协议层主要规定通信逻辑,统一双方的数据收发标准,即数据格式。

        UART 串​​行通信中的数据被组织成称为数据包或帧的块。每个数据包包含1个起始位,5-8个数据位,1个可选的奇偶校验位,1或1.5或2个停止位,数据格式如图二所示:

图二 UART通信数据格式

空闲位:表总线空闲,固定为逻辑“1”状态;

起始位:标志着一帧数据的开始,将总线拉低,固定为1bit的逻辑“0”;

数据位:一帧数据中的有效数据,实际需传输的数据。

校验位:偶校验或奇校验;奇校验时,发送方应使数据位与校验位中 1 的个数之和为奇数;接收方在接收数据时,对 1 的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查 1 的个数是否为偶数。

停止位:标志着一帧数据的结束,将总线拉高,固定为1bit的逻辑“1”;

数据传输顺序:先传送数据的低位;
 通信速度(波特率):它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用通信速度有2400、4800、9600、19200、115200 bit/s

UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置,使得通信双方的数据位、校验位、停止位和波特率保持一致。

三、Verilog代码设计

        由于针对UART的接口标准定义的电气特性不同,此次主要以RS232为例,设计一个串口环回实验,在此后应用时可直接单独应用UART接收模块和发送模块。串口环回具体功能:由PC机向FPGA发送串口数据,当FPGA接收到数据之后,再将接收到的数据发送至PC。模块总体框图如图三所示。

 图三 UART_LOOP总体框图

 图四 发送模块波形图设计

图五 接收模块波形图设计

接收模块:

 module uart_rx(
    input            clk,
    input            rst_n,
           
    input            uart_rxd,
    output reg       uart_rx_done,
    output reg [7:0] uart_rx_data
);
//parameter define
parameter CLK_FREQ = 50_000_000;               //系统时钟频率
parameter UART_BPS = 115200  ;               //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次

reg         uart_rxd_d0;//异步时钟打俩拍进行同步处理
reg         uart_rxd_d1;
reg         uart_rxd_d2;//下降沿提取,提取起始位
wire        start_flag;//起始位
reg         rx_flag;    //接收过程标志信号
reg         rx_cnt_flag;
reg [3:0]   rx_cnt;     //接收数据计数器
reg [15:0]  baud_cnt;   //波特率计数器,长位宽为了兼容高时钟、低波特率的情况
reg [7:0]   rx_data_reg;  //接收数据寄存器


//异步信号打拍处理,取沿变打三拍
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
        uart_rxd_d2 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
        uart_rxd_d2 <= uart_rxd_d1;
    end

end
assign start_flag = (~uart_rxd_d1)& uart_rxd_d2;//取起始位下降沿

//rx_flag接收标志处理
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        rx_flag <= 1'b0;
    else if(start_flag == 1'b1)//检测到起始位
        rx_flag <= 1'b1;//接收过程中,标志信号rx_flag拉高
    else if((rx_cnt == 4'd9) && (rx_cnt_flag == 1'b1))//在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低
        rx_flag <= 1'b0;
    else
        rx_flag <= rx_flag;
end

//baud_cnt计数器
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        baud_cnt <= 16'b0;
    else if(rx_flag)begin
        if(baud_cnt < BAUD_CNT_MAX - 1'b1)
            baud_cnt <= baud_cnt + 1'b1;
        else 
            baud_cnt <= 16'b0;
    end
    else 
        baud_cnt <= 16'b0;
end

//rx_cnt_flag产生一个周期高电平
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        rx_cnt_flag <= 1'b0;
    else if((rx_flag == 1'b1) && (baud_cnt == BAUD_CNT_MAX / 2 - 1'b1))//在中间时刻拉高,此信号用于数据接收
        rx_cnt_flag <= 1'b1;
    else
        rx_cnt_flag <= 1'b0;
end

//rx_cnt计数器,表示接收位数的累加
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        rx_cnt <= 4'b0;
    else if(rx_flag)begin
        if(rx_cnt_flag == 1'b1)
            rx_cnt <= rx_cnt + 1'b1;
        else 
            rx_cnt <= rx_cnt;
    end
    else 
        rx_cnt <= 4'b0;
end


//rx_data_t 存入接收数据
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        rx_data_reg <= 8'b0;
    else if(rx_flag)begin
        if(rx_cnt_flag == 1'b1)begin
            case(rx_cnt)
                4'd1:rx_data_reg[0] <= uart_rxd;
                4'd2:rx_data_reg[1] <= uart_rxd;
                4'd3:rx_data_reg[2] <= uart_rxd;
                4'd4:rx_data_reg[3] <= uart_rxd;
                4'd5:rx_data_reg[4] <= uart_rxd;
                4'd6:rx_data_reg[5] <= uart_rxd;
                4'd7:rx_data_reg[6] <= uart_rxd;
                4'd8:rx_data_reg[7] <= uart_rxd;
                default:rx_data_reg <= rx_data_reg;
            endcase 
         end
         else 
            rx_data_reg <= rx_data_reg;
    end
    else 
        rx_data_reg <= 8'b0;
end

//对uart_rx_done输出和uart_rx_data输出
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= 8'b0;
    end
    else if((rx_cnt == 4'd9) && (rx_cnt_flag == 1'b1))begin
        uart_rx_done <= 1'b1;
        uart_rx_data <= rx_data_reg;
    end
    else begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= uart_rx_data;
    end
end

endmodule



发送模块:

module uart_tx(
    input             clk           ,
    input             rst_n         ,
    input             uart_tx_en    ,
    input   [7:0]     uart_tx_data  ,
    output  reg       uart_tx_busy  ,
    output  reg       uart_txd
);


parameter CLK_FREQ = 50_000_000;               //系统时钟频率
parameter UART_BPS = 115200  ;               //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特

reg [3:0]   tx_cnt;     //接收数据计数器
reg [15:0]  baud_cnt;   //波特率计数器
reg [7:0]   tx_data_reg;  //发送数据寄存器
reg         tx_cnt_flag; //产生tx_cnt计数的脉冲 

//发送忙标志 数据寄存
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)begin
        uart_tx_busy <= 1'b0;
        tx_data_reg <= 8'b0;
    end
    else if(uart_tx_en)begin//收到发送使能,开始发送
        uart_tx_busy <= 1'b1;
        tx_data_reg <= uart_tx_data;
    end
    else if((tx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16))//发送的停止位的15/16,拉低
        uart_tx_busy <= 1'b0;
    else begin
        uart_tx_busy <= uart_tx_busy;
        tx_data_reg <= tx_data_reg;
    end
end

//baud_cnt计数器
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        baud_cnt <= 16'b0;
    else if(uart_tx_busy)begin
        if(baud_cnt < BAUD_CNT_MAX-1'b1)
            baud_cnt <= baud_cnt+1'b1;
        else 
            baud_cnt <= 16'b0;
    end
    else 
        baud_cnt <= 16'b0;
end

//tx_cnt_flag产生开始脉冲
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        tx_cnt_flag <= 1'b0;
    else if((uart_tx_busy) && (baud_cnt == BAUD_CNT_MAX-1'b1))
        tx_cnt_flag <= 1'b1;
    else 
        tx_cnt_flag <= 1'b0;
end


//tx_cnt计数器,表示接收位数的累加
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)
        tx_cnt <= 4'b0;
    else if(uart_tx_busy)begin
        if(tx_cnt_flag == 1'b1)
            tx_cnt <= tx_cnt + 1'b1;
        else
            tx_cnt <= tx_cnt;
    end
    else 
        tx_cnt <= 4'b0;
end

//根据tx_cnt来给uart发送端口赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        uart_txd <= 1'b1;
    else if(uart_tx_busy) begin
        case(tx_cnt) 
            4'd0 : uart_txd <= 1'b0        ; //起始位
            4'd1 : uart_txd <= tx_data_reg[0]; //数据位最低位
            4'd2 : uart_txd <= tx_data_reg[1];
            4'd3 : uart_txd <= tx_data_reg[2];
            4'd4 : uart_txd <= tx_data_reg[3];
            4'd5 : uart_txd <= tx_data_reg[4];
            4'd6 : uart_txd <= tx_data_reg[5];
            4'd7 : uart_txd <= tx_data_reg[6];
            4'd8 : uart_txd <= tx_data_reg[7]; //数据位最高位
            4'd9 : uart_txd <= 1'b1        ; //停止位
            default : uart_txd <= 1'b1;
        endcase
    end
    else
        uart_txd <= 1'b1;                    //空闲时发送端口为高电平
end

endmodule

 顶层模块:

module uart_232(
   input        sys_clk,//系统时钟
   input        sys_rst_n,//复位信号,低电平有效
   
   input        uart_rxd,
   output       uart_txd
);
parameter CLK_FREQ = 100_000_000;               //系统时钟频率
parameter UART_BPS = 115200  ;               //串口波特率

wire    [7:0]   uart_rx_data;
wire            uart_rx_done;
wire            uart_tx_busy;


uart_rx #(
    .CLK_FREQ       (CLK_FREQ),
    .UART_BPS       (UART_BPS)
)
u_uart_rx(
    .clk            (sys_clk),
    .rst_n          (sys_rst_n),

    .uart_rxd       (uart_rxd),
    .uart_rx_done   (uart_rx_done),
    .uart_rx_data   (uart_rx_data)
);

uart_tx #(
    .CLK_FREQ       (CLK_FREQ),
    .UART_BPS       (UART_BPS)
)
u_uart_tx(
    .clk            (sys_clk),
    .rst_n          (sys_rst_n),
    
    .uart_tx_en     (uart_rx_done),
    .uart_tx_data   (uart_rx_data),
    .uart_tx_busy   (uart_tx_busy),
    .uart_txd       (uart_txd)
);

endmodule

注:

  1. 在顶层模块,将波特率和系统时钟调整为所需数值。
  2. 如需添加奇偶校验码,添加奇偶校验代码,更改rx_flag截止条件,更改rx_cnt的最大值,将rx_data_reg和rx_data更改为合适的位数。

 参考:

UART串口通信_IoT_Joker的博客-CSDN博客

串口通信协议---UART_uart串口通信_LYY我的卢的博客-CSDN博客

正点原子UART讲解

野火UART讲解

(如有不恰当之处,敬请指教,谢谢。)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FPGA总线分析仪是一种用于调试和分析可编程逻辑器件(FPGA)上的总线通信的工具。FPGA是一种可重新编程的芯片,可以实现硬件和软件的结合,广泛应用于嵌入式系统和数字电路设计总线通信是在计算机系统各种硬件设备之间进行数据传输和通信的基础。总线分析仪可以帮助工程师对FPGA上的总线进行监测和分析,以确保总线通信的可靠性和高效性。 FPGA总线分析仪通常具有以下功能: 1. 数据捕获:可以捕获总线上的数据传输,并将其显示在用户界面上。工程师可以通过查看捕获的数据来了解总线通信的特征和模式。 2. 时序分析:可以分析总线传输的时间关系,包括信号的上升沿和下降沿、延迟和持续时间等。这可以帮助工程师找出总线通信的问题,并进行时序优化。 3. 协议解析:可以解析各种总线协议,如I2C、SPI、UART等,以便工程师对总线通信进行深入理解和调试。 4. 时钟调整:可以调整总线上的时钟信号,以便工程师对总线通信进行调试和优化。 5. 报错提示:如果出现总线通信错误或异常,总线分析仪可以提供错误提示和报告,帮助工程师快速定位和解决问题。 总之,FPGA总线分析仪是一种重要的工具,可以帮助工程师对FPGA上的总线通信进行深入分析和调试。通过使用总线分析仪,工程师可以加快开发进程,提高系统性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值