UART串口发送模块设计Verilog

1、知识点

1.1 UART通信协议实现

定义

        UART (Universal Asynchronous Receiver and Transmitter)即通用异步接收发送器,是一种通用的串行数据总线,属串口通信的一种,用于异步通信。该总线有发送和接收线,可双向通信实现全双工传输和接收。

        UART作为一个实现并行信转串行信号输出的芯片,常被集成于其他通讯接口的连接上。在FPAG开发板中,常用UART来与PC进行通信,包括数据通信、命令和控制信息的传输。

        异步通信:UART协议中接收方和发送方不使用统一的参考时钟 ,即发送和接收设备有各自的时钟来控制数据的发送和接收过程。双方以字符为单位,通信中两个字符间的时间间隔是不固定 的,然而在同一个字符中的两个相邻bit间的时间间隔是固定的。双方获得同步的方法就是采用固定的串行数据格式。 

     串口通信:串口通信是指外设和计算机之间,通过数据地线、控制线等,按位传输数据的一种通讯方式。这种方式使用的数据线少,在远距离传输时可节约通信成本,但是其传输速度要比并行传输低。

     串口按位:即bit发送和接收字节。通常串口用于ASCII码字符的传输,通信使用三根线即发送、接收和地线即可。由于串口通信是异步的,串口能在一根线上传输另一根线上接收数据。其他线用于握手,但不是必须的。

接口    

        在UART通信中,两个UART直接相互通信。发送UART将来自CPU等控制设备的并行数据转换为串行形式,并将其串行发送到接收UART,接收UART然后将串行数据转换回接收设备的并行数据。数据从发送UART的Tx引脚流向接收UART的Rx引脚,硬件连接比较简单,仅需要3条线,如果两个设备UART电平不一致需要转换电平再连接。

        TX - 数据发送接口
        RX - 数据接受接口
        GND - 保证两设备共地,有统一的参考平面

 串口配置基本属性

串口通信的重要参数:波特率、数据位、停止位和奇偶校验位。两个进行通信的双方,端口的这些参数必须匹配。

①波特率
        在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。波特率表示每秒钟传送的码元符号的个数,它是对符号传输速率的一种度量,它用单位时间内载波调制状态改变的次数来表示,1波特即指每秒传输1个符号。
        数据传输速率使用波特率来表示。单位bps(bits per second),其中如果串口速率越高,其传输的距离和稳定性就有所下降。一般常用为9600和115200。其他标准的波特率是1200,2400,4800,19200,38400,57600。举个例子,如果串口波特率设置为115200bps,那么传输一个比特需要的时间是1/115200≈8.68us。

②数据位

        数据位表征通信中实际数据位的参数。当计算机发送一个信息包,其中需指定有效数据位,一般有5、7和8位。常规使用一般定义为8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。

③停止位

        停止位表征单包数据的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,在传输中可能存在不同步的情况,因此停止位不仅仅是表示传输的结束,同时也是校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。 

④奇偶校验位

       在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。串口校验分几种方式:

        无校验(no parity)
        奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。
        偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
        mark parity:校验位始终为1(不常用)。
        parity:校验位始终为0(不常用)。

⑤其他参数

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

        起始位:开始进行数据传输时发送方要先发出一个低电平’0’来表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。

        传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输。

1.2 ISSP调试工具使用

        Quartus ii提供了In-System Sources and Probes Editor调试工具,通过JTAG接口使用该工具可以驱动和采样内部节点的逻辑值。即通过 Sources功能来驱动fpga内部信号,通过Probes功能来探测内部节点的逻辑值。在系统设计还不完整的时候可以利用该工具模拟众多的输入激励。比如,可以通过该IP核来实时修改内部某些寄存器的值,而不用重新修改代码,再全编译,再下载调试。

        驱动流程:通过Quartus ii软件发送驱动信号,经由JTAG接口发送到FPGA芯片,通过FPGA的JTAG接口传送到In-System Sources and Probes Editor IP 核,通过该IP核的Sources端口来驱动内部信号。
        探测流程:通过Probes 端口输入探测信号到In-System Sources and Probes Editor IP 核,IP核通过JTAG接口将探测的信号发送到Quartus ii软件。

2、设计内容

        在Quartus II中,使用In system sources and probes editor工具,输入需要通过串口发送出去的数据,然后按下学习板上的按键0,则FPGA自动将所需要发送的数据发送出去。

        目标:实现FPGA通过UART协议发送数据。

        串口发送模块主要包含两个组件:①发送波特率生成模块,②数据发送模块。 

3、代码实现

module uart_byte_tx(
		clk,
		Rst_n,
		data_byte,
		send_en,
		baud_set,
		Rs232_tx,
		Tx_done,
		uart_state);
		
	input clk;
	input Rst_n;
	input [7:0]data_byte;
	input send_en;
	input [2:0]baud_set;
	
	output reg Rs232_tx;
	output reg uart_state;
	output reg Tx_done;
	
	reg bps_clk;//波特率时钟

	
	reg [15:0]div_cnt;//分频计数器
	
	reg [15:0]bps_DR;//分频计数最大值
	reg [3:0]bps_cnt;//波特率时钟计数器
	
	reg [7:0]r_data_byte;
	
	localparam START_BIT = 1'b0;
	localparam STOP_BIT = 1'b1;
	
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(send_en)
		uart_state <= 1'b1;
	else if(bps_cnt == 4'd11)
		uart_state <= 1'b0;
	else 
		uart_state <= uart_state;
	
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		r_data_byte <= 8'd0;
	else if(send_en)
		r_data_byte <= data_byte;
	else
		r_data_byte <= r_data_byte;
		
		
	//设置查找表
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		bps_DR <= 16'd5207;
	else begin 
		case(baud_set)
			0:bps_DR <= 16'd5207;
			1:bps_DR <= 16'd2603;
			2:bps_DR <= 16'd1301;
			3:bps_DR <= 16'd867;
			4:bps_DR <= 16'd433;
			default:bps_DR <= 16'd5207;
			endcase
	end
	
	//分频寄存器
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt <= 16'd0;
	else if(uart_state)
	begin 
		if(div_cnt == bps_DR)
		div_cnt <= 16'd0;
		else
		div_cnt <= div_cnt+1'b1;
	end
	else
		div_cnt <= 16'd0;
		
	//bps_clk产生	
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		bps_clk <= 1'b0;
	else if(div_cnt == 16'd1)
		bps_clk <= 1'b1;
	else 
		bps_clk <= 1'b0;
	
	//bps_cnt计数	
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		bps_cnt <= 4'd0;
	else if(bps_cnt == 4'd11)
		bps_cnt <= 4'd0;
	else if(bps_clk)
		bps_cnt <= bps_cnt + 1'b1;
	else
		bps_cnt <= bps_cnt;
		
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		Tx_done <= 1'b0;
	else if(bps_cnt == 4'd11)//比较器
		Tx_done <= 1'b1;
	else 
		Tx_done <= 1'b0;
		
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		Rs232_tx <= 1'b1;
		else begin
			case(bps_cnt)
				0:Rs232_tx <= 1'b1;
				1:Rs232_tx <= START_BIT;
				2:Rs232_tx <= data_byte[0];
				3:Rs232_tx <= data_byte[1];
				4:Rs232_tx <= data_byte[2];
				5:Rs232_tx <= data_byte[3];
				6:Rs232_tx <= data_byte[4];
				7:Rs232_tx <= data_byte[5];
				8:Rs232_tx <= data_byte[6];		
				9:Rs232_tx <= data_byte[7];
				10:Rs232_tx <= STOP_BIT;
				default:Rs232_tx <= 1'b1;
			endcase
		end
		

endmodule

testbench文件 

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_tx_tb;

	reg clk;
	reg Rst_n;
	reg [7:0]data_byte;
	reg send_en;
	reg [2:0]baud_set;
	
	wire Rs232_tx;
	wire uart_state;
	wire Tx_done;
	uart_byte_tx uart_byte_tx1(
		.clk(clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(baud_set),
		.Rs232_tx(Rs232_tx),
		.Tx_done(Tx_done),
		.uart_state(uart_state)
		);
		

	
	initial clk = 1;
	always #(`clk_period/2) clk = ~clk;
	
	initial begin
	Rst_n = 1'b0;
	data_byte = 8'd0;
	send_en = 1'd0;
	baud_set = 3'd4;
	#(`clk_period*20+1);
	Rst_n = 1'b1;
	#(`clk_period*50);
	data_byte = 8'haa;
	send_en = 1'd1;
	#(`clk_period);
	send_en = 1'd0;
	
	
	@(posedge Tx_done)
	 
	#(`clk_period*5000)
	data_byte = 8'h55;
	send_en = 1'd1;
	#(`clk_period);
	send_en = 1'd0;
	
	@(posedge Tx_done)
	 
	#(`clk_period*5000)
	$stop;
	end
endmodule

调用key_filter

module key_filter(clk,Rst_n,key_in,key_flag,key_state);
	
	input clk;
	input Rst_n;
	input key_in;
	output reg key_flag;
	output reg key_state;
	
	localparam
		IDEL 		= 4'b0001,
		FILTER0  = 4'b0010,
		DOWN 		= 4'b0100,
		FILTER1  = 4'b1000 ;
		
		reg [3:0]state;
		reg [19:0]cnt;
		reg en_cnt;
		reg cnt_full;//计数满标志信号
		reg key_tmp0,key_tmp1;
		wire pedge,nedge;
		
		
		//对外部输入的异步信号同步处理
		reg key_in_s0,key_in_s1;
		always @(posedge clk or negedge Rst_n)
			if(!Rst_n)
				begin
					key_in_s0 <= 1'b0;
					key_in_s1 <= 1'b0;
				end
			else 
			begin
				key_in_s0 <= key_in;
				key_in_s1 <= key_in_s0;
			end
			


		//使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步)的电平状态
		always @(posedge clk or negedge Rst_n)
		begin 
			if(!Rst_n)
			begin 
				key_tmp0 <= 1'b0;
				key_tmp1 <= 1'b0;
			end
			else
			begin 
				key_tmp0 <= key_in_s1;
				key_tmp1 <= key_tmp0;
			end
		end
		assign nedge = (!key_tmp0)&key_tmp1;
		assign pedge = key_tmp0&(!key_tmp1);
		
	always @(posedge clk or negedge Rst_n)
	
		if(!Rst_n)
		begin
			state <= IDEL;
			en_cnt <= 1'b0;
			key_flag <= 1'b0;
			key_state <= 1'b1;
			end
		else begin
			case(state)
				IDEL:
					begin
						key_flag <= 1'b0;
					if(nedge)begin
						state <= FILTER0;
						en_cnt<=1'b1;
						end
					else
						state <= IDEL;
					end
					
				FILTER0:
					if(cnt_full)begin 
						key_flag <= 1'b1;
						key_state <= 1'b0;
						state <= DOWN;
						en_cnt <= 1'b0;
					end
					else if(pedge)begin 
						state <= IDEL;
						en_cnt <= 1'b0;
					end
					else 
						state <= FILTER0;
						
				DOWN:
					begin
						key_flag <= 1'b0;
						if(pedge)begin 
						state <= FILTER1;
						en_cnt <= 1'b1;
					end
					else 
					begin
						state <= DOWN;
					end
					end
					
				FILTER1:
					if(cnt_full)begin 
						key_flag <= 1'b1;
						key_state <= 1'b1;
						state <= IDEL;
					end
					else if(nedge)
						begin
							en_cnt <= 1'b0;
							state <= DOWN;
						end
					else
						begin
							state <= FILTER1;
						end
				default:
				begin 
					state <= IDEL;
					en_cnt <= 1'b0;
					key_flag <= 1'b0;
					key_state <= 1'b1;
					
				end
				endcase

	end
	
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		cnt <= 20'd0;
	else if(en_cnt)
		cnt <= cnt+1'b1;
	else
		cnt <= 20'd0;
		
		
	always @(posedge clk or negedge Rst_n)
	if(!Rst_n)
		cnt_full <= 1'b0;
	else if(cnt == 999_999)
		cnt_full <= 1'b1;
	else
		cnt_full <= 1'b0;
		

endmodule
 

调用ip内核

set_global_assignment -name IP_TOOL_NAME "In-System Sources and Probes"
set_global_assignment -name IP_TOOL_VERSION "13.0"
set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) "issp.v"]
set_global_assignment -name MISC_FILE [file join $::quartus(qip_path) "issp_bb.v"]

编写顶层文件

module uart_tx_top(clk,Rst_n,Rs232_tx,key_in0,led);
	
	input clk;
	input Rst_n;
	input key_in0;
	output Rs232_tx;
	output led;
	
	
	wire send_en;
	wire [7:0]data_byte;
	wire key_flag0;
	wire key_state0;
	
	assign send_en=key_flag0 & key_state0;
	
	uart_byte_tx uart_byte_tx1(
		.clk(clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte),
		.send_en(send_en),
		.baud_set(3'd0),
		.Rs232_tx(Rs232_tx),
		.Tx_done(),
		.uart_state(led)
		);
		
	key_filter key_filter0(
		.clk(clk),
		.Rst_n(Rst_n),
		.key_in(key_in0),
		.key_flag(key_flag0),
		.key_state(key_state0)
	);
	
	issp issp1(
		.probe(),
		.source(data_byte));
	
	
endmodule

4、仿真

引脚分配

板机验证

串口调试

 

  • 4
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发光中请勿扰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值