Zynq开发实践(FPGA之uart接收)

发布于:2025-09-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        uart之所以被称之为最简单的通信协议,一方面是因为它没有时钟信号,另外一方面是因为接收和发送是两条线分开来的。它不像一些协议,例如iic、sdio等等,数据位既可以用做输入、也可以用做输出。uart这样设计起来,就会简单很多。不过,很多sdio协议,也支持spi输入输出,当然这是后话了。

1、uart接收的顺序

        前面说过,uart的发送一般是10个bit,即起始位、数据位和停止位,默认没有校验位。起始位就是从1拉到0,而停止位则是恢复为1。数据位的发送,是从低到高依次发送。那么这里接收其实也是一样的,首先接收起始位,然后是数据位,最后是停止位。数据位也是从低到高接收。

2、uart接收的时机

        因为没有clock作为保护,所以这里这能靠数据的边沿触发作为接收的一个时机。平时uart的tx都是高电平,因此接收的时候如果发现从高到低拉低,那就是代表数据开始进行发送,这边就要做好接收的准备了。

3、uart接收的波特率

        即使双方商量好了接收的起始时间,但是接收的速度还是没有办法定下来的,因此这种发送、接收的速度只能靠提前的约定来进行处理。比如大家都约定好,发送、接收的帧率是4800、9600,或者是115200。如果没有按照约定的频率来发送、接收,就会出现两种情况,要么采样慢了漏掉数据,要么是采样快了,采样了多余的数据。

4、采样的时机

        实际uart的采样频率要比fpga时钟慢很多,一个周期内理论上什么时候采样都可以。不过为了保证数据的稳定性,我们一般还是在采样周期的中间那个点,去获取对应的bit信息,这是最好的处理方式。

5、uart接收的实现

        本身uart的实现不复杂。需要注意的主要就是状态机和开始接收、接收时机的处理。首先是设计好状态机,

always@(posedge clk or negedge rst)
	if(!rst)
		state <= 3'h0;
	else
		state <= next_state;

always@(*)
	if(!rst)
		next_state = 3'h0;
	else if(state == 3'h0 && (rx2 && !rx1))
		next_state = 3'h1;
	else if(state == 3'h1 && counter == HALF_CYCLE) // skip first data
		next_state = 3'h2;
	else if(state == 3'h2 && counter == HALF_CYCLE &&  num == 7) // middle 8 data
		next_state = 3'h3;
	else if(state == 3'h3 && counter == HALF_CYCLE) // last data
		next_state = 3'h0;
	else
		next_state = state;

        处理好了状态机,就要弄清楚什么时候开始接收,并且接收的周期是多少,

always@(posedge clk or negedge rst)
	if(!rst) begin
		rx1 <= 1'b1;
		rx2 <= 1'b1;
	end 
	else begin
		rx1 <= rx;
		rx2 <= rx1;
	end

always @(posedge clk or negedge rst)
	if(!rst) // reset state
		counter <= 16'h0;
	else if(state != 3'h0) begin // first data and middle 8 data
		if((counter == FULL_CYCLE) || (state == 3'h3 && counter == HALF_CYCLE))
			counter <= 16'h0;
		else
			counter <= counter + 1;
	end

        这些都定下来之后,最后就是接收数据、接收数量以及提示什么时候数据可用。

always @(posedge clk or negedge rst)
	if(!rst)
		num <= 3'h0;
	else if(state == 3'h2 && counter == HALF_CYCLE) // total 8 data, select data from the middle of the wave
		num <= num + 1;

always @(posedge clk or negedge rst)
	if(!rst)
		out_data <= 8'h0;
	else if(state == 3'h2 && counter == HALF_CYCLE) // save data to reg
		out_data[num] <= rx2;

always @(*)
	if(!rst)
		data_valid = 1'b0;
	else if(state == 3'h3 && counter == HALF_CYCLE)
		data_valid = 1'b1;
	else
		data_valid = 1'b0;

        一切都没有问题之后,就可以添加testbench,做好仿真测试即可。这是准备的testbench文件,最重要的部分就是rx下降沿的设计,因为只有有了明显的下降沿,才会触发状态机的运转。

`timescale 1ns/1ps

module test();

reg clk;
reg rst;
reg rx;
wire data_valid;
wire[7:0] data;

initial begin 
	clk = 0;
	rst = 1;
	rx = 1;
	#10 rst = 0;
	#30 rst = 1;
	#50 rx = 0;
	#300 rx = 1;
	#10000 $finish;
end

initial begin
	while(1)	
		clk = #5 ~clk;
end

uart_rx uart_rx(
	.clk(clk),
	.rst(rst),
	.rx(rx),
	.data_valid(data_valid),
	.out_data(data)
);

initial begin
	$dumpfile("hello.vcd");
	$dumpvars(0, test);
end

endmodule

        没有什么问题之后,就可以直接利用iverilog、vvp、gtkwave编译、仿真和查看波形了,

        


网站公告

今日签到

点亮在社区的每一天
去签到