基于FPGA的UART通信——基础篇

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

  根据数据传输采用时钟的不同,串行通信分为异步串行通信和同步串行通信。
  同步串行通信在数据传输过程中必须要保证数据传输的同步性,不能存在时间差,这就要求接收和发送设备之间时钟同源,否则数据传输就会失败。其优点是可以使用速度快,缺点是同步时钟易受干扰导致误码,传输距离有限。SPI总线便是同步串行通信的典型代表。
  异步串行通信在数据传输过程中不要求时钟同源,每传输一个字符就要用起始位等来进行接收和发送双方的同步,接收端和发送端不必同时开始、同时结束。异步串行通信是通过牺牲了传输速率和传输位数(添加了冗余项)来实现接收和发送端的同步的,其传输方式利用每一帧的起、止信号来建立接收和发送端之间的同步。其优点是线路简单且适合长距离传输,缺点是传输速度慢。UART串口便是异步串行通信的典型代表。
  总得来说,不论是异步通信还是同步通信都需要进行同步,只不过异步通信是通过传输字符内的起始位来进行同步,而同步通信采用相同的外部时钟进行同步,也就是前者是自同步,后者是外同步。由于异步串行通信在现实生活的应用方便不需要太复杂的硬件,在日常中应用广泛。

一、UART的基本原理

  1. 波特率和比特率
      波特率表示单位时间内传送的码元符号(symbol)的个数,它是对符号传输速率的一种度量,单位是B(Baud,即symbol/s)。比特率表示单位时间内传输的比特数,单位是bps(bit pro second,位每秒)。码元和比特数关系如下表所示,可以发现 比特位数 = l o g 2 N ( N 码元状态个数 ) 比特位数 = log_2N (N码元状态个数) 比特位数=log2N(N码元状态个数)
    在这里插入图片描述

  由于UART是进行二进制传输(只有0,1),因此波特率等于比特率。下面不区分波特率和比特率。UART常见的波特率为9600bps,115200bps等等,9600bps即为每秒传输9600bit的数据。

  1. 帧格式
      在进行异步串行通信过程中,是以一帧数据的格式进行传输的。在UART协议帧数据格式中包括开头的起始位、有效数据位、可选的奇偶校验位以及最后的停止位。其通信格式如下
    在这里插入图片描述
      在进行数据传输之前需要提前规定好传输的模式,需要一致数据传输的波特率,需要一致的传输数据位数,需要一致的奇偶校验,才能实现数据的发送和接受之间保持同步。
    (1)起始位
      起始位是在一帧数据的第一位,代表传输数据的开始,起着实现同步传输的功能。由于UART协议规定无数据传输时为高电平为空闲位,使用低电平作为起始位可以用来提示接收端开始接收数据。
    (2)数据位
      数据位是数据传输过程中的有效位,在使用UART协议进行传输数据时,一般可以选择7或8位数据位。在数据传输时,数据的传输方向由低位到高位。在异步传输过程中,需要保证接收端和发送端的数据位宽一致。
    (3)奇偶校验位
      奇偶校验位为可选位,可以实现检测被接收数据是否传输准确。数据发送端按照协议在数据为的后面加上一位0或者1。接收端则要检查此奇偶校验位是否保持正确的关系。如果接收到的校验位不正确,那么就表示接收到的数据位可能出现了错误。需要注意的是UART的检验位不具有前向纠错能力,只能知道错了。
      奇校验,是指数据位和奇校验位中1的个数必须位偶数;偶校验,是指数据位和奇偶校验位中1的个数必须为奇数。无校验,是指一帧数据中没有奇偶校验位的存在,接收设备也无法对数据位是否传输正确进行判断。
    (4)停止位
      一帧数据在传输完后上表示结束的高电平结束位,停止位可以是1位、1.5位或2位,一般为1位。
    (5)空闲位
      空闲位是在两帧数据之间间隔的位,保持高电平,表示当前没有数据进行传输,直到表示起始位的低电平来临。
      本次设计要实现的便是基于FPGA的异步收发传输器,能够实现波特率(9600、115200、460800bps)的选择,七位和八位数据位的选择,无校验、奇校验和偶校验的选择,起始位为1位,停止位为1位。

二、设计方案

  通过一中对UART时序的介绍,下面介绍两种常见的设计方案。
  为了传输数据的准确信,设计异步传输收发器最大的难处就在于何时对接收到的数据进心采样才是最稳定的。
  方法一,利用16倍采样倍数对系统时钟进行分频,然后通过计数器计数到传输数据的的中间位置进行采样,此时接收到的数据是最稳定的,并且发出的数据也是最稳定的。当系统时钟为100M是,波特率为9600bps,取采样倍数为16,则波特率分频因子为100M/(9600bps*16)=651.04167=651,下一步就是要将系统是时钟进行分频设计出采样时钟,然后计数到数据电平的中间位置时进行采样,下图红色框处为采样点。
请添加图片描述

方法二,直接生成一个采样脉冲不需要将系统时钟进行分频后再进行计数,也就是采样脉冲的间隔为一位数据的传输周期,那么可以计算得脉冲之间需要计数N个时钟 N = 100 M / 9600 b p s = 10416.6667 = 10417 N=100M/9600bps=10416.6667=10417 N=100M/9600bps=10416.6667=10417。采样的时钟格式如下图
在这里插入图片描述

  显然方法二数据采样点的位置不固定,如果处于信号的跳变沿处,采样结果无疑是不确定的,相对而言采样点在数据的中间位置更加稳定,方法一实现的误码率肯定会更低。但奈不住,方法二实现起来更简单资源也更少啊。下面将采取方法二进行UART收发器的实现,能够实现波特率(9600、115200、460800bps)的选择,七位和八位数据位的选择,无校验、奇校验和偶校验的选择,起始位为1位,停止位为1位。

三、代码实现

1. 时钟生成模块

  该模块用来时钟分频得到采样时钟BR_clk。可以通过model来设置所需要的波特率2’b00:9600bps;2’b01:115200bps;2’b10460800bps。方便切换波特率。需要注意的是这里系统时钟为100MHz。

//波特率发生模块
//参数:clk输入时钟,rst_n复位,BR_clk输出波特率
module BR_generate(
    input clk,
    input rst_n,
    input [1:0] model,
    output reg BR_clk
    );

reg [13:0] counter;
reg [13:0] bps;

//波特率选择9600bps,11500bps,460800bps
always @(*)
begin
    case (model)
          2'b00:bps=14'd10417;  //100M/9600
          2'b01:bps=14'd868;    //100M/115200
          2'b10:bps=14'd217;    //100M/460800
    default:bps=14'd10417;
endcase
end

always @ (posedge clk or negedge rst_n)
begin
    if(!rst_n)begin
        counter<=14'd0;
        BR_clk<=0;
        end
    else
        begin
        if(counter==bps-14'd1)begin
            counter<=14'd0;BR_clk<=1'b1;
            end
        else 
        begin
            counter<=counter+14'd1;BR_clk<=0;
        end
        end
end
endmodule


2.UART_TX发送端

  该模块为UART的发送端,可以通过odd_even设置校验位,width设置位宽。

//odd_even为奇偶校验位(0为无校验,1为偶校验,2为奇校验)
//width为位宽选择,0为八位,1为七位
module uart_tx(
    input clk,
    input rst_n,
    input tx_en,
    input [7:0] data_in,
    input [1:0] odd_even,
    input width,
    output reg txd
    );

    parameter idle    = 2'b00,
                s_start = 2'b01,
                s_data  = 2'b10,
                s_end   = 2'b11;
    reg[1:0] current_state;
    reg[1:0] next_state;
    reg[3:0] count,max_count;
    reg[8:0] temp;              //第八位时第八位奇偶校验位,七位时第七位是奇偶校验位
    
     //状态机第一段
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            current_state <= idle;
        else
            current_state <= next_state;
        end
    //状态机第二段
    always @(*)
    begin
        next_state = current_state;//初始化
        case(current_state)
            idle   :if(tx_en) next_state = s_start; //发送使能位
            s_start:next_state = s_data;            //发送起始位,发送低电平
            s_data :if(count == max_count-width)    //发送数据位
                        next_state = s_end;
            s_end  :if(tx_en) next_state = s_start; //发送停止位,高电平
            default:next_state = idle;
        endcase
    end
    //判断数据传输位
    always @(*)begin
        if(odd_even==2'b00) //无校验位
            max_count=7;
        else
            max_count=8;
     end
    
    //以下为状态机第三段,位计数器
    always @(posedge clk or negedge rst_n)begin//控制位宽
        if(!rst_n)
            count <= 'd0;  
        else if(current_state == s_data)
            count <= count+1;
        else
            count <= 0;
    end
        
    always @(posedge clk or negedge rst_n)begin//发送低位到高位
        if(!rst_n)
            temp <= 'd0;
        else if(current_state==s_start)
            begin 
                if(!width)
                    begin
                        temp[7:0] <= data_in;     //将发送数据导入变量
                        if(odd_even==2'b01)         //偶校验
                            temp[8]=^data_in[7:0];
                        else if(odd_even==2'b10)//奇校验
                            temp[8]=~(^data_in[7:0]);
                   end
                else//七位数据位,第八位为校验位
                    begin
                        temp[6:0]<=data_in;//将发送数据导入变量
                        if(odd_even==2'b01)//偶校验
                            temp[7]=^data_in[6:0];
                        else if(odd_even==2'b10)//奇校验
                            temp[7]=~(^data_in[6:0]);
                    end
            end
        else if(current_state==s_data)
            begin
                if(!width)
                    temp[7:0]<=temp[8:1];//数据移位
                else
                    temp[6:0]<=temp[7:1];//数据移位
            end
    end
        
    always @(posedge clk)
    case(current_state)
        s_start:txd<=0;//发送起始位
        s_data :txd<=temp[0];//发送数据位
        s_end  :txd<=1;//发送停止位
        default:txd<=1;//空闲状态
    endcase
endmodule

3.UART_RX接收端

  该模块为UART的接收端,可以通过odd_even设置校验位,width设置位宽。

//odd_even为奇偶校验位(0为无校验,1为偶校验,2为奇校验)
//width为位宽选择,0为八位,1为七位
module uart_rx(
    input clk,
    input rst_n,
    input rxd,
    input [1:0] odd_even,
    input width,
    output reg [7:0] data_out,
    output rx_done,
    output reg rx_valid
    );
    //wire[1:0] odd_even;
    //reg[7:0] data_out;
    //uart接收三个状态初始状态(等待低电平),接收,接收校验位,接收完成
    parameter idle   = 2'b00,
                r_data = 2'b01,
                r_odd  = 2'b10,
                r_end  = 2'b11;
    reg[1:0] current_state=idle;
    reg[1:0] next_state;
    reg[2:0] count;
    
    assign rx_done = (current_state == r_end)?1:0;//&& rx_valid
    //状态机第一段
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            current_state <= idle;
        else
            current_state <= next_state;
    end
    //状态机第二段
    always @(*)begin
        next_state = current_state;
        case(current_state)
            idle  :if(!rxd) next_state = r_data;//低电平开始信号
            r_data:
                    if(count == 7-width)//width为0时为八位
                    begin
                        if(odd_even == 2'b0)
                            next_state = r_end;//八位数数据接收计数,无校验
                        else
                            next_state = r_odd;//有校验
                    end
            r_odd:next_state = r_end;//接收校验位
            r_end:if(rxd) next_state = idle;//接收完成
            default:next_state = idle;
        endcase
    end
    
    always @(posedge clk or negedge rst_n)
        if(!rst_n)
           count <= 0;
        else if(current_state == r_data)
            count <= count+1;       //接收数据计数
        else
            count <= 0;
    
    always @(posedge clk or negedge rst_n)//从高位到低位发送数据
        if(!rst_n)begin
            data_out <= 'd0;
            rx_valid <= 1'b0;
        end
        else if(current_state == r_data)
        begin
               if(!width)//接收数据位,低位先接受
                   begin data_out[6:0] <= data_out[7:1]; data_out[7] <= rxd; end
               else
                   begin data_out[5:0] <= data_out[6:1]; data_out[6] <= rxd; end
        end
        else if(current_state == r_odd)
        begin
            if(!width && rxd == ^data_out[7:0] && odd_even == 2'b01)
                rx_valid <= 1;//偶校验
            else if(!width && rxd == ~(^data_out[7:0]) && odd_even == 2'b10)
                rx_valid <= 1;//奇校验^data_out[7:0]
            else if(width && rxd == ^data_out[6:0] && odd_even == 2'b01)
                rx_valid <= 1;//偶校验
            else if(width && rxd == ~(^data_out[6:0]) && odd_even == 2'b10)
                rx_valid <= 1;//偶校验
            else rx_valid <= 0;
        end
endmodule

3.UART Loopback回环——顶层

  该模块为顶层模块,为UART的回环测试,由于收发波特率一致,因此这里就不设置FIFO等缓存,有需要可添加一级FIFO。

`timescale 1ns / 1ps

module top(
    input clk,
    input rst_n,
    input rxd,
    output txd
    );

    wire BR_clk;
    wire [7:0] data_out;
    wire rx_valid;
    wire rx_done;


    BR_generate clk_generate(
        .clk      (clk     ) ,
        .rst_n    (rst_n   ) ,
        .BR_clk   (BR_clk  ) ,
        .model    (2'b10   )
     );

    uart_rx uart_rx(
        .clk       (BR_clk  ),
        .rst_n     (rst_n   ),
        .rxd       (rxd     ),
        .odd_even  (2'b0    ),
        .width     (1'b0    ),
        .data_out  (data_out),
        .rx_done   (rx_done ),
        .rx_valid  (rx_valid)
    );
    
    uart_tx uart_tx(
        .clk        (BR_clk   ),
        .rst_n      (rst_n    ),
        .tx_en      (rx_done  ),
        .data_in    (data_out ),
        .odd_even   (2'b0     ),
        .width      (1'b0     ),
        .txd        (txd      )
    );
    
endmodule

四、仿真验证

  测试文件,测试UART回环。只测试460800bps、8位数据、无校验情况。

module tb_top( );
    reg clk;
    reg rst_n;
    reg rxd;
    wire txd;
    parameter BPS = 217*10;
    always #5 clk=~clk;
    top top(
        .clk    (clk),
        .rst_n  (rst_n),
        .rxd    (rxd  ),
        .txd    (txd  )
        );
        
    initial
    begin
    clk=0;rxd=1;
    rst_n=0;
    #10 rst_n=1;
    #BPS rxd=0;//起始位
    
    //输入8'hd5,小端模式
    #BPS rxd=1;
    #BPS rxd=0;
    #BPS rxd=1;
    #BPS rxd=0;
    
    #BPS rxd=1;
    #BPS rxd=0;
    #BPS rxd=1;
    #BPS rxd=1;
    
    #BPS rxd=1;//停止位
    
    #50000 ;
    
    #BPS rxd=0;//起始位
    //输入8'hd5,小端模式
    #BPS rxd=1;
    #BPS rxd=0;
    #BPS rxd=1;
    #BPS rxd=0;
    
    #BPS rxd=1;
    #BPS rxd=0;
    #BPS rxd=1;
    #BPS rxd=1;
    
    #BPS rxd=1;//停止位

    #50000 ;
    
    #BPS rxd=0;//起始位
    //输入8'h74,小端模式
    #BPS rxd=0;
    #BPS rxd=0;
    #BPS rxd=1;
    #BPS rxd=0;
    
    #BPS rxd=1;
    #BPS rxd=1;
    #BPS rxd=1;
    #BPS rxd=0;
    
    #BPS rxd=1;//停止位
    end
    
endmodule

仿真结果如下,低位先输入,data_out为UART_RX模块解串出来的数据,仿真正确。
在这里插入图片描述

五、PC通信验证

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值