11 串口发送应用之使用状态机实现多字节数据发送

1. 使用串口发送5个字节数据到电脑

uart协议规定,发送的数据位只能是6,7,8位,如果数据位不符合,接收者接收不到数据。所以我们需要将40位数据data分为5个字节数据分别发送,那么接收者就能通过uart协议接收到数据了。

2. 第一次使用状态机写设计代码(不够简洁的版本)

为什么要使用状态机:由于在always语句块中,语句是并行执行的,当我想要处理有先后顺序的问题时,就需要用状态机来解决。

针对发送五个字节数据到电脑的目的按,可将状态机的使用分为以下三种情况:

1. 没有开始发送(数据请求trans_go信号没有出现)

2. 数据请求trans_go信号出现

3. 数据请求trans_go信号出现

2.1 设计代码

module uart_tx_data(
    clk,
    rstn,
    trans_go,
    data40,
    uart_tx,
    trans_done
);

    input clk;
    input rstn;
    input trans_go;
    input [39:0] data40;
    output uart_tx;
    output reg trans_done;
    
    reg [7:0] data;
    reg send_go;
    wire tx_done;
    
    uart_byte_tx uart_byte_tx(
        .clk(clk),
        .rstn(rstn),
        .blaud_set(3'd4),
        .data(data),
        .send_go(send_go),
        .uart_tx(uart_tx),
        .tx_done(tx_done)
    );
    
    reg [2:0]state;
    always@(posedge clk or negedge rstn)
    if(!rstn) begin
        state <= 0;
        send_go <= 0;
        data <= 0;
        trans_done <= 0;
    end    
    else 
        case(state)
        0:  begin
                if(trans_go)begin
                    trans_done <= 0;
                    data <= data40[7:0];
                    send_go <= 1;
                    state <= 1;
                end
                else begin
                    data <= data;
                    send_go <= 0;
                    state <= 0;
                end
            end
            
        1:begin
              if(tx_done)begin
                  data <= data40[15:8];
                  send_go <= 1;
                  state <= 2;
              end
              else 
                  send_go <= 0;
          end

       2:begin
              if(tx_done)begin
                  data <= data40[23:16];
                  send_go <= 1;
                  state <= 3;
              end
              else
                  send_go <= 0;
         end
            
       3:begin
              if(tx_done)begin
                  data <= data40[31:24];
                  send_go <= 1;
                  state <= 4;
              end
              else
                  send_go <= 0;
         end

       4:begin
              if(tx_done)begin
                  data <= data40[39:32];
                  send_go <= 1;
                  state <= 5;
              end
              else
                  send_go <= 0;
          end
        
        5:begin
              if(tx_done)begin
                  trans_done <= 1;
                  state <= 0;
              end
              else
                  send_go <= 0;
           end
        default: begin
                data <= data;
                send_go <= 0;
                state <= 0;
        end
     endcase

endmodule
module uart_byte_tx(
    clk,
    rstn,
    blaud_set,
    data,
    send_go,
    uart_tx,
    tx_done
);
    
    input clk;
    input rstn;
    input [2:0]blaud_set;
    input [7:0]data;
    input send_go;
    output reg uart_tx;
    output tx_done;
    
    //Blaud_set = 0时,波特率 = 9600;
    //Blaud_set = 1时,波特率 = 19200;
    //Blaud_set = 2时,波特率 = 38400;
    //Blaud_set = 3时,波特率 = 57600;
    //Blaud_set = 4时,波特率 = 115200;
    
    reg[17:0] bps_dr;
    always@(*)
        case(blaud_set)
            0: bps_dr = 1000000000/9600/20;
            1: bps_dr = 1000000000/19200/20;
            2: bps_dr = 1000000000/38400/20;
            3: bps_dr = 1000000000/57600/20;
            4: bps_dr = 1000000000/115200/20;
        endcase
        
    reg [7:0] r_data;
    always@(posedge clk)
    if(send_go)
        r_data <= data;
    else
        r_data <= r_data;
        
    reg send_en;  
    always@(posedge clk or negedge rstn)
    if(!rstn)
        send_en <= 0;
    else if(send_go)
        send_en <= 1;
    else if(tx_done)
        send_en <= 0;
        
    
    wire bps_clk;
    assign bps_clk = (div_cnt == 1);
    reg[17:0] div_cnt;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        div_cnt <= 0;
    else if(send_en)begin
        if(div_cnt == (bps_dr - 1))
            div_cnt <= 0;
        else
            div_cnt <= div_cnt + 1'd1;
    end
    else
        div_cnt <= 0;    
    
    reg[3:0] bps_cnt;    
    always@(posedge clk or negedge rstn)
    if(!rstn)
        bps_cnt <= 0;
    else if(send_en)begin
        if(bps_cnt == 11)
            bps_cnt <= 0;
        else if(div_cnt == 1)
            bps_cnt <= bps_cnt + 4'd1;
    end
    else
        bps_cnt <= 0;
    
    reg tx_done;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        uart_tx <= 1'd1;
    else 
        case(bps_cnt)
            0: tx_done <= 0;
            1: uart_tx <= 1'd0;
            2: uart_tx <= r_data[0];
            3: uart_tx <= r_data[1];
            4: uart_tx <= r_data[2];
            5: uart_tx <= r_data[3];
            6: uart_tx <= r_data[4];
            7: uart_tx <= r_data[5];
            8: uart_tx <= r_data[6];
            9: uart_tx <= r_data[7];
            10: uart_tx <= 1'd1;
            11: begin uart_tx <= 1'd1; tx_done <= 1; end
            default: uart_tx <= 1'd1;
        endcase
 
endmodule

2.2 仿真代码(学习trans_go脉冲信号以及数据发送完成信号)

以下两点需要学习:

  1. 通过控制trans_go信号的产生与结束,来模拟一个周期的脉冲信号
  2. 通过增加一个输出端口tx_done,来通知我输出何时完成
`timescale 1ns / 1ps

module uart_tx_data_tb();
    
    reg clk;
    reg rstn;
    reg trans_go;
    reg [39:0]data40;
    wire trans_done;
    wire uart_tx;

    uart_tx_data uart_tx_data_inst(
        .clk(clk),
        .rstn(rstn),
        .trans_go(trans_go),
        .data40(data40),
        .trans_done(trans_done),
        .uart_tx(uart_tx)
    );

    initial clk = 1;
    always #10 clk = ~clk;
    
    initial begin
        rstn = 0;
        trans_go = 0;
        data40 = 0;
        #201;
        rstn = 1;
        #200;
        data40 = 40'h123456789a;
        trans_go = 1; //trans_go脉冲信号的模拟
        #20;
        trans_go = 0; //trans_go脉冲信号的模拟
        @(posedge trans_done) //数据发送完成信号的标识
        #200000;
        
        data40 = 40'ha987654321;
        trans_go = 1;
        #20;
        trans_go = 0;
        @(posedge trans_done)
        #200000;
        $stop;
    end

endmodule

仿真波形

3. 优化状态机代码

1. 任务:优化状态机,实现只要个或3个状态实现发送的功能,并且易于修改为发送任意个字节的数据

2. 征集不使用状态机的思想来实现本任务的方案

任务1完成如下,对于任务2,我的思路是:由于fpga是并行发送数据的,如果我们想要多字节发送数据的话,肯定需要将多字节串起来发送,所以我们可以将五个字节的数据串起来,每个字节之间相隔起始位和结束位,以此来达到在遵循协议的情况下实现多字节的输出。

3.1 设计代码(三个状态):

三个状态:

状态1.等待发送请求

状态2.等待单字节数据发送完成

状态3.检查所有数据是否发送完成

module uart_tx_data1(
    clk,
    rstn,
    trans_go,
    data40,
    uart_tx,
    trans_done
);

    input clk;
    input rstn;
    input trans_go;
    input [39:0] data40;
    output uart_tx;
    output reg trans_done;
    
    reg [7:0] data;
    reg send_go;
    wire tx_done;
    
    uart_byte_tx uart_byte_tx(
        .clk(clk),
        .rstn(rstn),
        .blaud_set(3'd4),
        .data(data),
        .send_go(send_go),
        .uart_tx(uart_tx),
        .tx_done(tx_done)
    );
    
    reg [2:0]state;
    reg [2:0]counter;
    always@(posedge clk or negedge rstn)
    if(!rstn) begin
        state <= 0;
        send_go <= 0;
        data <= 0;
        trans_done <= 0;
        counter <= 0;
    end    
    else 
        case(state)
        0:begin //等待发送请求
              if(trans_go)begin 
                  trans_done <= 0;
                  send_go <= 1;
                  data <= (data40>>8*counter);
                  state <= 1;
              end
              else begin 
                  data <= data;
                  send_go <= 0;
                  state <= 0;
              end
            end
            
        1:begin //等待单字节数据发送完成
                if(tx_done)begin
                    counter <= counter + 1'd1;
                    state <= 2;
                end
                else 
                    send_go <= 0;
          end
        
        2:begin //检查所有数据是否发送完成
            if(counter == 5) begin
                  trans_done <= 1;
                  state <= 0;
                  counter <= 0;
            end
            else begin
                  send_go <= 1;
                  data <= (data40>>(8*counter));
                  state <= 1;
             end
           end
           
        default: begin
                data <= data;
                send_go <= 0;
                state <= 0;
        end
     endcase

endmodule

 

module uart_byte_tx(
    clk,
    rstn,
    blaud_set,
    data,
    send_go,
    uart_tx,
    tx_done
);
    
    input clk;
    input rstn;
    input [2:0]blaud_set;
    input [7:0]data;
    input send_go;
    output reg uart_tx;
    output tx_done;
    
    //Blaud_set = 0时,波特率 = 9600;
    //Blaud_set = 1时,波特率 = 19200;
    //Blaud_set = 2时,波特率 = 38400;
    //Blaud_set = 3时,波特率 = 57600;
    //Blaud_set = 4时,波特率 = 115200;
    
    reg[17:0] bps_dr;
    always@(*)
        case(blaud_set)
            0: bps_dr = 1000000000/9600/20;
            1: bps_dr = 1000000000/19200/20;
            2: bps_dr = 1000000000/38400/20;
            3: bps_dr = 1000000000/57600/20;
            4: bps_dr = 1000000000/115200/20;
        endcase
        
    reg [7:0] r_data;
    always@(posedge clk)
    if(send_go)
        r_data <= data;
    else
        r_data <= r_data;
        
    reg send_en;  
    always@(posedge clk or negedge rstn)
    if(!rstn)
        send_en <= 0;
    else if(send_go)
        send_en <= 1;
    else if(tx_done)
        send_en <= 0;
        
    
    wire bps_clk;
    assign bps_clk = (div_cnt == 1);
    reg[17:0] div_cnt;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        div_cnt <= 0;
    else if(send_en)begin
        if(div_cnt == (bps_dr - 1))
            div_cnt <= 0;
        else
            div_cnt <= div_cnt + 1'd1;
    end
    else
        div_cnt <= 0;    
    
    reg[3:0] bps_cnt;    
    always@(posedge clk or negedge rstn)
    if(!rstn)
        bps_cnt <= 0;
    else if(send_en)begin
        if(bps_cnt == 11)
            bps_cnt <= 0;
        else if(div_cnt == 1)
            bps_cnt <= bps_cnt + 4'd1;
    end
    else
        bps_cnt <= 0;
    
    reg tx_done;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        uart_tx <= 1'd1;
    else 
        case(bps_cnt)
            0: tx_done <= 0;
            1: uart_tx <= 1'd0;
            2: uart_tx <= r_data[0];
            3: uart_tx <= r_data[1];
            4: uart_tx <= r_data[2];
            5: uart_tx <= r_data[3];
            6: uart_tx <= r_data[4];
            7: uart_tx <= r_data[5];
            8: uart_tx <= r_data[6];
            9: uart_tx <= r_data[7];
            10: uart_tx <= 1'd1;
            11: begin uart_tx <= 1'd1; tx_done <= 1; end
            default: uart_tx <= 1'd1;
        endcase
 
endmodule

 

仿真代码

`timescale 1ns / 1ps

module uart_tx_data1_tb();
    
    reg clk;
    reg rstn;
    reg trans_go;
    reg [39:0]data40;
    wire trans_done;
    wire uart_tx;

    uart_tx_data1 uart_tx_data_inst1(
        .clk(clk),
        .rstn(rstn),
        .trans_go(trans_go),
        .data40(data40),
        .trans_done(trans_done),
        .uart_tx(uart_tx)
    );

    initial clk = 1;
    always #10 clk = ~clk;
    
    initial begin
        rstn = 0;
        trans_go = 0;
        data40 = 0;
        #201;
        rstn = 1;
        #200;
        data40 = 40'h123456789a;
        trans_go = 1;
        #20;
        trans_go = 0;
        @(posedge trans_done);
        #200000;
        
        data40 = 40'ha987654321;
        trans_go = 1;
        #20;
        trans_go = 0;
        @(posedge trans_done);
        #200000;
        $stop;
    end

endmodule

仿真波形

3.2 调试

调试1:counter位宽给错了,counter要记到5,但是只给了[1:0]两位:

调试2:counter记到5后未清零,导致数据多发了三次,且由于data = data40>>8*counter,导致数据为00:

  • 38
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 串口字节发送是指通过串口同时发送多个字节数据。在Verilog语言中,可以通过以下步骤实现串口字节发送功能: 1. 首先,需要定义一个或多个输入端口用于接收待发送的多字节数据,例如使用一个16位宽的输入端口data_in来接收要发送数据。 2. 然后,需要定义一个输出端口用于控制数据发送,例如使用一个单比特宽的输出端口send来控制数据发送。 3. 在顶层模块中,可以使用一个状态机控制数据发送过程。一种常见的状态机实现方式是使用一个寄存器来存储当前状态,并且根据状态的变化执行相应的操作。 4. 初始状态下,将发送控制信号send设为低电平,表示不发送数据。 5. 当需要发送字节数据时,将数据写入数据输入端口data_in。 6. 在状态机中,根据当前状态和外部触发条件来控制数据发送过程。例如,在一个发送状态中,可以在一个时钟周期内逐个发送每个字节的位。 7. 当前状态迁移的触发条件可以是时钟的下降沿或上升沿,也可以是其他的外部信号触发。触发条件的设置需要根据具体的应用场景来决定。 8. 在状态机中,当数据发送完成时,可以执行相应的操作,例如发送完成后将发送控制信号send置为低电平,表示结束发送。 9. 最后,将顶层模块与其他模块进行连接,完成多字节发送功能的实现。 以上是一种简单的实现方法,具体的实现方式和细节可以根据具体的需求来进行进一步的优化和调整。 ### 回答2: 串口字节发送指的是在串口通信中一次发送多个字节数据。在Verilog HDL中,我们可以通过设计一个串口发送模块来实现字节发送。 首先,我们需要定义串口发送模块的输入和输出信号。输入信号包括发送使能信号(send_en)、发送数据信号(send_data)和发送信号位数(send_width)。输出信号为串口发送信号(tx_out)。 接下来,在Verilog代码中,我们可以使用一个状态机实现串口发送过程。首先,我们需要定义几个状态,例如等待发送发送等待确认等。 在等待发送状态下,我们通过检测发送使能信号(send_en)是否为高电平,来判断是否需要发送数据。如果发送使能信号为高电平,则进入发送等待确认状态。 在发送等待确认状态下,我们通过检测串口发送信号(tx_out)是否为高电平,来判断是否可以继续发送下一个字节数据。如果串口发送信号为高电平,则表示可以发送下一个字节数据。我们可以通过一个计数器来记录已发送字节数,同时将要发送数据通过串口发送信号(tx_out)发送出去。 当计数器等于发送信号位数(send_width)时,表示所有数据发送完毕,则将发送使能信号(send_en)置为低电平,并返回到等待发送状态。 综上所述,我们可以根据以上逻辑设计一个Verilog模块,实现串口字节发送功能。通过定义输入输出信号和使用状态机来控制发送过程,我们可以根据具体的需求进行调整和扩展,以实现更复杂的串口通信功能。 ### 回答3: 串口字节发送是指在串行通信中,通过串口将多个字节数据同时发送出去。在Verilog中实现串口字节发送需要使用一些寄存器和状态机来控制发送过程。 首先,我们需要定义一个输入寄存器用于存储待发送的多字节数据,该寄存器的宽度需要与数据宽度相匹配。然后,我们可以使用一个状态机来控制发送的过程。 状态机的状态可以设计为发送空闲状态(Idle)、发送开始状态(Start)、数据发送状态(Send)、发送结束状态(End)等。当串口处于空闲状态时,如果检测到需要发送数据不为空,则进入发送开始状态。在发送开始状态下,我们可以通过串口的信号控制位将发送信号置高,表示开始发送数据。 进入数据发送状态后,我们可以使用一个计数器来控制数据发送。每个时钟周期,计数器减一,当计数器减到零时,表示一个字节数据发送完毕,此时我们可以将下一个字节写入串口数据寄存器。当所有字节发送完毕后,状态机会进入发送结束状态。 在发送结束状态下,我们可以将发送信号置低,表示数据发送结束。然后,我们可以回到空闲状态,等待下一次发送指令。 总之,实现串口字节发送的Verilog代码需要定义输入寄存器、状态机和计数器,并且通过串口的控制信号来控制发送的各个阶段。通过合理的状态转移和计数值的控制,可以实现可靠的多字节数据发送
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值