OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 1146|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第二十一章 UART串口通信实验

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4672
金钱
4672
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-11-21 16:53:32 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-11-21 16:53 编辑

第二十一章 UART串口通信实验


1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。本章我们将使用ATK-DFPGL22G开发板上的UART串口完成上位机与ATK-DFPGL22G开发板的通信。
本章包括以下几个部分:         
1.1           UART串口简介
1.2           实验任务
1.3           硬件设计
1.4           程序设计
1.5           下载验证

1.1 UART串口简介
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。UART在发送或接收过程中的一帧数据由4部分组成,起始位数据位奇偶校验位停止位,如图 21.1.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。                           
image001.png
图 21.1.1 异步串行通信数据格式
UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认),1.5或2位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、38400、57600以及115200等。
在设置好数据格式及传输速率之后,UART负责完成数据的串并转换,而信号的传输则由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输出,而RS-422/485为差分输入输出等。
RS232接口标准出现较早,可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准,本章主要介绍针对RS-232标准的UART串口通信。
RS-232标准的串口最常见的接口类型为DB9,样式如图 21.1.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过USB转串口线(图 21.1.3)来实现与外部设备的串口通信。
image003.png
图 21.1.2 DB9接口

image005.jpg
图 21.1.3 USB串口线
DB9接口定义以及各引脚功能说明如图 21.1.4所示,我们一般只用到其中的2(RXD)、3(TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用,如果大家想了解,可以自行百度下。
image008.png image009.png
图 21.1.4 DB9接口定义
1.2 实验任务
本节实验任务是上位机通过串口调试助手发送数据给开发板,开发板通过UART串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。

1.3 硬件设计
ATK-DFPGL22G开发板上UART串口部分的原理图如下所示:
image011.png
图 21.3.1 UART串口原理图
我们观察原理图,可以看到串口部分主要是由TypeC接口、CH340E USB转UART芯片、P1跳线帽插座以及一个5V转3.3V的器件组成,其中这个5V转3.3V的器件是专为CH340E提供一路供电,当插入USB线时,CH340的电源来自USB口,从而不受开发板总电源的控制,这样的目的也是为了防止CH340E上电/掉电速度过快而导致电脑识别不到CH340E。
需要注意的是,在做这个实验之前,务必要确认你的TypeC接口已经和电脑USB口相连,并且P1跳线帽安装正确且连接牢固,正确的安装示例请看本章下载验证部分。
本实验中,系统时钟、按键复位以及串口的接收、发送端口的管脚分配如下表所示:
QQ截图20231118164143.png
表21.3.1 串口通信实验管脚分配

对应的fdc约束语句如下所示:
  1. define_attribute {p:uart_txd}{PAP_IO_DIRECTION} {OUTPUT}
  2. define_attribute {p:uart_txd}{PAP_IO_LOC} {T10}
  3. define_attribute {p:uart_txd}{PAP_IO_VCCIO} {3.3}
  4. define_attribute {p:uart_txd}{PAP_IO_STANDARD} {LVCMOS33}
  5. define_attribute {p:uart_txd}{PAP_IO_DRIVE} {4}
  6. define_attribute {p:uart_txd}{PAP_IO_NONE} {TRUE}
  7. define_attribute {p:uart_txd}{PAP_IO_SLEW} {SLOW}
  8. define_attribute {p:sys_clk}{PAP_IO_DIRECTION} {INPUT}
  9. define_attribute {p:sys_clk}{PAP_IO_LOC} {B5}
  10. define_attribute {p:sys_clk}{PAP_IO_VCCIO} {3.3}
  11. define_attribute {p:sys_clk}{PAP_IO_STANDARD} {LVCMOS33}
  12. define_attribute {p:sys_clk}{PAP_IO_NONE} {TRUE}
  13. define_attribute {p:sys_rst_n}{PAP_IO_DIRECTION} {INPUT}
  14. define_attribute {p:sys_rst_n}{PAP_IO_LOC} {G5}
  15. define_attribute {p:sys_rst_n}{PAP_IO_VCCIO} {1.5}
  16. define_attribute {p:sys_rst_n} {PAP_IO_STANDARD}{LVCMOS15}
  17. define_attribute {p:sys_rst_n}{PAP_IO_NONE} {TRUE}
  18. define_attribute {p:uart_rxd}{PAP_IO_DIRECTION} {INPUT}
  19. define_attribute {p:uart_rxd}{PAP_IO_LOC} {R10}
  20. define_attribute {p:uart_rxd}{PAP_IO_VCCIO} {3.3}
  21. define_attribute {p:uart_rxd}{PAP_IO_STANDARD} {LVCMOS33}
  22. define_attribute {p:uart_rxd}{PAP_IO_NONE} {TRUE}
复制代码

1.4 程序设计
根据实验任务,我们不难想象本系统应该有一个串口接收模块,用来接收上位机发送的数据;还要有一个串口发送模块,用于将数据发回上位机;另外还应该有一个对数据进行环回控制的模块,它负责把从串口接收模块接收到的数据送给串口发送模块,以实现串口数据的环回。由此可以画出本次实验的系统框图,如下所示:

image013.png
图 21.4.1 系统框图
由系统总体框图可知,串口数据环回部分包括四个模块,顶层模块、接收模块、发送模块和数据环回模块。其中在顶层模块中完成对另外三个模块的例化,顶层模块原理图如下所示:

image015.png
图 21.4.2 顶层模块原理图
在图 21.4.2中,uart_recv为串口接收模块,从串口接收端口uart_rxd来接收上位机发送的串行数据,并在一帧数据接收结束后给出通知信号uart_done。
uart_send为串口发送模块,以uart_en为发送使能信号。uart_en的上升沿将启动一次串口发送过程,将uart_din接口上的数据通过串口发送端口uart_txd发送出去。
uart_loop模块负责完成串口数据的环回功能。它在uart_recv模块接收完成后,将接收到的串口数据发送到uart_send模块,并通过send_en接口给出一个上升沿,以启动发送过程。
在编写代码之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的一种模式,数据位为8位,停止位为1位,无校验位,波特率为115200bps。则传输一帧数据的时序图如下图所示:

image017.png
图 21.4.3 串口通信时序图
顶层模块的代码如下:
  1. 1  module uart_loopback_top(
  2. 2      input           sys_clk,            //外部50M时钟
  3. 3      input          sys_rst_n,          //外部复位信号,低有效
  4. 4  
  5. 5      input           uart_rxd,           //UART接收端口
  6. 6      output         uart_txd            //UART发送端口
  7. 7      );
  8. 8  
  9. 9  //parameter define
  10. 10 parameter  CLK_FREQ = 50000000;         //定义系统时钟频率
  11. 11 parameter  UART_BPS = 115200;           //定义串口波特率
  12. 12     
  13. 13 //wire define   
  14. 14 wire      uart_recv_done;              //UART接收完成
  15. 15 wire [7:0] uart_recv_data;              //UART接收数据
  16. 16 wire       uart_send_en;                //UART发送使能
  17. 17 wire [7:0] uart_send_data;              //UART发送数据
  18. 18 wire       uart_tx_busy;                //UART发送忙状态标志
  19. 19
  20. 20 //*****************************************************
  21. 21 //**                    main code
  22. 22 //*****************************************************
  23. 23
  24. 24 //串口接收模块     
  25. 25 uart_recv #(                        
  26. 26     .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
  27. 27     .UART_BPS       (UART_BPS))         //设置串口接收波特率
  28. 28 u_uart_recv(                 
  29. 29     .sys_clk        (sys_clk),
  30. 30     .sys_rst_n      (sys_rst_n),
  31. 31     
  32. 32     .uart_rxd       (uart_rxd),
  33. 33     .uart_done      (uart_recv_done),
  34. 34     .uart_data      (uart_recv_data)
  35. 35     );
  36. 36
  37. 37 //串口发送模块   
  38. 38 uart_send #(                        
  39. 39     .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
  40. 40     .UART_BPS       (UART_BPS))         //设置串口发送波特率
  41. 41 u_uart_send(                 
  42. 42     .sys_clk        (sys_clk),
  43. 43     .sys_rst_n      (sys_rst_n),
  44. 44      
  45. 45     .uart_en        (uart_send_en),
  46. 46     .uart_din       (uart_send_data),
  47. 47     .uart_tx_busy   (uart_tx_busy),
  48. 48     .uart_txd       (uart_txd)
  49. 49     );
  50. 50     
  51. 51 //串口环回模块   
  52. 52 uart_loopu_uart_loop(
  53. 53     .sys_clk        (sys_clk),            
  54. 54     .sys_rst_n      (sys_rst_n),           
  55. 55   
  56. 56     .recv_done      (uart_recv_done),   //接收一帧数据完成标志信号
  57. 57     .recv_data      (uart_recv_data),   //接收的数据
  58. 58   
  59. 59     .tx_busy        (uart_tx_busy),     //发送忙状态标志      
  60. 60     .send_en        (uart_send_en),     //发送使能信号
  61. 61     .send_data      (uart_send_data)    //待发送数据
  62. 62     );
  63. 63     
  64. 64 endmodule
复制代码
在顶层模块中完成了对其余各个子模块的例化。需要注意的是,顶层模块中第10、11行定义了两个变量:系统时钟频率CLK_FREQ与串口波特率UART_BPS,使用时可以根据不同的系统时钟频率以及所需要的串口波特率设置这两个变量。我们可以尝试将串口波特率UART_BPS设置为其他值(如9600),在模块例化时会将这个变量传递到串口接收与发送模块中,从而实现不同速率的串口通信。
串口接收模块的代码如下所示:
  1. 1   module uart_recv(
  2. 2       input            sys_clk,                  //系统时钟
  3. 3       input            sys_rst_n,                //系统复位,低电平有效
  4. 4      
  5. 5       input            uart_rxd,                 //UART接收端口
  6. 6       output  reg       uart_done,                //接收一帧数据完成标志
  7. 7       output  reg [7:0] uart_data                 //接收的数据
  8. 8       );
  9. 9      
  10. 10  //parameter define
  11. 11  parameter  CLK_FREQ = 50000000;                //系统时钟频率
  12. 12  parameter  UART_BPS = 9600;                   //串口波特率
  13. 13  localparam  BPS_CNT  = CLK_FREQ/UART_BPS;       //为得到指定波特率,
  14. 14                                                 //需要对系统时钟计数BPS_CNT次
  15. 15  //reg define
  16. 16  reg        uart_rxd_d0;
  17. 17  reg        uart_rxd_d1;
  18. 18  reg [15:0] clk_cnt;                             //系统时钟计数器
  19. 19  reg [ 3:0] rx_cnt;                              //接收数据计数器
  20. 20  reg        rx_flag;                             //接收过程标志信号
  21. 21  reg [ 7:0] rxdata;                              //接收数据寄存器
  22. 22  
  23. 23  //wire define
  24. 24  wire       start_flag;
  25. 25  
  26. 26  //*****************************************************
  27. 27  //**                    main code
  28. 28  //*****************************************************
  29. 29  //捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
  30. 30  assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);   
  31. 31  
  32. 32  //对UART接收端口的数据延迟两个时钟周期
  33. 33  always @(posedge sys_clk or negedge sys_rst_n) begin
  34. 34      if (!sys_rst_n) begin
  35. 35          uart_rxd_d0 <= 1'b0;
  36. 36          uart_rxd_d1 <= 1'b0;         
  37. 37      end
  38. 38      else begin
  39. 39          uart_rxd_d0  <= uart_rxd;                  
  40. 40          uart_rxd_d1  <= uart_rxd_d0;
  41. 41      end   
  42. 42  end
  43. 43  
  44. 44  //当脉冲信号start_flag到达时,进入接收过程           
  45. 45  always @(posedge sys_clk or negedge sys_rst_n) begin         
  46. 46      if (!sys_rst_n)                                 
  47. 47          rx_flag <= 1'b0;
  48. 48      else begin
  49. 49          if(start_flag)                          //检测到起始位
  50. 50              rx_flag <= 1'b1;                   //进入接收过程,标志位rx_flag拉高
  51. 51                                                 //计数到停止位中间时,停止接收过程
  52. 52          else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))
  53. 53              rx_flag <= 1'b0;                   //接收过程结束,标志位rx_flag拉低
  54. 54          else
  55. 55              rx_flag <= rx_flag;
  56. 56      end
  57. 57  end
  58. 58  
  59. 59  //进入接收过程后,启动系统时钟计数器
  60. 60  always @(posedge sys_clk or negedge sys_rst_n) begin         
  61. 61      if (!sys_rst_n)                             
  62. 62          clk_cnt <= 16'd0;                                 
  63. 63      else if ( rx_flag ) begin                   //处于接收过程
  64. 64          if (clk_cnt < BPS_CNT - 1)
  65. 65              clk_cnt <= clk_cnt + 1'b1;
  66. 66          else
  67. 67              clk_cnt <= 16'd0;                   //对系统时钟计数达一个波特率周期后清零
  68. 68      end
  69. 69      else                                            
  70. 70          clk_cnt <= 16'd0;                      //接收过程结束,计数器清零
  71. 71  end
  72. 72  
  73. 73  //进入接收过程后,启动接收数据计数器
  74. 74  always @(posedge sys_clk or negedge sys_rst_n) begin         
  75. 75      if (!sys_rst_n)                             
  76. 76          rx_cnt  <= 4'd0;
  77. 77      else if ( rx_flag ) begin                   //处于接收过程
  78. 78          if (clk_cnt == BPS_CNT - 1)             //对系统时钟计数达一个波特率周期
  79. 79              rx_cnt <= rx_cnt + 1'b1;            //此时接收数据计数器加1
  80. 80          else
  81. 81              rx_cnt <= rx_cnt;      
  82. 82      end
  83. 83       else
  84. 84          rx_cnt  <= 4'd0;                       //接收过程结束,计数器清零
  85. 85  end
  86. 86  
  87. 87  //根据接收数据计数器来寄存uart接收端口数据
  88. 88  always @(posedge sys_clk or negedge sys_rst_n) begin
  89. 89      if ( !sys_rst_n)  
  90. 90          rxdata <= 8'd0;                                    
  91. 91      else if(rx_flag)                            //系统处于接收过程
  92. 92          if (clk_cnt == BPS_CNT/2) begin         //判断系统时钟计数器计数到数据位中间
  93. 93              case ( rx_cnt )
  94. 94               4'd1 : rxdata[0] <= uart_rxd_d1;   //寄存数据位最低位
  95. 95               4'd2 : rxdata[1] <= uart_rxd_d1;
  96. 96               4'd3 : rxdata[2] <= uart_rxd_d1;
  97. 97               4'd4 : rxdata[3] <= uart_rxd_d1;
  98. 98               4'd5 : rxdata[4] <= uart_rxd_d1;
  99. 99               4'd6 : rxdata[5] <= uart_rxd_d1;
  100. 100              4'd7 : rxdata[6] <= uart_rxd_d1;
  101. 101              4'd8 : rxdata[7] <= uart_rxd_d1;   //寄存数据位最高位
  102. 102              default:;                                    
  103. 103             endcase
  104. 104         end
  105. 105         else
  106. 106             rxdata <= rxdata;
  107. 107     else
  108. 108         rxdata <= 8'd0;
  109. 109 end
  110. 110
  111. 111 //数据接收完毕后给出标志信号并寄存输出接收到的数据
  112. 112 always @(posedge sys_clk or negedge sys_rst_n) begin        
  113. 113     if (!sys_rst_n) begin
  114. 114         uart_data <= 8'd0;                              
  115. 115         uart_done <= 1'b0;
  116. 116     end
  117. 117     else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时           
  118. 118         uart_data <= rxdata;                    //寄存输出接收到的数据
  119. 119         uart_done <= 1'b1;                     //并将接收完成标志位拉高
  120. 120     end
  121. 121     else begin
  122. 122         uart_data <= 8'd0;                                   
  123. 123         uart_done <= 1'b0;
  124. 124     end   
  125. 125 end
  126. 126
  127. 127 endmodule
复制代码
串口接收模块程序中29至42行是一个经典的边沿检测电路,通过检测串口接收端uart_rxd的下降沿来捕获起始位。一旦检测到起始位,输出一个时钟周期的脉冲start_flag,并进入串口接收过程。串口接收状态用rx_flag来标志,rx_flag为高标志着串口接收过程正在进行,此时启动系统时钟计数器clk_cnt与接收数据计数器rx_cnt。
由第13行的公式BPS_CNT = CLK_FREQ/UART_BPS可知,BPS_CNT为当前波特率下,串口传输一位所需要的系统时钟周期数。因此clk_cnt从零计数到BPS_CN-1时,串口刚好完成一位数据的传输。由于接收数据计数器rx_cnt在每次clk_cnt计数到BPS_CN-1时加1,因此由rx_cnt的值可以判断串口当前传输的是第几位数据。第87行至第109行就是根据clk_cnt的值将uart接收端口的数据寄存到接收数据寄存器对应的位,从而实现接收数据的串并转换。其中第92行选择clk_cnt计数至BPS_CNT/2时寄存接收端口数据,是因为计数到数据中间时的采样结果最稳定。
程序中需要额外注意的地方是串口接收过程结束条件的判定,由第52行可知,在计数到停止位中间时,标志位rx_flag就已经拉低。这样做是因为虽然此时一帧数据传输还没有完成(停止位只传送到一半),但是数据位已经寄存完毕。而在连续接收数据时,提前半个波特率周期结束接收过程可以为检测下一帧数据的起始位留出充足的时间。
我们使用上位机通过串口向开发板发送数据,在串口接收过程中Debugger抓取的波形图如下所示:

image019.png
图 21.4.4 串口接收过程波形图
图 21.4.4中红色的触发线标识出了串口接收完成拉高的时刻,在整个接收过程中rx_flag保持为高电平,同时rx_cnt对串口数据进行计数。当rx_cnt计数到9时,串口数据接收完成,uart_recv_done拉高,同时tx_data给出接收到的数据。从图中可以看到,接收模块能够正确接收串口数据并完成串并转换。
串口发送模块代码如下所示:
  1. 1   module uart_send(
  2. 2       input         sys_clk,                  //系统时钟
  3. 3       input         sys_rst_n,                //系统复位,低电平有效
  4. 4      
  5. 5       input         uart_en,                  //发送使能信号
  6. 6       input  [7:0]  uart_din,                 //待发送数据
  7. 7       output       uart_tx_busy,             //发送忙状态标志      
  8. 8       output  reg   uart_txd                  //UART发送端口
  9. 9       );
  10. 10      
  11. 11  //parameter define
  12. 12  parameter  CLK_FREQ = 50000000;            //系统时钟频率
  13. 13  parameter  UART_BPS = 9600;                //串口波特率
  14. 14  localparam  BPS_CNT  = CLK_FREQ/UART_BPS;  //为得到指定波特率,对系统时钟计数BPS_CNT次
  15. 15  
  16. 16  //reg define
  17. 17  reg        uart_en_d0;
  18. 18  reg        uart_en_d1;  
  19. 19  reg [15:0] clk_cnt;                         //系统时钟计数器
  20. 20  reg [ 3:0] tx_cnt;                          //发送数据计数器
  21. 21  reg        tx_flag;                        //发送过程标志信号
  22. 22  reg [ 7:0] tx_data;                         //寄存发送数据
  23. 23  
  24. 24  //wire define
  25. 25  wire       en_flag;
  26. 26  
  27. 27  //*****************************************************
  28. 28  //**                    main code
  29. 29  //*****************************************************
  30. 30  //在串口发送过程中给出忙状态标志
  31. 31  assign uart_tx_busy = tx_flag;
  32. 32  
  33. 33  //捕获uart_en上升沿,得到一个时钟周期的脉冲信号
  34. 34  assign en_flag = (~uart_en_d1) & uart_en_d0;
  35. 35  
  36. 36  //对发送使能信号uart_en延迟两个时钟周期
  37. 37  always @(posedge sys_clk or negedge sys_rst_n) begin         
  38. 38      if (!sys_rst_n) begin
  39. 39          uart_en_d0 <= 1'b0;                                 
  40. 40          uart_en_d1 <= 1'b0;
  41. 41      end                                                     
  42. 42      else begin                                               
  43. 43          uart_en_d0 <= uart_en;                              
  44. 44          uart_en_d1 <= uart_en_d0;                           
  45. 45      end
  46. 46  end
  47. 47  
  48. 48  //当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程         
  49. 49  always @(posedge sys_clk or negedge sys_rst_n) begin         
  50. 50      if (!sys_rst_n) begin                                 
  51. 51          tx_flag <= 1'b0;
  52. 52          tx_data <= 8'd0;
  53. 53      end
  54. 54      else if (en_flag) begin                 //检测到发送使能上升沿                     
  55. 55              tx_flag <= 1'b1;                //进入发送过程,标志位tx_flag拉高
  56. 56              tx_data <= uart_din;            //寄存待发送的数据
  57. 57          end
  58. 58                                              //计数到停止位结束时,停止发送过程
  59. 59          else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT -(BPS_CNT/16))) begin                                       
  60. 60              tx_flag <= 1'b0;                //发送过程结束,标志位tx_flag拉低
  61. 61              tx_data <= 8'd0;
  62. 62          end
  63. 63          else begin
  64. 64              tx_flag <= tx_flag;
  65. 65              tx_data <= tx_data;
  66. 66          end
  67. 67  end
  68. 68  
  69. 69  //进入发送过程后,启动系统时钟计数器
  70. 70  always @(posedge sys_clk or negedge sys_rst_n) begin         
  71. 71      if (!sys_rst_n)                             
  72. 72          clk_cnt <= 16'd0;                                 
  73. 73      else if (tx_flag) begin                 //处于发送过程
  74. 74          if (clk_cnt < BPS_CNT - 1)
  75. 75              clk_cnt <= clk_cnt + 1'b1;
  76. 76          else
  77. 77              clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
  78. 78      end
  79. 79      else                             
  80. 80          clk_cnt <= 16'd0;                   //发送过程结束
  81. 81  end
  82. 82  
  83. 83  //进入发送过程后,启动发送数据计数器
  84. 84  always @(posedge sys_clk or negedge sys_rst_n) begin         
  85. 85      if (!sys_rst_n)                             
  86. 86          tx_cnt <= 4'd0;
  87. 87      else if (tx_flag) begin                 //处于发送过程
  88. 88          if (clk_cnt == BPS_CNT - 1)         //对系统时钟计数达一个波特率周期
  89. 89              tx_cnt <= tx_cnt + 1'b1;        //此时发送数据计数器加1
  90. 90          else
  91. 91              tx_cnt <= tx_cnt;      
  92. 92      end
  93. 93      else                              
  94. 94          tx_cnt  <= 4'd0;                   //发送过程结束
  95. 95  end
  96. 96  
  97. 97  //根据发送数据计数器来给uart发送端口赋值
  98. 98  always @(posedge sys_clk or negedge sys_rst_n) begin        
  99. 99      if (!sys_rst_n)  
  100. 100         uart_txd <= 1'b1;        
  101. 101     else if (tx_flag)
  102. 102         case(tx_cnt)
  103. 103             4'd0: uart_txd <= 1'b0;         //起始位
  104. 104             4'd1: uart_txd <= tx_data[0];   //数据位最低位
  105. 105             4'd2: uart_txd <= tx_data[1];
  106. 106             4'd3: uart_txd <= tx_data[2];
  107. 107             4'd4: uart_txd <= tx_data[3];
  108. 108             4'd5: uart_txd <= tx_data[4];
  109. 109             4'd6: uart_txd <= tx_data[5];
  110. 110             4'd7: uart_txd <= tx_data[6];
  111. 111             4'd8: uart_txd <= tx_data[7];   //数据位最高位
  112. 112             4'd9: uart_txd <= 1'b1;         //停止位
  113. 113             default: ;
  114. 114         endcase
  115. 115     else
  116. 116         uart_txd <= 1'b1;                   //空闲时发送端口为高电平
  117. 117 end
  118. 118
  119. 119 endmodule
复制代码
串口发送模块与串口接收模块异曲同工,代码中也给出了详尽的注释,此处不再赘述。需要注意的是,在程序的59行,我们将tx_flag提前1/16个停止位拉低,是为了确保发送模块发送数据的时间略小于接收模块接收数据的时间,否则当连续传输大量数据时,发送数据的时间会不断累积,最终导致在做串口环回实验时丢失数据。
尽管串口发送数据只是接收数据的反过程,理论上在传输的时间上是一致的,考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率同样可能会出现较小的偏差,因此这里为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。
需要说明的是,较小偏差的波特率在串口通信时是允许的,同样可以保证数据可靠稳定的传输。
另外我们在代码的第31行将用于标志串口发送过程的tx_flag信号赋值给uart_tx_busy,并通过模块端口输出。这样其他模块就可以通过检测uart_tx_busy信号是否为低电平,从而判断串口发送模块是否处于空闲状态。若uart_tx_busy为高电平,那么uart_send正处于发送过程,外部模块需要等待当前发送过程结束之后,才能通过uart_en信号的上升沿来启动新的发送过程。
图 21.4.5为串口发送过程中Debugger抓取的波形图,我们使用开发板通过串口向上位机向发送16进制数55。图中绿色的触发线标识出了串口发送使能信号uart_en的上升沿。在检测到uart_en的上升沿后,en_flag会拉高一个时钟周期,此时将uart_din端口上的待发送数据寄存到tx_data中,并进入串口发送过程。
在整个发送过程中tx_flag保持为高电平,tx_cnt对串口数据进行计数,同时tx_data的各个数据位依次通过串口发送端uart_txd发送出去。当tx_cnt计数到9时,串口数据发送完成,开始发送停止位。在一个波特率周期的停止位发送完成后,串口发送过程结束,uart_tx_busy信号拉低,表明串口发送模块进入空闲状态。

image021.png
图 21.4.5 串口发送过程波形图

从图 21.4.5中可以看出串口发送模块能够完成并串转换并正确发送串口数据。

在介绍完串口的接收和发送模块后,最后我们来看一下串口环回模块的代码:
  1. 1  module uart_loop(
  2. 2      input            sys_clk,                   //系统时钟
  3. 3      input           sys_rst_n,                 //系统复位,低电平有效
  4. 4      
  5. 5      input           recv_done,                 //接收一帧数据完成标志
  6. 6      input      [7:0] recv_data,                 //接收的数据
  7. 7      
  8. 8      input            tx_busy,                   //发送忙状态标志      
  9. 9      output reg       send_en,                   //发送使能信号
  10. 10     output reg [7:0] send_data                  //待发送数据
  11. 11     );
  12. 12
  13. 13 //reg define
  14. 14 reg recv_done_d0;
  15. 15 reg recv_done_d1;
  16. 16 reg tx_ready;
  17. 17
  18. 18 //wire define
  19. 19 wire recv_done_flag;
  20. 20
  21. 21 //*****************************************************
  22. 22 //**                    main code
  23. 23 //*****************************************************
  24. 24
  25. 25 //捕获recv_done上升沿,得到一个时钟周期的脉冲信号
  26. 26 assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
  27. 27                                                  
  28. 28 //对发送使能信号recv_done延迟两个时钟周期
  29. 29 always @(posedge sys_clk or negedge sys_rst_n) begin         
  30. 30     if (!sys_rst_n) begin
  31. 31         recv_done_d0 <= 1'b0;                                 
  32. 32         recv_done_d1 <= 1'b0;
  33. 33     end                                                     
  34. 34     else begin                                               
  35. 35         recv_done_d0 <= recv_done;                              
  36. 36         recv_done_d1 <= recv_done_d0;                           
  37. 37     end
  38. 38 end
  39. 39
  40. 40 //判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
  41. 41 always @(posedge sys_clk or negedge sys_rst_n) begin         
  42. 42     if (!sys_rst_n) begin
  43. 43         tx_ready  <= 1'b0;
  44. 44         send_en   <= 1'b0;
  45. 45         send_data <= 8'd0;
  46. 46     end                                                     
  47. 47     else begin                                               
  48. 48         if(recv_done_flag)begin                 //检测串口接收到数据
  49. 49             tx_ready  <= 1'b1;                  //准备启动发送过程
  50. 50             send_en   <= 1'b0;
  51. 51             send_data <= recv_data;             //寄存串口接收的数据
  52. 52         end
  53. 53         else if(tx_ready && (~tx_busy)) begin   //检测串口发送模块空闲
  54. 54             tx_ready <= 1'b0;                   //准备过程结束
  55. 55             send_en  <= 1'b1;                   //拉高发送使能信号
  56. 56         end
  57. 57     end
  58. 58 end
  59. 59
  60. 60 endmodule
复制代码
uart_loop模块的代码比较简单,首先代码的25至38行实现了上升沿检测功能。当检测到recv_done信号的上升沿时,recv_done_flag输出一个时钟周期的高电平,标志着串口接收模块接收到了一帧数据。在代码的48至52行,在判断到recv_done_flag为高电平时,寄存接收到的数据recv_data到send_data中;同时将tx_ready信号拉高,表示已经准备好了待发送的数据。另外还要将send_en信号拉低,为接下来产生一个上升沿作准备。
uart_loop模块还有一个输入信号tx_busy,它是由串口发送模块所输出,该信号为高电平表示串口发送模块正处于发送过程中。在代码的第53行,当tx_ready信号为高电平时,如果检测到tx_busy为低电平,则说明串口发送模块处于空闲状态。此时将send_en信号拉高,由此产生一个上升沿,以启动串口发送模块的发送过程,将寄存到send_data中的数据发送出去。于此同时,将tx_ready信号拉低,等待下一个串口接收数据的到来。

1.5 下载验证
编译工程并生成比特流.sbit文件。接下来我们下载程序,验证上位机与开发板通过UART串口进行串口数据环回功能。
首先我们需要准备一个Type_C数据线,将USB接口一端插入电脑上的USB口,另一端与开发板上的UART接口相连接,并将P1排针上的两个跳帽按照如下图所示的方式连接:

image023.png
图21.5.1 串口P1跳线帽连接
接下来分别连接JTAG接口和电源线,并打开电源开关。
注意上位机第一次使用USB转串口线与FPGA开发板连接时,需要安装USB串口驱动。在开发板随附的资料中找到“6_软件资料/1_软件/CH340驱动(USB串口驱动)”的文件夹,双击打开文件夹中的“SETUP.EXE”进行安装,驱动安装界面如图 21.5.2所示。界面中提示INF文件为CH341SER.INF,我们不需要理会(CH341,CH340驱动是共用的),直接点安装即可。

image025.png
图 21.5.2USB串口驱动驱动安装界面
开发板电源打开后,将本次实验的bit文件下载到开发板中。
接下来打开串口助手。串口助手是上位机中用于辅助串口调试的小工具,可以选择安装使用开发板随附资料中“6_软件资料/1_软件/串口调试助手”文件夹中提供的XCOM串口助手。在上位机中打开串口助手XCOM V2.0,如下图所示:

image027.png
图 21.5.3 串口助手操作界面
在串口助手中选择与开发板相连接的CH340虚拟串口,具体的端口号(这里是COM4)需要根据实际情况选择,可以在计算机设备器中查看,如下图所示:

image029.png
图21.5.4 电脑的设备管理器窗口
在串口助手中设置波特率为115200,数据位为8,停止位为1,无校验位,最后确认打开串口。
串口打开后,在发送文本框中输入数据“5A”并点击发送,可以看到串口助手中接收到数据“5A”,如图 21.5.3所示。串口助手接收到的数据与发送的数据一致,说明程序所实现的串口数据环回功能验证成功。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-25 21:11

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表