串行接口(UART)------verilog实现串口接收模块

一、串口基本概念

串行接口(Serial port),主要用于串行式逐位数据传输。常见的有一般计算机应用的RS-232(使用 25 针或 9 针连接器)和工业计算机应用的半双工RS-485与全双工RS-422。----------维基百科

按照电气标准划分,串口可以分为RS-232-C、RS-422、RS-485。

RS-232-C:也称标准接口,是目前最常用的一种串行通讯接口。台式计算机一般有两个串行口:COM1和COM2,均为9针D形接口通常在计算机后面能看到,一般笔记本上有一个串口,超薄本除外。RS-232-C电气标准接口传输速率低,传输距离近。

RS-422:为改进RS-232-C传输距离近,传输速率低的特点,RS-422定义了一种平衡通信接口,传输速率提高到10Mb/s,并允许在一条接收总线上挂多个接收器。

RS-485:为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485 标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为 TIA/EIA-485-A标准。

 下图是串口公口实物图,在台式机和笔记本上一般都能找到。维基百科

串行接口(UART)------verilog实现串口接收模块_第1张图片 图1 串口公口

 2、串口传输时序-------参考【FPGA黑金开发板】Verilog HDL那些事儿--串口模块(十一)

串行接口(UART)------verilog实现串口接收模块_第2张图片 图2  串口时序

 图2是串口传输数据的时序图,串口总线默认是高电平,一帧数据开始必须先拉低电平,对应图中起始位。串口传输数据是以数据包为单位的,一个数据包有11位,其中先传输起始位,紧接着传输8个数据位,数据位是按照由低到高的顺序进行传输的,接着传输校验位,最后传输停止位。

串口还有一个重要的参数就是波特率,一般用波特率来形容串口传输数据的速度。常用的波特率有9600bps和115200bps,本文主要介绍9600bps。bps:bit per second,就是每秒钟传输多少位;9600bps就是每秒钟传输9600位,对应的传输一位所用的时钟周期就是9600Hz。接下来本文参考《verilog那些事儿——建模篇》中串口模块的划分来实现串口的接收部分verilog代码。

3、串口接收分析

由于这里是串口接收模块的介绍,为简单考虑从串口外部输入的信号只考虑RX_data_in。由于串口接收数据有明显的先后顺序,因此可以使用状态机来描述串口接收数据过程。使用算法状态机描述串口接收过程如下图所示:

 

串行接口(UART)------verilog实现串口接收模块_第3张图片 图3 算法状态机

图3中s0状态实现数据的初始化,start信号对应串口发送的起始位数据,使用沿检测电路实现;s2状态是接收8位有效数据状态,由于串口是串行将数据接收进来,然后转成并行数据,因此s1部分的电路可以用右移的移位寄存器实现(串口先发送低位数据,所以选用右移移位寄存器);图中虽然画出了s3状态执行校验功能,但是本代码不考虑检验问题,因此s3状态相当于是空状态;s4状态是串口接收数据完毕发出的标志信号。根据算法状态机可以看出,实现串口的接收所需要的基本电路有移位寄存器电路、计数器电路和触发器电路。

4、数据路径

串行接口(UART)------verilog实现串口接收模块_第4张图片 图4 数据路径

 数据路径如图4所示,波特率定时信号的作用分析如下:

串行接口(UART)------verilog实现串口接收模块_第5张图片 图5 FPGA接收数据过程

 由于FPGA的工作时钟较快,串口发送数据较慢。如图5所示,串口发送一位数据可能占用多个FPGA时钟周期,因此需要增加采样信号sampling_signal,采样信号有效时占一个时钟周期,在采样信号有效时,FPGA才接收数据,保证每位数据只接收一次。波特率定时计数器就是用来产生采样信号的,在波特率定时计数器计数到一定数据后,采样信号有效,否则,采样信号无效,这样就可以产生如图5所示的采样信号。

5、控制信号

串行接口(UART)------verilog实现串口接收模块_第6张图片 图6  控制信号

图6给出了控制信号在哪个状态有效。在s0状态,移位寄存器和计数器加载0,D触发器使能信号en_b有效,检测串口发送的数据rx_pin_in的起始信号,待检测到起始信号后,start信号有效,进入s1状态,在s1状态使能波特率采样计时信号cnt_en,判断串口采样信号sinpling_signal是否有效,若有效,设置计数器使能信号有效,反之,设置计数器使能信号无效;在计数器cnt=1时,进入s2状态,此状态开始接收串口发送的8位数据位,首先使能波特率采样计时信号cnt_en,判断sinpling_signal是否有效,若有效,计数器使能信号和移位寄存器使能信号均设置为有效,反之,设置为无效;待8位数据位接收完毕后,进入s3状态,此时仍然使能波特率采样计时信号cnt_en,判断采样信号sinpling_signal是否有效,若有效,设置计数器使能信号有效,反之,设置无效。待计数器记到10时,进入s3状态,串口发送的11位信号接收完毕标志信号done拉高,此时仍然使能波特率采样计时信号cnt_en,判断sinpling_signal是否有效,若有效设置计数器使能信号有效,反之,设置无效。然后进入s0空闲状态,等待下一次数据接收。

6、verilog描述

接下来就可以使用verilog语言描述上述电路:

module RECEIVE_MODULE(input clk_in,
							 input rst,
							 input rx_pin_in,
							 output [7:0] rx_data,
							 output reg done,
							 output clk_50m
    );
//wire clk_50m;	 
clk_1 clk_1 (
    .CLKIN_IN(clk_in), 
    .CLKFX_OUT(clk_50m), 
    .CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT), 
    .CLK0_OUT(CLK0_OUT)
    );

parameter s0 = 'b00001;
parameter s1 = 'b00010;
parameter s2 = 'b00100;
parameter s3 = 'b01000;
parameter s4 = 'b10000;
reg[4:0]current_state = 'd0;
reg[4:0]next_state = 'd0;
//////////////////////////
wire start;
reg en_b;
reg cnt_en;
wire simpling_signal;
reg load_c;
reg en_c;
reg en_a;
reg load_a;
wire [3:0]count;
/////////////////////////////////////////
always @(posedge clk_50m or negedge rst)
	if(!rst)
		current_state <= s0;
	else
		current_state <= next_state;
/////////////////////////////////
always @(*)
	case(current_state)
		s0:	begin
			if(start)
				next_state = s1;
			else
				next_state = s0;
		end
		s1:	begin///////////////////////起始位
			if(count == 'd1)
				next_state = s2;
			else
				next_state = s1;
		end
		s2:	begin///////////////////////数据位
			if(count == 'd9)
				next_state = s3;
			else
				next_state = s2;
		end
		s3:	begin
			if(count == 'd10)
				next_state = s4;
			else
				next_state = s3;
		end
		s4:	begin
			if(count == 'd0)
				next_state = s0;
			else
				next_state = s4;
		end
		default:next_state = s0;			
	endcase
/////////////////////
always @(*)
	case(current_state)
		s0:	begin
			en_b = 'd1;
			load_a = 'd1;
			load_c = 'd1;
			cnt_en = 'd0;
			en_a = 'd0;
			en_c = 'd0;
			done = 'd0;
		end
		s1:	begin
			en_b = 'd1;
			load_a = 'd0;
			load_c = 'd1;
			cnt_en = 'd1;
			en_c = 'd0;
			done = 'd0;
			if(simpling_signal)	begin
				en_a = 'd1;
			end
			else	begin
				en_a = 'd0;
			end
		end
		s2:	begin
			en_b = 'd0;
			load_a = 'd0;
			load_c = 'd0;
			cnt_en = 'd1;
			done = 'd0;
			if(simpling_signal)	begin
				en_c = 'd1;
				en_a = 'd1;
			end
			else	begin
				en_c = 'd0;
				en_a = 'd0;
			end
		end
		s3:	begin
			en_b = 'd0;
			load_a = 'd0;
			load_c = 'd0;
			cnt_en = 'd1;	
			en_c = 'd0;
			done = 'd0;
			if(simpling_signal)
				en_a = 'd1;
			else
				en_a = 'd0;
		end
		s4:	begin
			en_b = 'd0;
			load_a = 'd0;
			load_c = 'd0;
			cnt_en = 'd1;	
			en_c = 'd0;
			done = 'd1;
			if(simpling_signal)
				en_a = 'd1;
			else
				en_a = 'd0;			
		end
		default:	begin
			en_b = 'd0;
			load_a = 'd0;
			load_c = 'd0;
			cnt_en = 'd0;
			en_a = 'd0;
			en_c = 'd0;
			done = 'd0;
		end
	endcase
// Instantiate the module
edge_detection edge_detection (
    .clk_50m(clk_50m), 
    .en_b(en_b), 
    .rx_pin_in(rx_pin_in), 
    .start(start)
    );
// Instantiate the module
BPS_TIMER BPS_TIMER (
    .clk_50m(clk_50m), 
    .cnt_en(cnt_en), 
    .simpling_signal(simpling_signal)
    );
// Instantiate the module
right_shifter right_shifter (
    .clk_50m(clk_50m), 
    .load_c(load_c), 
    .en_c(en_c), 
    .rx_pin_in(rx_pin_in), 
    .rx_data(rx_data)
    );
// Instantiate the module
COUNT_NUM COUNT_NUM (
    .clk_50m(clk_50m), 
    .load_a(load_a), 
    .en_a(en_a), 
    .count(count)
    );
endmodule

 边沿检测模块代码:

module edge_detection(input clk_50m,
							 input en_b,
							 input rx_pin_in,
							 output start
    );
reg rx_pin_in_1 = 'd0;
always @(posedge clk_50m)
	if(en_b)
		rx_pin_in_1 <= rx_pin_in;
	else
		rx_pin_in_1 <= rx_pin_in_1;
		
assign start = (!rx_pin_in) & rx_pin_in_1;

endmodule

 波特率定时模块代码:

////////由于使用的时钟是50mhz的,而串口的波特率是9600bps,即串口发送数据的时钟是9600hz,因此需要使用50mhz的时钟产生个计数器,
///////使其每1/9600s产生一个允许采样脉冲。
//////计数器大小设置:500*10^3/96 = 5208,\,因此计数器需要计数5208个数,由于在数据中间在中间时刻更稳定,因此,在5208/2=2604时对数据进行采样更准确,
//////由于计数是从零开始,因此在2603时对数据进行采样,数据计数到5207清零。
//////////////////////////////////////////////////////////////////////////////////
module BPS_TIMER(input clk_50m,
					  input cnt_en,
					  output simpling_signal
    );
reg [12:0] cnt = 'd0;
always @(posedge clk_50m)
	if(cnt_en)	begin
		if(cnt == 'd5207)
			cnt <= 'd0;
		else
			cnt <=  cnt + 'd1;
	end
	else
		cnt <= 'd0;
assign simpling_signal = (cnt == 'd2603)?'b1:'b0;
endmodule

 右移寄存器模块代码:

//此模块用于将串行发送的数据转成并行接收
//////////////////////////////////////////////////////////////////////////////////
module right_shifter(input clk_50m,
							input load_c,
							input en_c,
							input rx_pin_in,
							output reg[7:0]rx_data
    );
always @(posedge clk_50m)
	if(load_c)
		rx_data <= 'd0;
	else if(en_c)
		rx_data <= {rx_pin_in,rx_data[7:1]};
	else
		rx_data <= rx_data;
endmodule

 计数器模块代码:

module COUNT_NUM(input clk_50m,
					  input load_a,
					  input en_a,
					  output reg [3:0]count
    );
always @(posedge clk_50m)
	if(load_a)
		count <= 'd0;
	else if(en_a)	begin
		if(count == 'd10)
			count <= 'd0;
		else
			count <= count + 'd1;
	end
	else
		count <= count;
		
endmodule

 仿真激励文件代码:

module test;

	// Inputs
	reg clk_in;
	reg rst;
	reg rx_pin_in = 'd1;

	// Outputs
	wire [7:0] rx_data;
	wire done;
	wire clk_50m;

	// Instantiate the Unit Under Test (UUT)
	RECEIVE_MODULE uut (
		.clk_in(clk_in), 
		.rst(rst), 
		.rx_pin_in(rx_pin_in), 
		.rx_data(rx_data), 
		.done(done),
		.clk_50m(clk_50m)
	);

	initial begin
		// Initialize Inputs
		clk_in = 0;
		rst = 0;
		rx_pin_in = 1;

		// Wait 100 ns for global reset to finish
		#100;
		// Add stimulus here

	end
	reg clk1= 'd1;
      always #5 clk_in = ~clk_in;
		
	reg [15:0]cnt = 'd0;
	always @(posedge clk_50m)
		if(cnt>0)
			rst <= 'd1;
		else
			rst <= rst;
			
		always @(posedge clk_50m)
			if(cnt == 'd60000)/////////'d57287
				cnt <= 'd0;
			else
				cnt <= cnt + 'd1;
		always @(posedge clk_50m)
			if(cnt>'d10&&cnt<='d5208)
				rx_pin_in <= 'd0;//////////////////////1
			else if(cnt>='d5208&&cnt<='d10415)
				rx_pin_in <= 'd1;//////////////////////2
			else if(cnt>='d10416&&cnt<='d15623)
				rx_pin_in <= 'd0;////////////////////3
			else if(cnt>='d15624&&cnt<='d20831)
				rx_pin_in <= 'd1;///////////////////4
			else if(cnt>='d20832&&cnt<='d26039)
				rx_pin_in <= 'd0;////////////////////5
			else if(cnt>='d26040&&cnt<='d31247)
				rx_pin_in <= 'd1;///////////////////6
			else if(cnt>='d31248&&cnt<='d36455)
				rx_pin_in <= 'd0;///////////////////7
			else if(cnt>='d36456&&cnt<='d41663)
				rx_pin_in <= 'd1;///////////////////8
			else if(cnt>='d41664&&cnt<='d46871)
				rx_pin_in <= 'd1;///////////////////9
			else if(cnt>='d46872&&cnt<='d52079)
				rx_pin_in <= 'd1;///////////////////10
			else if(cnt>='d52080&&cnt<='d57287)
				rx_pin_in <= 'd1;///////////////////11
endmodule

Isim行为仿真结果:

串行接口(UART)------verilog实现串口接收模块_第7张图片 图7 仿真结果

从仿真波形中可以看出,当串行发送信号 01010101111时,在done信号拉高时接收数据完毕,显示接收的数据是11010101。因此,从仿真上看串口的接收可以正常工作。

 

你可能感兴趣的:(串行接口(UART)------verilog实现串口接收模块)