FPGA串口多字节发送、仿真

本意是可以把一帧数据发送出去,外部只需要输入一个发送脉冲和一帧数据就可以了。在单字节发送的基础上进行设计,本来是想通过发送完成标志位进行下一个字节的数据装填和发送,可是实在太麻烦了,没有理好逻辑,索性采用时间控制的方式。
程序分为两个大部分,一部分是单字节的发送,另一部分是时间控制下的字节的状态和发送使能信号的设置。单字节发送是正点原子的例程。

程序源码

频率50MHz,波特率 9600,发送8个字节,8位数据,1位停止位。

module uart_top(
     input	      sys_clk,                  //系统时钟
     input         sys_rst_n,                //系统复位,低电平有效 
     input         uart_en,                  //发送使能信号
	 input [63:0]  uart_data,                //发送多字节数据
	 
	 output  reg  [7:0] uart_din,                 //待发送数据
	 output  wire       en_flag,    //捕捉外部发送的上升沿
	 output  reg        byteEn_flag,//发送每一个自己的启动位
	 output  reg  [3:0] byte_cnt,
	 output  reg        pack_busy,//处于发送状态中
	 output  reg        tx_done,
	 output  reg        tx_busy,  
 
	 output  reg   uart_done,              //发送完成
    output  reg   uart_txd                  //UART发送端口
    );
    
//parameter define
parameter  CLK_FREQ = 50_000_000;             //系统时钟频率
parameter  UART_BPS = 9600;                 //串口波特率
localparam BPS_CNT  = CLK_FREQ/UART_BPS;    //为得到指定波特率,对系统时钟计数BPS_CNT次
localparam Byte_Num = 8;
//reg define
reg        uart_en_d0; 
reg        uart_en_d1;  
reg [15:0] clk_cnt;                         //系统时钟计数器
reg [ 3:0] tx_cnt;                          //发送数据计数器
//reg        tx_busy;                         //发送过程标志信号
reg [ 7:0] tx_data;                         //寄存发送数据
//reg        tx_done;

//reg  [7:0]  uart_din;                 //待发送数据
//wire define
//wire       en_flag;    //捕捉外部发送的上升沿
//reg        byteEn_flag;//发送每一个自己的启动位
//reg  [3:0] byte_cnt;
//reg    pack_busy;//处于发送状态中


reg [23:0]  time_cnt;
localparam  TimeSet = BPS_CNT*10;//定时发送byte_en信号
//*****************************************************
//**                    main code
//*****************************************************
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;
                                                 
//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or posedge sys_rst_n) begin         
    if (sys_rst_n) begin
        uart_en_d0 <= 1'b0;                                  
        uart_en_d1 <= 1'b0;
    end                                                      
    else begin                                               
        uart_en_d0 <= uart_en;                               
        uart_en_d1 <= uart_en_d0;                            
    end
end

// 根据脉冲信号en_flag到达时,把数据依次存入uart_din中//用状态机可能比较好
//--------------时间计数器----用来发送数据--------------//
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
always @(posedge sys_clk or posedge sys_rst_n) begin         
    if (sys_rst_n) begin                                  
        pack_busy <= 1'b0;
    end 
    else if (en_flag) begin                 //检测到发送使能上升沿                      
            pack_busy <= 1'b1;                //进入发送过程,标志位tx_busy拉高    
        end
        else 
        if ((byte_cnt == Byte_Num)) //停止
        begin                               //计数到停止位中间时,停止发送过程
            pack_busy <= 1'b0;                //发送过程结束,标志位tx_busy拉低
        end
        else begin
            pack_busy <= pack_busy;
        end 
end
//数据发送完毕后给出标志信号
always @(posedge sys_clk or posedge sys_rst_n) begin        
    if (sys_rst_n) begin                         
        uart_done <= 1'b0;
    end
    else if( !pack_busy && tx_done) begin               //接收数据计数器计数到停止位时           
        uart_done <= 1'b1;                                   //并将接收完成标志位拉高
    end
    else begin                                  
        uart_done <= 1'b0; 
    end    
end
//--------------时间计数器----用来发送数据--------------//
always @(posedge sys_clk or posedge sys_rst_n) begin         
    if (sys_rst_n) begin                             
        time_cnt <= 24'd0;
		  byteEn_flag <= 1'b0;	
        uart_din <= 8'd0;
		  byte_cnt <= 4'd0;  
    end  
	 else if (pack_busy || en_flag) begin //启动超时计时
	       if(time_cnt > TimeSet-1 || en_flag) begin //时间到,准备发送数据 第一包en_flag
					 time_cnt <= 24'd0; 
					 byte_cnt <= byte_cnt+ 1'b1;
					 byteEn_flag <= 1'b1;
					 case ( byte_cnt )
					 4'd0 : uart_din <= uart_data[7:0];  
					 4'd1 : uart_din <= uart_data[8*1+7:8*1];   
					 4'd2 : uart_din <= uart_data[8*2+7:8*2];
					 4'd3 : uart_din <= uart_data[8*3+7:8*3];
					 4'd4 : uart_din <= uart_data[8*4+7:8*4];
					 4'd5 : uart_din <= uart_data[8*5+7:8*5];
					 4'd6 : uart_din <= uart_data[8*6+7:8*6];
					 4'd7 : uart_din <= uart_data[8*7+7:8*7];
					 default:  uart_din <= 8'h55;                                         
					 endcase
			end
			else begin //time_cnt超过限制 
			    time_cnt <= time_cnt + 1'b1;
				 byteEn_flag <= 1'b0;
				 uart_din <= uart_din;
				 byte_cnt <= byte_cnt;	
			end
	 end 
	 else begin   //发送完成
			 time_cnt <= 24'd0;               //对系统时钟计数达一个波特率周期后清零
			 byteEn_flag <= 1'b0;
			 byte_cnt <= 4'd0;
		    uart_din <= 8'h00;	 
	end
end

//==========================发送一个字节========================//
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
always @(posedge sys_clk or posedge sys_rst_n) begin         
    if (sys_rst_n) begin                                  
        tx_busy <= 1'b0;
        tx_data <= 8'd0;
    end 
    else if (byteEn_flag) begin                 //检测到发送使能上升沿                      
            tx_busy <= 1'b1;                //进入发送过程,标志位tx_busy拉高
            tx_data <= uart_din;            //寄存待发送的数据
        end
        else 
        if ((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
        begin                               //计数到停止位中间时,停止发送过程
            tx_busy <= 1'b0;                //发送过程结束,标志位tx_busy拉低
            tx_data <= 8'd0;
        end
        else begin
            tx_busy <= tx_busy;
            tx_data <= tx_data;
        end 
end
//进入发送过程后,启动系统时钟计数器与发送数据计数器
always @(posedge sys_clk or posedge sys_rst_n) begin         
    if (sys_rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        tx_cnt  <= 4'd0;
    end                                                      
    else if (tx_busy) begin                 //处于发送过程
        if (clk_cnt < BPS_CNT - 1) begin
            clk_cnt <= clk_cnt + 1'b1;
            tx_cnt  <= tx_cnt;
        end
        else begin
            clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
            tx_cnt  <= tx_cnt + 1'b1;       //此时发送数据计数器加1
        end
    end
    else begin                              //发送过程结束
        clk_cnt <= 16'd0;
        tx_cnt  <= 4'd0;
    end
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or posedge sys_rst_n) begin        
    if (sys_rst_n)  
        uart_txd <= 1'b1;        
    else if (tx_busy)
        case(tx_cnt)
            4'd0: uart_txd <= 1'b0;         //起始位 
            4'd1: uart_txd <= tx_data[0];   //数据位最低位
            4'd2: uart_txd <= tx_data[1];
            4'd3: uart_txd <= tx_data[2];
            4'd4: uart_txd <= tx_data[3];
            4'd5: uart_txd <= tx_data[4];
            4'd6: uart_txd <= tx_data[5];
            4'd7: uart_txd <= tx_data[6];
            4'd8: uart_txd <= tx_data[7];   //数据位最高位
            4'd9: uart_txd <= 1'b1;         //停止位
            default: ;
        endcase
    else 
        uart_txd <= 1'b1;                   //空闲时发送端口为高电平
end
//数据发送完毕后给出标志信号
always @(posedge sys_clk or posedge sys_rst_n) begin        
    if (sys_rst_n) begin                         
        tx_done <= 1'b0;
    end
    else if(tx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时           
        tx_done <= 1'b1;                      //并将接收完成标志位拉高
    end
    else begin                                  
        tx_done <= 1'b0; 
    end    
end

endmodule	          

ModelSIM 仿真

发送两帧数据,注意中间的间隔时间要足够数据发送完成。

`timescale 1 ps/ 1 ps
module uart_top_vlg_tst();
// constants                                           
// general purpose registers
// test vector input registers
reg       sys_clk;
reg       sys_rst_n;
reg       uart_en;
reg [63:0]  uart_data;                //发送多字节数据
	 
// wires                                               
wire      uart_txd;      //串口发送信号线 
wire [7:0]  uart_din;                 //待发送数据
wire       en_flag;    //捕捉外部发送的上升沿
wire       byteEn_flag;//发送每一个自己的启动位
wire [3:0] byte_cnt;
wire       pack_busy;//处于发送状态中
wire       tx_done;
wire      uart_done;              //发送完成

 //时钟参数
parameter SYS_CLK_FRE = 50_000_000;     //系统频率40MHz  40_000_000
parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE;  //周期25ns
parameter BAUD_RATE = 115200;   //串口波特率
parameter BAUD_RATE_PERIOD  = 1_000_000_000/BAUD_RATE;
 //波特率周期,0.104ms = 104us,1/9600 s = 1^9 /9600 ns = 4167 sys_clk
 //波特率周期, 1/115200 = 8680 ns  =  8.7us = 347 sys_clk
parameter [15:0]  BPS_NUM = SYS_CLK_FRE / BAUD_RATE; //时钟/波特率,用时钟周期构造波特率周期
 //  BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
 //  1 bit位宽所需时钟周期的个数。最长的波特率计数,10417,二进制有14位,取16位
 //  parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
 //  parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
 //  parameter BPS_115200: 40MHz set 347; 50MHz set 434

//==========================================================================
//模拟:信号的输入,显示输出结果
//==========================================================================
    //模拟系统时钟:40MHz,25ns
   // always #((SYS_CLK_PERIOD+1)/2-1) sys_clk = ~sys_clk; //延时,电平翻转
	 //模拟系统时钟:50MHz,20ns
    always #((SYS_CLK_PERIOD)/2) sys_clk = ~sys_clk; //延时,电平翻转
    initial begin
        //模拟复位信号:拉低一次
        #0;
            sys_clk = 1'b0;
            sys_rst_n = 1'b1;      //复位拉低,有效,
        //#RST_TIME;               //延时:保持足够长时间(至少5个clk)
        #BAUD_RATE_PERIOD;         //5个clk时间轴太短,仿真改为1个BPS,更明显
            sys_rst_n = 1'b0;      //解除复位       
        //==========================================================================
        //模拟串口发送:并行数据,串行输出
        //==========================================================================    
        uart_en = 0;
        uart_data = 64'h0; 
        #BAUD_RATE_PERIOD;        //直接延时,一个波特率周期      
        
        $display("Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行显示初始化完成,输出BAUD_RATE
        //传递 第一组数据:64位并行数据,一次性送入串口发送模块
        uart_data = 64'h1122334455667788;            //00001010
        #BAUD_RATE_PERIOD;
        
        //开启触发信号:串口发送
        uart_en = 1;               //高有效
        #BAUD_RATE_PERIOD;
        //结束触发:串口发送
        uart_en = 0;
		  #(BAUD_RATE_PERIOD*100);
        #BAUD_RATE_PERIOD;
        $display("The first data  has been sent.");  //命令行显示:第1组数据发送完
		  
		  
        //延时 12个 BPS 波特率周期,再发第二组数据
        repeat( BPS_NUM*12 ) @(posedge sys_clk);
        //传递 第二组数据:8位并行数据,一次性送入串口发送模块
        uart_data = 64'h5511117887654355;  //00001100
        #BAUD_RATE_PERIOD;
        
        //开启触发信号:串口发送
        uart_en = 1;
        #BAUD_RATE_PERIOD;
        
        //结束触发:串口发送
        uart_en = 0;
        #BAUD_RATE_PERIOD;
		  #(BAUD_RATE_PERIOD*100);
		  #BAUD_RATE_PERIOD;
        $display("The second data  has been sent.");   //命令行显示:第2组数据发送完
        
        repeat( BPS_NUM*5 ) @(posedge sys_clk);
            $stop;      //结束仿真
    end
        

// assign statements (if any)                          
 uart_top #(
		.CLK_FREQ   (  SYS_CLK_FRE), //系统时钟
		.UART_BPS   (  BAUD_RATE  )  // 时钟/波特率,用时钟周期构造波特率周期
 )
 u_uart_top(
		.sys_clk(sys_clk),
		.sys_rst_n(sys_rst_n),
		.uart_en(uart_en),
		.uart_data(uart_data),
		
		.uart_txd(uart_txd),
		.uart_done(uart_done),
		.uart_din (uart_din),
		.en_flag (en_flag),
		.byteEn_flag (byteEn_flag),
		.byte_cnt (byte_cnt),
		.pack_busy (pack_busy),
		.tx_done (tx_done),
		.tx_busy (tx_busy)	
 );   		  
endmodule

在这里插入图片描述
仿真中有大量的变量是为了观察信号变化,实际使用中输出只需要最后的帧发送完成标志uart_done。
相应工程项目已上传,欢迎各位参考指正。

  • 4
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值