FPGA串口通信实验

串口通信是FPGA较为基础的一个实验,本人在初学FPGA后决定将其整理一下并进一步加强自身理解。

串口简介

串口也称串行通信接口或串行通讯接口,是采用串行通信方式的扩展接口。目前人们使用的所有计算机操作系统都支持串行接口,其运用较为广泛。

串行指的是其将发送的数据一位一位的通过数据线传给对方,因此其通信只需要tx、rx两根数据线,从而大大降低了成本,较适合远距离通信。但也正是因为其串行的特点导致其通信速度较慢。

串口通信简介

由于其为串行通信,因此串口传送一个字节的数据要分为8次,并且从低到高按顺序一位一位进行传送。因此双方在传送数据时每一位都要有一个固定的时间间隔,以便能正确识别出两位数据;同时接收方也要能确定一个字节数据的开始于结束,因此双方还必须约定开始位与结束位。

常用的串行通信方式分为同步通信和异步通信。

同步通信

同步通信(SYNC:synchronous data communication)是指在约定的通信速率下,发送端和接收端的时钟信号频率和相位始终保持一致(同步),这样就保证了通信双方在发送和接收数据时具有完全一致的定时关系。

同步通信把许多字符组成一个信息组(信息帧),每帧的开始用同步字符来指示,一次通信只传送一帧信息。在传输数据的同时还需要传输时钟信号,以便接收方可以用时针信号来确定每个信息位。

同步通信的优点是传送信息的位数几乎不受限制,一次通信传输的数据有几十到几千个字节,通信效率较高。同步通信的缺点是要求在通信中始终保持精确的同步时钟,即发送时钟和接收时钟要严格的同步(常用的做法是两个设备使用同一个时钟源)。

异步通信

异步通信(ASYNC:asynchronous data communication),又称为起止式异步通信,是以字符为单位进行传输的,字符之间没有固定的时间间隔要求,而每个字符中的各位则以固定的时间传送。

在异步通信中,收发双方取得同步是通过在字符格式中设置起始位和停止位的方法来实现的。具体来说就是,在一个有效字符正式发送之前,发送器先发送一个起始位,然后发送有效字符位,在字符结束时再发送一个停止位,起始位至停止位构成一帧。停止位至下一个起始位之间是不定长的空闲位,并且规定起始位为低电平(逻辑值为0),停止位和空闲位都是高电平(逻辑值为1),这样就保证了起始位开始处一定会有一个下跳沿,由此就可以标志一个字符传输的起始。而根据起始位和停止位也就很容易的实现了字符的界定和同步。

显然,采用异步通信时,发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,可以互不同步。

串口接头

常用的串口接头有两种,一种是9针串口(简称DB-9),一种是25针串口(简称DB-25)。每种接头都有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。9针串口的外观如下图所示。
Alt
两种接口的管脚说明如下图所示:

Alt

协议标准

常用的串行通信接口标准有RS-232C、RS-422、RS-423和RS-485。其中,RS-232C作为串行通信接口的电气标准定义了数据终端设备(DTE:data terminal equipment)和数据通信设备(DCE:data communication equipment)间按位串行传输的接口信息,合理安排了接口的电气信号和机械要求,在世界范围内得到了广泛的应用。下面简单介绍下RS-232C的相关特性。

电气特性

RS-232C对电器特性、逻辑电平和各种信号功能都做了规定,如下:

在TXD和RXD数据线上:

(1)逻辑1为-3~-15V的电压

(2)逻辑0为3~15V的电压

在RTS、CTS、DSR、DTR和DCD等控制线上:

(1)信号有效(ON状态)为3~15V的电压

(2)信号无效(OFF状态)为-3~-15V的电压

由此可见,RS-232C是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。

时序介绍

起始位: 在发送一个字节的数据前先发出一个逻辑“0”信号,表示传输的开始。
数据位: 在起始位之后所接的为数据位,注意串口通信是从低位开始传输,由低到高发送数据位。
奇偶校验位: 可选择是否添加奇偶校验,此位可让接收端验证收到的数据是否有错误。奇校验或偶校验都是判断“1”的个数为奇数或偶数。
停止位: 它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位: 处于逻辑“1”的状态。

发送过程时序图如下图所示。
Alt
TX为发送数据的数据线;SCLK为时钟线,当通信双方约定好波特率之后,双方都将按照此波特率进行传输数据,因此SCLK为FPGA端与接收端进行时钟对准的时钟线,其周期将根据波特率的变化而变化。

在传输过程中,首先将TX线拉低,此时即发送起始位信号,紧随其后的就是8位数据位,从低到高的数据依次发送,最后将TX拉高传输停止位。

设计思想

通过上文分析的时序图后可知数据传输需要SCLK时钟线,因此我们需要一个计数器来根据波特率来产生SCLK信号;由于在不同的时间需要传输不同的数据位因此我们还需要一个计数器来计数已发送的数据bit,并根据此来传输对应的数据。

发送端代码:

module uart_tx(
	input			clk,
	input			rst,
	input			data_rdy,
	input	[7:0]	data,
	
	output reg	txd,
	output reg	tx_busy
);

	parameter	BAUD = 9600;
	parameter	CLOCK= 50_000_000;
	localparam	TIME_BIT = CLOCK / BAUD ;
	
	wire			tx_start;
	
	reg				flag;
	reg	[12:0]	cnt_clk;
	reg	[ 3:0]	cnt_length;
	
	always@(posedge clk or negedge rst)begin //计时一个bit
		if(!rst)begin
			cnt_clk <= 13'd0;
		end
		else if(flag == 1'b1)begin
			if(cnt_clk == TIME_BIT - 1)begin
				cnt_clk <= 13'd0;
			end
			else begin
				cnt_clk <= cnt_clk + 1'b1;
			end
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			cnt_length <= 4'd0;
		end
		else begin
			if(cnt_clk == TIME_BIT - 1 && flag == 1'b1 && cnt_length == 10 - 1)begin
				cnt_length <= 4'd0;
			end
			else if(cnt_clk == TIME_BIT - 1 && flag == 1'b1)begin
				cnt_length <= cnt_length + 1'b1;
			end
		end
	end
	
	assign tx_start = data_rdy & (!flag);
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			flag <= 1'b0;
		end
		else if(tx_start)begin
			flag <= 1'b1;
		end
		else if(cnt_clk == TIME_BIT - 1 && flag == 1'b1 && cnt_length == 10 - 1)begin
			flag <= 1'b0;
		end
	end
	
	always@(*)begin
		if(!rst)begin
			tx_busy = 1'b0;
		end
		else if(flag || data_rdy)begin
			tx_busy = 1'b1;
		end
		else begin
			tx_busy = 1'b0;
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			txd <= 1'b1;
		end
		else if(flag == 1'b1)begin
			case(cnt_length)
				4'd0:begin
					txd <= 1'b0;
				end
				1,2,3,4,5,6,7,8:begin
					txd <= data[cnt_length - 1];
				end
				9:begin
					txd <= 1'b1;
				end
			endcase
		end
	end
	
endmodule

接收端代码

module uart_rx(
	input				clk,
	input				rst,
	input				rxd,
	
	output reg[7:0]	data,
	output reg			data_rdy
);

	parameter	BAUD = 9600;
	parameter	CLOCK= 50_000_000;
	localparam	TIME_BIT = CLOCK / BAUD ;
	
	reg				rxd_d1;
	reg				rxd_d2;
	wire				rxd_negedge;
	reg				flag/*synthesis noprune*/;
	reg	[12:0]	cnt_clk;
	reg	[ 3:0]	cnt_length;
	reg	[ 7:0]	data_r;
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			rxd_d1 <= 1'b1;
			rxd_d2 <= 1'b1;
		end
		else begin
			rxd_d1 <= rxd;
			rxd_d2 <= rxd_d1;
		end
	end
	assign rxd_negedge = rxd_d2 & (!rxd_d1);  //检测下降沿,即为起始位
	
	always@(posedge clk or negedge rst)begin //计时一个bit
		if(!rst)begin
			cnt_clk <= 13'd0;
		end
		else if(flag == 1'b1)begin
			if(cnt_clk == TIME_BIT - 1)begin
				cnt_clk <= 13'd0;
			end
			else begin
				cnt_clk <= cnt_clk + 1'b1;
			end
		end
		else begin
			cnt_clk <= 13'd0;
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			cnt_length <= 4'd0;
		end
		else if(flag == 1'b1)begin
			if(cnt_clk == TIME_BIT - 1 && flag == 1'b1 && cnt_length == 10 - 1)begin
				cnt_length <= 4'd0;
			end
			else if(cnt_clk == TIME_BIT - 1 && flag == 1'b1)begin
				cnt_length <= cnt_length + 1'b1;
			end
		end
		else begin
			cnt_length <= 4'd0;
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			flag <= 1'b0;
		end
		else begin
			if(rxd_negedge)begin
				flag <= 1'b1;
			end
			else if(cnt_clk == TIME_BIT - 2 && cnt_length == 10 - 1)begin
				flag <= 1'b0;
			end
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			data_r <= 8'd0;
		end
		else if(flag == 1'b1 && cnt_clk == 2604 - 1)begin //在中间读取数据
			case(cnt_length)
				1,2,3,4,5,6,7,8:begin
					data_r[cnt_length - 1] <= rxd_d2;
				end
				default:;
			endcase
		end
	end
	
	always@(posedge clk or negedge rst)begin
		if(!rst)begin
			data_rdy <= 1'b0;
			data <= 8'd4;
		end
		else begin
			if(cnt_clk == 2604 - 1 && flag == 1'b1 && cnt_length == 10 - 1)begin
				data_rdy <= 1'b1;
				data <= data_r;
			end
			else begin
				data_rdy <= 1'b0;
			end
		end
	end
	


endmodule

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值