当前位置:   article > 正文

小梅哥Xilinx FPGA学习笔记10——串口通信发送_uart接口xilinx

uart接口xilinx

前言

如果不看分析步骤,需要了解代码,可以直接跳到第四节。

1、UART(通用异步收发传输器)

1.1UART基本介绍

RS232 通信接口标准,通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据,可以实现全双工传输和接收。
在这里插入图片描述
UART 是异步串行通信的总称(UART 是一种协议)。而 RS232、RS449、RS423、RS422 和 RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。若系统存在多个 UART接口,则可分别称为 COM1、COM2 等。
在这里插入图片描述
RS-232 是美国电子工业联盟(EIA)制定的串行数据通信的接口标准,原始编号全称是 EIA-RS-232(简称 232,RS232),被广泛用于计算机串行接口外设连接。
DB9 接口的针脚定义如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通常使用RXD、TXD、GND这三个信号

1.2UART关键参数

UART 通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。
在这里插入图片描述
数据位(Data bits):该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。可选择为:5、6、7 或者 8(默认)

波特率(Baud):是指从一设备发到另一设备的波特率,即每秒钟可以通信的数据比特个数。典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等。一般通信两端设备都要设为相同的波特率,但有些设备也可设置为自动检测波特率。通俗的说,就是一个数据的高电平/低电平持续的时间,是一个最基本的时间单位。
  串口全部都是使用的二进制,所以波特率就是比特率。

奇偶校验类型(Parity Type):是用来验证数据的正确性。奇偶校验一般不使用,如果使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。在偶校验中,因为奇偶校验位会被相应的置 1 或 0(一般是最高位或最低位),所以数据会被改变以使得所有传送的数位(含字符的各数位和校验位)中“1”的个数为偶数;在奇校验中,所有传送的数位(含字符的各数位和校验位)中“1”的个数为奇数。奇偶校验可以用于接受方检查传输是否发送生错误,如果某一字节中“1”的个数发生了错误,那么这个字节在传输中一定有错误发生。如果奇偶校验是正确的,那么要么没有发生错误,要么发生了偶数个的错误。如果用户选择数据长度为 8 位,则因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验(Non)”。

停止位(Stop bits):在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。

1.3UART时序图

在 RS-232 标准中,最常用的配置是 8N1(即八个数据位、无奇偶校验、一个停止位),其发送一个字节时序图如图:
在这里插入图片描述
按照一个完整的字节包括一位起始位、8 位数据位、一位停止位即总共十位数据来算, 要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束。

bps_clk的电平宽度是一个系统时钟周期,如:50MHz,是20ns;bps_clk高电平时,uart_tx的值更新一位。

如果波特率是9600,传输一位的时间为104.1666……ns
1s=1109ns;109ns / 9600 =104,166.666……ns=104.1666……us
如果波特率是115200,传输一位的时间为8,680.555……ns
1s=1
109ns;109ns / 115200 =8,680.555n……ns=8.680……us

2、基于FPGA的串口(UART)发送实验

1、串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口θ
2、串口通信,支持不同的波特率,所以需要有一个波特率设置端口。
3、串口通信的本质就是将8位的并行数据通过一根信号线.在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。
4、串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束。
5、控制信号,控制并转串模块什么开始工作。什么时候一个数据发送完成?须有一个发送开始信号,以及一个发送完成信号。
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
并转串图示
在这里插入图片描述
串口发送模块(手绘版)
在这里插入图片描述
串口发送模块整体框图
在这里插入图片描述

3、代码实现步骤分析

3.1 端口声明

module uart_byte_tx(
    clk,
    reset_n, 
    data,
    send_en,
    baud_set,
    
    uart_tx,
    tx_done   
    );
    input clk;//模块全局时钟 50MHz    
    input reset_n;//模块全局复位信号
    input [7:0]data;//待传输 8bit 数
    input send_en;//发送使能信号
    input [2:0]baud_set;//baud set 波特率设置,支持 5 种,baud set为 3 位
    
    output uart_tx;//串口发送信号输出
    output tx_done;//发送结束信号,一个时钟周期高电平
              
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3.2 波特率时钟生成

在波特率时钟生成模块中,计数器需要的计数值与波特率之间的关系如下表所示,其中系统时钟周期为System_clk_period,这里为 20ns(因为是50MHz)。如果接入到该模块的时钟频率为其他值,需要根据具体的频率值修改该参数。典型的波特率有 300, 1200, 2400, 9600(波特率一般都大于9600), 19200, 115200 等。
在这里插入图片描述
波特率为300,对应时间3333333ns最小时间单元:3333333ns/20ns(即时钟频率50HZ)=16666次(2进制18位)
波特率为9600,对应时间104167ns最小时间单元:104167ns/20ns(即时钟频率50HZ)=5208次(2进制13位)
波特率为115200,对应时间8680ns最小时间单元:8680ns/20ns(即时钟频率50HZ)=434次=2’b110110010(9位)

计数器由波特率(波特率一般都大于9600)决定

//生成一个时钟计数器
    reg [17:0]div_cnt;
    always@(posedge clk or negedge reset_n)
    if(!reset_n)
        div_cnt<=0;
    else if(div_cnt==bps_DR-1)//bps_DR由波特率决定
        div_cnt<=0;
    else
        div_cnt<=div_cnt + 1'b1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

增加一个send_en来控制,将上面代码修改为下面代码

//生成一个时钟计数器
    reg [17:0]div_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        div_cnt<=0;
    else if(send_en)begin
        if(div_cnt==bps_DR-1)//bps_DR由波特率决定
            div_cnt<=0;
        else
            div_cnt<=div_cnt + 1'b1;
    end
    else
        div_cnt<=0;
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

波特率的设置

//波特率的设置    
//baud_set = 0 就让波特率 = 9600 ;
//baud_set = 1 就让波特率 = 19200 ;    
//baud_set = 2 就让波特率 = 38400 ;   
//baud_set = 3 就让波特率 = 57600 ;
//baud_set = 4 就让波特率 = 115200 ;   
    reg [17:0]bps_DR;//波特率寄存器,因为要和div_cnt做比较,所以也是[17:0]
    always@(*)begin
    if(!reset_n)
        bps_DR <= 1000000000/9600/20;
    else begin
        case(baud_set)//使用组合逻辑来写,常量可以计算出来,FPGA保留整数,不存在除不尽的状态
            0:bps_DR <= 1000000000/9600/20;//0:bps_DR <= 16'd5208;
            1:bps_DR <= 1000000000/19200/20;
            2:bps_DR <= 1000000000/38400/20;
            3:bps_DR <= 1000000000/57600/20;
            4:bps_DR <= 1000000000/115200/20;
            default:bps_DR <= 1000000000/9600/20;               
        endcase
    end
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.3 数据输出模块设计

通过对波特率时钟进行计数,来确定数据发送的循环状态10个信号需要11个点来确定,参照1.3时序图所示

//数据发生的循环状态,共10个信号    
//计数器计数11位,二进制用4位来表示
    reg [3:0]bps_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        bps_cnt<=0;
    else if(bps_cnt==bps_DR-1)begin//bps_cnt== 4'd11
        if(bps_cnt==11)
            bps_cnt<=0;
        else
            bps_cnt<=bps_cnt + 1'b1;   
        end 
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

修改由bps_clk来控制的代码

//分析后,bps_cnt也受send_en控制   
//数据发生的循环状态,共10个信号    
//计数器计数11位,二进制用4位来表示

	wire bps_clk;
    assign bps_clk=(div_cnt==1);
 
    reg [3:0]bps_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        bps_cnt<=0;
    else if(send_en)begin
        if(bps_clk)begin
             if(bps_cnt==10)
                bps_cnt<=0;
             else
                bps_cnt<=bps_cnt + 1'b1;   
            end
    end 
    else
        bps_cnt<=0;
    end    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

数据传输状态控制模块设计
还有一个十选一多路器 ,作用是根据 bps_cnt 的值来确定数据传输的状态。如1.3时序图所示,在不同的波特率时钟计数值时,有对应的传输数据。

    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)begin
      uart_tx<=1'b0;tx_done<=1'b0;
    end
    else begin
        case(bps_cnt) 
            0:begin uart_tx<=1'b0;tx_done<=1'b0;end
            1:uart_tx<=data[0];
            2:uart_tx<=data[1];
            3:uart_tx<=data[2];
            4:uart_tx<=data[3];
            5:uart_tx<=data[4];
            6:uart_tx<=data[5];
            7:uart_tx<=data[6];
            8:uart_tx<=data[7];
            9:uart_tx<=1'b1;//此处确保stop是持续一个脉冲
            10:begin uart_tx<=1'b1;tx_done<=1'b1;end
            default:uart_tx<=1'b1;
        endcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

添加使能端控制

//让send_en控制div_cnt
    reg [17:0]div_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        div_cnt<=0;
    else if(send_en)begin
        if(div_cnt==bps_DR-1)//bps_DR由波特率决定
            div_cnt<=0;
        else
            div_cnt<=div_cnt + 1'b1;
    end
    else
        div_cnt<=0;
    end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

目前,基本功能可以实现了

4、代码实现总结

4.1 设计文件

整合第3步的所有代码,形成下面设计文件。

`timescale 1ns / 1ps
module uart_byte_tx(
    clk,
    reset_n, 
    data,
    send_en,
    baud_set,
    
    uart_tx,
    tx_done   
    );
    input clk;//模块全局时钟 50MHz    
    input reset_n;//模块全局复位信号
    input [7:0]data;//待传输 8bit 数
    input send_en;//发送使能信号
    input [2:0]baud_set;//baud set 波特率设置,支持5种,baud set 为3位
    
    output reg uart_tx;//串口发送信号输出
    output reg tx_done;//发送结束信号,一个时钟周期高电平
      
//波特率的设置    
//baud_set = 0 就让波特率 = 9600 ;
//baud_set = 1 就让波特率 = 19200 ;    
//baud_set = 2 就让波特率 = 38400 ;   
//baud_set = 3 就让波特率 = 57600 ;
//baud_set = 4 就让波特率 = 115200 ;   
    reg [17:0]bps_DR;//波特率寄存器,因为要和div_cnt做比较,所以也是[17:0]
    always@(*)begin
    if(!reset_n)
        bps_DR <= 1000000000/9600/20;
    else begin
        case(baud_set)//使用组合逻辑来写,常量可以计算出来,FPGA保留整数,不存在除不尽的状态
            0:bps_DR <= 1000000000/9600/20;//0:bps_DR <= 16'd5208;
            1:bps_DR <= 1000000000/19200/20;
            2:bps_DR <= 1000000000/38400/20;
            3:bps_DR <= 1000000000/57600/20;
            4:bps_DR <= 1000000000/115200/20;
            default:bps_DR <= 1000000000/9600/20;               
        endcase
    end
    end
        
  
//让send_en控制div_cnt                                                    
    reg [17:0]div_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        div_cnt<=0;
    else if(send_en)begin
        if(div_cnt==bps_DR-1)//bps_DR由波特率决定
            div_cnt<=0;
        else
            div_cnt<=div_cnt + 1'b1;
    end
    else
        div_cnt<=0;
    end    


    wire bps_clk;
    assign bps_clk=(div_cnt==1);    
//分析后,bps_cnt也受send_en控制   
//数据发生的循环状态,共10个信号    
//计数器计数11位,二进制用4位来表示
    reg [3:0]bps_cnt;
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)
        bps_cnt<=0;
    else if(send_en)begin
 //          if(bps_cnt==1)begin
             if(bps_clk)begin
                if(bps_cnt==10)
                bps_cnt<=0;
                else
                bps_cnt<=bps_cnt + 1'b1;   
            end
    end 
    else
        bps_cnt<=0;
    end    

    
    always@(posedge clk or negedge reset_n)begin
    if(!reset_n)begin
      uart_tx<=1'b0;tx_done<=1'b0;
    end
    else begin
        case(bps_cnt) 
            0:begin uart_tx<=1'b0;tx_done<=1'b0;end
            1:uart_tx<=data[0];
            2:uart_tx<=data[1];
            3:uart_tx<=data[2];
            4:uart_tx<=data[3];
            5:uart_tx<=data[4];
            6:uart_tx<=data[5];
            7:uart_tx<=data[6];
            8:uart_tx<=data[7];
            9:uart_tx<=1'b1;//此处确保stop是持续一个脉冲
            10:begin uart_tx<=1'b1;tx_done<=1'b1;end
            default:uart_tx<=1'b1;
        endcase
    end
    end   
             
endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

4.2 仿真文件

写仿真文件快捷方法:
1.复制端口声明,例化连接,ctrl可以实现列编辑,在端口名前加点。
2.复制输入端口,直接将input替换成reg。在vivado中,单击鼠标右键,选择replace in files,可以选择设计文件/仿真文件的替换 。

`timescale 1ns / 1ps
module uart_byte_tx_tb(    );
    reg clk;  
    reg reset_n;
    reg [7:0]data;
    reg send_en;
    reg [2:0]baud_set;
    wire uart_tx;
    wire tx_done;
    uart_byte_tx uart_byte_tx_inst(
    .clk(clk),
    .reset_n(reset_n), 
    .data(data),
    .send_en(send_en),
    .baud_set(baud_set),
    .uart_tx(uart_tx),
    .tx_done(tx_done)  
    );
    initial clk=1;
    always #10 clk=~clk;//20ns一个周期
    //对输入进行赋值
    initial begin     
    reset_n=0;   
    data=0; 
    send_en=0;   
    baud_set=4;//115200速度快,仿真时间短
    #201
    reset_n=1;
    #100
    data=8'h57; 
    send_en=1;   
    #20
    @(posedge tx_done)//死循环,一直等待TX_done的到来,再执行下一语句
    send_en=0; 
    #20000 
    
    data=8'h75; 
    send_en=1;   
    #20
    @(posedge tx_done)
    #20000 
    send_en=0;   
    $stop; 
    end    
          
endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

testbench的新语法 @(posedge TX_done),死循环,一直等待TX_done的到来,再执行下一语句

4.3 仿真结果

每一个时间单位是115200,数据完成了串转并。
在这里插入图片描述

5、注意事项总结

  1. if…else 嵌套,一定要用begin…end来区分。
  2. 仿真中,testbench的新语法 @(posedge TX_done),死循环,一直等待TX_done的到来,再执行下一语句
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/464971
推荐阅读
相关标签
  

闽ICP备14008679号