搜索
bottom↓
回复: 0

《DFZU2EG_4EV MPSoc之FPGA开发指南_V1.0》第十四章 UART串口通信实验

[复制链接]

出0入234汤圆

发表于 2022-12-16 10:34:48 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-12-16 10:34 编辑

1)实验平台:正点原子MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png

lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png

第十四章 UART串口通信实验

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


14.1UART串口简介

串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图 14.1.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
UART串口通信实验744.png
图 14.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,样式如图 14.1.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过USB转串口线
图 14.1.4来实现与外部设备的串口通信


UART串口通信实验1468.png
图 14.1.2 DB9接口

DB9接口定义以及各引脚功能说明如图 14.1.3所示,我们一般只用到其中的2(RXD)、3(TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用,如果大家想了解,可以自行百度下。
UART串口通信实验1649.png UART串口通信实验1650.png
图 14.1.3 DB9接口定义

UART串口通信实验1712.png
图 14.1.4 USB Type-C接口

于传统的DB9接口体积较大,会占用开发板过多空间,在DFZU2EG/4EV MPSoC开发板上我们采用的是Type-C接口,样式如图 14.1.4所示,另一端直接和电脑USB相连。连接示意图如下图所示:
UART串口通信实验1905.png
图 14.1.5 USB串口通信示意图

14.2实验任务
本节实验任务是上位机通过串口调试助手发送数据给MPSoC,MPSoC PL端通过UART串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
14.3硬件设计
DFZU2EG/4EV MPSoC开发板上UART串口部分的原理图如下所示:
UART串口通信实验2094.png
图 14.3.1 MPSoC UART串口原理图

原理图中CH340的工作电压为3.3V,所以需要加电平转换芯片XC6206P332MR将PL_VUSB转换为PL_UART_3V3给CH340模块使用。
从图 14.3.1中可以看到,MPSoC的FPGA芯片的PL_UART1_TX连接到CH340的RXD管脚,MPSoC的FPGA芯片的PL_UART1_RX连接到CH340的TXD管脚,CH340的PL_CH340_P/N分别连接到开发板的USB Type-C插座D+/-上。CH340具体的工作原理是:发送的时候把FPGA的PL_UART1_TX管脚送出的信号由TTL电平转换为USB差分电平并送到USB Type-C插座,接收的时候则把USB Type-CB插座送来的USB差分电平转换为TTL电平送给FPGA的PL_UART1_RX管脚,同时我们通过USB数据线将USB Type-C插座D+/-直连到PC上相应的USB口的D+/-,当然也包括电源和地,这样就完成了USB串口通信的硬件准备工作。
本实验中,系统时钟、按键复位以及串口的接收、发送端口的管脚分配如下表所示:
表 14.3.1 串口通信实验管脚分配
831048C6-F07D-40c5-88ED-68DB76BA1595.png

对应的XDC约束语句如下所示:
#IO管脚约束
#时钟周期约束
create_clock -name sys_clk_p -period 10.000 [get_ports sys_clk_p]
#时钟
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
set_property PACKAGE_PIN AE5 [get_ports sys_clk_p]
set_property PACKAGE_PIN AF5 [get_ports sys_clk_n]
#复位
set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN AB9 IOSTANDARD LVCMOS33} [get_ports uart_rxd]
set_property -dict {PACKAGE_PIN AB10 IOSTANDARD LVCMOS33} [get_ports uart_txd]
14.4程序设计
根据实验任务,我们不难想象本系统应该有一个串口接收模块,用来接收上位机发送的数据;还要有一个串口发送模块,用于将数据发回上位机;另外还应该有一个对数据进行环回控制的模块,它负责把从串口接收模块接收到的数据送给串口发送模块,以实现串口数据的环回。由此可以画出本次实验的系统框图,如下所示:
UART串口通信实验3655.png
图 14.4.1 系统框图

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

在图 14.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。则传输一帧数据的时序图如下图所示:
UART串口通信实验4274.png
图 14.4.3 串口通信时序图

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

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

从图 14.4.5中可以看出串口发送模块能够完成并串转换并正确发送串口数据。
在介绍完串口的接收和发送模块后,最后我们来看一下串口环回模块的代码:
1  module uart_loop(
2      input             sys_clk,                   //系统时钟
3      input            sys_rst_n,                 //系统复位,低电平有效
4      
5      input            recv_done,                 //接收一帧数据完成标志
6      input      [7:0] recv_data,                 //接收的数据
7      
8      input            tx_busy,                   //发送忙状态标志      
9      output reg       send_en,                   //发送使能信号
10     output reg [7:0] send_data                  //待发送数据
11     );
12
13 //reg define
14 reg recv_done_d0;
15 reg recv_done_d1;
16 reg tx_ready;
17
18 //wire define
19 wire recv_done_flag;
20
21 //*****************************************************
22 //**                    main code
23 //*****************************************************
24
25 //捕获recv_done上升沿,得到一个时钟周期的脉冲信号
26 assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
27                                                  
28 //对发送使能信号recv_done延迟两个时钟周期
29 always @(posedge sys_clk or negedge sys_rst_n) begin         
30     if (!sys_rst_n) begin
31         recv_done_d0 <= 1'b0;                                 
32         recv_done_d1 <= 1'b0;
33     end                                                      
34     else begin                                               
35         recv_done_d0 <= recv_done;                              
36         recv_done_d1 <= recv_done_d0;                           
37     end
38 end
39
40 //判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
41 always @(posedge sys_clk or negedge sys_rst_n) begin         
42     if (!sys_rst_n) begin
43         tx_ready  <= 1'b0;
44         send_en   <= 1'b0;
45         send_data <= 8'd0;
46     end                                                      
47     else begin                                               
48         if(recv_done_flag)begin                 //检测串口接收到数据
49             tx_ready  <= 1'b1;                  //准备启动发送过程
50             send_en   <= 1'b0;
51             send_data <= recv_data;             //寄存串口接收的数据
52         end
53         else if(tx_ready && (~tx_busy)) begin   //检测串口发送模块空闲
54             tx_ready <= 1'b0;                   //准备过程结束
55             send_en  <= 1'b1;                   //拉高发送使能信号
56         end
57     end
58 end
59
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信号拉低,等待下一个串口接收数据的到来。
14.5下载验证
编译工程并生成比特流.bit文件。接下来我们下载程序,验证上位机与开发板通过UART串口进行串口数据环回功能。
首先我们需要准备一个Type-C接口的USB转串口线,将USB接口一端插入电脑上的USB口,另一端Type-C接口与开发板上的PL_UART接口相连接.
然后分别连接JTAG接口和电源线,最后拨动开关按键给开发板上电。
硬件连接如下图所示:
UART串口通信实验21554.png
图 14.5.1 开发板PL端串口连接

注意上位机第一次使用USB转串口线与FPGA开发板连接时,需要安装USB串口驱动。在开发板随附的资料中找到“6_软件资料/1_软件/CH340驱动(USB串口驱动)”的文件夹,双击打开文件夹中的“SETUP.EXE”进行安装,驱动安装界面如图 14.5.2所示。界面中提示INF文件为CH341SER.INF,我们不需要理会(CH341,CH340驱动是共用的),直接点安装即可。
UART串口通信实验21834.png
图 14.5.2 USB串口驱动驱动安装界面

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

在串口助手中选择与开发板相连接的CH340虚拟串口,具体的端口号(这里是COM4)需要根据实际情况选择,可以在计算机设备器中查看,如下图所示:
UART串口通信实验22178.png
图 14.5.4 电脑的设备管理器窗口

在串口助手中设置波特率为115200,数据位为8,停止位为1,无校验位,最后确认打开串口。
串口打开后,在发送文本框中输入数据“5A”并点击发送,可以看到串口助手中接收到数据“5A”,如图 14.5.3所示。串口助手接收到的数据与发送的数据一致,说明程序所实现的串口数据环回功能验证成功。

回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-29 22:55

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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