UART通信,接收与发送(详细版,附代码)

UART通信——字节的发送与接受

串行接口是一种可以将接受来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接受的串行数据流转换为并行的数据字符供给CPU的器件。

在FPGA入门阶段,UART通讯协议是必学的知识,因为很多模块都是使用UART协议进行传输的,详细的这里我就不再多做介绍了,先贴上我的代码模板再慢慢做解释。
协议发送原理:
UART通信,接收与发送(详细版,附代码)_第1张图片

在串口通信中波特率表示每秒能传输的数据位,9600就是每秒能传输9600位,1word=2byte、1byte=8bit,这里的9600位指的是9600bit。
每个开发板上都有它的系统时钟,初级阶段本人用的是basys2开发板,对于入门的种子选手是个不错的开端,此款开发板的系统时钟是50MHZ,我们假定波特率是9600bit/s传输,那么传输1bit就需要50M/9600个时钟周期。
依次推理,每传输1bit需要 **“系统时钟/波特率”**个时钟周期。

先来关于字节的发送,代码如下:

//-------1字节数据的发送-------
module uart_tx(
    input clk,
    input rst_n,
    input cnt_start,                                        //发送使能
	input [7:0]tx_data,
    output reg tx,
    output reg tx_done                                      //传输一个字节完成标志
    );
    
    reg [12:0]cnt_bps; 
    parameter bps_t = 13'd5207;                             //传输1bit所需计数值 
    always @(posedge clk or negedge rst_n)
    begin
       if(!rst_n) 
          cnt_bps <= 13'd0;
       else if(cnt_bps == bps_t) 
          cnt_bps <= 13'd0;
       else if(cnt_start) 
          cnt_bps <= cnt_bps + 1'b1;
       else 
          cnt_bps <= 1'b0;
    end
    wire bps_sig;
    assign bps_sig = (cnt_bps ==  13'd2604) ? 1'b1 : 1'b0;  //将采集数据的时刻放在波特率计数器每次循环计数的中间位置
       
    reg [3:0]state;	
    always @(posedge clk or negedge rst_n)
    begin
       if(!rst_n) begin
          state <= 4'd0;
          tx <= 1'b1;
          tx_done <= 1'b0;
		  end
       else begin
          case(state)
			0: if(cnt_start & bps_sig) begin
				state <= state + 1'b1;
				tx <= 1'b0;
				end
			else begin
				state <= state;
				tx <= 1'b1;
				end
			1,2,3,4,5,6,7,8: if(bps_sig) begin
					tx <= tx_data[state - 1'b1]; //注意,从低位依次往高位发送
					state <= state + 1'b1;
					end
				else begin
					state <= state;
					tx <= tx;
					end
			9,10: if(bps_sig) begin
					state <= state + 1'b1;
					tx <= 1'b1;
					end
			11: begin
					state <= state + 1'b1;
					tx_done <= 1'b1;
				end
			12: begin
					state <= 1'b0;
					tx_done <= 1'b0;
				end
          endcase
		  end
    end
endmodule

这时候我们来看看字节发送的仿真图,更能清晰的理解整个过程:
UART通信,接收与发送(详细版,附代码)_第2张图片
从仿真图中可以看出,我们可以看出,输入端tx_data中数据11000101,输出tx为10100011,所以一定要明白,传输过程是从低字节开始传输!
【注:本博客中所有仿真图,紫线表示输出信号,黄线表示中间变量,绿线表示输入信号】

接下来就是数据的接收部分了,接收部分还是依照协议原理,字节传输形式:1-0-x-x-x-x-x-x-x-x-0-1,检测到下降沿即-1-0-(起始标志)后开始进行接收字节,将接收到的一一存入寄存器中,8个bit后结束接收,就是简单的1个字节的接收。
值得注意的是,虽然1字节=8bit,但是因为有起始标志(2bit:-1-0-)和结束标志(2bit:-0-1-),所以在发送和接收过程中,都需要考虑到这一点。

//-------1字节的接收-------
module uart_rx(
    input clk,
    input rst_n,
	input data_rx,
    output [7:0] data_tx,
    output reg rx_int                                       //接收字节时状态为1
    );
    
    reg [12:0]cnt_bps;
	reg bps_start;
    parameter bps_t = 13'd5207;                             //传输1bit所需计数值 
    always @(posedge clk or negedge rst_n)
    begin
       if(!rst_n) 
          cnt_bps <= 13'd0;
       else if(cnt_bps == bps_t) 
          cnt_bps <= 13'd0;
       else if(bps_start) 
          cnt_bps <= cnt_bps + 1'b1;
       else 
          cnt_bps <= 1'b0;
    end
    wire bps_sig;
    assign bps_sig = (cnt_bps ==  13'd2604) ? 1'b1 : 1'b0;  //将采集数据的时刻放在波特率计数器每次循环计数的中间位置
	
	reg	[1:0]	rx;
	always @(posedge	clk	or	negedge	rst_n)begin
		if(!rst_n)	rx <= 2'b11;
		else	begin
			rx[0]	<=	data_rx;
			rx[1]	<=	rx[0];
		end
	end
	wire nege_edge;
	assign nege_edge= rx[1]	& ~rx[0];						//检测下降沿

	reg	[3:0]num;	
	always@(posedge	clk	or	negedge	rst_n)begin
		if(!rst_n)	begin	
			bps_start <= 1'b0;	
			rx_int <= 1'b0;
		end
		else	if(nege_edge)begin
			bps_start <= 1'b1;
			rx_int <= 1'b1;
		end
		else if(num == 4'd10)begin
			bps_start <= 1'b0;	
			rx_int <= 1'b0;
		end
	end

	reg	[7:0]	rx_data_temp_r;								//当前数据接收寄存器
	reg	[7:0]	rx_data_r;									//用来锁存数据
	always@(posedge	clk	or	negedge	rst_n)begin
		if(!rst_n)	begin	
			rx_data_r	<= 8'd0;
			rx_data_temp_r	<= 8'd0;
			num <= 4'd0;
			end
		else if(rx_int) begin
			if(bps_sig) begin
				num <= num + 1'b1;
				case(num)
					4'd1: rx_data_temp_r[0] <= data_rx;		//锁存第0bit
					4'd2: rx_data_temp_r[1] <= data_rx;		//锁存第1bit
					4'd3: rx_data_temp_r[2] <= data_rx;		//锁存第2bit
					4'd4: rx_data_temp_r[3] <= data_rx;		//锁存第3bit
					4'd5: rx_data_temp_r[4] <= data_rx;		//锁存第4bit
					4'd6: rx_data_temp_r[5] <= data_rx;		//锁存第5bit
					4'd7: rx_data_temp_r[6] <= data_rx;		//锁存第6bit
					4'd8: rx_data_temp_r[7] <= data_rx;		//锁存第7bit
					default: ;
				endcase
			end
			else if(num == 4'd10)begin
				rx_data_r <= rx_data_temp_r;
				num <= 4'd0;
			end
		end
	end

	assign	data_tx = rx_data_r;							
	
endmodule

下来就是我们的仿真测试部分了,在这里给大家一个方便省事的建议,先看我们的测试代码:

module tb_uart_rx;

	// Inputs
	reg clk;
	reg rst_n;
	reg data_rx;

	// Outputs
	wire [7:0] data_tx;
	wire rx_int;

	// Instantiate the Unit Under Test (UUT)
	uart_rx uut (
		.clk(clk), 
		.rst_n(rst_n), 
		.data_rx(data_rx), 
		.data_tx(data_tx), 
		.rx_int(rx_int)
	);

	always #10 clk = ~clk;
	initial begin
		clk = 0;
		rst_n = 0;
		data_rx = 0;
		// Wait 20 ns for global reset to finish
		#20
		rst_n = 1;
		data_rx = 1;
		#104160
		rst_n = 1;
		data_rx = 0;	//起始位:-1-0-
		#104160			//传输11001011 (倒序)
		rst_n = 1;
		data_rx = 1;
		#104160
		rst_n = 1;
		data_rx = 1;
		#104160
		rst_n = 1;
		data_rx = 0;
		#104160
		rst_n = 1;
		data_rx = 1;
		#104160
		rst_n = 1;
		data_rx = 0;
		#104160
		rst_n = 1;
		data_rx = 0;
		#104160
		rst_n = 1;
		data_rx = 1;
		#104160
		rst_n = 1;
		data_rx = 1;
		#104160			//结束位 -0-1-
		rst_n = 1;
		data_rx = 0;
		#104160
		rst_n = 1;
		data_rx = 1;
		
		#104160			//复位
		rst_n = 0;
		data_rx = 0;
	end
      
endmodule

注意到了吗,我给的1bit变化的持续时间是#104160ns,可大于但不能小于它,为什么?大家可以看到测试代码中我给的clk是20ns一个周期,那么此时1bit的传输就需要时间:5207/20 ns ,那么以后关于接收数据的仿真,套这个模板就对啦!
下面就是我们的仿真图:
UART通信,接收与发送(详细版,附代码)_第3张图片
依旧老原理啦,输入的是11010011,可以看到输出的是11001011,这个一定要记住噢。

好啦我的关于UART通讯协议就到这啦,这是简单的单字节的接收与发送,那关于多字节的也类似啦,以后有机会我会更新多字节的。

PS:我们都在各自生活的领域里,忙忙碌碌,如果有所选择,还是不顾一切向前跑吧。

你可能感兴趣的:(出发FPGA)