Verilog | UART接口实现

UART接口协议是一种比较简单、非常常用的一种接口协议,使用它的场景很常见,是我们学习FPGA一定要会的接口协议。

一、UART协议

​ 通用异步收发器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种串行、异步、全双工的通信协议,在嵌入式领域应用的非常广泛。其数据通信格式如下图:

在这里插入图片描述

UART 数据传输格式

LSB:least significant bit 表示二进制数据的最低位。

MSB:most significant bit 表示二进制数据的最高位。

起始位: 
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。

数据位: 
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。

奇偶校验位: 
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。

停止位: 
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。

空闲位: 
 UART协议规定,当总线处于空闲状态时信号线的状态为 ‘1’ 即高电平,表示当前线路上没有数据传输。

二、UART发送模块(verilog)

为了更加简单的描述串口协议,我们这里使用常用的1位起始位,1位停止位,8位数据位,无奇偶校验位,波特率9600(本次波特率以参数形式提供,如需测试其他波特率,计算后更改即可)。

2.1 设计思路

  • 当使能信号有效后(即使能信号为1),标志模块进入发送过程;当发送完10个bit后,拉低发送标志信号,标志发送过程结束。使能信号有效时将要发送的数据寄存。
  • 假设波特率为9600,则发送一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/9600;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么发送一个bit所需要的系统周期数为(1s/9600)/ 20ns ≈ 5208(个)。在发送过程中使用一个计数器计数,计数区间为(0-5208-1),这样的区间一共10个(一个字节需要发送10个bit);此外还需一个计数器对发送的bit数计数(每当上一个计数器计数到5207则表示发送完了一个bit),计数区间(0~9)。程序中增加波特率产生模块先对系统时钟进行分频处理,然后利用分频后的时钟计算所需时钟周期数。
  • 在发送过程,根据计数器的值(发送bit计数器),对发送数据线进行操作。
    • 若发送bit计数器 = 0,则代表此时需要发送起始位;
    • 若发送bit计数器 = 1,则代表此时需要发送发送数据的最低位LSB(数据的发送总是低位在前,高位在后);
    • ······
    • 若发送bit计数器 = 8,则代表此时需要发送发送数据的最高位MSB;
    • 若发送bit计数器 = 9,则代表此时需要发送停止位;
  • 发送数据线在不处于发送状态时需拉高,以满足UART时序的空闲状态

2.2 UART发送端代码

分频器:

`timescale 1ns / 1ps

//波特率发生器,即分频器
module baud_gen #(
		parameter max_baud = 326 //时钟周期/(波特率*N),N选取16
	)
	(
	input 			clk,
	input 			rst_n,
	output 	reg		bclk
    );
    reg [$clog2(max_baud):0] cnt;
	always @(posedge clk or negedge rst_n)
	begin
		if(!rst_n) begin 
			cnt <= 0;
			bclk <= 0;
		end
        else if(cnt == max_baud>> 1) //大约50%占空比
			begin
				cnt <= cnt + 1'b1;
				bclk <= 1'b1;
			end 
        else if(cnt ==  max_baud-1'b1)
			begin
				cnt <= 0;
				bclk <= 0;
			end
		else cnt <= cnt +1'b1;
     end
endmodule

发送模块:

`timescale 1ns / 1ps

module uart_tx(
	
	input		bclk	,			//分频时钟
	input		rst_n	,			//系统复位
    input		[7:0] uart_tx_data, //待发送数据
	input		uart_tx_en,			//发送使能
	output reg  uart_txd			//发送数据
    );
    parameter idle = 'd0, start = 3'd1, shift = 3'd2, stop = 3'd3;
    reg [2:0] state,next_state;
    reg [3:0] bclk_cnt;
    reg [2:0] dcnt;
    reg [7:0] tx_data_reg;
    reg 	  tx_state;
//    当发送使能信号到达时,将待发送数据暂存
    always @(posedge bclk or negedge rst_n)
    begin
    	if(!rst_n)
    		tx_data_reg <= 8'd0;
		else if(uart_tx_en)
			tx_data_reg <= uart_tx_data;
		 else 
		 	tx_data_reg <= tx_data_reg ;
    end
//    当发送使能信号到达时,将发送状态标识置为1,发送结束后置为0,
//    该标识可表示发送数据线是否处于繁忙状态
    always @(posedge bclk or negedge rst_n)
    begin
    if(!rst_n) 
    	tx_state <= 0;
	else if(uart_tx_en)
		tx_state <= 1'b1;
	else if(state == stop && bclk_cnt == 4'd15)
		tx_state <= 0;
	else 
		tx_state <= tx_state;
    end
//    使用状态机进行数据发送
    always @(posedge bclk or negedge rst_n)
    begin
    	if(!rst_n) begin
    		state 		 <= 	idle;
//    		uart_txd 	 <= 	1'b1;
//   		next_state 	 <= 	idle;
    	end
    	else 
    		state <= next_state;
    end 
//   always @(bclk_cnt or uart_tx_en or dcnt or state)
always @(*)
   begin
		case(state)
				idle: 	
					begin
						next_state = (tx_state && bclk_cnt == 4'd15 )? start : idle;
						uart_txd = 1;
					end
				start: //数据发送起始位
					begin
						next_state = (bclk_cnt==4'd15) ? shift:start;
						uart_txd = 0;						
					end
				shift://发送数据
					begin
						next_state = (bclk_cnt == 4'd15)&&(dcnt == 3'd7 ) ? stop:shift;
						uart_txd = tx_data_reg[dcnt];
					end
				stop://数据结束位
					begin
						next_state = tx_state?stop:idle;
						uart_txd = 'b1;
					end
				default : 
					begin
						next_state = idle ;
						uart_txd = 'b1;
					end
			endcase
   end
//   进入发送过程后,启动时钟计数器
   always @(posedge bclk or negedge rst_n)
   begin
   		if(!rst_n) begin
   			bclk_cnt <= 0;
		end
		else if(tx_state) 
					bclk_cnt <= bclk_cnt + 'b1;
				else bclk_cnt <= bclk_cnt;
   end 
//   发送数据bit位计数
   always @(posedge bclk or negedge rst_n)
   begin
   		if(!rst_n) begin
   			dcnt <= 0;
		end
		else if(tx_state && bclk_cnt == 'd15 && state == shift) 
					dcnt <= dcnt + 'b1;
				else dcnt <= dcnt;
   end 
endmodule

三、UART接收模块(verilog)

3.1 设计思路

  • 该模块应该与发送模块保持相同的波特率,数据位8位,起始位和停止位各1位,无奇偶校验。
  • 串口的传输起始位是将数据线拉低 ,所以需要捕捉数据线的下降沿,将接收数据线打拍3次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉高数据线,并拉低接收标志信号,标志接收过程结束。
  • 假设波特率为9600,则传输一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/960;假设系统时钟为100MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么传输一个bit所需要的系统周期数为(1s/9600)/ 10ns ≈ 10416(个)。在接收过程中使用一个计数器计数,计数区间为(0-10416-1),这样的区间一共10个(一个字节需要传输10个bit);此外还需一个计数器对接收的bit数计数(每当上一个计数器计数到10416则表示接收完1个bit),计数区间(0~9)。程序中同样先使用了分频器对系统时钟进行了分频处理。
  • 在接收过程,根据计数器的值(接收bit计数器),在每个bit计数器的中间接收数据,将其移位寄存(在电平中间数据最稳定)
    • 若接收bit计数器 = 0,则代表是起始位,不需要接收;
    • 若接收bit计数器 = 1,则代表此时接收到数据的最低位LSB(数据的传输总是低位在前,高位在后),将其赋值给寄存数据的最低位;
    • ······
    • 若接收bit计数器 = 8,则代表此时接收到数据的最高位MSB,将其赋值给寄存数据的最高位;
    • 若接收bit计数器 = 9,则代表是停止位,不需要接收。

3.2 UART接收端代码

分频器:同上

接收模块:

`timescale 1ns / 1ps

module uart_rx
	(
	input			 bclk	,
	input			 rst_n	,
	input			 uart_rxd,
	output reg [7:0] rx_data
    );
    
     reg rx_data_reg1, rx_data_reg2, rx_data_reg3;
     reg uart_rx_cmd;
     
     reg [3:0] bit_cnt;
     reg [3:0] bclk_cnt;
     //将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
    always @(posedge bclk or negedge rst_n)
    begin
    	if(!rst_n) begin
    		rx_data_reg1 <=  'b0;
    		rx_data_reg2 <=  'b0;
    		rx_data_reg3 <=  'b0;
    	end
    	else begin
    	 	rx_data_reg1 <=  uart_rxd ;
    	 	rx_data_reg2 <=  rx_data_reg1;
    	 	rx_data_reg3 <=  rx_data_reg2;
    	 end    	 
    end
    
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,
//并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
	always @(posedge bclk or negedge rst_n)
	begin
		if(!rst_n) begin
			uart_rx_cmd <= 0;
		end
		else if(rx_data_reg3  && !rx_data_reg2) begin
			uart_rx_cmd <= 1;
			
		end
		else if((bit_cnt == 4'd9) && (bclk_cnt == 4'd7) && (rx_data_reg3 == 1'b1))
		begin
			uart_rx_cmd <= 0;
		end
		else uart_rx_cmd <= uart_rx_cmd;
	end    
	
	//时钟计数
	always @(posedge bclk or negedge rst_n)
	begin
		if(!rst_n) 
			bclk_cnt <= 0;
		else if(uart_rx_cmd)
		begin
				if(bclk_cnt <= 4'd15)
					bclk_cnt <= bclk_cnt +1;
				else 
					bclk_cnt <= 0;
		end
		else bclk_cnt <= 0;
		
	end 
	//bit计数
	always @(posedge bclk or negedge rst_n)
	begin 
		if(!rst_n)
			bit_cnt <= 0;
		else if(uart_rx_cmd)
		begin
			if(bclk_cnt == 4'd7)
				bit_cnt <= bit_cnt + 1;
			else
				bit_cnt <= bit_cnt;
		end
		else bit_cnt <= 0;
		
	end
	
	//	数据接收		
	always @(posedge bclk or negedge rst_n)
	begin
		if(!rst_n) rx_data <= 0;
		else if(uart_rx_cmd && bit_cnt!=0)
			if(bclk_cnt == 4'd7)
			rx_data[bit_cnt-1'b1] <=  rx_data_reg3;
//			case(bit_cnt)
//				4'd1: rx_data[0] <=  rx_data_reg3;
//				4'd2: rx_data[1] <=  rx_data_reg3;
//				4'd3: rx_data[2] <=  rx_data_reg3;
//				4'd4: rx_data[3] <=  rx_data_reg3;
//				4'd5: rx_data[4] <=  rx_data_reg3;
//				4'd6: rx_data[5] <=  rx_data_reg3;
//				4'd7: rx_data[6] <=  rx_data_reg3;
//				4'd8: rx_data[7] <=  rx_data_reg3;
//				default: rx_data<= 0;
//			endcase
			else  rx_data <= rx_data;
		else 
			rx_data <= 0;
			
	end
endmodule

3.3 模块连接

四、 Testbench及仿真结果

Testbench:

`timescale 1ns / 1ps

module uart_top_tb(

    );
    reg clk1;	   
    reg clk2;	                    
	reg 	rst_n;                 
	reg	[7:0] uart_tx_data;       
	reg	uart_en;                   
	wire [7:0] rx_data;           
	wire uart_txd     ;             
		
	initial begin
	clk1 = 0;
	clk2 = 0;
	rst_n = 0;
	uart_en = 0;
	#10 rst_n = 1;
	#20 uart_en = 1;
	uart_tx_data = 8'b0101_0010;
	#3250 uart_en = 0;
	end
    
    always begin
    #10 clk1 = ~clk1;
    end
    always begin
    #5 clk2 = ~clk2;
    end
    
    uart_top uart_top(
    clk1	,	
    clk2 ,                     
 	rst_n,                  
	uart_tx_data,      
	uart_en,                 
    rx_data,
    uart_txd         
    );
endmodule

请添加图片描述
能够实现基本的通信功能,时序上可能不够严谨,还需要继续优化

五、其他

(1)UART存在的问题
电气接口不统一:对于UART来说,它只是对信号的时序进行了定义,并没有定义接口的电气特性;UART通信一般使用的都是处理器的电平,也就是TTL电平,但是由于不同处理器之间的电平存在差异,所以不同的处理器之间的UART不能直接相连;
抗干扰能力差:采用TTL电平的高低代表0和1,在数据传输过程中很容易出错。并且,由于抗干扰能力很差,所以通信距离也很短,一般只能用在一个板子上不同的芯片的通信;
(2)改进
针对于电气接口不统一的问题,于是产生了RS232协议。

RS232协议: 在串行通讯时,要求通讯双方都采用一个标准接口,使不同的设备可以方便地连接起来进行通讯。规定逻辑“1”的电平为-5V~-15 V,逻辑“0”的电平为+5 V~+15 V。选用该电气标准的目的在于提高抗干扰能力,增大通信距离。RS -232的噪声容限为2V,接收器将能识别高至+3V的信号作为逻辑“0”,将低到-3 V的信号作为逻辑“1”。由于RS -232采用串行传送方式,并且将微机的TTL电平转换为RS-232C电平,其传送距离一般可达30 m。

那么对于RS232来说,又存在一些问题:
首先,由于接口芯片电平较高,容易损坏接口电路的芯片,又因为和TTL电平不兼容,所以需要电平转换芯片才可以和TTL电路连接;通信速度地,通信距离短,抗干扰能力较弱,易产生共模干扰;并且这种接口只可以实现点对点的通信方式,不能实现联网功能。针对这些问题,就产生了如下的RS485;
RS485协议:RS485标准规定采用差分信号进行数据传输,两线间的电压差+2~+6v表示逻辑‘1’,两线间-2v到-6v表示逻辑0;使用差分信号可以有效地减少噪声信号的干扰,延长通信距离,可达到1500m;并且RS485接口信号的电平相比于RS232降低了,所以不宜损坏接口芯片,并且该电平和TTL兼容,可以方便的和TTL电路连接;但是由于RS485采用两线制,所以数据的发送和接收都要使用这对差分信号线,所以是半双工的工作方式,在编程时需要加以处理。同时具有多站能力,可以很方便的建立起一个设备网络,最多可以有32个节点。

请添加图片描述

  • 2
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
抱歉,作为AI语言模型,我无法提供完整的verilog代码。但是,我可以提供一个基本的测试脚本框架,你可以根据你的需要进行修改和完善。 以下是一个基本的UART接口测试脚本: ``` module uart_interface_test; // 输入信号 reg clk; reg reset; reg [7:0] tx_data; reg enable_tx; reg enable_rx; // 输出信号 wire [7:0] rx_data; // 实例化UART接口模块 uart_interface uart_inst ( .clk(clk), .reset(reset), .tx_data(tx_data), .enable_tx(enable_tx), .rx_data(rx_data), .enable_rx(enable_rx) ); // 时钟生成器 always #5 clk = ~clk; initial begin // 初始化 clk = 0; reset = 1; tx_data = 8'h00; enable_tx = 0; enable_rx = 0; // 复位 #10 reset = 0; // 发送数据 #20 tx_data = 8'hFF; #25 enable_tx = 1; #30 enable_tx = 0; // 接收数据 #40 enable_rx = 1; #50 $display("Received data: %h", rx_data); #60 enable_rx = 0; // 结束测试 #70 $finish; end endmodule ``` 在这个测试脚本中,我们首先实例化了UART接口模块,并将输入和输出信号连接到测试脚本中定义的reg和wire信号上。然后,我们创建一个时钟生成器,以便为UART接口提供时钟信号。 在initial块中,我们进行了一些初始化操作,并在10个时钟周期后将reset信号拉低,以便将UART接口复位。然后,我们设置了一个测试用例,将8个1的数据发送到UART接口,并在接收到数据后打印出来。最后,我们使用$finish指令结束测试。 你可以根据需要修改测试用例,例如更改发送的数据或接收的时机。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值