【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

时间:2023-02-12 07:52:17

1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第八章UART串口中断实验

我们在使用PS的时候,通常会添加UART控制器,用于打印信息和调试代码。除此之外,PS在和外部设备通信时,也会经常使用串口进行通信。在“Hello World实验”章节中,我们在PS中已经添加了UART控制器,本章我们进一步向大家介绍UART控制器以及UART控制器利用中断进行通信的方法。
本章包括以下几个部分:
88.1简介
8.2实验任务
8.3硬件设计
8.4软件设计
8.5下载验证

8.1简介

UART控制器介绍
UART控制器是一个全双工异步收发控制器,MPSOC内部包含两个UART控制器,UART0和UART1。每一个UART控制器支持可编程的波特率发生器、64字节的接收FIFO和发送FIFO、产生中断、RXD和TXD信号的环回模式设置以及可配置的数据位长度、停止位和校验方式等。
UART控制器的配置以及状态的获取由控制(Control)和状态寄存器(Status Registers)完成。另外,UART控制器不仅可以连接至MIO,也可以映射到EMIO,从而使用PL的端口来实现串口通信的功能。当UART控制器连接到MIO时,只有Tx(发送)和Rx(接收)两个引脚;而当连接EMIO时,除Tx和Rx引脚外,可选的还有CTS、RTS、DSR、DCD、RI、DTR等引脚,这些引脚用于串口的流控制,即调制解调器的数据通讯中。
UART控制器采用独立的接收和发送数据路径,每个路径包含一个64字节的FIFO,控制器对发送和接收FIFO中的数据进行串并转换操作。FIFO的中断标志支持轮询处理或中断驱动处理两种方式。另外,控制器中还有一个模式开关,支持RXD和TXD信号的各种环回配置。UART控制器内部框图如下图所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.1.1 UART控制器内部框图
UART控制器的寄存器通过APB 从机接口和PS AXI总线互联,控制器的寄存器用于对UART控制器进行配置和获取状态。波特率发生器(Baud Rate Generator)为UART控制器的接收端和发送端提供位周期时钟;中断控制器(GIC)为串口的收发提供了中断服务的功能。
APB总线接口通过向TxFIFO寄存器写值,将数据加载到TxFIFO存储器中。当数据加载至TxFIFO后,TxFIFO的空标志变成无效的状态,直到最后一个数据从TxFIFO中移出,加载至传输移位寄存器,TxFIFO恢复空的标志位。同时TxFIFO使用TFULL(满中断状态)用于表示当前TxFIFO已经写满,并且会阻止数据继续写入。如果此时继续执行写操作,那么会触发溢出,数据不会加载到TxFIFO中。
RxFIFO存储器接收来自接收移位寄存器的数据,当接收完数据后,RxFIFO空标志信号同样变成无效的状态,直到所有的数据通过APB总线发送出去。RxFIFO的满标志状态用于表示RxFIFO已经写满,并且会阻止更多的数据写入。
图8.1.2中的模式切换(Mode Switch)控制RxD和TxD的信号连接方式,总共分为四种模式,分别为:正常模式(Normal Mode)、自动回音模式(Automatic Echo Mode)、本地环回模式(Local Loopback Mode)和远程环回模式(Remote Loopback Mode)。
模式切换的功能示意图如所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.1.2 模式切换功能示意图
从上图中可以清晰的看出UART不同模式下所实现的功能。正常模式是标准的UART操作模式;自动回音模式下,RxD连接至TxD,控制器可以接收数据,但是不能发送数据;本地环回模式没有连接RxD和TxD的引脚,用于本地程序的环回测试;远程环回模式下,RxD连接至TxD,但是并没有和控制器连接,因此控制器在此模式下无法发送数据和接收数据。当然在实际应用中,最常用的就是UART的正常模式。
在讲解完UART控制器之后,接下来我们向大家介绍程序中UART控制器的设计方法。如果我们只是用串口来打印信息的话,那么可以直接使用print()或者xil_printf()函数就可以了,无需在程序中对串口做配置。但是如果我们需要使用UART来完成某些特定功能的话,如串口接收中断,那么就要了解UART控制器初始化、UART中断初始化以及UART常用的API函数等相关内容了。
UART的启动顺序
UART的启动顺序如下:
1、复位控制器(在PS系统复位时);
2、配置IO引脚信号。RxD和TxD可以连接至MIO或者EMIO,只有EMIO可以使用串口的流控制。
3、配置UART参考时钟;
4、配置控制器功能(UART控制器初始化);
5、配置中断,通过中断来管理RxFIFO和TxFIFO;
6、配置串口流控制(可选);
7、管理发送和接收的数据,可以采用轮询或中断驱动处理两种方式。
配置控制器功能
控制器功能主要配置字符帧、波特率、FIFO触发器等级、Rx超时机制,并启用控制器。重置控制器后必须要配置所有这些参数。步骤如下:
1、配置UART数据帧格式。如:数据位长度、停止位、校验方式、IO模式等;
2、设置波特率;
3、设置RxFIFO触发器等级,可以选择启用或禁用该功能;
4、使能UART控制器;
5、配置接收器的超时机制,可以选择启用或禁用该功能。
发送数据
我们可以使用轮询或者中断两种方式控制TxFIFO和RxFIFO的数据流。这两个FIFO大小均为64个字节,因此当TxFIFO的空标志有效时,我们可以直接向其写入64个字节,无需检查TxFIFO的状态。实际上当发送器处于活跃状态时,可写入的字节数要超过64个字节,因为控制器同时也在移出数据,将其串行化转移到TxD信号上。
采用轮询方法发送数据的顺序如下:
1、检查TxFIFO是否为空;
2、向TxFIFO写入数据,可以写入64个字节;
3、向TxFIFO中写入更多数据。我们可以等待TxFIFO为空之后再写入64个字节,即执行第2步;也可以检测TxFIFO是否写满,即不停的读取TFUL标志和写单个字节的数据。
采用中断方法发送数据的顺序如下:
1、禁用TxFIFO空中断;
2、向TxFIFO写数据,可以写入64个字节的数据;
3、检测TxFIFO是否为满状态,不停的读取TFUL标志和写单个字节的数据;
4、重复步骤2和3,直到TxFIFO已满;
5、使能TxFIFO空中断;
6、等待,直到TxFIFO为空,然后从步骤1重新开始;
接收数据
采用轮询方法接收数据的顺序如下:
1、等待,直到RxFIFO中的数据数量达到触发等级;
2、从RxFIFO中读取数据;
3、重复步骤2直到FIFO空;
4、发生Rx超时中断时将其重置。
采用中断方法接收数据的顺序如下:
1、使能中断;
2、等待,直到RxFIFO中的数据数量达到触发等级或者发生超时;
3、从RxFIFO中读取数据;
4、重复步骤2和3,直到RxFIFO为空;
5、清除中断标志。

8.2实验任务

本章的实验任务是使用UART控制器,完成串口中断数据环回的功能。

8.3硬件设计

从实验任务我们可以画出如下的系统框图,DDR4中存放和运行程序、UART实现串口通信。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.3.1 系统框图
由系统框图可知,本次实验的框图和《Hello World实验》的框图一样,同样只需要搭建嵌入式最小系统,最小系统只包括PS部分。这里添加的UART控制器不仅仅只是打印信息,同时为了实现串口数据环回的功能。
由于本次实验嵌入式系统的搭建和《Hello World实验》完全相同,这里不再详细讲解搭建的步骤,大家可以按照《Hello World实验》章节的步骤来创建一个新的嵌入式系统,或者将《Hello World实验》章节的工程另存为本次实验的工程,工程名为uart_intr_loop。
这里简单介绍下MPSOC PS的配置界面,如图 8.3.2所示。MPSOC开发板上的USB UART连接的引脚是MIO42和MIO43,因此在配置界面选择的是UART0 MIO42…MIO43。图中的Modem signals表示是否添加串口的流控制功能,即调制解调器,如果选中的话,会额外增加一些引脚,一般不勾选。需要注意的是,串口的流控制功能只能用于EMIO接口,MIO接口不支持此功能。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.3.2 UART MIO配置界面
如果想要把UART控制器的引脚映射到EMIO接口,只需要将UART的引脚改为EMIO,如图 8.3.3所示。然后在Vivado工程中添加对应的管脚约束,生成Bitstream文件并导出Hardware即可。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.3.3 UART控制器EMIO接口配置
本次实验使用的是板载的USB UART接口,连接的是MIO42和MIO43引脚,因此这里不做修改。
接下来,直接导出硬件,然后新建vitis文件夹,将导出的xsa文件拷贝到里面,最后打开Vitis软件,并将路径指向新建的vitis文件夹下。

8.4软件设计

在硬件设计的最后,我们打开了Vitis开发环境,如下图所示。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.4.1 Vitis开发环境界面
下面我们在Vitis中创建应用工程,选择菜单File->New->Application Project, 新建一个应用工程。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.4.2 新建应用工程
在弹出的图 8.4.3所示界面中,输入工程名“uart_intr_loop”,然后点击“Next >”按钮。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图 8.4.3 输入工程名
接下添加硬件平台文件,然后点击“Next”按钮,如下图所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.4.4 添加硬件平台文件
在弹出的界面中,使用默认设置,然后点击“Next”。然后在接下来的界面中选择空应用工程,如下图所示,这样应用工程就搭建好了。
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.4.5 选择空应用工程
可以看到Vitis中创建了一个名为uart_intr_loop的应用工程。展开design_1_wrapper,找到platform.spr并双击,右面的界面中出现design_1_wrapper的标签页,然后找到板级支持包并点击,可以看到UART文档和导入示例,如图8.4.6和图8.4.7所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.4.6 板级支持包
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.4.7 uart文档和示例
如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于UART有5个示例,如下图所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.4.8 导入示例
感兴趣的朋友可以参考下官方提供的UART例程,其中xuartps_intr_example是串口中断的示例。
这里我们不导入官方的例程,而是新建一个源文件。在uart_intr_loop/src目录上右键,选择New->File一栏。在弹出的对话框中File name一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧uart/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:

1   #include "xparameters.h"
2   #include "xuartps.h"
3   #include "xil_printf.h"
4   #include "xscugic.h"
5   #include "stdio.h"
6   
7   #define UART_DEVICE_ID     XPAR_XUARTPS_0_DEVICE_ID     //串口设备ID
8   #define INTC_DEVICE_ID     XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
9   #define UART_INT_IRQ_ID    XPAR_XUARTPS_0_INTR          //串口中断ID
10  
11  XScuGic Intc;              //中断控制器驱动程序实例
12  XUartPs Uart_Ps;           //串口驱动程序实例
13  
14  //UART初始化函数
15  int uart_init(XUartPs* uart_ps)
16  {
17      int status;
18      XUartPs_Config *uart_cfg;
19  
20      uart_cfg = XUartPs_LookupConfig(UART_DEVICE_ID);
21      if (NULL == uart_cfg)
22          return XST_FAILURE;
23      status = XUartPs_CfgInitialize(uart_ps, uart_cfg, uart_cfg->BaseAddress);
24      if (status != XST_SUCCESS)
25          return XST_FAILURE;
26  
27      //UART设备自检
28      status = XUartPs_SelfTest(uart_ps);
29      if (status != XST_SUCCESS)
30          return XST_FAILURE;
31  
32      //设置工作模式:正常模式
33      XUartPs_SetOperMode(uart_ps, XUARTPS_OPER_MODE_NORMAL);
34      //设置波特率:115200
35      XUartPs_SetBaudRate(uart_ps,115200);
36      //设置RxFIFO的中断触发等级
37      XUartPs_SetFifoThreshold(uart_ps, 1);
38  
39      return XST_SUCCESS;
40  }
41  
42  //UART中断处理函数
43  void uart_intr_handler(void *call_back_ref)
44  {
45      XUartPs *uart_instance_ptr = (XUartPs *) call_back_ref;
46      u32 rec_data = 0 ;
47      u32 isr_status ;                           //中断状态标志
48  
49      //读取中断ID寄存器,判断触发的是哪种中断
50      isr_status = XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
51                     XUARTPS_IMR_OFFSET);
52      isr_status &= XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
53                     XUARTPS_ISR_OFFSET);
54  
55      //判断中断标志位RxFIFO是否触发
56      if (isr_status & (u32)XUARTPS_IXR_RXOVR){
57          rec_data = XUartPs_RecvByte(XPAR_PSU_UART_0_BASEADDR);
58          //清除中断标志
59          XUartPs_WriteReg(uart_instance_ptr->Config.BaseAddress,
60                  XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR) ;
61      }
62      XUartPs_SendByte(XPAR_PSU_UART_0_BASEADDR,rec_data);
63  }
64  
65  //串口中断初始化
66  int uart_intr_init(XScuGic *intc, XUartPs *uart_ps)
67  {
68      int status;
69      //初始化中断控制器
70      XScuGic_Config *intc_cfg;
71      intc_cfg = XScuGic_LookupConfig(INTC_DEVICE_ID);
72      if (NULL == intc_cfg)
73          return XST_FAILURE;
74      status = XScuGic_CfgInitialize(intc, intc_cfg,
75              intc_cfg->CpuBaseAddress);
76      if (status != XST_SUCCESS)
77          return XST_FAILURE;
78  
79      //设置并打开中断异常处理功能
80      Xil_ExceptionInit();
81      Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
82              (Xil_ExceptionHandler)XScuGic_InterruptHandler,
83              (void *)intc);
84      Xil_ExceptionEnable();
85  
86      //为中断设置中断处理函数
87      XScuGic_Connect(intc, UART_INT_IRQ_ID,
88              (Xil_ExceptionHandler) uart_intr_handler,(void *) uart_ps);
89      //设置UART的中断触发方式
90      XUartPs_SetInterruptMask(uart_ps, XUARTPS_IXR_RXOVR);
91      //使能GIC中的串口中断
92      XScuGic_Enable(intc, UART_INT_IRQ_ID);
93      return XST_SUCCESS;
94  }
95  
96  //main函数
97  int main(void)
98  {
99      int status;
100 
101     status = uart_init(&Uart_Ps);    //串口初始化
102     if (status == XST_FAILURE) {
103         xil_printf("Uart Initial Failed\r\n");
104         return XST_FAILURE;
105     }
106 
107     uart_intr_init(&Intc, &Uart_Ps); //串口中断初始化
108     while (1);
109     return status;
110 }

在代码的第11行和第12行,XScuGic和XUartPs为程序中定义的两个结构体。如果在Vitis软件中,按住Ctrl键不放,将鼠标移动到XScuGic或者XUartPs上,当鼠标变成手指状时,单击鼠标左键,会自动跳转到定义这两个结构体的地方。其中XScuGic包含了中断控制器相关的参数和数据,而XUartPs则包含了串口相关的参数和数据。
在程序的main函数中,首先对串口进行初始化(uart_init),如第101行所示。初始化完成后,函数返回初始化的结果,如果初始化失败,打印错误信息并返回;如果初始化成功,则开始执行串口中断初始化函数(uart_intr_init),如第107行所示。最后主程序会一直停留在while无限循环,如代码中第108行所示。
在代码的第15行至第40行完成了对UART的初始化。其中代码的第28行XUartPs_SelfTest函数实现了UART设备自检的功能,即使用UART本地环回的模式,并验证数据是否可以正确发送和接收。XUartPs_SetOperMode函数设置串口的工作模式,这里输入的参数XUARTPS_OPER_MODE_NORMAL为正常的工作模式。XUartPs_SetBaudRate函数用于设置串口的通信波特率,这里设置的波特率为115200,如果需要修改成其它波特率,可直接在此修改输入的参数即可。XUartPs_SetFifoThreshold函数用于设置RxFIFO的中断触发等级,即触发RxFIFO中断的数据个数,这里设置的值为1,即每收到一个值就触发中断。注意,中断触发等级最大值不超过63。
在代码的第65行至第94行完成了串口中断的初始化。程序首先对中断控制器进行初始化,随后设置并打开中断异常处理的功能。接下来为串口中断设置中断处理函数,通过XScuGic_Connect函数进行设置,这里设置的串口中断处理函数为uart_intr_handler。XUartPs_SetInterruptMask函数用于设置UART的中断触发方式,函数输入的参数为XUARTPS_IXR_RXOVR,表示达到RxFIFO的触发等级时,开始触发中断,当然也可以设置成RxFIFO为满时触发中断或者为空时触发中断等。最后,通过XScuGic_Enable函数来使能GIC中的串口中断。
在代码的第42行至第63行为UART中断处理函数,由于RxFIFO的触发等级设置为1,因此每次接收到数据都会进入此中断函数。程序中首先读取中断ID寄存器,判断触发的是哪种中断,再读取中断的状态。当判断中断标志位为RxFIFO触发中断时,通过XUartPs_RecvByte函数来读取接收到的数据,并清除对应的中断标志位。最后通过XUartPs_SendByte函数发送接收到的数据,实现串口环回的功能。
程序设计完成后,按快捷键Ctrl+S保存main.c文件,然后编译工程。编译完成后控制台(Console)中会出现提示信息“Build Finished”,同时在应用工程的Binaries目录下可以看到生成的elf文件。
软件设计部分到这里就完成了。

8.5下载验证

首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。
打开Vitis Terminal终端,设置并连接串口。然后下载本次实验的程序。下载完成后,在Terminal中点击下图箭头处的命令输入图标,打开命令输入窗口,如下图所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.5.1 打开命令输入栏
然后输入待发送的数据,这里输入“Hello Zynq”,然后按下回车键,即可在接收数据窗口中接收到数据,如下图所示:
【正点原子FPGA连载】第八章UART串口中断实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

图8.5.2 发送数据操作界面
程序成功打印出了“Hello Zynq”字符串,说明本次实验在MPSOC开发板上面下载验证成功。
最后这里强调几点程序需要注意的地方。首先程序会对UART串口进行初始化,我们知道,当使用一些打印函数的时候(如:xil_printf()),实际上调用的还是UART相关的API函数,如果在初始化的过程中,使用打印函数,或者在打印的过程中对串口进行初始化,都会导致串口助手打印信息出错。其次当需要Debug在线调试的时候(定时器中断实验的下载验证部分有Debug调试教程),单步执行或者当程序停留在打断点的地方,都会使程序暂停执行,此时如果在串口助手中发送数据,会导致程序收不到串口数据或者接收到的数据异常。