FPGA串口发送(超详细注释)

串口发送

  • I 串口发送协议
  • II 实现方法
  • III 模块代码
  • IV testbench
  • V 前仿真
  • VI板上测试

I 串口发送协议

FPGA串口发送(超详细注释)_第1张图片

II 实现方法

FPGA串口发送(超详细注释)_第2张图片

III 模块代码

//功能: 并行转串口的发送模块
//      发送时需要先再并行口准备好数据
//      然后需要一个周期的data_update才能将准备好的数据推出去
//创建时间:2020/07/10
//创建人:windoo
//缺点:会无休止的发送寄存器rdata中的数据
module ser_send(data,clk,rst,baud,data_update,sys_state,send_done,tx_data);
    input [7:0] data;      //并行输入八位宽数据
    input data_update;     //发送使能信号
    input [2:0] baud;      //波特率选择位
    input clk;
    input rst;
    
    output reg sys_state;      //系统运行状态:1:发送状态 0:空闲 (可用led显示)
    output reg send_done;      //为1时候表示一次8bit数据发送完成,空闲为0
    output reg tx_data;        //串口发送数据线

    //对data进行一级寄存,并引入data_update信号,为1时更新寄存器的值
    reg [7:0]rdata;   
    always@(posedge clk or negedge rst) begin
        if(!rst)
            rdata <= 8'b0;
        else if(data_update)begin  //这里有一个周期的data_update就行 秒存
            rdata <= data;     //没有这一个周期的data_update都得给我等着,谁也别进来
        end                    //只有进来过后的才能通过下面的操作,发出去。
        else
            rdata <= rdata;
    end

    //波特率选择
    // 对FPGA:f=50M,T=2*10^-8s    
    // 9600bit/s相当于1bit用时1/9600s
    // 使用FPGA时钟周期个数:1/9600/(2*10^-8)=5208 由于是从0开始计数所以用5207 
    // 5207(D) = 1457(H) 占用位宽:16bit
    // 
    reg [15:0] baud_cnt_full_value;  //产生对应波特率的满计数值
    always@(posedge clk or negedge rst) begin
        if (!rst) begin
            baud_cnt_full_value <= 16'd5207;
        end
        else begin
            case (baud)
            3'd0:baud_cnt_full_value <= 16'd5207; //9600
            3'd1:baud_cnt_full_value <= 16'd2603; //19200
            3'd2:baud_cnt_full_value <= 16'd1301; //38400
            3'd3:baud_cnt_full_value <= 16'd867;  //19200
            3'd4:baud_cnt_full_value <= 16'd433;  //115200
            default:baud_cnt_full_value <= 16'd5207;
            endcase
        end
    end

    //波特率循环计数器
    //循环计数以产生特定波特率
    reg [15:0] baud_cnt_value; //波特率计数器
    always@(posedge clk or negedge rst) begin
        if(!rst)
            baud_cnt_value <= 16'b0;
        else if(sys_state == 1'b1)begin    //如果当前正在处于发送状态,就开启计数
            if(baud_cnt_value == baud_cnt_full_value)
                baud_cnt_value <= 16'b0;
            else
                baud_cnt_value <= baud_cnt_value+1'b1;
        end
        else
            baud_cnt_value <= 16'b0;
    end

    //计数完成一次就产生一个有效信号
    reg baud_cnt_ok;
    always@(posedge clk or negedge rst) begin
        if(!rst)
            baud_cnt_ok <= 1'b0;
        else if(baud_cnt_value == 16'd1)//每次计数到1的时候产生一个有效信号1,
            baud_cnt_ok <= 1'b1;        //其余计数的时候都为0
        else
            baud_cnt_ok <= 1'b0;
    end

    
    //因为这个有效信号baud_cnt_ok的产生频率就是波特率,
    //所以直接用这个波特率做十次计数(0→10)
    reg [3:0] cnt_to_10;
    always@(posedge clk or negedge rst) begin
        if(!rst)
            cnt_to_10 <= 4'd0;
        else if(cnt_to_10 == 4'd11) //cnt_to_10计数到11自动清零
            cnt_to_10 <= 4'd0;            
        else if(baud_cnt_ok == 1'b1)//用波特率计数
            cnt_to_10 <= cnt_to_10 + 4'b1;       
        else
            cnt_to_10 <= cnt_to_10;
    end

    //每次计数到11说明一次发送完成,输出标志信号:send_done
    always@(posedge clk or negedge rst) begin
        if(!rst)
            send_done <= 1'd0;
        else if(cnt_to_10 == 4'd11) //cnt_to_10计数到11自动清零
            send_done <= 1'd1;                 
        else
            send_done <= 1'd0;
    end

    //利用case语句配合十次计数遍历发送输入的8bit数据+起止位
    always@(posedge clk or negedge rst) begin
        if(!rst)
            tx_data <= 1'd1;        //复位拉高数据线(看发送协议图)           
        else
            case (cnt_to_10)
            4'd0: tx_data <= 1'd1;  //空闲状态
           
            4'd1: tx_data <= 1'd0;  //起始位
           
            4'd2: tx_data <= rdata[0]; //低位在前
            4'd3: tx_data <= rdata[1]; 
            4'd4: tx_data <= rdata[2];    
            4'd5: tx_data <= rdata[3]; 
            4'd6: tx_data <= rdata[4]; 
            4'd7: tx_data <= rdata[5]; 
            4'd8: tx_data <= rdata[6]; 
            4'd9: tx_data <= rdata[7]; 

            4'd10: tx_data <= 1'd1; //终止位
            default:tx_data <= 1'd1;
            endcase            
    end

    //当前状态输出:正在发送还是处于空闲?
    always@(posedge clk or negedge rst) begin
        if(!rst)
            sys_state <= 1'd0;
        else if(cnt_to_10 == 4'd11 && data_update == 1)
            sys_state <= 1'd0;                 
        else
            sys_state <= 1'd1;
    end

endmodule
               

IV testbench

//供模块:ser_send直接使用
//功能:并行数据转串口数据测试
//创建时间:2020/07/10
//创建人:windoo
`timescale 1ns/1ns
module ser_send_tb;

    reg [7:0] data1;  //并行输入八位宽数据
    reg data_update1;     //发送使能信号
    reg [2:0] baud1;  //波特率选择位
    reg clk1;
    reg rst1;
    
    wire  sys_state1;      //系统运行状态:1:发送状态 0:空闲 (可用led显示)
    wire  send_done1;      //一次8bit数据发送完成
    wire  tx_data1;        //串口发送数据线

    ser_send fun1(
        .data(data1),
        .clk(clk1),
        .rst(rst1),
        .baud(baud1),
        .data_update(data_update1),
        .sys_state(sys_state1),
        .send_done(send_done1),
        .tx_data(tx_data1)
        );

    initial clk1 = 1'b1;
    always#10 clk1 = ~clk1;

    initial begin
        rst1 = 1'b0;    //未运行状态

        #400;
        rst1 = 1'b1;

        data1 = 8'haa;

        data_update1 = 1;
        #20;//一个时钟周期
        data_update1 = 0;

        #2000000;

        data1 = 8'hf0;

        data_update1 = 1;
        #20;//一个时钟周期
        data_update1 = 0;

        #2000000;
        //@(posedge send_done1) //等待发送完成信号的上升沿
        $stop;
    end
endmodule

V 前仿真

在这里插入图片描述

VI板上测试

//简单的对串口发送功能做个板级测试 
//data_update1:E16(Key0)
//clk1:E1
//rst1:M1
//sys_state1:D11(LED0)
//send_done1:C11(LED1)
//tx_data1:M7(uart_tx)
//创建时间:2020/07/10
//创建人:windoo
module send_top_test1(data_update1,clk1,rst1,sys_state1,tx_data1,send_done1);

    input data_update1;     //发送寄存器更新信号
    input clk1;
    input rst1;
    
    output  sys_state1;      //系统运行状态:1:发送状态 0:空闲 (可用led显示)
    output  send_done1;      //一次8bit数据发送完成
    output  tx_data1;        //串口发送数据线

    wire [7:0] da;
    assign da = 8'haa;
    ser_send fun1(
        .data(da),
        .clk(clk1),
        .rst(rst1),
        .baud(),
        .data_update(!data_update1), //板子上的这个按键闲时为高
        .sys_state(sys_state1),
        .send_done(send_done1),
        .tx_data(tx_data1)
        );

endmodule

FPGA串口发送(超详细注释)_第3张图片

你可能感兴趣的:(FPGA从硬件描述到删核跑路,串口通信,verilog,fpga,物联网)