官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

【案例】串口回环工程

发布时间:2023-04-13   作者:admin 浏览量:
【上板现象】

串口回环工程现象:  https://www.bilibili.com/BV1Af4y117H4?p=18


【设计教程】



至简设计系列_串口回环工程

--作者:小黑同学

本案例的编号为:000900000237,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)


 

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

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顶层信号


  
信号名
  
接口方向
定义
  
clk
  
输入
系统时钟,50Mhz
  
rst_n
  
输入
低电平复位信号
  
rx_uart
  
输入
1位串口数据接收信号
  
tx_uart
  
输出
1位串口数据发送信号





1.1.6
参考代码
下面是本工程的顶层代码:
  1. module mdyUartLoopBack(
  2.     rst_n  ,
  3.     clk    ,
  4.     rx_uart,
  5.     tx_uart
  6. );
  7. parameter               BPS          =        5208;

  8. input               rst_n       ;
  9. input               clk         ;
  10. input               rx_uart     ;
  11. output              tx_uart     ;

  12. wire  [7:0]         uart_in     ;
  13. wire                uart_in_vld ;
  14. wire  [7:0]         uart_out    ;
  15. wire                uart_out_vld;
  16. wire                rdy         ;

  17. uart_rx#(.BPS(BPS))  uart_rx(
  18.                  .clk     (clk        ),
  19.                  .rst_n   (rst_n      ),
  20.                  .din     (rx_uart    ),
  21.                  .dout    (uart_in    ),
  22.                  .dout_vld(uart_in_vld)
  23.              );
  24. uart_tx#(.BPS(BPS))  uart_tx(
  25.                  .clk     (clk         ),
  26.                  .rst_n   (rst_n       ),
  27.                  .din     (uart_out    ),
  28.                  .din_vld (uart_out_vld),
  29.                  .rdy     (rdy         ),
  30.                  .dout    (tx_uart     )
  31.              );
  32. data_handle  u_data_handle(
  33.                  .clk     (clk         ),
  34.                  .rst_n   (rst_n       ),
  35.                  .din     (uart_in     ),
  36.                  .din_vld (uart_in_vld ),
  37.                  .dout    (uart_out    ),
  38.                  .dout_vld(uart_out_vld),
  39.                  .rdy     (rdy         )   
  40.                   );         
  41.          
  42. endmodule  
复制代码



1.2 串口接收模块设计1.2.1 接口信号


  
信号
  
接口方向
定义
  
clk
  
输入
系统时钟
  
rst_n
  
输入
低电平复位信号
  
din
  
输入
1bit串口输入数据
  
dout
  
输出
8bit输出数据
  
dout_vld
  
输出
输出数据有效指示信号



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并持续一段时间(起始位),然后发送data[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. always @ (posedge clk or negedge rst_n)begin
  14.         if(!rst_n) begin
  15.                 flag_add <= 1'b0;
  16.         end
  17.         else if(din_ff2 & ~din_ff1) begin               
  18.                 flag_add <= 1'b1;        
  19.         end
  20.         else if(data_num==4'd8&&end_cnt) begin               
  21.                 flag_add <= 1'b0;        
  22.         end
  23. end

  24. always @ (posedge clk or negedge rst_n)begin
  25.     if(!rst_n)begin
  26.         cnt <= 0;
  27.     end
  28.     else if(add_cnt)begin
  29.         if(end_cnt)begin
  30.             cnt <= 0;         
  31.         end
  32.         else begin
  33.             cnt <= cnt+1'b1;
  34.         end
  35.     end
  36.     else begin
  37.         cnt <= 0;
  38.     end
  39. end
  40. assign add_cnt = flag_add;
  41. assign end_cnt = add_cnt && cnt == BPS-1;



  42. always @(posedge clk or negedge rst_n) begin
  43.     if (rst_n==0) begin
  44.         data_num <= 0;
  45.     end
  46.     else if(add_data_num) begin
  47.         if(end_data_num)
  48.             data_num <= 0;
  49.         else
  50.             data_num <= data_num+1 ;
  51.    end
  52. end
  53. assign add_data_num = end_cnt;
  54. assign end_data_num = add_data_num  && data_num == 9-1 ;

  55. always @ (posedge clk or negedge rst_n)begin
  56.         if(!rst_n) begin
  57.                 dout <= 8'd0;
  58.         end
  59.         else if(add_cnt && cnt==BPS_P-1 && data_num!=0) begin               
  60.             dout<={din,{dout[7:1]}};
  61.         end
  62.     else begin
  63.         dout<=dout;
  64.     end
  65. end

  66. always @ (posedge clk or negedge rst_n)begin
  67.         if(!rst_n) begin
  68.                 dout_vld <= 1'b0;
  69.         end
  70.     else if(add_data_num && data_num == 4'd8) begin               
  71.                 dout_vld <= 1'b1;        
  72.         end
  73.         else begin        
  74.         dout_vld <= 1'b0;                        
  75.         end
  76. end
  77. endmodule
复制代码



1.3 数据处理模块设计1.3.1接口信号


  
信号
  
接口方向
定义
  
clk
  
输入
系统时钟
  
rst_n
  
输入
低电平复位信号
  
din
  
输入
输入8bit数据信号
  
din_vld
  
输入
输入数据有效指示信号
  
dout
  
输出
输出8bit数据信号
  
dout_vld
  
输出
输出数据有效指示信号
  
rdy
  
输入
下游模块准备好指示信号



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课程

&#216;  架构设计
按照功能要求,本模块需要对接收的数据进行存储,当存够60个的时候开始发送,下面是本模块的架构图。

本模块大致分为三部分:FIFO的写控制电路、FIFO、FIFO的读控制电路。
写控制电路:只要输入数据有效,就将数据写进FIFO,主要信号为写数据data和写使能wrreq。
FIFOip核:存储数据。
读控制电路:当FIFO内部有60个数据、FIFO非空并且下游模块准备好接收数据的时候,就开始读。主要信号为输出数据q、FIFO有效数据量指示信号usedw、空指示信号empty、读使能rdreq。


1.3.3
参考代码
  1.     assign   data  = din        ;
  2.     assign   wrreq = din_vld    ;


  3.     my_fifo u_my_fifo (
  4.                       .clock(clk  ),
  5.                       .data (data ),
  6.                       .rdreq(rdreq),
  7.                       .wrreq(wrreq),
  8.                       .empty(empty),
  9.                       .q    (q    ),
  10.                       .usedw(usedw)
  11.               );

  12.   
  13.     always@(*)begin
  14.         if(rd_flag && empty==1'b0 && rdy)
  15.             rdreq = 1'b1;
  16.         else
  17.             rdreq = 1'b0;
  18.     end

  19.     always@(posedge clk or negedge rst_n)begin
  20.         if(rst_n==1'b0)begin
  21.             rd_flag <= 1'b0;
  22.         end
  23.         else if(rd_flag==1'b0 && usedw>=60) begin
  24.             rd_flag <= 1'b1;
  25.         end
  26.         else if(rd_flag==1'b1 && empty)begin
  27.             rd_flag <= 1'b0;
  28.         end
  29.     end

  30.     always  @(posedge clk or negedge rst_n)begin
  31.         if(rst_n==1'b0)begin
  32.             dout <= 0;
  33.         end
  34.         else begin
  35.             dout <= q;
  36.         end
  37.     end

  38.     always  @(posedge clk or negedge rst_n)begin
  39.         if(rst_n==1'b0)begin
  40.             dout_vld <= 1'b0;
  41.         end
  42.         else begin
  43.             dout_vld <= rdreq;
  44.         end
  45.     end


  46. endmodul
复制代码


1.4 串口发送模块设计1.4.1接口信号


  
信号
  
接口方向
定义
  
clk
  
输入
系统时钟
  
rst_n
  
输入
低电平复位信号
  
din
  
输入
8bit输出数据
  
din_vld
  
输入
输入数据有效指示信号
  
rdy
  
输出
准备好接收指示信号
  
dout
  
输出
1bit输出数据



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. always @ (posedge clk or negedge rst_n)begin
  13.     if(!rst_n)begin
  14.         cnt <=0;
  15.     end
  16.     else if(tx_flag)begin
  17.         if(cnt==BPS-1)begin
  18.             cnt<=14'd0;
  19.         end
  20.         else begin
  21.             cnt <=cnt+1'b1;         
  22.         end
  23.     end
  24.     else begin
  25.         cnt<=0;
  26.     end
  27. end
  28. assign add_cnt = tx_flag;
  29. assign end_cnt = add_cnt && cnt==BPS-1;


  30. always @ (posedge clk or negedge rst_n) begin
  31.         if(!rst_n) begin
  32.                 tx_data_tmp <=8'd0;
  33.         end
  34.         else if(tx_flag==1'b0 && din_vld) begin        
  35.                 tx_data_tmp <= din;        
  36.         end
  37. end


  38. always @(posedge clk or negedge rst_n) begin
  39.     if (rst_n==0) begin
  40.         data_num <= 0;
  41.     end
  42.     else if(add_data_num) begin
  43.         if(end_data_num)
  44.             data_num <= 0;
  45.         else
  46.             data_num <= data_num+1 ;
  47.    end
  48. end
  49. assign add_data_num = end_cnt;
  50. assign end_data_num = add_data_num  && data_num == 10-1 ;



  51. always @ (posedge clk or negedge rst_n) begin
  52.         if(!rst_n) begin
  53.                 dout <= 1'b1;
  54.         end
  55.         else if(tx_flag)begin
  56.         if(data_num==0)begin
  57.             dout<=1'b0;
  58.         end
  59.         else if(data_num==9)begin
  60.             dout<=1'b1;
  61.         end
  62.         else begin
  63.             dout <= tx_data_tmp[data_num-1];
  64.         end
  65.         end
  66.     else begin
  67.         dout<=1'b1;
  68.     end
  69. end

  70. always  @(*)begin
  71.     if(din_vld || tx_flag)
  72.         rdy = 1'b0;
  73.     else
  74.         rdy = 1'b1;
  75. end

  76. endmodule
复制代码


1.5 效果和总结


下图是该工程的现象
由于该工程在不同开发板上的上板效果是相同的,所以就统一展示。下图为串口调试助手的界面,下方一栏中是要发送的数据:010203040506,共6个字节,点击手动发送10次之后,串口调试助手接收到的数据将在上方空白区域显示。



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

感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:
基于FPGA的密码锁设计
波形相位频率可调DDS信号发生器
基于FPGA的曼彻斯特编码解码设计
基于FPGA的出租车计费系统
数电基础与Verilog设计
基于FPGA的频率、电压测量
基于FPGA的汉明码编码解码设计
关于锁存器问题的讨论
阻塞赋值与非阻塞赋值
参数例化时自动计算位宽的解决办法


1.6 公司简介
明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801
开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。

【设计教程下载】

 至简设计系列_串口回环工程.pdf (1.34 MB, 下载次数: 71)

【设计视频教程】

https://www.bilibili.com/BV1Af4y117H4?p=17


【工程源码】

 mdyUartLoopBack.zip (3.35 MB, 下载次数: 55)
   拓展阅读