FPGA自学笔记--串口通信发送多字节数据(verilog版)

1.需求分析

        关于uart协议实现这部分大家可以参考我上一篇的博客。《FPGA自学笔记--串口通信实现(vivado&verilog版)》。在上一篇博客中,主要实现了将单字节的数据,我们其实就是用上一篇博客的模块来实现多字节数据的发送。

        在真实的数据传输过程中,我们不只是发送 6 7 8 位数据,也有可能发送12 16 20位的数据,所以我们需要调用多次串口发送模块来发送多字节数据。通过状态机实现,假设我们这次发送一个40位,5字节的数据,所以朴素的来讲,我们可以有6个状态,0状态是整个模块等待状态,1,2,3,4,5状态分别为5个字节数据的发送状态。

2.总体模块和状态转移图。

        上一篇博客中,单字节的串口发送模块为uart_tx。实现的具体功能为,当send_go为高电平时,将data里的并行数据以串行数据发出,发送完成时 tx_done 产生一个单脉冲。我们需要在上层模块中调用这个模块。上层模块的框图如昨图。需要调用的uart_tx模块如右图所示。

        

所以,需要trans_go启动状态机,状态机内部产身send_go信号,传输一字节数据。发送完成后,uart_tx模块会产生tx_done信号,tx_done信号用来激活状态机的下一个状态,开始发送下一个数据,再次产生一个send_go脉冲,送入data数据,依次送完5字节数据,当最后一个字节发送完成成,最后一个状态5在tx_done下返回第一个状态,并产生五字节发送完成信号,trans_down.回到第一个状态后,等待发送下一个四十位,五字节数据的trans_go。

状态转移图如下。

当然,这个状态转移图也可以简化,由于我目前也是小白状态,只能写出这种比较好理解,简单的写法,大家以后也可以尝试比较高级的写法。即只用两个状态机实现,或者讲后面五个状态总结为一个大状态,

3.设计文件和testbench文件

        在top文件中例化uart_byte_tx模块,这部分具体代码请参考上一个博客。代码参考了B站小梅哥的视频。新手强烈推荐。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/30 20:20:59
// Design Name: 
// Module Name: uart_tx_5byte
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//
// 使用串口发送5个字节(40 bit)的数据到电脑

module uart_tx_5byte(
    clk,
    reset,
    data40,
    trans_go,
    uart_tx,
    trans_done
    );
    
    input clk;
    input reset;
    input trans_go;
    input [39:0]data40;
    output uart_tx;
    output reg trans_done;
    reg [7:0]data;
    reg send_go;
    //reg tx_done;
    
uart_byte_tx uart_byte_tx(
        .clk(clk),
        .reset(reset),
        .send_go(send_go),
        .data(data),
        .baud_set(4),
        .uart_tx(uart_tx),
        .tx_done(tx_done)
    );

    reg [2:0]state;
   
    always@(posedge clk or negedge reset)
        if(!reset)begin
            state <= 0;
            send_go <= 0;
            data <= 0;
            trans_done <= 0;
        end
        else if(state == 0)begin
            trans_done <= 0;
            //if(tx_done)begin             // 当发完的时候  发新的
            if(trans_go)begin             //  由trans_go 点燃第一个状态  启动状态  用tx_done 是无法启动状态机的  tx_done 是 0状态结束标志
                data <= data40[7:0];
                send_go <= 1;
                state <= 1;
            end
            else begin
                data <= data;
                send_go <= 0;
                state   <= 0;
            end        
        end
        else if(state == 1)begin
            if(tx_done)begin
                data <= data40[15:8];
                send_go <= 1;
                state <= 2;
            end
            else begin
                data <= data;
                send_go <= 0;
                state   <= 1;
            end        
        end
        else if(state == 2)begin
            if(tx_done)begin
                data <= data40[23:16];
                send_go <= 1;
                state <= 3;
            end
            else begin
                data <= data;
                send_go <= 0;
                state   <= 2;
            end        
        end
        else if(state == 3)begin
            if(tx_done)begin
                data <= data40[31:24];
                send_go <= 1;
                state <= 4;
            end
            else begin
                data <= data;
                send_go <= 0;
                state   <= 3;
            end        
        end
        else if(state == 4)begin
            if(tx_done)begin
                data <= data40[39:32];
                send_go <= 1;
                state <= 5;
            end
            else begin
                data <= data;
                send_go <= 0;
                state   <= 4;
            end        
        end
        else if(state == 5)begin
            if(tx_done)begin               // 当发完的时候 回到初始状态
                send_go <= 0;
                state <= 0;
                trans_done <= 1;
            end
            else begin                    // 当没发完的时候 等他发完
                data <= data;
                send_go <= 0;
                state   <= 5;
            end        
        end
endmodule

对应的testbench文件

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/30 21:51:02
// Design Name: 
// Module Name: uart_tx_5byte_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module uart_tx_5byte_tb(
    );
    reg clk;
    reg reset;
    reg [39:0]data40;
    reg trans_go;
    wire trans_done;
    wire uart_tx;
   
    
uart_tx_5byte uart_tx_5byte(
    .clk(clk),
    .reset(reset),
    .data40(data40),
    .trans_go(trans_go),
    .uart_tx(uart_tx),
    .trans_done(trans_done)
    );
    
    initial clk = 1;
    always #10 clk = ~clk;
    
    initial begin
    reset = 0;
    data40 = 0;
    trans_go = 0;
    # 201;
    # 200;
    reset = 1;
    data40 = 40'h123456789a;
    trans_go = 1;
    # 20
    trans_go = 0;
    
    @(posedge trans_done);
    # 200000;
    data40 = 40'habc1234655;
    trans_go = 1;
    # 20
    trans_go = 0;
    @(posedge trans_done);
    # 200000;
    $stop;
    end 
endmodule

 4.仿真结果分析

显然,如图所示,一定要注意,多字节发送,每个字节还是存在起始位和终止位的,所以应该还是10位10位的发,看时序图的时候一定不要看错了,去除箭头所示的标志位后,对比数据,并行40位输入和串行输出,结果一致,从低位到高位,完全正确。有兴趣的同学可以直接下载我的vivado工程。

猜你喜欢

转载自blog.csdn.net/lgk1996/article/details/124527371
今日推荐