uart协议FPGA实现

该文详细介绍了UART协议的原理,包括起始位、数据位和停止位,并阐述了在FPGA中实现UART的关键参数如波特率的选择。通过Verilog代码展示了模块设计,包括信号设计思想,如send_en、tx_done、num_cnt和send_go之间的关系。在调试过程中,提到了一些常见的问题和解决方案,如num_cnt的计数问题和信号同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

uart协议FPGA实现

1.uart协议是什么???

在这里插入图片描述
如图是简化版的uart协议,包括start位,数据位(Bit0-Bit7),stop位;说白了就是用来传递数据的。

2.uart主要参数

  • 波特率:每s传输的bit数;常见波特率有9600,115200等
  • 起始位:uart协议以拉低作为起始位
  • 终止位:uart协议以最终拉高作为终止位

3.信号设计:

信号设计中,我主要按照“输入”,“输出”,“内部信号”,“系统信号”进行分类:
在这里插入图片描述
说明一下,send_en是内部信号,send_en==1时,表示数据可以发送,是uart的数据可以发送,不是外部的数据可以输入进来!!!

4.设计思想

主要是需要领悟到send_en,tx_done,num_cnt,send_go这几个信号互相之间的关系,我的习惯是把每个信号单独列出来,然后变化过程列出来:
在这里插入图片描述

5.verilog代码

模块设计:

module uart_byte_send(
				data,
				Boud,
				clk,
				rst_n,
				send_go,
				uart_tx,
				tx_done
				
    );
	 
			parameter TBS=1_000_000_000;
			input [7:0] data;//数据传入
			input [2:0] Boud;//波特率选择,共8种
			input clk;
			input rst_n;
			input send_go;//使能发送
			output reg uart_tx;
			output reg tx_done;//发送结束
			
			reg send_en;
			
			
			//Boud选择--计数时间
			reg [17:0] boud_time;
			always@(*) begin
			case (Boud)  //波特率问中间的数字,/20是根据板载晶振50MHz
			3'd0: boud_time = TBS/600/20;
			3'd1: boud_time = TBS/1200/20;
			3'd2: boud_time = TBS/2400/20;
			3'd3: boud_time = TBS/4800/20;
			3'd4: boud_time = TBS/9600/20;
			3'd5: boud_time = TBS/19200/20;
			3'd6: boud_time = TBS/38400/20;
			default: boud_time = TBS/9600/20;//默认9600;
			endcase
			end

        //boud计数器,计数单位数据的时间
		  reg [17:0] boud_cnt;
		  always@(posedge clk or negedge rst_n or negedge send_en)
			if(!rst_n)
				boud_cnt<=0;
			else if(boud_cnt==boud_time-1)
				boud_cnt<=0;
			else if (send_en==0)
				boud_cnt<=0;
			else 
				boud_cnt<=boud_cnt+1'b1;
			
			//num计数器,计数多少帧,这里的开始帧和结束帧以及8位数据帧共10帧
			reg [4:0] num_cnt;
			always@(posedge clk or negedge rst_n or posedge send_en)
			if(!rst_n)
				num_cnt<=0;
			else if(num_cnt==10) 
				num_cnt<=0;	
			else if (send_en==0)
				num_cnt<=0;
			else if(send_en) begin
				if(boud_cnt==1)
				  num_cnt<=num_cnt+1'b1;
			   else 
				num_cnt<=num_cnt;
				end

		 //send_en判断能否发送数据
		 always@(posedge clk or negedge tx_done or negedge rst_n)
			if(!rst_n)
			   send_en<=0;
			else if (num_cnt==10)
				send_en<=0;
			else if (tx_done==0)
				send_en<=1;
			
			
       //判断tx_done信号何时表示发送完成			
			always@(posedge clk or posedge send_go or negedge rst_n)
			if(!rst_n)
				tx_done<=1;
			else if (num_cnt==10 )
				tx_done<=1;
			else if (send_go==1)
				tx_done<=0;
			
		
		//uart_tx发送数据
		always@(posedge clk or negedge rst_n or posedge send_en)
		 if(!rst_n)
				uart_tx<=1'b1;
		 else if(send_en) begin
				case (num_cnt)
		      4'd0:uart_tx<=1'b0;
		      4'd1:uart_tx<=data[7];
				4'd2:uart_tx<=data[6];
				4'd3:uart_tx<=data[5];
				4'd4:uart_tx<=data[4];
				4'd5:uart_tx<=data[3];
				4'd6:uart_tx<=data[2];
				4'd7:uart_tx<=data[1];
				4'd8:uart_tx<=data[0];
				4'd9:uart_tx<=1'b1;
				default:uart_tx<=1'b1;
		endcase
		end
		else
		uart_tx<=1'b1;

endmodule

激励设计:

`timescale 1ns / 1ps

module uart_byte_send_tb;


	

	// Inputs
	reg [7:0] data;
	reg clk;
	reg rst_n;
	reg send_go;

	// Outputs
	wire uart_tx;
	wire tx_done;

	// Instantiate the Unit Under Test (UUT)
	uart_byte_send uut (
		.data(data), 
		.Boud(4), 
		.clk(clk), 
		.rst_n(rst_n), 
		.send_go(send_go), 
		.uart_tx(uart_tx), 
		.tx_done(tx_done)
	);

	 initial clk=1;
    always #10 clk=~clk;//20ns一个周期
    //对输入进行赋值
    initial begin     
    rst_n=0;   
    data=0; 
    send_go=0;   
    #201;
    rst_n=1;
    #100;
    data=8'h57; 
    send_go=1;   
    #20
    @(posedge tx_done)//死循环,一直等待TX_done的到来,再执行下一语句
    send_go=0; 
    #20000 
    
    data=8'h75; 
    send_go=1;   
    #20
    @(posedge tx_done)
  
    send_go=0;  
    #20000 	 
    $stop; 
    end


      
endmodule


6.调试过程

调试真的。。。难受的一匹,一开始的代码都跑不出循环,接下来慢慢说明调试的过程和方法!!!

Ⅰ.调试方法:

我觉得调试的话可以按照信号传入的先后顺序,列出先后顺序图,我这里的调试顺序就是如下:
在这里插入图片描述
从send_go开始看,同时看tx_done和send_en的变化状态,注意在发数据的时候也要注意这三个信号的变化。

Ⅱ.一些小bug

①第一次做的时候,一直跑不出循环,然后波形图出来之后,发现一直等不到tx_done的上升沿,然后发现uart_tx一直是0,找到控制元素num_cnt也一直是0,所以就是num_cnt的问题了!那他为什么不+1呢???原来是刚开始只检测了send_en的上升沿,忘记检测clk的上升沿了!

②噢对,还有就是为什么要在uart_tx检测send_en的上升沿,实际上是因为num_cnt的常态是“0”,也就是说他平常就是0,但是我们是需要“0”这个状态来发起始位的,所以就需要另外一个条件,当然你可以选择放弃“0”态来发起始位,用“1”态来发,小梅哥就是这么做的,但是我觉得这样子后面写循环的时候就很乱,而且也不合逻辑。所以找另外条件的话可以考虑,怎么把平常的状态给区分开,我选择的就是用send_en,因为send_en置1的时候是代表系统在传数据,那正好就跟常态区分开了!
在这里插入图片描述
③还有一个地方,是看了小梅哥学的,就是把num_cnt在boud_cnt为1的时候就+1,这样子可以避免等一个整数据位
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值