当前位置: 首页 > news >正文

FPGA开发——UART回环实现之发送模块的设计和数据回环整体实现

一、简介


        在上一篇文章当中我们实现了UART接收模块的相关设计和功能实现,在今天的文章中我们继续实现剩下的发送模块的相关设计和完成完整的串口数据回环的实验。

在文章的最后我会给出完整的工程,给小伙伴们参考。

二、接收模块的基本设计


        在接收模块设计的时候我们就采用了状态机的实现方式,所以在本次的发送模块的设计中我们也采用状态机进行实现。本将状态机划分为四个,第一个就是空闲状态,表示发送模块没有接收到来自接收模块的信号和数据,第二个是开始状态,和接受模块一样也是表示设备检测到起始位,第三个发送数据过程状态,用于表示设备发送数据的过程,最后一个就是停止位,表示设备发送数据完成。

84fb58dcea744f928d012776893bf7ef.png

三、接收模块的波形图绘制


        根据上面的状态机,我们可以据此展开波形图的绘制,分别就是对于来自接收模块转换的数据以及发送信号标志位,状态机转移、相关计数器、busy等信号的表示。

        当发送模块接收到来自接收模块的标志信号时,状态机就从IDLE进入到START,然后利用波特率计数器计数1bit的起始位,来到DATA,利用波特率计数器和bit计数器用于接收数据,接收完数据之后进入STOP,最后利用波特率计数器计数1bit的停止位,状态又回到初始的IDLE状态。

eb445838cadd4df0bc52c963362cb1d3.png

        

四、发送模块代码编写

1、设计代码的编写

在上一篇文章中的基础之上新建一个uart_tx.v文件,如下:在代码中可能会看到中间部分会和屏蔽了一样,这里应该是verilog使用其他语言格式造成的,这里不影响,直接使用代码就行,哪些代码时没有屏蔽的。

//发送模块
module uart_tx (
    input               clk         ,
    input               rst_n       ,
    input   [7:0]       tx_din      ,//进入发送模块准备发送的数据
    input               tx_din_vld  ,//要发送数据的有效信号
    output  reg         tx_dout     ,//串行发送出去的数据
    output              busy      //发送一字节完成信号
);

//参数定义
    localparam  IDLE  = 4'b0001,//空闲状态
                START = 4'b0010,//发送起始位
                DATA  = 4'b0100,//发送数据
                STOP  = 4'b1000;//发送停止位

    parameter   CLOCK_FRQ   = 50_000_000,//50M时钟
                BAUD_9600   = 9600      ,//波特率设置
                BAUD_14400  = 14400     ,
                BAUD_19200  = 19200     ,
                BAUD_38400  = 38400     ,
                BAUD_115200 = 115200    ;

//中间信号定义 
    reg  [12:0]     cnt_bps     ;//波特率计数器,最大值是50M/9600 13位宽
    wire            add_cnt_bps ;
    wire            end_cnt_bps ;

    reg  [3:0]      cnt_bit     ;//bit计数器,只记数据位 8位
    wire            add_cnt_bit ;
    wire            end_cnt_bit ;

    reg  [7:0]      tx_din_r    ;//寄存一次数据,否则第一个数据会被丢弃

    reg  [3:0]      state_c     ;//现态
    reg  [3:0]      state_n     ;//次态

    wire            idle2start  ;//状态机转移条件
    wire            start2data  ;
    wire            data2stop   ;
    wire            stop2idle   ;

//状态机
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
       state_c <= IDLE;
   else
       state_c <= state_n;
end
 
always @(*)begin
    case (state_c)
        IDLE  : begin
            if(idle2start)begin
                state_n = START;
            end
            else begin
                state_n = state_c;
            end
        end
        START : begin
            if(start2data)begin
                state_n = DATA;
            end
            else begin
                state_n = state_c;
            end
        end
        DATA  : begin
            if(data2stop)begin
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        STOP  : begin
            if(stop2idle)begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end        
        default: state_n = IDLE; 
    endcase       
end

assign idle2start = state_c == IDLE  && (tx_din_vld );//数据来临有效即可以发
assign start2data = state_c == START && (end_cnt_bps);//发完1bit
assign data2stop  = state_c == DATA  && (end_cnt_bit );//发完8bit
assign stop2idle  = state_c == STOP  && (end_cnt_bps);//发完1bit

//tx_din_r
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_din_r <= 'd0;
    end 
    else if(tx_din_vld)begin 
        tx_din_r <= tx_din;
    end 
end
//cnt_bps
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt_bps <= 0;
    end 
    else if(add_cnt_bps)begin 
        if(end_cnt_bps)begin 
            cnt_bps <= 0;
        end
        else begin 
            cnt_bps <= cnt_bps + 1;
        end 
    end
    else begin
        cnt_bps <= cnt_bps;
    end
end 

assign add_cnt_bps = (state_c != IDLE);
assign end_cnt_bps = add_cnt_bps && (cnt_bps == (CLOCK_FRQ/BAUD_115200 - 1));

//cnt_bit  
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt_bit <= 0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1;
        end 
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end 

assign add_cnt_bit = (state_c == DATA && end_cnt_bps);//每发完1bit才会加1
assign end_cnt_bit = add_cnt_bit && (cnt_bit == 8 - 1);

//tx_dout
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_dout <= 1'b1;
    end 
    else begin 
        case (state_c)
            IDLE : tx_dout <= 1'b1;
            START: tx_dout <= 1'b0;
            DATA : tx_dout <= tx_din_r[cnt_bit];//LSB发送数据
            STOP : tx_dout <= 1'b1;
            default: tx_dout <= 1'b1;
        endcase
    end 
end

//busy
assign busy = state_c !=IDLE;

endmodule

2、测试文件的编写

新建一个uart_tx_tb.v文件,如下:先对编写的发送模块进行一个设计再进行整个回环实验进行一个实现。这里直接对输入数据进行一个8位赋值,观察发送模块发送出的数据。

`timescale 1ns/1ns
 
module uart_tx_tb();
//激励信号定义 
reg          clk          ;
reg          rst_n        ;
reg          tx_din_vld   ;
wire         tx_dout      ;
wire         tx_done   ;
//时钟周期参数定义 
parameter CYCLE = 20;

uart_tx uart_tx_inst(
    /*input          */ .clk       (clk       )  ,
    /*input          */ .rst_n     (rst_n     )  ,
    /*input   [7:0]  */ .tx_din    (8'b10100101    )  ,//进入发送模块准备发送的数据
    /*input          */ .tx_din_vld(tx_din_vld && ~busy)  ,//要发送数据的有效信号
    /*output  reg    */ .tx_dout   (tx_dout   )  ,//串行发送出去的数据
    /*output  reg    */ .busy      (busy   )   //发送一字节完成信号
);
//产生时钟 
initial  clk = 1'b0; 
always #(CYCLE/2) clk = ~clk; 
 
//产生激励 
initial  begin 
    rst_n = 1'b0; 
    #(CYCLE*10+3); 
    rst_n = 1'b1; 
    repeat (10)begin
        //起始位
        tx_din_vld = 1;
        #20;
        #(434*CYCLE);
        
        #(434*CYCLE*8);
        //停止位
        #(434*CYCLE);

        #(CYCLE*100);
        $stop;
    end
end 
endmodule

3、波形仿真

通过波形图我们可以看到发送模块的发送端发出的数据和我们设定的值一样,发送模块设计没有问题。

a159276f9c7247ffb08386084f484631.png

五、UART回环实验的实现

1、编写顶层模块

编写一个顶层模块,将接收模块和发送模块两者结合起来,从而实现数据回环,新建一个top.v文件,如下:

 

module top (
    input       clk     ,
    input       rst_n   ,
    input       rx      ,
    output      tx          
);
wire  [7:0] rx_data;
wire        rx_dout_vld;
wire            busy;
//不加控制模块
uart_rx uart_rx_inst(  
    /*input		   */	  .clk		   (clk   ),
    /*input		   */	  .rst_n	   (rst_n ),
    /*input		   */	  .din_rx      (rx    ),
    /*output [7:0]*/      .dout_data   (rx_data  ),
    /*output	   */     .dout_flag   (rx_dout_vld  )	
);	
uart_tx uart_tx_inst(
    /*input        */ .clk        (clk      ) ,
    /*input        */ .rst_n      (rst_n    ) ,
    /*input   [7:0]*/ .tx_din     (rx_data     ) ,//进入发送模块准备发送的数据
    /*input        */ .tx_din_vld (rx_dout_vld && ~busy    ) ,//要发送数据的有效信号
    /*output  reg  */ .tx_dout    (tx       ) ,//串行发送出去的数据
    /*output  reg  */ .busy    (busy   )  //发送一字节完成信号
);

endmodule

2、顶层测试文件的编写

`timescale 1ns/1ns
    
module top_tb();

//激励信号定义 
    reg				tb_clk  	;
    reg				tb_rst_n	;
    reg				rx		;

//输出信号定义	 
    wire            tx     ;
//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

//模块例化
    top tb_top( 
    /*input				  */.clk		(tb_clk     ),
    /*input				  */.rst_n	    (tb_rst_n   ),
    /*input               */.rx         (rx     ),
    /*output              */.tx         (tx    )
);	

//产生时钟
    initial 		tb_clk = 1'b0;
    always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;

//产生激励
    integer i;
    initial  begin 
        tb_rst_n = 1'b1;
        rx = 1;//空闲为高电平
        #(CLOCK_CYCLE*2);
        tb_rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        tb_rst_n = 1'b1;
        #1002;

        //模拟UART接收模块的串行输入
        repeat(20)begin
            //起始位
            rx = 0;
            #(434*CLOCK_CYCLE);
            //数据位:
            for (i=0;i<8;i=i+1) begin
                rx = $random;
                #(434*CLOCK_CYCLE);
            end
            //停止位
            rx = 1;
            #(434*CLOCK_CYCLE);
            //两次发送的间隔时间
            #(CLOCK_CYCLE*1000);
        end
        
        // #(CLOCK_CYCLE*434*11);
        // $stop;
    end

endmodule 

3、波形仿真

从最后的仿真图中我们可以看到接收端和发送端最后的数据都是一样,说明我们的串口回环实验设计成功。

4ea5b71b32ab436eaf773efd4fd817e4.png

4、下板验证

经过我们最后的下板验证之后,发现数据能够正确的进行回环,如图所示:

d03ad8a49460441481a1455ebcbc5b9b.png

 到这里UART回环就设计完成了。

六、完整工程

 

https://pan.baidu.com/s/12IrXc6A00a_zLP3SHXlriA?pwd=7f9i 
提取码:7f9i

 

相关文章:

  • 革新测试管理:集远程、协同、自动化于一身的统一测试管理平台
  • ubuntu20.04 安装docker
  • [C#]基于winform结合photocartoon算法实现人物卡通化源码实现
  • 安全基础学习-RC4加密算法
  • 加密狗创新解决方案助力工业自动化
  • 计算机网络
  • 八股总结----计算机网络
  • Git工具练习网站
  • 冷热数据拆分
  • C++11中的Lambda表达式
  • 记录git push时的报错以及解决方案
  • 七大排序算法
  • OD C卷 - 贪吃的猴子
  • 快速把文件名统计到excel表的方法
  • Javaweb学习之HTML(二)
  • 最新的APS高级计划排程系统推动的MRP供应链计划是什么?
  • multiprocessing,threading与subprocess区别
  • 单独编译bootloader部分
  • C++类和对象(2)
  • c语言基础知识学习
  • 百亿基金经理调仓路径曝光,张坤、陈皓、胡昕炜又有新动作
  • 上海市委常委会扩大会议传达学习习近平总书记考察上海重要讲话和在部分省区市“十五五”时期经济社会发展座谈会上的重要讲话精神
  • 余姚警方通报:一司机未悬挂车牌,事故现场就地粘贴安装
  • 出口管制不能将民用技术泛安全化,也不能破坏全球供应链稳定
  • 一代名伶程砚秋经典影像:一箱旧影,芳华满堂
  • 同日哑火丢冠,双骄的下山路,手牵手一起走