协议-UART

一、UART简介

        UART(universal asynchronous receiver-transmitter)是一种采用异步串行通信方式的通用异步收发传输器。一般来说,UART总是和RS232成对出现,那RS232又是什么呢? RS232也就是我们计算机上的串口,它的全称是EIA-RS-232C (简称232,或者是RS232 )。其中EIA(Electronic Industry Association)代表美国电子工业协会,RS是Recommended Standard的缩写,代表推荐标准,232 是标识符,C表示修改次数,它被广泛用于计算机串行接口外设连接。如果你的计算机上还有串口的话,那么你就可以在主机箱后面看到RS232的接口:

UART


        随着时代的发展,这种借口已经很少用了,取而代之的是“USB转串口”,功能和原先一样,但接口更高效了。

        串口的主要功能为:在发送数据时将并行数据转换成串行数据进行传输,在接收数据时将接收到的串行数据转换成并行数据。这应该是大多数人接触电子后学习到的第一个通信协议吧。

二、通信格式

下面说明串口的具体要点:

1、传输时序

        UART串口通信需要两个信号线来实现,一根用于串口发送,另外一根负责串口接收。一开始高电平,然后拉低表示开始位,接着8个数据位,然后校验位,最后拉高表示停止位,并且进入空闲状态,等待下一次的数据传输。

时序图


        很多时候我们的校验位是允许省略的,所以协议就变成了:开始+数据+停止。

省略校验位

2、传输速率:波特率

        串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位bps(位每秒)。常用的波特率有9600、19200、35400、57600以及115200等。
        FPGA开发串口时设计波特率的方法:FPGA的时钟频率/波特率。例如FPGA开发板时钟频率为50Mhz,要使用的波特率为9600bps,因此需要的计数为:50000000/9600=5208;

三、串口回环实验

        现在用FPGA开发板做一个串口回环的实验,要求是PC端通过串口助手发送数据给FPGA,FPGA接收到数据后返回给PC端,并在串口助手处显示数值。即串口助手发什么就能收回什么。实验框图如下:

 1、uart_rx

//**************************************************************************
// *** 名称 : uart_rx.v
// *** 作者 : 木子
// *** 日期 : 2024-05-13
// *** 描述 : 串口接收模块,计数9.5下,其中停止位0.5下
//            因为串口助手发送本次停止位和下次开始位中间没有留空闲位
//            若计满10下,则才结束本次传输下次数据就来了,会来不及接收
//**************************************************************************

module uart_rx
//========================< 参数 >==========================================
#(
parameter  CLK              = 50_000_000        , //系统时钟,50Mhz
parameter  BPS              = 9600              , //波特率
parameter  BPS_CNT          = CLK/BPS             //波特率计数
)
//========================< 端口 >==========================================
(
input   wire                clk                 , //时钟,50Mhz
input   wire                rst_n               , //复位,低电平有效
input   wire                din                 , //输入数据
output  reg   [7:0]         dout                , //输出数据
output  reg                 dout_vld              //输出数据的有效指示
);
//========================< 信号 >==========================================
reg                         rx0                 ;
reg                         rx1                 ;
reg                         rx2                 ;
wire                        rx_en               ;
reg                         flag                ;
reg   [15:0]                cnt0                ;
wire                        add_cnt0            ;
wire                        end_cnt0            ;
reg   [ 3:0]                cnt1                ;
wire                        add_cnt1            ;
wire                        end_cnt1            ;
reg   [ 7:0]                data                ;

//==========================================================================
//==    消除亚稳态 + 下降沿检测
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rx0 <= 1;
        rx1 <= 1;
        rx2 <= 1;
    end
    else begin
        rx0 <= din;
        rx1 <= rx0;
        rx2 <= rx1;
    end
end

assign rx_en = rx2 && ~rx1;

//==========================================================================
//==    接收状态指示
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        flag <= 0;
    else if(rx_en)
        flag <= 1;
    else if(end_cnt1)
        flag <= 0;
end

//==========================================================================
//==    波特率计数
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt0 <= 0;
    else if(add_cnt0) begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

assign add_cnt0 = flag;
assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1;

//==========================================================================
//==    开始1位(不接收) + 数据8位 + 停止0.5位(不接收),共10位
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt1 <= 0;
    else if(add_cnt1) begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1;

//==========================================================================
//==    缓存数据
//==========================================================================
always @ (posedge clk or negedge rst_n)begin
    if(!rst_n)
        data <= 8'd0;
    else if(cnt1>=1 && cnt1<=8 && cnt0==BPS_CNT/2-1) //中间采样
        data[cnt1-1] <= rx2;                         //或 dout <= {rx2,dout[7:1]};
end

//==========================================================================
//==    输出数据
//==========================================================================
always @ (posedge clk or negedge rst_n)begin
    if(!rst_n)
        dout <= 0;
    else if(end_cnt1)
        dout <= data;
end

always @ (posedge clk or negedge rst_n)begin
    if(!rst_n)
        dout_vld <= 0;
    else if(end_cnt1)
        dout_vld <= 1;
    else
        dout_vld <= 0;
end



endmodule

 2、uart_tx

//**************************************************************************
// *** 名称 : uart_tx.v
// *** 作者 : 木子
// *** 日期 : 2024-05-13
// *** 描述 : 串口接收模块,计数9.5下,其中停止位0.5下
//            因为极端情况是本次停止位和下次开始位中间没有留空闲位
//            若计满10下,则才结束本次传输下次数据就来了,会来不及发送
//**************************************************************************

module uart_tx
//========================< 参数 >==========================================
#(
parameter  CLK              = 50_000_000        , //系统时钟,50Mhz
parameter  BPS              = 9600              , //波特率
parameter  BPS_CNT          = CLK/BPS             //波特率计数
)
//========================< 端口 >==========================================
(
input   wire                clk                 , //时钟,50Mhz
input   wire                rst_n               , //复位,低电平有效
input   wire  [7:0]         din                 , //输入数据
input   wire                din_vld             , //输入数据的有效指示
output  reg                 dout                  //输出数据
);
//========================< 信号 >==========================================
reg                         flag                ;
reg   [ 7:0]                din_tmp             ;
reg   [15:0]                cnt0                ;
wire                        add_cnt0            ;
wire                        end_cnt0            ;
reg   [ 3:0]                cnt1                ;
wire                        add_cnt1            ;
wire                        end_cnt1            ;
wire  [ 9:0]                data                ;

//==========================================================================
//==    数据暂存(din可能会消失,暂存住)
//==========================================================================
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        din_tmp <=8'd0;
    else if(din_vld)
        din_tmp <= din;
end

//==========================================================================
//==    发送状态指示
//==========================================================================
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)
        flag <= 0;
    else if(din_vld)
        flag <= 1;
    else if(end_cnt1)
        flag <= 0;
end

//==========================================================================
//==    波特率计数
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt0 <= 0;
    else if(add_cnt0) begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

assign add_cnt0 = flag;
assign end_cnt0 = cnt0== BPS_CNT-1 || end_cnt1;

//==========================================================================
//==    开始1位 + 数据8位 + 停止0.5位,共10位
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt1 <= 0;
    else if(add_cnt1) begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = cnt1==10-1 && cnt0==BPS_CNT/2-1;

//==========================================================================
//==    数据输出(用case语句也行)
//==========================================================================
assign data = {1'b1,din_tmp,1'b0};  //停止,数据,开始

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        dout <= 1'b1;
    else if(flag)
        dout <= data[cnt1];
end



endmodule

3、top层

//**************************************************************************
// *** 名称 : uart_top.v
// *** 作者 : 木子
// *** 日期 : 2024-08-10
// *** 描述 : 串口实验顶层文件
//**************************************************************************

module uart_top
//========================< 端口 >==========================================
(
input  wire                 clk                 , //时钟,50Mhz
input  wire                 rst_n               , //复位,低电平有效
input  wire                 uart_rx             , //FPGA通过串口接收的数据
output wire                 uart_tx               //FPGA通过串口发送的数据
);

//========================< 连线 >==========================================
wire [7:0]                  data                ;
wire                        data_vld            ;

//==========================================================================
//==    模块例化
//==========================================================================
uart_rx
#(
    .BPS_CNT                (52                 )    //仿真用
)
u_uart_rx
(
    .clk                    (clk                ),
    .rst_n                  (rst_n              ),
    .din                    (uart_rx            ),
    .dout                   (data               ),
    .dout_vld               (data_vld           )
);

uart_tx
#(
    .BPS_CNT                (52                 )   //仿真用
)
u_uart_tx
(
    .clk                    (clk                ),
    .rst_n                  (rst_n              ),
    .din_vld                (data_vld           ),
    .din                    (data               ),
    .dout                   (uart_tx            )
);



endmodule

四、仿真调试

1、testbench

`timescale 1ns/1ps  //时间精度
`define    Clock 20 //时钟周期

module uart_top_tb;

//========================< 端口 >==========================================
reg                         clk                 ; //时钟,50Mhz
reg                         rst_n               ; //复位,低电平有效
reg                         uart_rx             ;
wire                        uart_tx             ;

//==========================================================================
//==    模块例化
//==========================================================================
uart_top u_uart_top
(
    .clk                    (clk                ),
    .rst_n                  (rst_n              ),
    .uart_rx                (uart_rx            ),
    .uart_tx                (uart_tx            )
);

//==========================================================================
//==    时钟信号和复位信号
//==========================================================================
initial begin
    clk = 1;
    forever
        #(`Clock/2) clk = ~clk;
end

initial begin
    rst_n = 0; #(`Clock*20+1);
    rst_n = 1;
end

//==========================================================================
//==    task任务
//==========================================================================
reg  [7:0]              mem[15:0]           ; //位宽为8,深度为16个数据
integer                 i                   ;
integer                 j                   ;

//读取外部数据
initial $readmemh("./data.txt",mem);

//位赋值
task rx_bit
(
    input [7:0]         data
);
    begin
        for(i=0;i<=9;i=i+1) begin   //10个bit为
            case(i)
                 0: uart_rx = 1'b0;
                 1: uart_rx = data[i-1];
                 2: uart_rx = data[i-1];
                 3: uart_rx = data[i-1];
                 4: uart_rx = data[i-1];
                 5: uart_rx = data[i-1];
                 6: uart_rx = data[i-1];
                 7: uart_rx = data[i-1];
                 8: uart_rx = data[i-1];
                 9: uart_rx = 1'b1;
            endcase
            #1040; //一个完整波特延时:52*20=1040
        end        //考虑到空闲位,也可以设置得1040稍大一些
    end
endtask

//字节赋值
task rx_byte;
    begin
        for(j=0;j<=15;j=j+1) //16个byte数据
            rx_bit(mem[j]);
    end
endtask

//==========================================================================
//==    调用task
//==========================================================================
initial begin
    #(`Clock*20+1);
    rx_byte();
end

initial begin
    #180000;
    $stop;
end

endmodule

2、data.txt

  testbench中调用了一个 data.txt 文本文档,里面存储了此次仿真的16个数据,将其放置到 Modelsim 软件的工程目录中(非 work)即可。

0 1 2 3 4 5 6 7 8 9 a b c d e f

3、仿真波形

  由波形可以看到,本次设计应该是成功的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值