【FPGA】UART串口通信---基于FIFO

前言

我们在上一章完成了UART串口通信的收发模块,这一章我们将FIFO引入进来,使用FIFO进行缓存数据,来连接串口通信的收发模块

一丶FIFO介绍

1.什么是FIFO?

FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据

2.FIFO分类

同步FIFO
读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer1

异步FIFO
读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。

3.FIFO主要参数

同步FIFO和异步FIFO略有不同,下面的参数适用于两者。

宽度,用参数FIFO_data_size表示,也就是FIFO存储的数据宽度;
深度,用参数FIFO_addr_size表示,也就是地址的大小,也就是说能存储多少个数据;
满标志,full,当FIFO中的数据满了以后将不再能进行数据的写入;
空标志,empty,当FIFO为空的时候将不能进行数据的读出;
写地址,w_addr,由自动加一生成,将数据写入该地址;
读地址,r_addr,由自动加一生成,将该地址上的数据读出;

同步FIFO和异步FIFO的最主要的不同就体现在空满标志产生的方式上,由此引出两者一些不同的参数。
同步FIFO

  • 时钟,clk,rst,读写应用同一个时钟;
  • 计数器,count,用计数器来进行空满标志的判断;

异步FIFO

  • 时钟,clk_w,rst_w,clk_r,rst_r,读写应用不同的时钟;
  • 指针,w_pointer_gray,r_pointer_gray,用指针来判断空满标识;
  • 同步指针,w_pointer_gray_sync,r_pointer_gray_sync,指针的同步操作,用来做对比产生空满标志符;

4.测试

首先配置IP核

在这里插入图片描述
设置路径,我们一般会在工程目录下创建一个文件夹 ip 用来存放IP核文件

在这里插入图片描述
在这里插入图片描述配置参数
在这里插入图片描述在这里插入图片描述
正常模式与前显模式:
在这里插入图片描述

区别:正常模式,输出数据与读请求信号差一个时钟周期;前显模式,将数据放于数据线上,在读请求信号拉高时,在下一个时钟周期,输出FIFO中的第二个数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
最后这样就成功引入FIFO了
在这里插入图片描述

5.仿真

调用ip核

module control (
    input   	        clk     ,
    input       	    rst_n   ,
    input	[7:0]       data    ,
    input   	        rdreq   ,
    input           	wrreq   ,
    output         	    empty   ,
    output       	    full    ,
    output	[7:0]       q       ,
    output	[7:0]       usedw
);
fifo	fifo_inst (
	.aclr        ( ~rst_n    ),     //复位信号取反
	.clock       ( clk       ),     //系统时钟  
	.data        ( data      ),     //写入数据     
	.rdreq       ( rdreq     ),     //读使能
	.wrreq       ( wrreq     ),     //写使能
	.empty       ( empty     ),     //fifo为空信号
	.full        ( full      ),     //fifo存满信号
	.q           ( q         ),     //读出数据
	.usedw       ( usedw     )      //可用数据量
	);

endmodule //control

testbench编写

`timescale 1ns/1ps
module tb_control ();
reg            clk      ; 
reg            rst_n    ; 
reg  [7:0]     data     ; 
reg            rdreq    ; 
reg            wrreq    ; 
wire           empty    ; 
wire           full     ; 
wire [7:0]     q        ; 
wire [7:0]     usedw    ; 

control control(
    .clk            (clk    )  ,
    .rst_n          (rst_n  )  ,
    .data           (data   )  ,
    .rdreq          (rdreq  )  ,
    .wrreq          (wrreq  )  ,
    .empty          (empty  )  ,
    .full           (full   )  ,
    .q              (q      )  ,
    .usedw          (usedw  )
);

parameter CYCLE = 20;
always #(CYCLE/2) clk=~clk;
integer i=0,j=0;

initial begin
    clk=1;
    rst_n=1;
    data=0;
    #200.1;
    rst_n=0;   //复位
    rdreq=0;
    wrreq=0;
    #(CYCLE*10);
    rst_n=1;
    #(CYCLE*10)
   //wrreq  50M
    for(i=0;i<256;i=i+1)begin     //因为我们的数据深度设置的是256,所以这里写进去256个数据
        wrreq = 1'b1;//写使能
        data = {$random};
        #CYCLE;
    end
    wrreq = 1'b0;//写完拉低
    #(CYCLE*5);

    //rdreq  50M
    for(j=0;j<256;j=j+1)begin
        rdreq = 1'b1;//读使能
        #CYCLE;
    end
    rdreq = 1'b0;
    #(CYCLE*10);
    

    $stop;
end

endmodule //tb_control

写数据:
在这里插入图片描述

读数据:

在这里插入图片描述

二丶UART引入FIFO

思路
首先我们将整个项目分为4个模块

uart_rx:接收模块- - -从上位机接收数据,然后将数据发送给control模块
uart_tx:发送模块- - -从control模块接收数据,然后发送给上位机
control:FIFO缓存模块- - -缓存uart_rx接收的数据并输出给uart_tx
top: 顶层模块

1.模块原理图

在这里插入图片描述
其中发送模块uart_tx增加了一个ready输出信号,因为发送模块每434个周期发送一位数据,为了防止FIFO不停的输出数据给发送模块,使用ready信号控制FIFO输出数据

2.代码设计

由于只改动了发送模块和新增了control模块,这里只展示这两部分,源码见文章末尾

control:

module control (
    input   	        clk        ,
    input       	    rst_n      ,

    input	[7:0]       dout       ,
    input               dout_vld   ,

    input               ready      , 

    output  [7:0]       din        ,
    output	            din_vld          
);
wire     [7:0]      data ;
wire     	        rdreq;
wire             	wrreq;
wire            	empty;
wire          	    full ;
wire     [7:0]      q    ;
wire     [7:0]      usedw;
reg                 flag ;

assign data=dout;
assign wrreq=dout_vld&&~full;

assign din=q;

//flag
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag<=0;
    end
    else if(usedw>7) begin  //存满8个字节拉高flag
        flag<=1;
    end
    else if (empty) begin
        flag<=0;
    end
end

assign rdreq=flag&&ready&&~empty;  
assign din_vld=rdreq;   //每次将din_vld拉高一个周期,输出一字节数据

fifo	fifo_inst (
	.aclr        ( ~rst_n    ),     //复位信号取反
	.clock       ( clk       ),     //系统时钟  
	.data        ( data      ),     //写入数据     
	.rdreq       ( rdreq     ),     //读使能
	.wrreq       ( wrreq     ),     //写使能
	.empty       ( empty     ),     //fifo为空信号
	.full        ( full      ),     //fifo存满信号
	.q           ( q         ),     //读出数据
	.usedw       ( usedw     )      //可用数据量
	);

endmodule //control

uart_tx:

module uart_tx (
    input  wire         clk,
    input  wire         rst_n,
    input  wire [7:0]   din,
    input  wire         din_vld,
    output reg          tx,
    output reg          ready
);
//定义一个寄存器来锁存 din_vld 时的din
reg [9:0]       data;

//波特率计数器
reg [8:0]       cnt_bps;
wire            add_cnt_bps;
wire            end_cnt_bps;

//比特计数器
reg [4:0]       cnt_bit;
wire            add_cnt_bit;
wire            end_cnt_bit;

reg             flag;   //计数器开启标志位

parameter       BPS_115200=434;  //发送一bit数据需要的周期数

//data
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data<=0;
    end
    else if(din_vld) begin
        data<={1'b1,din,1'b0};   //拼接起始位和停止位
    end
    else
        data<=data;
end

//发送数据 tx
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tx<=1'b1;
    end
    else if(cnt_bps==1) begin
        tx<=data[cnt_bit];
    end
end

//flag
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag<=0;
    end
    else if(din_vld) begin
        flag<=1;
    end
    else if(end_cnt_bit) begin   //发送完成关闭计数器
        flag<=0;
    end
    else
        flag<=flag;
end

//ready
always @(*) begin
    if (!rst_n) begin
        ready<=1;
    end
    else if(flag) begin
        ready<=0;
    end
    else begin
        ready<=1;
    end
end

//cnt_bps
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_bps<=0;
    end
    else if(add_cnt_bps) begin
        if (end_cnt_bps) begin
            cnt_bps<=0;
        end
        else
            cnt_bps<=cnt_bps+1;
    end
end

assign add_cnt_bps=flag;
assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1;

//cnt_bit
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_bit<=0;
    end
    else if(add_cnt_bit) begin
        if (end_cnt_bit) begin
            cnt_bit<=0;
        end
        else
            cnt_bit<=cnt_bit+1;
    end
end

assign add_cnt_bit=end_cnt_bps;
assign end_cnt_bit=add_cnt_bit&&cnt_bit==9;

endmodule //uart_tx

3.仿真与分析

testbench:

`timescale 1ns/1ps
module tb_uart ();
reg         clk;
reg         rst_n;
reg [7:0]   din;
reg         din_vld;
wire        tx_r;   //用来连接上位机的tx和从机的rx
wire        rx;

parameter       CYCLE=20;

//例化从机(顶层模块,包含了一个uart_rx和一个uart_tx)
uart uart(
    .clk            (clk),
    .rst_n          (rst_n),
    .rx             (tx_r),    //接收
    .tx             (tx)     //发送
);

//例化上位机(用来给从机发送数据)
uart_tx uart_tx(
    .clk              (clk),
    .rst_n            (rst_n),
    .din              (din),
    .din_vld          (din_vld),
    .tx               (tx_r)
);

always #(CYCLE/2)  clk=~clk;

initial begin
    clk=1;
    rst_n=1;
    #200;
    rst_n=0;
    din_vld=0;
    #(CYCLE*10);
    rst_n=1;

    send(8'h11);
    send(8'h22);
    send(8'h33);
    send(8'h44);
    send(8'h55);
    send(8'h66);
    send(8'h77);
    send(8'h88);
    #2000000;
    $stop;

end

task send;
    input [7:0] send_data;
    begin
        din=send_data;
        din_vld=1;
        #CYCLE;
        din_vld=0;
        #(CYCLE*434*22);
    end
endtask

endmodule //tb_uart_tx

分析:
在这里插入图片描述
1.上位机发送数据到FPGA之后由FPGA的接收模块将数据dout数据有效信号dout_vld输出给FIFO缓存
2.dout_vld作为写使能信号,在写使能开启的时候存储dout
在这里插入图片描述
3.在FIFO中存储的数据大于7个的时候开启读使能,因为FIFO模式设置的前显模式,所以在读使能生效前,第一位数据就有效了,也就是时序图中的q信号:8’h11

在这里插入图片描述

然后来看发送数据:
在这里插入图片描述
箭头处,din_vld拉高一个周期,目的是为了在我们发送完一帧数据之前,只锁存一次数据,保证发送一帧数据期间数据不改变,将数据din拼接起始位和停止位锁存到data

三丶上板验证

因为设置的FIFO存储满8个数据才开始读数据,所以这里看到发送8’h88之后才收到数据!!!
请添加图片描述

四丶源码

https://github.com/xuranww/uart_fifo.git

参考文章:
1.https://www.cnblogs.com/xuqing125/p/8337586.html
2.https://blog.csdn.net/QWERTYzxw/article/details/121295258


  1. 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分预留的内存空间叫缓冲区。
    使用缓冲区有两个好处:
    ①减少实际物理读写次数
    ②缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数 ↩︎

  • 36
    点赞
  • 186
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
串口通信是一种常见的电子设备之间数据传输的方式,常用于计算机和外部设备之间的数据传递。串口通信涉及到几个重要的概念,包括UART、RS232、RS422和RS485。 UART代表通用异步收发器(Universal Asynchronous Receiver/Transmitter),它是指一种硬件电路,用于实现串行数据传输。UART负责将并行数据转换为串行数据并进行传输,同时将接收到的串行数据转换为并行数据供主设备使用。UART通常使用两条线进行传输,一条用于发送数据,一条用于接收数据。 RS232是一种串口通信标准,常用于计算机和外部设备之间的数据传输。它使用串口连接并使用DB-9或DB-25连接器。RS232定义了传输数据的电气特性和信号电平,以及传输的协议。RS232支持全双工通信,即可以同时传输和接收数据。 RS422是一种更为先进的串口通信标准,用于在长距离和高速传输的环境下进行数据传输。RS422可以同时传输数据和接收数据,并使用四线电缆进行传输。RS422使用差分信号来提高传输的抗干扰能力和传输距离。 RS485是另一种串口通信标准,也适用于长距离和高速传输环境。RS485可以连接多个设备,允许多个设备之间进行数据传输。RS485也使用差分信号进行传输,提供了更高的传输距离和抗干扰能力。 在实际应用中,如果需要通过串口进行数据传输,首先需要确定要使用的串口标准。如果是较短距离和低速传输,可以选择RS232;如果是较长距离和高速传输,RS422或RS485可能更适合。然后,需要配置对应的硬件设备和软件驱动程序来实现串口通信。在数据传输过程中,发送端将数据转换成串行数据,并通过所选的串口标准进行传输,然后接收端将串行数据转换为并行数据进行处理。 总之,串口通信是一种常见的数据传输方式,需要了解UART、RS232、RS422和RS485等概念。选择合适的串口标准来进行数据传输,并配置相应的硬件设备和软件驱动程序来实现串口通信
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值