FPGA—串口RS232(附实现代码)

目录

1. 理论

1.1 串口简介

1.2 RS232信号线

1.3 RS232通信协议简介

2. 实操

2.1 硬件资源

2.2  顶层模块

2.2.1 模块说明

2.2.2 RTL 代码

2.2.3  仿真验证

2.3 串口数据接收模块

2.3.1 模块说明

2.3.2 波形设计

2.3.3 RTL代码

2.3.4 仿真验证

2.4 串口数据发送模块

2.4.1 模块说明

 2.4.2 波形设计

​编辑2.4.3 RTL代码

2.4.4 仿真部分

2.5 上板测试

3. 总结


1. 理论

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

1.1 串口简介

       串口作为常用的三大低速总线(UART、 SPI、 IIC)之一,在通信接口和调试时占有重要地位。但 UART 和 SPI、 IIC 不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,双方收发端有各自的时钟。注意在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位的帮助下实现信息同步的。而 SPI、 IIC 是同步通信接口,同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。
         UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如下图所示。对于 PC 来说它的 tx 要和对于 FPGA 来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接。UART 可以实现全双工(可以同时进行发送数据和接收数据)。

FPGA—串口RS232(附实现代码)_第1张图片

串口的优点:

1.很多传感器芯片或 CPU 都带有串口功能,进行串口调试方便。

2.串口数据线两根使用简单。

3.在较为复杂的高速数据接口和数据链路集合的系统中往往联合调试比较困难,可以先使用串口将数据链路部分验证后,再把串口换成高速数据接口。

缺点:只能短距离传输,传输速率相对较慢。

1.2 RS232信号线

信号线的连接方式: 有以下两种   

       (1) 接口为 DB9 接口的串口线

       准备一根串口线连接在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口)和FPGA的公头接口上,如下图。

FPGA—串口RS232(附实现代码)_第2张图片

FPGA—串口RS232(附实现代码)_第3张图片

       由上图可知 COM 口有许多信号线,但一般只使用RXD、 TXD 以及 GND 三条信号线传输数据信号。

      (2) USB接口的串口线,连接FPGA与PC的两端。

1.3 RS232通信协议简介

        RS232 是 UART 的一种,没有时钟线,只有两根数据线, rx 和 tx,这两根线都为1bit 位宽(一位二进制数)。由于FPGA内部接收模块与发送模块是并行传输的,所以要模块要分别经行串转并,并转串的处理。

       串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。一帧数据的组成为起始位(固定为 0)+  有效数据( 8bit )+ 停止位(固定为 1),即最基本的帧结构有10bit(不包括校验等)。在不发送或者不接收数据的情况下, rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平。下面是一个最基本的 RS232 帧结构示意图

       波特率:在信息传输通道中,携带数据信息的信号单元叫码元(串口是 1bit 进行传的,所以其码元就代表一个二进制数),每秒时间内传送二进制数据的位数简称波特率, 常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、 9600、 115200 等,这里选用 9600 的波特率。
         比特率:比特率表示有效数据的传输速率每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。 9600 的波特率,其串口的比特率为: 9600Bps *1bit= 9600bps。       

       假设数据以一帧10bit进行传输,那么码元为10个二进制数(有效位8位)。每秒传输65帧数据,故Baud = 65 x 10 = 650 Bps, 波特率 = 650 x (4/5)= 520bps。注意区别传输是一帧还是1bit(串行或并行)

         在9600Bps下,计算传输1bit数据需要花多少个时钟周期?每秒传输9600个码元数,即传1位数据需要1/9600秒 。在50Mhz的系统时钟下,一个时钟脉冲为20ns,需要花  (1s * 10^9)ns /
9600) / 20ns ≈ 5208 个系统时钟周期

2. 实操

       实验目标:PC 机的串口调试助手发送一串数据,经过 FPGA 后再传回到 PC 机的串口调试助手中显示。

2.1 硬件资源

       由于RS-232电平标准的信号不能直接被控制器直接识别,所以信号会经过一个"电平转换芯片" (MA3232 芯片)转换成控制器能识别的"TTL校准"的电平信号才能实现通讯。

FPGA—串口RS232(附实现代码)_第4张图片

 结构示意图:

FPGA—串口RS232(附实现代码)_第5张图片

RS232 收发器电路:

FPGA—串口RS232(附实现代码)_第6张图片

        为了方便使用,开发板中还搭载了 USB 转串口的芯片 CH340,供 USB 线进行串口调试。

FPGA—串口RS232(附实现代码)_第7张图片

2.2  顶层模块

2.2.1 模块说明

       模块功能是完成串口数据接收模块与发送模块的各信号之间的连接。

      loopback 的数据传输的详细过程为: PC 机的串口调试助手发送一帧串行数据,给 rs232 模块的 rx 端,uart_rx 模块负责解析出一帧数据中的有用数据,并将其转化为 8bit 并行数据 po_data。 8bit 并行数据 po_data 和数据有效标志信号 po_flag 通过 FPGA 的内部连线直接传输给 uart_tx 模块的 8bit 数据输入端 pi_data 和数据有效标志信号输入端 pi_flag, uart_tx 模块再将接收到的并行数据转为串行数据传回到 PC 机的串口调试助手中打印显示。实现了发送什么就接收什么,如果发送和接收的数据不一致,那就说明链路存在错误。

FPGA—串口RS232(附实现代码)_第8张图片

FPGA—串口RS232(附实现代码)_第9张图片

 实验整体框图

 ​​​该设计分为三个模块。

2.2.2 RTL 代码

`timescale  1ns/1ns
//FPGA中RS232通信模块
module  rs232
(
    input   wire    sys_clk     ,   
    input   wire    sys_rst_n   ,   
    input   wire    rx          ,   //串口接收数据

    output  wire    tx              //串口发送数据
);

parameter   UART_BPS    =   20'd9600        ,   //波特率
            CLK_FREQ    =   26'd50_000_000  ;   //时钟频率
			
wire    [7:0]   po_data;
wire            po_flag;
//串口接受数据模块
uart_rx
#(
    .UART_BPS    (UART_BPS  ),  //串口波特率
    .CLK_FREQ    (CLK_FREQ  )   //时钟频率
)
uart_rx_inst
(
    .sys_clk    (sys_clk    ), 
    .sys_rst_n  (sys_rst_n  ), 
    .rx         (rx         ), 
            
    .po_data    (po_data    ), 
    .po_flag    (po_flag    )  
);
//串口发送数据模块
uart_tx
#(
    .UART_BPS    (UART_BPS  ),  //串口波特率
    .CLK_FREQ    (CLK_FREQ  )   //时钟频率
)
uart_tx_inst
(
    .sys_clk    (sys_clk    ),  
    .sys_rst_n  (sys_rst_n  ),  
    .pi_data    (po_data    ),  
    .pi_flag    (po_flag    ),  
                
    .tx         (tx         )   
);
endmodule

2.2.3  仿真验证

`timescale  1ns/1ns

module  tb_rs232();

wire    tx          ;

reg     sys_clk     ;
reg     sys_rst_n   ;
reg     rx          ;

initial begin
    sys_clk    = 1'b1;
    sys_rst_n <= 1'b0;
    rx        <= 1'b1;
    #20;
    sys_rst_n <= 1'b1;
end
//初始化任务函数
initial begin
    #200
    rx_byte();  //调用任务rx_byte
end

always #10 sys_clk = ~sys_clk;

//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task    rx_byte();  //因为不需要外部传递参数,所以括号中没有输入
    integer	j;
    for(j=0; j<8; j=j+1)    //调用8次rx_bit任务,每次发送的值从0变化7。for 括号中最后执行的内容不可写成i= i++
        rx_bit(j);
endtask

//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task    rx_bit(input   [7:0]   data);
    integer i;
    for(i=0; i<10; i=i+1)   begin
        case(i)
            0: rx <= 1'b0;     //起始位为0
            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;    //终止位为1
        endcase
        #(5208*20);   //每发送1位数据延时5208个时钟周期
    end
endtask

rs232   rs232_inst
(
    .sys_clk    (sys_clk    ),  
    .sys_rst_n  (sys_rst_n  ),  
    .rx         (rx         ),  

    .tx         (tx         )   
);
endmodule

FPGA—串口RS232(附实现代码)_第10张图片

2.3 串口数据接收模块

2.3.1 模块说明

        该模块功能是接收来自PC 机上的串口调试助手发送的固定波特率的串行数据,解析提取有用数据,再转化为并行数据(并行数据在 FPGA 内部传输的效率更高)同时产生一个数据有效信号标志信号(后级模块或系统在使用该并行数据的时候可能无法知道该时刻采样的数据是否有效的)伴随着并行的有效数据一同输出。模块框图如下。

FPGA—串口RS232(附实现代码)_第11张图片

2.3.2 波形设计

FPGA—串口RS232(附实现代码)_第12张图片

(1)reg1、reg2、reg3设计思路:      

      rx_reg1和rx_reg2对rx进行了打两拍处理,原因是时钟信号(来自fpga)和数据传输 rx (来自pc端)是异步的关系,输出很容易产生亚稳态。亚稳态是指触发器无法在某个规定的时间段内到达一个可以稳定的状态。产生亚稳态的本质原因是在时钟上升沿的建立时间或保持时间段输入数据不稳定,导致寄存器输出数据在一段时间也不稳定但在亚稳态后下一个时钟沿来临前会稳定下来(数据随机)。所以使用打两拍的方式消除亚稳态。示意图如下。

FPGA—串口RS232(附实现代码)_第13张图片

(2)staet_nedge设计思路:      

      rx_reg2取反再和rx_reg3进行 与 运算,产生staet_nedge信号,作为开始接收数据的标志。

(3)work_en、baud_cnt、bit_flag、bit_cnt、rx_flag设计思路:

     标记有效位。

(4)rx_data设计思路:

      使用位拼接的方式将串行数据转为并行然后存在寄存器中。

2.3.3 RTL代码

`timescale 1ns/1ns
//接收8位串行数据后转并行

module    uart_rx
#(
    parameter     UART_BPS = 'd9600,       //9600BPS
	              CLK_FREQ = 'd50_000_000  //50MHZ
)
(  
    input    wire        sys_clk      ,
	input    wire        sys_rst_n    ,
	input    wire        rx           ,
	                           
    output   reg  [7:0]  po_data      ,
    output   reg         po_flag      
);

localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;  //接受1bit数据所需多少个脉冲周期
reg             rx_reg1; 
reg             rx_reg2;  
reg             rx_reg3;
reg             start_nedge;
reg             bit_flag;
reg    [3:0]    bit_cnt;
reg             work_en;
reg    [15:0]   baud_cnt;
reg    [7:0]    rx_data;
reg             rx_flag;

//rx_reg: 进行打拍操作,消除亚稳态					   
always @(posedge sys_clk or negedge sys_rst_n)
     if( !sys_rst_n ) begin
	    rx_reg1 <= 1'b1;
		rx_reg2 <= 1'b1;
		rx_reg3 <= 1'b1;
		end
     else   begin
        rx_reg1 <= rx;	
        rx_reg2 <= rx_reg1;
        rx_reg3 <= rx_reg2;	
		end
		
//start_nedge:开始传输数据的标志								   
always @(posedge sys_clk or negedge sys_rst_n)
     if( !sys_rst_n )
         start_nedge	<= 1'b0;
     else if((rx_reg2 == 1'b0 && rx_reg3 == 1'b1 ) && (work_en == 1'b0))
	     start_nedge	<= 1'b1;
	 else 
	     start_nedge	<= 1'b0;
		 
//work_en:高电平传输数据使能	
always @(posedge sys_clk or negedge sys_rst_n)
     if( !sys_rst_n )
         work_en	<= 1'b0;
     else if (start_nedge == 1'b1)
	     work_en	<= 1'b1;
	 else if ((bit_flag == 1'b1) && (bit_cnt == 4'd8))
	     work_en	<= 1'b0;		 
//传输一位数据需要花 5208 脉冲周期
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	    baud_cnt	<= 1'b0;
	else  if (baud_cnt == BAUD_CNT_MAX - 1'b1) 
	    baud_cnt	<= 1'b0;
    else  if (work_en	== 1'b1)
	    baud_cnt  <= baud_cnt + 1'b1;
	else  
  	    baud_cnt	<= 1'b0;
		
//bit_flag:接收数据标志	
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	   bit_flag	<= 1'b0;    		
	else  if (( baud_cnt == (BAUD_CNT_MAX/2)  - 1 ))
        bit_flag	<= 1'b1; 
	else	
	    bit_flag	<= 1'b0;
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	   bit_cnt <= 1'b0; 
	else if ((bit_flag	== 1'b1) && (bit_cnt < 4'd8))
       bit_cnt <= bit_cnt + 1'b1;
	else if ((bit_flag	== 1'b1) && (bit_cnt == 4'd8))
	   bit_cnt <= 1'b0;
	
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )  
       rx_data	<= 1'b0; 
	else if ((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
	   rx_data <= {rx_reg3, rx_data[7:1]};
	 
		
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_flag <= 1'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;
		
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)  begin
        po_data <= 1'b0;
		po_flag  <= 1'b0;
		end
    else if (rx_flag == 1'b1)  begin	
   	    po_data <= rx_data;
		po_flag  <= 1'b1; 		
		end
	else 	
	    po_flag  <= 1'b0;
endmodule

2.3.4 仿真验证

     模拟PC机的串口调试助手产生一组串行数据 RX 输入接收模块。

`timescale  1ns/1ns

module  tb_uart_rx();

reg             sys_clk;
reg             sys_rst_n;
reg             rx;

wire    [7:0]   po_data;
wire            po_flag;

always #10 sys_clk = ~sys_clk;

initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        rx        <= 1'b1;
        #20;
        sys_rst_n <= 1'b1;
end

//模拟发送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

task rx_bit(input   [7:0]   data);
        integer i;      //定义一个变量
		                //integer类型也是一种寄存器数据类型,integer类型的变量为有符号数
        for(i=0; i<10; i=i+1) begin  //不可用i=i++的方式
            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;
            endcase
            #(5208*20); //每发送1位数据延时5208个时钟周期
        end
endtask         //任务以endtask结束

uart_rx uart_rx_inst(
        .sys_clk    (sys_clk    ),  
        .sys_rst_n  (sys_rst_n  ),  
        .rx         (rx         ),  
                
        .po_data    (po_data    ),  
        .po_flag    (po_flag    )   
);
endmodule

FPGA—串口RS232(附实现代码)_第14张图片

2.4 串口数据发送模块

2.4.1 模块说明

    该模块的功能是将 FPGA 中的数据转化为串行数据。

FPGA—串口RS232(附实现代码)_第15张图片

 2.4.2 波形设计

FPGA—串口RS232(附实现代码)_第16张图片2.4.3 RTL代码

`timescale 1ns/1ns
//并转串行

module     uart_tx
#(     
    parameter    UART_BPS    =   'd9600 ,  
    parameter    CLK_FREQ    =   'd50_000_000 

)
(
    input   wire            sys_clk     , 
    input   wire            sys_rst_n   , 
    input   wire    [7:0]   pi_data     , 
    input   wire            pi_flag     , 
    
    output  reg             tx            
);

reg          work_en;
reg  [15:0]  baud_cnt;
reg          bit_flag;
reg  [3:0]   bit_cnt;

localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	    work_en <= 1'b0;
    else   if(pi_flag == 1'b1)  
	    work_en <= 1'b1;
	else   if((bit_flag == 1'b1) && (bit_cnt == 4'd9) )
	    work_en <= 1'b0;
		
//传输一位数据需要花 5208 脉冲周期
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	    baud_cnt	<= 1'b0;
	else  if (baud_cnt == BAUD_CNT_MAX - 1'b1) 
	    baud_cnt	<= 1'b0;
    else  if (work_en	== 1'b1)
	    baud_cnt  <= baud_cnt + 1'b1;
	else  
  	    baud_cnt	<= 1'b0;
	
//bit_flag:接收数据标志	
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	   bit_flag	<= 1'b0;    		
	else  if (( baud_cnt == (BAUD_CNT_MAX/2)  - 1 ))
        bit_flag	<= 1'b1; 
	else	
	    bit_flag	<= 1'b0;
		
always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
	   bit_cnt <= 1'b0; 
	else if ((bit_flag	== 1'b1) && (bit_cnt < 4'd9))
       bit_cnt <= bit_cnt + 1'b1;
	else if ((bit_flag	== 1'b1) && (bit_cnt == 4'd9))
	   bit_cnt <= 1'b0;		

always @(posedge sys_clk or negedge sys_rst_n)
    if( !sys_rst_n )
        tx <=  1'b1;
	else if(bit_flag == 1'b1)
	    case(bit_cnt)
		    0    :   tx  <= 1'b0;        //起始位
	        1    :   tx  <= pi_data[bit_cnt - 1];
            2    :   tx  <= pi_data[bit_cnt - 1];
            3    :   tx  <= pi_data[bit_cnt - 1];
            4    :   tx  <= pi_data[bit_cnt - 1];
            5    :   tx  <= pi_data[bit_cnt - 1];
            6    :   tx  <= pi_data[bit_cnt - 1];
            7    :   tx  <= pi_data[bit_cnt - 1];
            8    :   tx  <= pi_data[bit_cnt - 1];
            9    :   tx  <= 1'b1 ;       //结束位
			default : tx <= 1'b1;
			endcase

endmodule
			
			

2.4.4 仿真部分

      模拟并行数据传入发送模块。

`timescale  1ns/1ns

module  tb_uart_tx();

reg         sys_clk;
reg         sys_rst_n;
reg [7:0]   pi_data;
reg         pi_flag;

wire        tx;

initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20;
        sys_rst_n <= 1'b1;
end

always #10 sys_clk = ~sys_clk;

initial begin
        pi_data <= 8'b0;
        pi_flag <= 1'b0;
        #200
        //发送数据0
        pi_data <= 8'd0;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
//每发送1bit数据需要5208个时钟周期,一帧数据为10bit
//所以需要数据延时(5208*20*10)后再产生下一个数据
        #(5208*20*10);
        //发送数据1
        pi_data <= 8'd1;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据2
        pi_data <= 8'd2;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据3
        pi_data <= 8'd3;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据4
        pi_data <= 8'd4;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据5
        pi_data <= 8'd5;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据6
        pi_data <= 8'd6;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*20*10);
        //发送数据7
        pi_data <= 8'd7;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
end

uart_tx uart_tx_inst(
        .sys_clk    (sys_clk    ),
        .sys_rst_n  (sys_rst_n  ),
        .pi_data    (pi_data    ),
        .pi_flag    (pi_flag    ),

        .tx         (tx         ) 
);
endmodule

FPGA—串口RS232(附实现代码)_第17张图片

2.5 上板测试

FPGA—串口RS232(附实现代码)_第18张图片

3. 总结

重点理解整个回环的工作流程以及如何进行并行、串行数据数据的相互转换。

说明:

       本人参考的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出。

开发软件:ise14.7     仿真:modelsim 10.5 

如需上述资料私信或留下邮箱。

你可能感兴趣的:(FPGA_Xilinx,Spartan6野火实验,fpga开发)