官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > 教程中心 > 案例中心 > 至简设计案例 >

【案例】串口回环工程

发布时间:2021-06-18   作者:admin 浏览量:

本文为明德扬原创及录用文章,转载请注明出处!

案例编号:000900000024


1.1 总体设计


1.1.1 概述

串行接口简称串口,也称串行通讯接口或者串行通信接口,是采用串行通信方式的扩展接口。串行接口是指数据一位一位地顺序传送 ,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适合用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通信方式称作串行通信。串行通信的特点是:数据位的传送,按位顺序进行,最少只需要一根传输线即可完成;成本低,但传送速度慢。串行通信的距离可以从几米到几千米;根据信息的传送方向,串行通信可以进一步分为单工、半双工和全双工三种。

串口的出现是在 1980 年前后,数据传输率是 115kbps~230kbps。串口出现的初期是为了实现连接计算机外设的目的,初期串口一般用来连接鼠标和外置 Modem 以及老式摄像头和写字板等设备。

串口也可以应用于两台计算机(或设备)之间的互联及数据传输。由于串口不支持热插拔及传输速率较低,部分新主板和大部分便携电脑已开始取消该接口。串口多用于工业控制和测量设备以及部分通信设备中。


1.1.2 设计目标

本练习要求实现串口回环功能,具体功能要求如下:

1、 上位机于 FPGA 之间通过串口进行通信,规定波特率为 9600,数据位为 8bit,无奇偶校

验位,停止位为 1。

2、 FPGA 内部有一个可保存 128 字节的 FIFO。

3、 FPGA 从上位机接收到数据后,将数据保存到 FIFO 中。

4、 当 FIFO 保存的数据超过 60 个数据时,启动发送数据操作:读取 FIFO 的数据,将数据返

回给上位机。

5、 在启动发送数据操作过程中,如果 FIFO 变空,结束发送操作,等待下一次的启动。

注意:上位机接收到的数据与发送的数据相同,不能多也不能少。


1.1.3 系统结构框图

系统结构框图如下所示:

「每周案例」至简设计系列_串口回环工程


1.1.4 模块功能

➢ 串口接收模块实现功能

1、 将输入数据进行同步化处理。

2、 解析串口时序,将有效数据进行串并转换。

➢ 数据处理模块实现功能

1、 包含一个 FIFO,用来存储接收到的数据。

2、 满足发送条件后,读出 FIFO 的数据送给下游模块。

➢ 串口发送模块实现功能

1、 将接收到的数据进行并串转换,发送给上位机。


1.1.5 顶层信号

「每周案例」至简设计系列_串口回环工程


1.1.6 参考代码

下面是本工程的顶层代码:

1.	module com_prj(  
2.	    rst_n  ,  
3.	    clk    ,  
4.	    rx_uart,  
5.	    tx_uart   
6.	);  
7.	parameter          BPS    = 5208;  
8.	  
9.	input               rst_n       ;  
10.	input               clk         ;  
11.	input               rx_uart     ;  
12.	output              tx_uart     ;  
13.	  
14.	wire  [7:0]         uart_in     ;  
15.	wire                uart_in_vld ;  
16.	wire  [7:0]         uart_out    ;  
17.	wire                uart_out_vld;  
18.	wire                rdy         ;  
19.	  
20.	uart_rx#(.BPS(BPS))  uart_rx(  
21.	                 .clk     (clk        ),  
22.	                 .rst_n   (rst_n      ),  
23.	                 .din     (rx_uart    ),  
24.	                 .dout    (uart_in    ),  
25.	                 .dout_vld(uart_in_vld)  
26.	             );  
27.	uart_tx#(.BPS(BPS))  uart_tx(  
28.	                 .clk     (clk         ),  
29.	                 .rst_n   (rst_n       ),  
30.	                 .din     (uart_out    ),  
31.	                 .din_vld (uart_out_vld),  
32.	                 .rdy     (rdy         ),  
33.	                 .dout    (tx_uart     )  
34.	             );  
35.	data_handle  u_data_handle(  
36.	                 .clk     (clk         ),  
37.	                 .rst_n   (rst_n       ),  
38.	                 .din     (uart_in     ),  
39.	                 .din_vld (uart_in_vld ),  
40.	                 .dout    (uart_out    ),  
41.	                 .dout_vld(uart_out_vld),  
42.	                 .rdy     (rdy         )     
43.	                  );           
44.	           
45.	endmodule 


1.2 串口接收模块设计


1.2.1 接口信号


「每周案例」至简设计系列_串口回环工程


1.2.2 设计思路

➢ UART 异步串行口简介

数据通信的基本方式可分为并行通信和串行通信两种:

并行通信:

是指利用多条数据线将一个资料的各位同时传送。特点是传输速度快,适合用于短距

离通信,但要求通信速率较高的应用场合。

串行通信:

是指利用一条传输线将资料一位位的顺序传送。特点是通信线路简单,利用简单的线

缆就可以实现通信,减低成本,适用于远距离通信,但传输速度慢的应用场合。

在 FPGA 看来,串口只有两根线,一根线用于接收,一根线用于发送。

「每周案例」至简设计系列_串口回环工程

➢ UART 异步串行口的传输格式

异步通信以一个字符为传输单位,通信中两个字符间的时间间隔是不固定的,然而在同一个字符

中的两个相邻位代码间的时间间隔是固定的。

通信协议(通信规程):是指通信双方约定的一些规则。在使用异步串行口传送一个字符的信息

时,对资料格式有如下规定:规定有空闲位、起始位、数据位、奇偶校验位、停止位。通讯时序图如

下:

「每周案例」至简设计系列_串口回环工程

每一个数据位的宽度等于传送波特率的倒数。微机异步串行通信中,常用的波特率为 110,150,

300,600,1200,2400,4800,9600 ,19200,38400,115200 等。代表每个码元传输的速

率。在二进制数据传输中,波特率和比特率相同都是为每个比特数据传输的速率,其倒数为 1bit

数据的宽度,也就是 1bit 数据持续的时间。确定这一时间,就可用 FPGA 构造计数器实现比特

周期的延时,从而实现特定波特率的数据传输。

➢ 开始前,线路处于空闲状态,送出连续"1"。传送开始时首先发一个"0"作为起始位,

然后出现在通信线上的是字符的二进制编码数据。

➢ 每个字符的数据位长可以约定为 5 位、6 位、7 位或 8 位。

➢ 后面是奇偶校验位,顾名思义,检验位适用于数据校验。分为奇校验和偶校验。奇校验需

要保证传输数据总共有奇数个逻辑高电平,偶校验则需要保证传输数据有偶数个逻辑高电

平。即"奇偶"指的是数据中(包括该校验位)1 的个数。例如:传输的数据是 01000011,

如果校验方式是奇校验,则校验位为 0 ,若是偶校验则校验位是 1.传输中校验位不是必须

项,双方可以约定不要校验位,或者使用奇/偶校验方式。

➢ 最后是表示停止位的"1"信号,这个停止位可以约定持续 1 位、1.5 位或 2 位的时间宽

度。由于每台设备有其自己的时钟,很可能再通信中两台设备间出现小小的不同步。因此

停止位不仅仅是表示传输的结束,并且提供一个校正时钟同步的机会,让从机可以正确的

识别下一轮数据的起始位。停止位的位数越多,不同时钟同步的容忍程度就越大,但是数

据传输速率也越慢。

➢ 至此一个字符传送完毕,线路又进入空闲,持续为"1"。经过一段随机的时间后,下一个

字符开始传送才又发出起始位。

➢ 架构设计

上位机发送的数据会按照上图所示串口的时序图的顺序过来,因此我们需要按照其对应的格式进

行接收。发送 8 位数据 data 前,串口接收数据线会先变 0 并持续一段时间(起始位),然后发送 da

ta[0]、data[1],以此类推直至发送完 data[7],发送每位数据时都会持续一段时间,发送完毕后数据

线会变为 1 并持续一段时间(结束位)。至此,完成数据的发送。可以看出每段有效信号的开始前和

结束后,都会有特殊信号:有效数据开始前会有一段变 0 的信号,用以告知 FPGA 开始传送数据;

结束后会有一段变 1 的信号,告知 FPGA 此数据传送结束。

由上面时序分析可知,当我们检测到数据线从高电平(空闲位)变为低电平(起始位)就表示开

始数据的传输了,因此需要进行下降沿的检测,检测方法如下:

「每周案例」至简设计系列_串口回环工程

该检测方法主要利用 D 触发器打拍来实现。

din:输入串口数据。

din_ff0:输入串口数据经过一级缓存之后的信号,目的是为了将异步信号同步化。

din_ff1:输入串口数据经过二级缓存之后的信号,目的是为了减少亚稳态造成的影响。

din_ff2:输入串口数据经过三级缓存之后的信号,目的是为了检测信号的下降沿。

flag_add:接收状态指示信号,当在时钟上升沿检测到 din_ff1 等于 0 并且 din_ff2 等于 1 的时候

表示检测到了下降沿,此时将 flag_add 信号拉高,表示进入到接收状态。

根据串口时序,可以提出两个计数器的架构,如下图所示:

「每周案例」至简设计系列_串口回环工程

该架构由两个计数器组成:时钟计数器 cnt 和数据计数器 data_num。

时钟计数器 cnt:用于计数发送 1bit 数据所需要的时间,加一条件为 flag_add,表示进入接收状

态时就开始计数;结束条件为数 5208 个,开发板晶振时钟是 50M,对应周期为 20ns,每位数据的

持续时间为 104166ns/20ns=5208.3 个时钟周期,近似为 5208 个时钟周期。

数据计数器 data_num:用于对接收的每一比特数据进行计数,加一条件为 end_cnt,表示接收

到 1bit 的数据就加一;结束条件为数 9 个,一个起始位加上八个数据位,共 9 位,数完就清零。

➢ 注意事项

1、 串口接收模块中的数据计数器一定不要把停止位也数上去,否则在接受的数据的时

候会出错。感兴趣的同学可以使用 signaltap 抓取信号进行分析(仿真没有用)。

2、 由于工程中串口的每 1bit 数据传输所需要的时间是近似值,也就是存在误差,因此

串口接收在采集数据的时候,需要在数据的中间时刻进行采样,这样才能保证数据

的正确性。


1.2.3 参考代码

下面是使用明德扬的计数器模板等写出来的本模块代码。

1.	always @ (posedge clk or negedge rst_n) begin
2.		if(!rst_n) begin
3.			din_ff0 <= 1'b1;
4.	        din_ff1 <= 1'b1;
5.	        din_ff2 <= 1'b1;
6.		end
7.		else begin
8.			din_ff0 <= din;
9.	        din_ff1 <= din_ff0;
10.	        din_ff2 <= din_ff1;
11.		end
12.	end
13.	
14.	always @ (posedge clk or negedge rst_n)begin
15.		if(!rst_n) begin
16.			flag_add <= 1'b0;
17.		end
18.		else if(din_ff2 & ~din_ff1) begin		
19.			flag_add <= 1'b1;	
20.		end
21.		else if(data_num==4'd8&&end_cnt) begin		
22.			flag_add <= 1'b0;	
23.		end
24.	end
25.	
26.	always @ (posedge clk or negedge rst_n)begin
27.	    if(!rst_n)begin
28.	        cnt <= 0;
29.	    end
30.	    else if(add_cnt)begin
31.	        if(end_cnt)begin
32.	            cnt <= 0;	 
33.	        end
34.	        else begin
35.	            cnt <= cnt+1'b1; 
36.	        end
37.	    end
38.	    else begin
39.	        cnt <= 0; 
40.	    end
41.	end
42.	assign add_cnt = flag_add;
43.	assign end_cnt = add_cnt && cnt == BPS-1;
44.	
45.	
46.	
47.	always @(posedge clk or negedge rst_n) begin 
48.	    if (rst_n==0) begin
49.	        data_num <= 0; 
50.	    end
51.	    else if(add_data_num) begin
52.	        if(end_data_num)
53.	            data_num <= 0; 
54.	        else
55.	            data_num <= data_num+1 ;
56.	   end
57.	end
58.	assign add_data_num = end_cnt;
59.	assign end_data_num = add_data_num  && data_num == 9-1 ;
60.	
61.	always @ (posedge clk or negedge rst_n)begin
62.		if(!rst_n) begin
63.			dout <= 8'd0;
64.		end
65.		else if(add_cnt && cnt==BPS_P-1 && data_num!=0) begin		
66.		    dout<={din,{dout[7:1]}};
67.		end
68.	    else begin
69.	        dout<=dout; 
70.	    end
71.	end
72.	
73.	always @ (posedge clk or negedge rst_n)begin
74.		if(!rst_n) begin
75.			dout_vld <= 1'b0;
76.		end
77.	    else if(add_data_num && data_num == 4'd8) begin		
78.			dout_vld <= 1'b1;	
79.		end
80.		else begin	
81.	        dout_vld <= 1'b0;			
82.		end
83.	end 


1.3 数据处理模块设计


1.3.1 接口信号

「每周案例」至简设计系列_串口回环工程


1.3.2 设计思路

➢ FIFO 原理简介

FIFO(first input first output),即先进先出的数据缓存器,本质上还是 RAM,与普通存储器的区别:没有外部读写地址线,这样使用起来非常简单。特点就是只能顺序写入数据,顺序读出数据,其数据地址由内部读写指针自动加一完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

FIFO 根据读写时钟的区别,分为同步 FIFO 和异步 FIFO。同步 FIFO 读写共用一个相同的时钟;

异步 FIFO 读写可以使用不同的时钟。由于异步 FIFO 内部存在同步化电路,因此资源占用要比同步FIFO 大。

再 FPGA 中,FIFO 的使用主要有两种场合,应用于缓存和跨时钟域处理。FIFO 本身就是存储器,自然可以用作数据的缓存,只不过在选择上需要跟普通的 RAM 区分开。又由于异步 FIFO 的存

在,可以使得其写侧和读侧时钟不同,因此又可用作跨时钟域处理。

更多关于 FIFO 的内容,可以关注明德扬 FIFO 专题课,或者是明德扬免费的 FIFO 课程

➢ 架构设计

按照功能要求,本模块需要对接收的数据进行存储,当存够 60 个的时候开始发送,下面是本模

块的架构图。

「每周案例」至简设计系列_串口回环工程

本模块大致分为三部分:FIFO 的写控制电路、FIFO、FIFO 的读控制电路。

写控制电路:只要输入数据有效,就将数据写进 FIFO,主要信号为写数据 data 和写使能 wrreq。

FIFO ip 核:存储数据。

读控制电路:当 FIFO 内部有 60 个数据、FIFO 非空并且下游模块准备好接收数据的时候,就开

始读。主要信号为输出数据 q、FIFO 有效数据量指示信号 usedw、空指示信号 empty、读使能 rdreq。


1.3.3 参考代码

84.     assign   data  = din      ;
85.	    assign   wrreq = din_vld ;
86.	
87.	
88.	    my_fifo u_my_fifo (
89.		              .clock(clk  ),
90.		              .data (data ),
91.		              .rdreq(rdreq),
92.		              .wrreq(wrreq),
93.		              .empty(empty),
94.		              .q     (q    ),
95.		              .usedw(usedw) 
96.	              );
97.	
98.	  
99.	    always@(*)begin
100.	        if(rd_flag && empty==1'b0 && rdy)
101.	            rdreq = 1'b1;
102.	        else
103.	            rdreq = 1'b0;
104.	    end
105.	
106.	    always@(posedge clk or negedge rst_n)begin
107.	        if(rst_n==1'b0)begin
108.	            rd_flag <= 1'b0;
109.	        end
110.	        else if(rd_flag==1'b0 && usedw>=60) begin
111.	            rd_flag <= 1'b1;
112.	        end
113.	        else if(rd_flag==1'b1 && empty)begin
114.	            rd_flag <= 1'b0;
115.	        end
116.	    end
117.	
118.	    always  @(posedge clk or negedge rst_n)begin
119.	        if(rst_n==1'b0)begin
120.	            dout <= 0;
121.	        end
122.	        else begin
123.	            dout <= q;
124.	        end
125.	    end
126.	
127.	    always  @(posedge clk or negedge rst_n)begin
128.	        if(rst_n==1'b0)begin
129.	            dout_vld <= 1'b0;
130.	        end
131.	        else begin
132.	            dout_vld <= rdreq; 
133.	        End
134.	end 


1.4 串口发送模块设计


1.4.1 接口信号

「每周案例」至简设计系列_串口回环工程


1.4.2 设计思路

串口发送就是要按照串口的时序,对数据进行并串转换,在介绍架构之前,先来描述一下本模块

一些重要的信号的含义:

工作状态指示信号 tx_flag:初始状态为 0,表示处于空闲状态,当检测到输入数据有效的时候,

该信号变为 1,表示处于工作状态,当数据发送完之后,重新拉低,进入空闲状态,等待下一个数据的输入。

数据锁存信号 tx_data_tmp:位宽为 8bit,初始状态为 0,当模块处于空闲状态,并且输入数据

有效的时候,就接收输入的数据进行锁存。由于输入数据在串口发送的时间内都需要用到,因此为了防止数据发生变化,导致串口发送出现问题,所以引入此信号进行数据的锁存。

准备好接收指示信号 rdy:当接收到输入数据,或者模块处于发送状态的时候,此信号为 1,表

示不能接收数据,其他情况为 1,表示准备好接收数据了。由于上游模块数据输出速率要比串口发送模块发送的速度快得多,所以需要此信号来控制上游模块的输出,当串口发送模块收到有效数据的时候,需要立刻把此信号拉高,所以需要用组合逻辑产生。

我们可以得到两个计数器组成的计数器架构,如下图所示:

「每周案例」至简设计系列_串口回环工程

该架构由两个计数器组成:时钟计数器 cnt 和数据计数器 data_num。

时钟计数器 cnt:用于计数发送 1bit 数据所需要的时间,加一条件为 tx_flag,表示进入工作状态

时就开始计数;结束条件为数 5208 个,开发板晶振时钟是 50M,对应周期为 20ns,每位数据的持

续时间为 104166ns/20ns=5208.3 个时钟周期,近似为 5208 个时钟周期。

数据计数器 data_num:用于对接收的每一比特数据进行计数,加一条件为 end_cnt,表示发送

1bit 的数据就加一;结束条件为数 10 个,一个起始位加上八个数据位,再加上一个结束位,共 10 位,数完就清零。


1.4.3 参考代码

使用明德扬的计数器等模板,可以很快速很熟练地写出此模块代码。

1.	 always  @(posedge clk or negedge rst_n)begin
2.	    if(rst_n==1'b0)begin
3.	        tx_flag <= 1'b0;
4.	    end
5.	    else if(tx_flag==1'b0 && din_vld) begin
6.	        tx_flag <= 1'b1;
7.	    end
8.	    else if(tx_flag && data_num==9 && cnt==BPS-1)begin
9.	        tx_flag <= 1'b0;
10.	    end
11.	end
12.	
13.	
14.	always @ (posedge clk or negedge rst_n)begin
15.	    if(!rst_n)begin
16.	        cnt <=0;
17.	    end
18.	    else if(tx_flag)begin
19.	        if(cnt==BPS-1)begin
20.	            cnt<=14'd0;
21.	        end
22.	        else begin
23.	            cnt <=cnt+1'b1;	 
24.	        end
25.	    end
26.	    else begin
27.	        cnt<=0;
28.	    end
29.	end
30.	assign add_cnt = tx_flag;
31.	assign end_cnt = add_cnt && cnt==BPS-1;
32.	
33.	
34.	always @ (posedge clk or negedge rst_n) begin
35.		if(!rst_n) begin
36.			tx_data_tmp <=8'd0;
37.		end
38.		else if(tx_flag==1'b0 && din_vld) begin	
39.			tx_data_tmp <= din;	
40.		end
41.	end
42.	
43.	
44.	always @(posedge clk or negedge rst_n) begin 
45.	    if (rst_n==0) begin
46.	        data_num <= 0; 
47.	    end
48.	    else if(add_data_num) begin
49.	        if(end_data_num)
50.	            data_num <= 0; 
51.	        else
52.	            data_num <= data_num+1 ;
53.	   end
54.	end
55.	assign add_data_num = end_cnt;
56.	assign end_data_num = add_data_num  && data_num == 9-1 ;
57.	
58.	
59.	
60.	always @ (posedge clk or negedge rst_n) begin
61.		if(!rst_n) begin
62.			dout <= 1'b1;
63.		end
64.		else if(tx_flag)begin
65.	        if(data_num==0)begin
66.	            dout<=1'b0;
67.	        end
68.	        else if(data_num==9)begin
69.	            dout<=1'b1;
70.	        end
71.	        else begin
72.	            dout <= tx_data_tmp[data_num-1];
73.	        end
74.		end
75.	    else begin
76.	        dout<=1'b1;
77.	    end 
78.	end
79.	
80.	always  @(*)begin
81.	    if(din_vld || tx_flag)
82.	        rdy = 1'b0;
83.	    else
84.	        rdy = 1'b1;
85.	end 


1.5 效果和总结

➢ 下图是该工程的现象

由于该工程在不同开发板上的上板效果是相同的,所以就统一展示。下图为串口调试助手的界面,

下方一栏中是要发送的数据:010203040506,共 6 个字节,点击手动发送 10 次之后,串口调试助手接收到的数据将在上方空白区域显示。

「每周案例」至简设计系列_串口回环工程

由于该项目的上板现象是动态的,想观看完整现象的朋友可以看一下现象演示的视频。


视频讲解教程和工程源代码下载请移步明德扬论坛


感兴趣的朋友也可以访问明德扬论坛(www.fpgabbs.cn)进行 FPGA 相关工程设计学习,

也可以看一下我们往期的文章:

《基于 FPGA 的密码锁设计》

《波形相位频率可调 DDS 信号发生器》

《基于 FPGA 的曼彻斯特编码解码设计》

《基于 FPGA 的出租车计费系统》

《数电基础与 Verilog 设计》

《基于 FPGA 的频率、电压测量》

《基于 FPGA 的汉明码编码解码设计》

《关于锁存器问题的讨论》

《阻塞赋值与非阻塞赋值》

《参数例化时自动计算位宽的解决办法》


明德扬是一家专注于 FPGA 领域的专业性公司,公司主要业务包括开发板、教育培训、项目承

接、人才服务等多个方向。

点拨开发板——学习 FPGA 的入门之选。

MP801 开发板——千兆网、ADDA、大容量 SDRAM 等,学习和项目需求一步到位。

网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习 FPGA。

周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。

就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。

专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO 架构

设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。

项目承接——承接企业 FPGA 研发项目。

人才服务——提供人才推荐、人才代培、人才派遣等服务。

   拓展阅读