基于FPGA的串口UART-强化篇

目录

串口介绍

RS232信号线

[RS232 通信协议](#RS232 通信协议)

RS232电路分析

串口发送模块UART_TX

绘制模块框图及波形图

编写模块代码

编写仿真代码

仿真验证

串口接收模块UART_RX

绘制模块框图及波形图

编写模块代码

编写仿真代码

仿真验证

RS232串口数据回显的设计

绘制模块框图

编写模块代码

编写仿真代码

仿真验证

上板验证

串口连续发送8字节

绘制模块框图及波形图

编写模块代码

编写仿真代码

仿真验证

上板验证


串口介绍

通用异步收发传输器 (Universal Asynchronous Receiver/Transmitter),通常称作 UART。UART 是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。

  • 单工,数据只能沿一个方向传输,不能实现反向传输。
  • 半双工,数据可以沿两个方向传输,但需要分时进行。
  • 全双工,数据可以同时进行两个方向传输。

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

串口作为常用的三大低速总线(UART、SPI、IIC)之一,常用于通信接口和调试。但UART和SPI、IIC不同的是,它是异步通信接口(接收方和发送方使用的时钟不同) ,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位 的帮助下实现信息同步的 。而 SPI、IIC 是同步通信接口 ,同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。

UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫rx(Receiver)。UART可以实现全双工,即可以同时进行发送数据和接收数据。

RS232信号线

在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称 "猫")之间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和数据通讯设备 DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。 在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口)。

其中接线口以针式 引出信号线的称为公头 ,以孔式 引出信号线的称为母头 。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号使用 RS-232 标准调制。在各种应用场合下, DB9 接口中的公头及母头的各个引脚的标准信号线接法见图。

DB9 信号线说明:

RS232 通信协议

1、RS232是UART的一种,没有时钟线,只有两根数据线,分别是rx和tx,这两根线都是1bit位宽的。其中rx是接收数据的线,tx是发送数据的线。

2、rx 位宽为1bit,PC机通过串口调试助手往FPGA发8bit数据时,FPGA通过串口线 rx 一位一位地接收,从最低位到最高位依次接收,最后在FPGA里面位拼接成8比特数据。

3、tx 位宽为1bit,FPGA通过串口往PC机发8bit数据时,FPGA把8bit数据通过tx线一位一位的传给PC机,从最低位到最高位依次发送,最后上位机通过串口助手按照 RS232协议把这一位一位的数据位拼接成8bit数据。

4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除 了中间包含8bit有效数据外,还在每一帧的开头都必须有一个起始位,且固定为0;在每一帧的结束时也必须有一个停止位,且固定为1,即最基本的帧结构(不包括校验等) 有 10bit。在不发送或者不接收数据的情况下,rx和tx处于空闲状态,此时rx和tx线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是8bit的数据位,接着有1bit 的停止位,然后rx和tx继续进入空闲状态,然后等待下一次的数据传输。如图所示 为一个最基本的RS232帧结构。

5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是1bit进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号"Baud"表示,其单位为"波特每秒(Bps)"。串 口常见的波特率有4800、9600、115200等,我们选用9600的波特率。

6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为 "每秒比特数(bps)"。比特率可由波特率计算得出,**公式为:比特率=波特率 * 单个调制状态对应的二进制位数。**如果使用的是9600的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。

7、由计算串口发送或者接收1bit数据的时间为一个波特,即1/9600秒,如果用 50MHz(周期为20ns)的系统时钟来计数,需要计数的个数为cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个bit数据之间的间隔要在50MHz的时钟频率下计数5208次。

8、上位机通过串口发8bit数据时,会自动在发8位有效数据前发一个波特时间的起始位,也会自动在发完8位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完8bit的数据后,再接收一个波特时间的停止位。

RS232电路分析

如图所示,MAX3232为RS232收发器芯片 。由于 RS-232 电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的 "TTL"的电平信号,才能实现通讯。

根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准。TTL 电平标准与RS232电平标准如下图:

由于 FPGA 串口输入输出引脚为 TTL电平, 用 3.3V代表逻辑"1", 0V代表逻辑 "0";所以常常会使用 MA3232 芯片对 TTL 及 RS-232电平的信号进行互相转换。

同时开发板中还搭载了USB转串口的芯片CH340,可使用 USB线进行串口调试。

串口发送模块UART_TX

绘制模块框图及波形图

第一种使用计数器的方法。

第二种使用状态机的方法。

下面使用的是第一种方法进行编写代码。

编写模块代码

复制代码
module  uart_tx
#(
    parameter   UART_BPS        = 'd9600        ,//波特率
                CLK_FREQ        = 'd50_000_000  ,//时钟频率
                UART_DATA_BIT   = 'd8            //串口发送数据位数
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire [7:0]  pi_data ,   //8bit的并行数据
    input   wire        pi_flag ,   //并行数据有效标志信号(周期必须大于一帧数据的时长)
    
    output  reg         tx      ,   //并行转串行发送
    output  reg         done        //完成一帧数据发送的标志信号
);

localparam  BAUD_CNT_MAX = CLK_FREQ / UART_BPS          , //一个码元要保持的时长
            BIT_CNT_MAX  = 1'b1 + UART_DATA_BIT + 1'b1  ; //一帧数据的总位数,起始位+数据位+停止位(无校验位)

reg [7:0]   data_reg;//用于存储并行数据,防止并行数据没有保持发送一帧数据的时长
reg         work_en ;//发送数据工作使能信号
reg [31:0]  cnt_baud;//码元计数器
reg [3:0]   cnt_bit ;//数据位的位数计数器

//data_reg
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        data_reg <= 8'd0;
    else    if(pi_flag)
        data_reg <= pi_data;
    else
        data_reg <= data_reg;
end
//work_en
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        work_en <= 1'b0;
    else    if(pi_flag)
        work_en <= 1'b1;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        work_en <= 1'b0;
    else
        work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_baud <= 32'd0;
    else    if(work_en && cnt_baud == BAUD_CNT_MAX - 1'b1)
        cnt_baud <= 32'd0;
    else    if(work_en)
        cnt_baud <= cnt_baud + 1'b1;
    else
        cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;
end
//tx
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        tx <= 1'b1;
    else    if(work_en)
        case(cnt_bit)
            0:tx <= 1'b0        ;
            1:tx <= data_reg[0] ;
            2:tx <= data_reg[1] ;
            3:tx <= data_reg[2] ;
            4:tx <= data_reg[3] ;
            5:tx <= data_reg[4] ;
            6:tx <= data_reg[5] ;
            7:tx <= data_reg[6] ;
            8:tx <= data_reg[7] ;
            9:tx <= 1'b1        ;
            default:tx <= 1'b1;
        endcase
    else
        tx <= 1'b1;
end
//done
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        done <= 1'b0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        done <= 1'b1;
    else
        done <= 1'b0;
end
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  uart_tx_tb();

reg         clk    ;
reg         rst_n  ;
reg  [7:0]  pi_data;
reg         pi_flag;

initial
begin
    clk     = 1'b0  ;
    rst_n   = 1'b0  ;
    pi_data = 8'h00 ;
    pi_flag = 1'b0  ;
    #123
    rst_n   = 1'b1  ;
    #20000
    //发送数据1
    pi_data = 8'h01 ;
    #20
    pi_flag = 1'b1  ;
    #20
    pi_flag = 1'b0  ;
    #(5208*10*20)   ;
    //发送数据2
    pi_data = 8'h02 ;
    #20
    pi_flag = 1'b1  ;
    #20
    pi_flag = 1'b0  ;
    #(5208*10*20)   ;
    //发送数据3
    pi_data = 8'h03 ;
    #20
    pi_flag = 1'b1  ;
    #20
    pi_flag = 1'b0  ;
    #(5208*10*20)   ;
    //发送数据4
    pi_data = 8'h04 ;
    #20
    pi_flag = 1'b1  ;
    #20
    pi_flag = 1'b0  ;
    #(5208*10*20)   ;
    #(5208*10*20)   ;
    //发送数据5
    pi_data = 8'h05 ;
    #20
    pi_flag = 1'b1  ;
    #20
    pi_flag = 1'b0  ;
    #(5208*10*20)   ;
end

always #10 clk = ~clk;

uart_tx
#(
    .UART_BPS        ( 'd9600        ),//波特率
    .CLK_FREQ        ( 'd50_000_000  ),//时钟频率
    .UART_DATA_BIT   ( 'd8           ) //串口发送数据位数
)
uart_tx_inst
(
    .clk     (clk    ),
    .rst_n   (rst_n  ),
    .pi_data (pi_data),   //8bit的并行数据
    .pi_flag (pi_flag),   //并行数据有效标志信号(周期必须大于一帧数据的时长)

    .tx      (),   //并行转串行发送
    .done    ()    //完成一帧数据发送的标志信号
);
endmodule

仿真验证

data_reg、work_en、cnt_baud、cnt_bit、tx、done开始部分没有问题。

可以发现data_reg,work_en没有问题,两个计数器也没有问题,tx发送一帧数据没有问题,done完成发送一帧的标志信号也没问题。

仿真验证通过。

串口接收模块UART_RX

绘制模块框图及波形图

rx串行数据一开始直接打了两拍,就是经过了 两级寄存器,理论上我们应该按照串口接收数据的时序要求找到rx的下降沿,然后开始接收起始位的数据,但为什么先将数据打了两拍呢?那就要先从跨时钟域会导致"亚稳态" 的问题上说起。

产生亚稳态的原因: FPGA在接收rx数据时不满足内部寄存器(D触发器)的建立时间Tsu (指触发器的时钟信 号上升沿到来以前,数据稳定不变的最小时间)和保持时间 Th (指触发器的时钟信号上升沿到来以后,数据稳定不变的最小时间),此时 FPGA 的第一级寄存器的输出端在时钟沿到来之后比较长的一段时间内都处于不确定的状态,在 0和 1之间处于振荡状态,而不是 等于串口输入的确定的rx值。产生亚稳态的波形示意图,如下图

注意:打两拍后虽然能让信号稳定到 0或者1中确定的值,但究竟是0还是1却是随机的,与打拍之前输入信号的值没有必然的关系。

注:单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。 第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为70%~80%左右,第二级寄存 器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以数据进来后一般选择打两拍即可。

单比特信号从快速时钟域同步到慢速时钟域还仅仅使用打两拍的方式会漏采数据,所以往往使用脉冲同步法握手信号法

多比特信号跨时钟域 需要进行格雷码编码 (多比特顺序数才可以)后才能进行打两拍的处理,或者通过使用 FIFO、RAM 来处理数据与时钟同步的问题。 亚稳态振荡时间 Tmet 关系到后级寄存器的采集稳定问题,Tmet 影响因素包括:器件的生产工艺、温度、环境以及寄存器采集到亚稳态里稳定态的时刻等。甚至某些特定条件,如干扰、辐射等都会造成Tmet增长。

data_reg信号是参与移位的数据,在移位的过程中数据是变动的,不可以被后级模块所使用,而可以肯定的是在移位完成时,data_reg信号一定是移位完成的稳定的 8bit 有用数据。

编写模块代码

复制代码
module  uart_rx
#(
    parameter   UART_BPS        = 'd9600        ,   //波特率
                CLK_FREQ        = 'd50_000_000  ,   //时钟频率
                UART_DATA_BIT   = 'd8               //串口接收的有效数据位数
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire        rx      ,
    
    output  reg  [7:0]  po_data ,
    output  reg         po_flag
);

localparam  BAUT_CNT_MAX = CLK_FREQ / UART_BPS         , //一个码元要保持的时长
            BIT_CNT_MAX  = 1'b1 + UART_DATA_BIT + 1'b1 ;//一帧数据的总位数,起始位+数据位+停止位(无校验位)

reg         rx_r1       ;//rx打拍,将rx同步到本系统时钟下
reg         rx_r2       ;
reg         rx_r3       ;//用于下降沿采集,和数据提取
wire        fall_flag   ;//下降沿标志信号
reg         work_en     ;//下降沿到来,开始解析数据
reg [31:0]  cnt_baud    ;//码元计数器,计数一个码元要保持的时长
reg [3:0]   cnt_bit     ;//位数计数器
reg [7:0]   data_reg    ;//用于提取有效数据
//rx_r1,rx_r2,rx_r3
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            rx_r1 <= 1'b1   ;
            rx_r2 <= 1'b1   ;
            rx_r3 <= 1'b1   ;
        end
    else
        begin
            rx_r1 <= rx     ;
            rx_r2 <= rx_r1  ;
            rx_r3 <= rx_r2  ;
        end
end
//fall_flag
assign  fall_flag = (rx_r3 && !rx_r2 && !cnt_bit) ? 1'b1 : 1'b0;
//work_en
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        work_en <= 1'b0;
    else    if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
        work_en <= 1'b0;
    else    if(fall_flag)
        work_en <= 1'b1;
    else
        work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_baud <= 32'd0;
    else    if(work_en && cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_baud <= 32'd0;
    else    if(work_en)
        cnt_baud <= cnt_baud + 1'b1;
    else
        cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_bit <= 4'd0;
    else    if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;
end
//data_reg
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        data_reg <= 8'd0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2
            && cnt_bit  >= 1'b1
            && cnt_bit  <= UART_DATA_BIT)
        data_reg <= {rx_r3, data_reg[7:1]};
    else
        data_reg <= data_reg;
end
//po_data
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        po_data <= 8'd0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
            && cnt_bit  == UART_DATA_BIT)
        po_data <= data_reg;
    else
        po_data <= po_data;
end
//po_flag
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        po_flag <= 1'b0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
            && cnt_bit  == UART_DATA_BIT)
        po_flag <= 1'b1;
    else
        po_flag <= 1'b0;
end
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  uart_rx_tb();

reg         clk  ;
reg         rst_n;
reg         rx   ;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    rx    = 1'b1;
    #123
    rst_n = 1'b1;
    #20000
    //模拟8'b1010_0000
    rx    = 1'b0;
    #(5208*20)  ;
    
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    
    rx    = 1'b1;
    #(5208*20)  ;
    
    #20000
    //模拟8'b1010_1010
    rx    = 1'b0;
    #(5208*20)  ;
    
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    
    rx    = 1'b1;
    #(5208*20)  ;
    
    //模拟8'b1010_0101
    rx    = 1'b0;
    #(5208*20)  ;
    
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    rx    = 1'b0;
    #(5208*20)  ;
    rx    = 1'b1;
    #(5208*20)  ;
    
    rx    = 1'b1;
    #(5208*20)  ;
end

always #10 clk = ~clk ;

uart_rx
#(
    .UART_BPS        ( 'd9600        ),   //波特率
    .CLK_FREQ        ( 'd50_000_000  ),   //时钟频率
    .UART_DATA_BIT   ( 'd8           )    //串口接收的有效数据位数
)
uart_rx_inst
(
    .clk     (clk   ),
    .rst_n   (rst_n ),
    .rx      (rx    ),

    .po_data (),
    .po_flag ()
);
endmodule

仿真验证

三级打拍和下降沿采集没有问题,工作使能信号和cnt_baud起始没问题。

两个计数器和工作使能信号没有问题。

两个输出信号没问题。

仿真验证通过。

RS232串口数据回显的设计

实现目标:设计并实现基于串口 RS232 的数据收、发模块,使用收、发模块,完成串口数据回环 实验。

绘制模块框图

可以看到我在uart_rx模块加了一个tx_done信号,表示一帧帧结构发送完毕,这个可以避免我们在tx还没发送完数据时,pi_flag的单脉冲信号就到了,从而会导致丢失数据或出现乱码的情况。利用tx_done信号可以实现连续的发送数据。

如果直接使用上面的已经验证超过的两个模块,那你会发现回显设计的仿真是没有问题的,但是上班验证会丢失偶数位的数据,这是因为tx发送的是完完整整的一帧数据,pc机发送的帧可能会有误差。

所以这里需要修改一下uart_rx的po_flag信号,当uart_tx忙(还没发完一帧)时,po_flag如果提前到来时,让其保持高电平;当uart_tx空闲时(发送完一帧),也就是done为高电平时,将其po_flag拉低,此时可以检测到有效的数据有效信号。

编写模块代码

顶层模块

复制代码
module  uart_rx_tx(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire        rx      ,
    
    output  wire        tx
);

wire    [7:0]   po_data;
wire            po_flag;
wire            done   ;

uart_rx
#(
    .UART_BPS        ( 'd9600        ),   //波特率
    .CLK_FREQ        ( 'd50_000_000  ),   //时钟频率
    .UART_DATA_BIT   ( 'd8           )    //串口接收的有效数据位数
)
uart_rx_inst1
(
    .clk    (clk    ),
    .rst_n  (rst_n  ),
    .rx     (rx     ),
    .tx_done(done   ),

    .po_data(po_data),
    .po_flag(po_flag)
);

uart_tx
#(
    .UART_BPS        ( 'd9600       ),//波特率
    .CLK_FREQ        ( 'd50_000_000 ),//时钟频率
    .UART_DATA_BIT   ( 'd8          ) //串口发送数据位数
)
uart_tx_inst1
(
    .clk     (clk    ),
    .rst_n   (rst_n  ),
    .pi_data (po_data),   //8bit的并行数据
    .pi_flag (po_flag),   //并行数据有效标志信号(周期必须大于一帧数据的时长)

    .tx      (tx     ),   //并行转串行发送
    .done    (done   )    //完成一帧数据发送的标志信号
);
endmodule

功能模块uart_rx

复制代码
module  uart_rx
#(
    parameter   UART_BPS        = 'd9600        ,   //波特率
                CLK_FREQ        = 'd50_000_000  ,   //时钟频率
                UART_DATA_BIT   = 'd8               //串口接收的有效数据位数
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire        rx      ,
    input   wire        tx_done ,
    
    output  reg  [7:0]  po_data ,
    output  reg         po_flag
);

localparam  BAUT_CNT_MAX = CLK_FREQ / UART_BPS         , //一个码元要保持的时长
            BIT_CNT_MAX  = 1'b1 + UART_DATA_BIT + 1'b1 ;//一帧数据的总位数,起始位+数据位+停止位(无校验位)

reg         rx_r1       ;//rx打拍,将rx同步到本系统时钟下
reg         rx_r2       ;
reg         rx_r3       ;//用于下降沿采集,和数据提取
wire        fall_flag   ;//下降沿标志信号
reg         work_en     ;//下降沿到来,开始解析数据
reg [31:0]  cnt_baud    ;//码元计数器,计数一个码元要保持的时长
reg [3:0]   cnt_bit     ;//位数计数器
reg [7:0]   data_reg    ;//用于提取有效数据
//rx_r1,rx_r2,rx_r3
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            rx_r1 <= 1'b1   ;
            rx_r2 <= 1'b1   ;
            rx_r3 <= 1'b1   ;
        end
    else
        begin
            rx_r1 <= rx     ;
            rx_r2 <= rx_r1  ;
            rx_r3 <= rx_r2  ;
        end
end
//fall_flag
assign  fall_flag = (rx_r3 && !rx_r2 && !cnt_bit) ? 1'b1 : 1'b0;
//work_en
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        work_en <= 1'b0;
    else    if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
        work_en <= 1'b0;
    else    if(fall_flag)
        work_en <= 1'b1;
    else
        work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_baud <= 32'd0;
    else    if(work_en && cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_baud <= 32'd0;
    else    if(work_en)
        cnt_baud <= cnt_baud + 1'b1;
    else
        cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_bit <= 4'd0;
    else    if(cnt_bit == UART_DATA_BIT && cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUT_CNT_MAX - 1'b1)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;
end
//data_reg
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        data_reg <= 8'd0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2
            && cnt_bit  >= 1'b1
            && cnt_bit  <= UART_DATA_BIT)
        data_reg <= {rx_r3, data_reg[7:1]};
    else
        data_reg <= data_reg;
end
//po_data
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        po_data <= 8'd0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
            && cnt_bit  == UART_DATA_BIT)
        po_data <= data_reg;
    else
        po_data <= po_data;
end
//po_flag
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        po_flag <= 1'b0;
    else    if(cnt_baud == BAUT_CNT_MAX / 2 + 1'b1
            && cnt_bit  == UART_DATA_BIT)
        po_flag <= 1'b1;
    else    if(tx_done)
        po_flag <= 1'b0;
    else
        po_flag <= po_flag;
end
endmodule

功能模块uart_tx

复制代码
module  uart_tx
#(
    parameter   UART_BPS        = 'd9600        ,//波特率
                CLK_FREQ        = 'd50_000_000  ,//时钟频率
                UART_DATA_BIT   = 'd8            //串口发送数据位数
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire [7:0]  pi_data ,   //8bit的并行数据
    input   wire        pi_flag ,   //并行数据有效标志信号(周期必须大于一帧数据的时长)
    
    output  reg         tx      ,   //并行转串行发送
    output  reg         done        //完成一帧数据发送的标志信号
);

localparam  BAUD_CNT_MAX = CLK_FREQ / UART_BPS          , //一个码元要保持的时长
            BIT_CNT_MAX  = 1'b1 + UART_DATA_BIT + 1'b1  ; //一帧数据的总位数,起始位+数据位+停止位(无校验位)

reg [7:0]   data_reg;//用于存储并行数据,防止并行数据没有保持发送一帧数据的时长
reg         work_en ;//发送数据工作使能信号
reg [31:0]  cnt_baud;//码元计数器
reg [3:0]   cnt_bit ;//数据位的位数计数器

//data_reg
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        data_reg <= 8'd0;
    else    if(pi_flag)
        data_reg <= pi_data;
    else
        data_reg <= data_reg;
end
//work_en
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        work_en <= 1'b0;
    else    if(pi_flag)
        work_en <= 1'b1;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        work_en <= 1'b0;
    else
        work_en <= work_en;
end
//cnt_baud
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_baud <= 32'd0;
    else    if(work_en && cnt_baud == BAUD_CNT_MAX - 1'b1)
        cnt_baud <= 32'd0;
    else    if(work_en)
        cnt_baud <= cnt_baud + 1'b1;
    else
        cnt_baud <= 32'd0;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        cnt_bit <= 4'd0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;
end
//tx
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        tx <= 1'b1;
    else    if(work_en)
        case(cnt_bit)
            0:tx <= 1'b0        ;
            1:tx <= data_reg[0] ;
            2:tx <= data_reg[1] ;
            3:tx <= data_reg[2] ;
            4:tx <= data_reg[3] ;
            5:tx <= data_reg[4] ;
            6:tx <= data_reg[5] ;
            7:tx <= data_reg[6] ;
            8:tx <= data_reg[7] ;
            9:tx <= 1'b1        ;
            default:tx <= 1'b1;
        endcase
    else
        tx <= 1'b1;
end
//done
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        done <= 1'b0;
    else    if(cnt_baud == BAUD_CNT_MAX - 1'b1 && cnt_bit == BIT_CNT_MAX - 1'b1)
        done <= 1'b1;
    else
        done <= 1'b0;
end
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  uart_rx_tx_tb();

reg         clk  ;
reg         rst_n;
reg         rx   ;

wire        tx;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    rx    = 1'b1;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk ;

//模拟发送8次数据,分别为0~7
initial
begin
    #200
    //任务的调用,任务名+括号中要传递进任务的参数
    rx_bit(8'd0);
    rx_bit(8'd1);
    rx_bit(8'd2);
    rx_bit(8'd3);
    rx_bit(8'd4);
    rx_bit(8'd5);
    rx_bit(8'd6);
    rx_bit(8'd7);
end
//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来 
//任务以task开头,后面紧跟着的是任务名,调用时使用 
task rx_bit(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
    input   [7:0]   data
);
    integer i; //定义一个常量 
    //用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1 
    //不可以写成C语言i=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    ;
            default:rx <= 1'b1    ;
        endcase
        #(5208*20); //每发送1位数据延时5208个时钟周期
    end
endtask//任务以endtask结束

uart_rx_tx  uart_rx_tx_inst(
    .clk    (clk  ) ,
    .rst_n  (rst_n) ,
    .rx     (rx   ) ,

    .tx     (tx   )
);
endmodule

仿真验证

仿真没有问题

上板验证

验证通过。

串口连续发送8字节

功能:使用串口连续发送8字节(50Mhz,波特率9600Bps)

绘制模块框图及波形图

第二种使用状态机的方法。

编写模块代码

顶层模块uart_send_8byte

复制代码
module  uart_send_8byte(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire        tx
);

wire        send_done;
wire        pi_flag  ;
wire [7:0]  pi_data  ;

fsm     fsm_inst1(
    .clk       (clk      ) ,
    .rst_n     (rst_n    ) ,
    .send_done (send_done) ,//控制串口发送信号线

    .pi_flag   (pi_flag  ) ,
    .pi_data   (pi_data  )
);

uart_tx     uart_tx_inst1(
    .clk       (clk      ) ,
    .rst_n     (rst_n    ) ,
    .pi_flag   (pi_flag  ) ,
    .pi_data   (pi_data  ) ,

    .send_done (send_done) ,
    .tx        (tx       )
);
endmodule

模块fsm

复制代码
module  fsm(
    input   wire        clk         ,
    input   wire        rst_n       ,
    input   wire        send_done   ,//控制串口发送信号线
    
    output  reg         pi_flag     ,
    output  reg  [7:0]  pi_data
);
//定义一个数组,存放8个字节需要发送的数据
wire [7:0]	mem	[0:7];

assign	mem[0]	= 8'h26;//年
assign	mem[1]	= 8'h07;//月
assign	mem[2]	= 8'h01;
assign	mem[3]	= 8'h14;
assign	mem[4]	= 8'h15;
assign	mem[5]	= 8'h16;
assign	mem[6]	= 8'h17;
assign	mem[7]	= 8'h18;

parameter   idle = 8'd0;
parameter   s0   = 8'd1;
parameter   s1   = 8'd2;
parameter   s2   = 8'd3;
parameter   s3   = 8'd4;

reg [7:0]   state;

//内部变量的定义
reg [31:0]  delay_cnt;
reg [7:0]   send_done_cnt;
reg [7:0]   index;

parameter   TIME_1S  = 32'd50_000_000;
parameter   BYTE_NUM = 32'd8         ;

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            delay_cnt       <= 32'd0;
            send_done_cnt   <=  8'd0;
            index           <=  8'd0;
            state           <=  idle;
            pi_flag         <=  1'b0;
            pi_data         <=  8'd0;
        end
    else
        begin
            case(state)
                idle:  
                    begin
                        if(delay_cnt == TIME_1S)
                            begin
                                delay_cnt       <= 32'd0;
                                send_done_cnt   <=  8'd0;
                                pi_flag         <=  1'b0;
                                pi_data         <=  8'd0;
                                index           <=  8'd0;
                                state           <=  s0  ;
                            end
                        else
                            begin
                                state       <= state;
                                delay_cnt   <= delay_cnt + 1'b1;
                            end
                    end
                s0  :
                    begin
                        pi_flag <= 1'b1;
                        pi_data <= mem[index];
                        state   <= s1;
                    end
                s1  :
                    begin
                        pi_flag <= 1'b0;
                        
                        if(send_done)
                            begin
                                state <= s2;
                                send_done_cnt <= send_done_cnt + 1'b1;
                                index <= index + 1'b1;
                            end
                        else
                            begin
                                state <= state;
                                send_done_cnt <= send_done_cnt;
                                index <= index;
                            end
                    end
                s2  :
                    begin
                        if(send_done_cnt == BYTE_NUM)
                            state <= s3;
                        else
                            state <= s0;
                    end
                s3  :
                    begin
                        delay_cnt       <= 32'd0;
                        send_done_cnt   <=  8'd0;
                        index           <=  8'd0;
                        state           <= state;
                        pi_flag         <=  1'b0;
                        pi_data         <=  8'd0;
                    end
                default:
                    begin
                        delay_cnt       <= 32'd0;
                        send_done_cnt   <=  8'd0;
                        index           <=  8'd0;
                        state           <= state;
                        pi_flag         <=  1'b0;
                        pi_data         <=  8'd0;
                    end
            endcase
        end
end
endmodule

模块uart_tx

复制代码
module  uart_tx(
    input   wire        clk         ,
    input   wire        rst_n       ,
    input   wire        pi_flag     ,
    input   wire [7:0]  pi_data     ,
    
    output  reg         send_done   ,
    output  reg         tx
);

parameter   CLK_FREQ = 'd50_000_000 ;
parameter   BPS      = 'd9600       ;

localparam  BAUD_TIME= CLK_FREQ / BPS ;

parameter   idle = 8'd0 ;
parameter   s0   = 8'd1 ;
parameter   s1   = 8'd2 ;
parameter   s2   = 8'd3 ;
parameter   s3   = 8'd4 ;
parameter   s4   = 8'd5 ;
parameter   s5   = 8'd6 ;
parameter   s6   = 8'd7 ;
parameter   s7   = 8'd8 ;
parameter   s8   = 8'd9 ;
parameter   s9   = 8'd10;
parameter   s10  = 8'd11;
parameter   done = 8'd12;

reg [7:0]   state       ;
reg [15:0]  delay_cnt   ;
reg [9:0]   send_data   ;

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
            state     <= idle;
            delay_cnt <= 16'd0;
            send_done <= 1'b0;
            tx        <= 1'b1;
        end
    else
        case(state)
            idle:
                begin
                    if(pi_flag)
                        state <= s0;
                    else      
                        state <= state;
                    
                    delay_cnt <= 16'd0;
                    tx        <= 1'b1;
                    send_done <= 1'b0;
                end
            s0  :
                begin
                    send_data <= {1'b1,pi_data[7:0],1'b0};
                    state <= s1;
                end
            s1  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s2;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[0];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s2  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s3;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[1];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s3  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s4;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[2];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s4  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s5;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[3];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s5  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s6;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[4];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s6  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s7;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[5];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s7  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s8;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[6];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s8  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s9;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[7];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s9  :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= s10;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[8];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            s10 :
                begin
                    if(delay_cnt == BAUD_TIME - 1'b1)
                        begin
                            state     <= done;
                            delay_cnt <= 16'd0;
                        end
                    else
                        begin
                            tx        <= send_data[9];
                            delay_cnt <= delay_cnt + 1'b1;
                        end
                end
            done:
                begin
                    state <= idle;
                    send_done <= 1'b1;
                end
            default:
                begin
                    state     <= idle;
                    delay_cnt <= 16'd0;
                    send_done <= 1'b0;
                    tx        <= 1'b1;
                end
        endcase
end
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  uart_send_8byte_tb();

reg     clk ;
reg     rst_n;

wire    tx;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk;

uart_send_8byte     uart_send_8byte_inst(
    .clk   (clk  ) ,
    .rst_n (rst_n) ,
            
    .tx    (tx   )
);
endmodule

仿真验证

上板验证

相关推荐
国科安芯10 小时前
ASC4T245S分组双向控制架构深度解析:独立DIR/OE控制、QFN16封装与混合方向总线桥接
单片机·嵌入式硬件·物联网·fpga开发·架构·risc-v
尤老师FPGA18 小时前
GT系列2:GT基础架构(二)
fpga开发
想你依然心痛20 小时前
电源时序控制:多路电源的上电顺序与监控——复位、看门狗
fpga开发
Eloudy1 天前
hsb fpga/ 目录分析
fpga开发·量子计算
Hello-FPGA1 天前
GPU Direct DMA RDMA 与FPGA 通讯在Jetson 平台的测试表现
fpga开发
坏孩子的诺亚方舟18 天前
FPGA系统架构设计实践15_高云Arora V系列时钟体系
fpga开发·系统架构
FPGA小徐18 天前
入门 CNN 结构全解析|从流程图理论到 FPGA Verilog 硬件实现(含习题带讲解)
fpga开发
FPGA小徐18 天前
FPGA 数字信号处理:并行 FIR 与串行滤波器设计原理、对比与完整 Verilog 实现
fpga开发
Saniffer_SH19 天前
【高清视频】Gen6 服务器还没到,Gen6 SSD 怎么测?Emily 现场演示三种测试环境
人工智能·驱动开发·测试工具·缓存·fpga开发·计算机外设·压力测试