草庐IT

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

咖啡0糖 2023-05-15 原文

目录

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 等接口标准规范和总线标准规范。
       本文进行串口 RS232 相关知识的学习,最终设计并实现基于 RS232 的串口收、发功能模块,并完成串口数据回环实验。

1.1 串口简介

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

串口的优点:

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

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

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

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

1.2 RS232信号线

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

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

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

       由上图可知 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校准"的电平信号,才能实现通讯。

 结构示意图:

RS232 收发器电路:

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

2.2  顶层模块

2.2.1 模块说明

       将FPGA内部传输部分看成一个整体,设计框图如下。模块功能是将串口数据接收模块与串口数据发送模块的各信号之间的连接,需注意相连的信号间位宽要相同。

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

 实验整体框图

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

 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

代码重难点积累:

(1)参数

     a. 参数是一种常量,通常出现在 module 内部,常被用于定义状态机的状态、数据位宽和计数器计数个数大小等, 这样的写法(参数在module内部的在例化),需注意在顶层例化时必须再次带上参数。好处是增加代码的复用性。例如:

接收模块代码( 参数出现在 module 内部):
module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)

(
input wire sys_clk , //系统时钟 50MHz
input wire sys_rst_n , //全局复位
    ...   ...


 );

顶层例化代码:

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 )
);     


    b. 如果不在module 内部,则顶层模块不需出现相关参数,也就不方便代码的复用。

参数不在module 内部:

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  ;   //时钟频率

       ...     ...

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

代码重难点积累:task任务函数的调用。 

 

2.3 串口数据接收模块

2.3.1 模块说明

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

2.3.2 波形设计

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

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

(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,         
    parameter   CLK_FREQ    =   'd50_000_000    
)
(
    input   wire            sys_clk     ,   
    input   wire            sys_rst_n   ,   //全局复位
    input   wire            rx          ,   //串口接收数据

    output  reg     [7:0]   po_data     ,   //串转并后的8bit数据
    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         work_en     ;
reg [12:0]  baud_cnt    ;
reg         bit_flag    ;
reg [3:0]   bit_cnt     ;
reg [7:0]   rx_data     ;
reg         rx_flag     ;

//插入两级寄存器进行数据同步,以消除亚稳态
//rx_reg1:第一级寄存器,
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;   //寄存器空闲状态复位为1
    else
        rx_reg1 <= rx;
//rx_reg2:第二级寄存器,
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_nedge <= 1'b0;
    else    if((~rx_reg2) && (rx_reg3))  //rx_reg2 = 0  且  rx_reg_3 = 1
        start_nedge <= 1'b1;
    else
        start_nedge <= 1'b0;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(start_nedge == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 13'b0;
    else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
        baud_cnt <= 13'b0;
    else    if(work_en == 1'b1)    
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if(baud_cnt == BAUD_CNT_MAX/2 - 1)  //中间数
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        bit_cnt <= 4'b0;
    else    if(bit_flag ==1'b1)
        bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位,串转并
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_data <= 8'b0;
    else    if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
        rx_data <= {rx_reg3, rx_data[7:1]};    //拼接符号向右移位

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
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;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <= 8'b0;
    else    if(rx_flag == 1'b1)
        po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;

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

2.4 串口数据发送模块

2.4.1 模块说明

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

 2.4.2 波形设计

         uart_tx 功能整体的波形图如下图。

 bit_flag设计思路:

       在计数前期开始计数与接收模块的读数据不同(考虑接收数据传输的稳定性),但在发送模块就不需要,数据已经获取到了发送时间没有严格要求。 

2.4.3 RTL代码

`timescale  1ns/1ns
//并行数据转化为8bit数据发出fpga
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     ,   //模块输入的8bit数据
     input   wire            pi_flag     ,   //并行数据有效标志信号
 
     output  reg             tx              //并转串后的1bit数据
);

//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg [12:0]  baud_cnt;
reg         bit_flag;
reg [3:0]   bit_cnt ;
reg         work_en ;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            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;

//baud_cnt:发送1bit数据所需时间
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            baud_cnt <= 13'b0;
        else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 13'b0;
        else    if(work_en == 1'b1)
            baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            bit_flag <= 1'b0;
        else    if(baud_cnt == 13'd1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;

//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            tx <= 1'b1; //空闲状态时为高电平
        else    if(bit_flag == 1'b1)
            case(bit_cnt)
                0       : tx <= 1'b0;
                1       : tx <= pi_data[0];
                2       : tx <= pi_data[1];
                3       : tx <= pi_data[2];
                4       : tx <= pi_data[3];
                5       : tx <= pi_data[4];
                6       : tx <= pi_data[5];
                7       : tx <= pi_data[6];
                8       : tx <= pi_data[7];
                9       : tx <= 1'b1;
                default : tx <= 1'b1;
            endcase

endmodule

代码重难点积累: 

(1)tx通过拆分pi_data数组将串转并行数据。

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

2.5 上板测试

       由于本人笔记本的usb接口故障,无法实现fpga与pc的数据传输,故这里不展示串口助手收发的图片。

3. 总结

1.理解数据在fpga和pc两个端口间的传递过程。

2.理解亚稳态的概念以及如何避免亚稳态。

3.如何提取有效数据以及将数据串转并(位拼接  + 寄存器),并转串(拆分数组)。

4.使用for函数产生一帧数据。

说明:

       本人使用的是野火家Xilinx Spartan6系列开发板及配套教程主要用于自我学习,以上内容如有疑惑或错误欢迎评论区指出,或者移步B站观看野火家视频教程。

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

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

有关FPGA—串口RS232(附实现代码)的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  4. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  5. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  6. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

随机推荐