【FPGA】八、UART串口通信

文章目录


前言

        在我们进行FPGA设计时,常常会用到一些数据通信接口,这些通信接口都是有着特定的功能以及协议的,其中最常见的莫过于串口uart了,它对于每一个做硬件和嵌入式软件的人来说,几乎就是一个必备的工具,用来调试一个带MCU或者CPU的系统。

        串口uart是一种非常通用的设备接口,可以实现不同硬件间的通信,对于FPGA开发来说,串口也同样可以实现FPGA开发板与电脑PC端的通信,下面我们就来简单介绍一下串口uart的基本协议及功能。

一、UART简介

1、基本概念

        通用异步收发传输器,英文全称为Universal Asynchronous Receiver/Transmitter,简称UART,是一种异步收发传输器,在发送数据通过将并行数据转换成串行数据进行传输,在接收数据时将串行数据转换成并行数据。

        串行通信分为同步串行通信和异步串行通信。同步串行通信即需要时钟的参与,通信双方需要在同一时钟的控制下,同步传输数据;异步串行通信则不需要时钟的干预,通信双方使用各种的时钟来控制数据的发送和接收。uart属于异步串行通信,即没有时钟信号来同步或验证从发送器发送并由接收器接收的数据,这就要求发送器和接收器必须事先就时序参数达成一致。

     UART是通用异步收发器的简称,它包括了RS232、RS422、RS423、RS449以及RS485等接口标准和总线规范标准,UART是异步串行通信接口的总称。而RS323、RS422、RS423、RS449和RS485等是对应各种异步串行通信的接口标准和总线标准,它规定了通信接口的电器特性、传输速率以及接口的机械特性等内容。

2、UART协议

        UART串口通信需要两根信号线来实现,一根用于串口发送数据,一根用于串口接收数据。UART串口传输的数据被组织成数据包,每个数据包包含了一个起始位,5至9个数据位,可选的奇偶校验位和1或者2个停止位,如下图所示。

【FPGA】八、UART串口通信

        UART串口协议规定,当总线处于空闲状态时信号线的状态为高电平,表示当前线路上没有数据传输。

        起始位:开始进行数据传输时发送方要先发送一个低电平来表示传输字符的开始。

        数据位:起始位之后就需要传输数据,数据位可以是5~9位,构成一个字符,一般是8位,先发送最低位后发送最高位。

        奇偶校验位:奇偶校验位是用来检验数据在传输过程中是否出错。在奇校验时,发送方应使数据位中1的个数与校验位中1的个数为奇数,接收方在接收数据时,对1的个数进行检测,若1的个数不为奇数个,则说明数据在传输过程中存在差错。偶校验则相反。

        停止位:数据结束标志,可以是1位或者2位的高电平。由于数据在传输线上是定时传输的,并且每一个设备有自己的时钟,很可能在通信中两台设备之间出现了小小的不同步,因此停止位不仅仅是表示数据传输的结束,并且提供计算机校正时钟的机会。停止位越多,数据传输月稳定,但是数据传输速度越慢。

【FPGA】八、UART串口通信

3、波特率简介

        在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。

        波特率表示每秒钟传送码元符号的个数,它是对符号传输速率的一种度量,用单位时间内载波调制状态改变的次数来表示,1波特指每秒传输1个字符。

        数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率有9600、19200、38400、57600、115200等。例如将串口波特率设置位115200bps,那么传输一个bit需要的时间是1/115200 ≈ 8.68us。

二、UART串口回环实验

1、设计思路

        实验任务:通过电脑端的串口调试助手向FPGA发送数据,FPGA通过串口接收数据并将接受到的数据发送给上位机,实现串口回环功能。

        接收模块(RX):通过检测起始位来表示数据传输的开始,在波特率中间时刻去采样总线上的数据,最后将数据进行串并转换。

        发送模块(TX):将并行数据转换成串行数据,然后在串行数据帧头加上起始位,帧尾加上停止位,发送给上位机。

2、程序代码

        本次程序设计中没有用到奇偶校验位,一帧数据为8bit,停止位为1位,波特率可供选择(代码为115200),FPGA的系统晶振时钟为50MHZ。

        ① 串口接收模块

/*===============================*
    filename    : uart_rx.v
    description : 串口接收模块
    time        : 2022-12-22 
    author      : 卡夫卡与海
*================================*/

module uart_rx(
    input              clk       ,//时钟  50MHZ
    input              rst_n     ,//复位

    input              uart_rx   ,//rx数据线
    input       [2:0]  baud_sel  ,//波特率选择

    output reg  [7:0]  po_data   ,//接收的数据
    output reg         po_flag    //数据使能
);

//参数定义
parameter   SCLK = 50_000_000;//系统时钟  50MHZ

//波特率选择
parameter   BAUD_9600   = SCLK/9600  ,
            BAUD_19200  = SCLK/19200 ,
            BAUD_38400  = SCLK/38400 ,
            BAUD_57600  = SCLK/57600 ,
            BAUD_115200 = SCLK/115200;

//信号定义
reg           uart_rx_1    ;//同步、打拍
reg           uart_rx_2    ;
wire          rx_nedge     ;//下降沿检测

reg           start_flag   ;//起始标志
reg           work_en      ;//工作使能

reg   [15:0]  cnt_baud     ;//波特率计数器
reg   [15:0]  BAUD_NUM     ;

reg           bit_flag     ;//接收数据使能
reg   [3:0]   cnt_bit      ;//bit计数器

reg   [7:0]   rx_data      ;//数据
reg           rx_flag      ;//数据标志


//同步、打拍  检测下降沿
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        uart_rx_1 <= 1'b1;
        uart_rx_2 <= 1'b1;
    end
    else begin
        uart_rx_1 <= uart_rx;
        uart_rx_2 <= uart_rx_1;
    end
end
assign rx_nedge = uart_rx_2 && ~uart_rx_1;

//start_flag  
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        start_flag <= 1'b0;
    end
    else if(rx_nedge && work_en == 1'b0)begin
        start_flag <= 1'b1;
    end
    else begin
        start_flag <= 1'b0;
    end
end

//work_en
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        work_en <= 1'b0;
    end
    else if(start_flag == 1'b1)begin
        work_en <= 1'b1;
    end
    else if(cnt_bit == 4'd8 && bit_flag == 1'b1)begin
        work_en <= 1'b0;
    end
    else begin
        work_en <= work_en;
    end
end

//cnt_baud
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_baud <= 16'd0;
    end
    else if((cnt_baud == BAUD_NUM - 1) || (work_en == 1'b0))begin
        cnt_baud <= 16'd0;
    end
    else if(work_en == 1'b1)begin
        cnt_baud <= cnt_baud + 1'b1;
    end
end

//BAUD_NUM
always @(*)begin
    case(baud_sel)
        3'd0 : BAUD_NUM = BAUD_9600  ;
        3'd1 : BAUD_NUM = BAUD_19200 ;
        3'd2 : BAUD_NUM = BAUD_38400 ;
        3'd3 : BAUD_NUM = BAUD_57600 ;
        3'd4 : BAUD_NUM = BAUD_115200;
        default : BAUD_NUM = BAUD_115200;
    endcase
end

//bit_flag
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        bit_flag <= 1'b0;
    end
    else if(cnt_baud == (BAUD_NUM >> 1) - 1)begin
        bit_flag <= 1'b1;
    end
    else begin
        bit_flag <= 1'b0;
    end
end

//cnt_bit
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_bit <= 4'd0;
    end
    else if((cnt_bit == 4'd8) && (bit_flag == 1'b1))begin
        cnt_bit <= 4'd0;
    end
    else if(bit_flag == 1'b1)begin
        cnt_bit <= cnt_bit + 1'b1;
    end
end

//rx_data
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rx_data <= 8'b0;
    end
    else if((cnt_bit>=4'd1)&&(cnt_bit<=4'd8)&&(bit_flag==1'b1))begin
        rx_data <= {uart_rx_2,rx_data[7:1]};
    end
end

//rx_flag
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rx_flag <= 1'b0;
    end
    else if((cnt_bit == 4'd8) && (bit_flag == 1'b1))begin
        rx_flag <= 1'b1;
    end
    else begin
        rx_flag <= 1'b0;
    end
end

//输出
//po_data
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        po_data <= 8'b0;
    end
    else if(rx_flag == 1'b1)begin
        po_data <= rx_data;
    end
end

//po_flag
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        po_flag <= 1'b0;
    end
    else begin
        po_flag <= rx_flag;
    end
end


endmodule

        ② 串口发送模块

/*===============================*
    filename    : uart_tx.v
    description : 串口发送模块
    time        : 2022-12-22 
    author      : 卡夫卡与海
*================================*/

module uart_tx(
    input         clk      ,//时钟  50MHZ
    input         rst_n    ,//复位

    input  [2:0]  baud_sel ,//波特率选择
    input  [7:0]  pi_data  ,//数据
    input         pi_flag  ,//数据使能

    output  reg   uart_tx   //tx数据线
);

//参数定义
parameter   SCLK = 50_000_000;//系统时钟  50MHZ

//波特率选择
parameter   BAUD_9600   = SCLK/9600  ,
            BAUD_19200  = SCLK/19200 ,
            BAUD_38400  = SCLK/38400 ,
            BAUD_57600  = SCLK/57600 ,
            BAUD_115200 = SCLK/115200;

//信号定义
reg            work_en    ;//工作使能

reg   [15:0]   cnt_baud   ;//波特率计数器
reg   [15:0]   BAUD_NUM   ;

reg            bit_flag   ;//bit标志信号
reg   [3:0]    cnt_bit    ;//bit计数器


//work_en
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        work_en <= 1'b0;
    end
    else if(pi_flag == 1'b1)begin
        work_en <= 1'b1;
    end
    else if((cnt_bit == 4'd9) && (bit_flag == 1'b1))begin
        work_en <= 1'b0;
    end
end

//cnt_baud
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_baud <= 16'd0;
    end
    else if((work_en == 1'b0) || (cnt_baud == BAUD_NUM - 1))begin
        cnt_baud <= 16'd0;
    end
    else if(work_en == 1'b1)begin
        cnt_baud <= cnt_baud + 1'b1;
    end
end

//BAUD_NUM
always @(*)begin
    case(baud_sel)
        3'd0 : BAUD_NUM = BAUD_9600  ;
        3'd1 : BAUD_NUM = BAUD_19200 ;
        3'd2 : BAUD_NUM = BAUD_38400 ;
        3'd3 : BAUD_NUM = BAUD_57600 ;
        3'd4 : BAUD_NUM = BAUD_115200;
        default : BAUD_NUM = BAUD_115200;
    endcase
end

//bit_flag 
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        bit_flag <= 1'b0;
    end
    else if(cnt_baud == 16'd1)begin
        bit_flag <= 1'b1;
    end
    else begin
        bit_flag <= 1'b0;
    end
end

//cnt_bit
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_bit <= 4'd0;
    end
    else if((cnt_bit == 4'd9)&&(bit_flag == 1'b1))begin
        cnt_bit <= 4'd0;
    end
    else if((work_en == 1'b1)&&(bit_flag == 1'b1))begin
        cnt_bit <= cnt_bit + 1'b1;
    end
end

//输出  uart_tx
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        uart_tx <= 1'b1;
    end
    else if(bit_flag == 1'b1)begin
        case(cnt_bit)
            0 : uart_tx <= 1'b0;//起始位
            1 : uart_tx <= pi_data[0];
            2 : uart_tx <= pi_data[1];
            3 : uart_tx <= pi_data[2];
            4 : uart_tx <= pi_data[3];
            5 : uart_tx <= pi_data[4];
            6 : uart_tx <= pi_data[5];
            7 : uart_tx <= pi_data[6];
            8 : uart_tx <= pi_data[7];
            9 : uart_tx <= 1'b1;//停止位
            default : uart_tx <= 1'b1;
        endcase
    end
end


endmodule

        ③ 串口顶层模块

/*==============================*
    filename    : uart_top.v
    description : 串口顶层模块
    time        : 2022-12-22 
    author      : 卡夫卡与海
*================================*/

module uart_top(
    input       clk      ,
    input       rst_n    ,

    input       uart_rx  ,
    output      uart_tx  
);
//波特率选择
/*常用波特率选择:
baud_sel == 3'd0 :波特率为:9600
baud_sel == 3'd1 :波特率为:19200
baud_sel == 3'd2 :波特率为:38400
baud_sel == 3'd3 :波特率为:57600
baud_sel == 3'd4 :波特率为:115200   */
wire   [2:0]    baud_sel ;//波特率选择

assign baud_sel = 3'd4;//波特率 = 115200

//信号定义
wire   [7:0]    data     ;
wire            flag     ;

//模块例化
//串口接收模块
uart_rx u_uart_rx(
    /*input              */.clk       (clk     ),
    /*input              */.rst_n     (rst_n   ),
    /*input              */.uart_rx   (uart_rx ),
    /*input       [2:0]  */.baud_sel  (baud_sel),
    /*output reg  [7:0]  */.po_data   (data    ),
    /*output reg         */.po_flag   (flag    ) 
);

//串口发送模块
uart_tx u_uart_tx(
    /*input              */.clk       (clk     ),
    /*input              */.rst_n     (rst_n   ),
    /*input   [2:0]      */.baud_sel  (baud_sel),
    /*input   [7:0]      */.pi_data   (data    ),
    /*input              */.pi_flag   (flag    ),
    /*output  reg        */.uart_tx   (uart_tx )
);


endmodule

        ④ 串口仿真模块

/*========================================*
    filename    : uart_top_tb.v
    description : 串口顶层模块仿真文件
    time        : 2022-12-22 
    author      : 卡夫卡与海
*========================================*/
`timescale 1ns/1ns 

module uart_top_tb();
    reg       clk   ;
    reg       rst_n ;
    reg       rx    ;
    wire      tx    ;


//产生时钟
initial begin
    clk = 1'b1;
    rx  = 1'b1;
    forever 
    #10
    clk = ~clk;
end

//产生复位
initial begin
    rst_n = 1'b0;
    #20;
    rst_n = 1'b1;
end

//产生激励
task rx_bit(
    input   [7:0]   data
);

integer i;
for(i = 0 ; i < 10 ; i = i + 1)begin
    case(i)
        0:rx <= 1'b0;//起始位
        1:rx <= data[0];
        2:rx <= data[1];
        3:rx <= data[2];
        4:rx <= data[3];
        5:rx <= data[4];
        6:rx <= data[5];
        7:rx <= data[6];
        8:rx <= data[7];
        9:rx <= 1'b1;//停止位
    endcase
    #(434*20);//根据不同的波特率,延时的时间不同
end
endtask

task rx_byte();
    integer j;
        for(j = 0 ; j < 8 ; j = j + 1)
            rx_bit(j);
endtask

initial begin
    #200
    rx_byte();
end


//模块例化
uart_top u_uart_top(
    /*input       */.clk       (clk  ),
    /*input       */.rst_n     (rst_n),
    /*input       */.uart_rx   (rx   ),
    /*output      */.uart_tx   (tx   )
);


endmodule

3、仿真验证

【FPGA】八、UART串口通信

        通过对我们项目工程的仿真来看,显示接收到的数据为0~7共八个字节的数据,发送给上位机的数据也为0~7,说明我们的功能能够正确的接收并发送数据。

        通过放大波形图可以看到,波特率计数器总共计数434次,波特率为115200,系统时钟为50MHZ,则50_000_000/115200≈434,波特率计数器正确。我们的采样时刻是在波特率计数器的中间时刻去采样,这样能够保证采样到的数据的稳定性,这里的bit标志信号在波特率计数器计数到217时拉高,表示此刻进行数据的采样操作。

        最后就是进行串并转换了,接收模块将串行数据转换成并行数据传给发送模块,而发送模块将接收模块传入的并行数据转换成串行数据通过uart_tx信号线发送给上位机,而上位机通过串口调试助手将接收到的数据串并转换并打印出来显示在屏幕上。

 总结

        UART串口通信协议还是比较简单的,在编写代码时最重要的是思路的完整性,要有一个全局的概念,不能写了一部分就不知道接下来要干嘛了。实现UART串口通信的思路还有很大,也可以通过前面讲过的状态机的方法来实现,还可以在中间调用FIFO IP核,我这里这个算是一个简单的串口回环实现方式了,在后续的项目中还得加以改进和优化。

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2023年3月28日
下一篇 2023年3月28日

相关推荐