FPGA学习[3]——异步串口的发送实验

疫情未散,在家里开始投入新的一年的学习。去年零零散散研究了一点FPGA,今年打算系统学习一下。

串口通信是单片机、FPGA学习逃不过的一课,也是很好的FPGA入门实验。

串口数据包格式

对于一个无校验位的串口数据包,包含如下几个部分

起始位[0] Data[1] Data[2] Data[3] Data[4] Data[5] Data[6] Data[7] 停止位[1]

当串口数据发送开始,TX拉低表示一个起始位,紧接着从低到高 依次发送8位数据位,数据发送完毕以后释放TX,表示数据结束。

波特率与产生

由于笔者的FPGA开发板使用外部50MHz晶振提供时钟,因此 f c l k = 50 M H z f_{clk} = 50MHz ,通讯实验使用的波特率位115200bps,这意味着每一秒钟需要发送115200个数据位。

不同于单片机程序设计,FPGA做不到直接延时,需要使用累加计数控制每一位发送的时间,波特率技术器的值可以用下式计算
c o u n t b a u d = f c l k b a u d count_{baud}=\frac{f_{clk}}{baud}
式中 c o u n t b a u d count_{baud} 就是累加值。

累加的方式体现了分频的思想高速运行的逻辑门不可能“停下来”等那么几个毫秒

Verilog HDL实现

1.使能信号上升沿的提取

reg en1, en2;
wire tx_en;
assign tx_en = en1 & (~en2);
always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		en1<=1'b0;
		en2<=1'b0;
	end
	else begin  //打两拍,提取上升沿
		en1<=en;
		en2<=en1;
	end
end

这里使能信号采用了打两拍的方式,连续的非阻塞赋值使得en1、en2保持了跳变沿前后的值,因此对于上升沿检测,只需要
   e n 1      &    e n 2 \;en1\;\ \& \;\overline{en2}
反之检测下降沿,只需要
   e n 2      &    e n 1 \;en2\;\ \& \;\overline{en1}

为什么要使用边沿信号而非电平信号?
边沿信号可以避免数据装载与发送速率不同步带来的数据发送异常。

2.开始标志位

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		tx_start <= 1'b0;
	end
	else begin
		if(tx_en) begin
			tx_data <= data;//装载数据
			tx_start <= 1'b1;//开始
		end 
		else begin
			if(data_count == 5'd9 && (count == baud_count)) begin  //最后一位停止位
				tx_start <= 1'b0;
			end 
			else begin
				tx_start <= tx_start;
			end
		end
	end
end

当检测到使能信号上升沿以后,发送标志位tx_en被置位,同时数据装入八位寄存器tx_data。当数据传输至最后一位(停止位)时,发送标志位复位。

data_count == 5'd9 && (count == baud_count)

这句话的使用能够保证发送标志位在整个数据传输期间都为高电平。

3.波特率计数器

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		count <= 16'b0;
	end
	else begin
		if(tx_start) begin
			if(count < baud_count) begin
				count <= count + 1'b1;
			end
			else begin
				count <= 16'b0;
			end 
		end 
		else begin
			count <= 16'b0;
		end 
	end
end

波特率计数器其实就是一个分频器,用分频代替C语言中的Delay。

4.数据计数器

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		data_count <= 5'b0;
	end
	else begin
		if(tx_start) begin
			if(count == baud_count - 1) begin
				if(data_count < 5'd9) begin
					data_count <= data_count + 1'b1;
				end
				else begin
					data_count <= 5'b0;
				end 
			end
			else begin
				data_count <= data_count;
			end 
		end
		else begin
			data_count <= 5'b0;
		end 
	end
end

数据计数器记录了发送数据的数量。

5.并行转串行

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		tx <= 1'b1;
	end
	else begin
		if(tx_start) begin
			case(data_count)
				5'd0:tx <= 1'b0;
				5'd1:tx <= tx_data[0];
				5'd2:tx <= tx_data[1];
				5'd3:tx <= tx_data[2];
				5'd4:tx <= tx_data[3];
				5'd5:tx <= tx_data[4];
				5'd6:tx <= tx_data[5];
				5'd7:tx <= tx_data[6];
				5'd8:tx <= tx_data[7];
				5'd9:tx <= 1'b1;
			endcase 
		end
		else begin
			tx <= 1'b1;
		end 
	end
end

这是串口发送中最重要的一部分。

为什么数据计数器不在这段程序中完成?
对于每一个always块而言他们都是并行执行的,而always块中的程序则是顺序执行的,对于每一个并行执行的代码而言只需要各司其职在合适的时候产生合适的电平信号。将多个不同的功能都在同一个always块中完成无疑增加了逻辑复杂度,使得代码难以编写。

数据测试模块

module tx_test(
input 					clk,
input 					nrst,

output reg [7:0]		data,
output reg 				en
);

reg [3:0] 				data_count;
reg [19:0] 				div;

parameter d0 = "H";
parameter d1 = "e";
parameter d2 = "l";
parameter d3 = "l";
parameter d4 = "o";
parameter d5 = "\n";
parameter div_count = 20'd100_0000;

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		div <= 20'b0;
	end 
	else begin
		if(div<div_count) begin
			div <= div + 20'b1;
		end 
		else begin
			div <= 20'b0;
		end 
	end 
end 

always@(posedge clk or negedge nrst) begin
	if(!nrst) begin
		data_count <= 4'b0;
	end 
	else begin
		if(div == div_count - 1'b1) begin
			if(data_count < 4'd5)	data_count <= data_count + 4'b1;
			else data_count <= 4'b0;
		end
		else begin
			data_count <= data_count;
		end
	end
end 
 
always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		en <= 1'b0;
	end 
	else begin
		case(div)
			20'd0:en <= 1'b1;
			div_count - 20'b1:en <= 1'b0;
			default:en <= en;
		endcase
	end
end 

always @(posedge clk or negedge nrst) begin
	if(!nrst) begin
		data <= 8'b0;
	end 
	else begin
		case(data_count)
			4'd0:data <= d0;
			4'd1:data <= d1;
			4'd2:data <= d2;
			4'd3:data <= d3;
			4'd4:data <= d4;
			4'd5:data <= d5;
			default:data <= 8'b0;
		endcase 
	end
end 
endmodule 

这部分程序几乎与发送部分采用了同样的逻辑,代码比较类似。

测试

在这里插入图片描述
设置好波特率与校验位、停止位,串口打印出Hello字符串,异步串口发送实验成功。

发布了17 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/little_cats/article/details/104403290