《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章 UART串口中断实验
  95kVyaJuybju 2023年11月02日 60 0

UART串口中断实验​

我们在使用PS的时候,通常会添加UART控制器,用于打印信息和调试代码。除此之外,PS在和外部设备通信时,也会经常使用串口进行通信。在“Hello World实验”章节中,我们在PS中已经添加了UART控制器,本章我们进一步向大家介绍UART控制器以及UART控制器利用中断进行通信的方法。

本章包括以下几个部分:

  1. 简介
  2. 实验任务
  3. 硬件设计
  4. 软件设计
  5. 下载验证


简介

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时,除TxRx引脚外,可选的还有CTS、RTS、DSR、DCD、RI、DTR等引脚,这些引脚用于串口的流控制,即调制解调器的数据通讯中。

UART控制器采用独立的接收和发送数据路径,每个路径包含一个64字节的FIFO,控制器对发送和接收FIFO中的数据进行串并转换操作。FIFO的中断标志支持轮询处理或中断驱动处理两种方式。另外,控制器中还有一个模式开关,支持RXD和TXD信号的各种环回配置。UART控制器内部框图如下图所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_串口


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)。

模式切换的功能示意图如所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_02


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. 清除中断标志。

实验任务

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

硬件设计

从实验任务我们可以画出如下的系统框图,DDR4中存放和运行程序、UART实现串口通信。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_串口_03


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接口不支持此功能。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_数据_04


8.3.2 UART MIO配置界面

如果想要把UART控制器的引脚映射到EMIO接口,只需要将UART的引脚改为EMIO,如8.3.3所示。然后在Vivado工程中添加对应的管脚约束,生成Bitstream文件并导出Hardware即可。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_05


8.3.3 UART控制器EMIO接口配置

本次实验使用的是板载的USB UART接口,连接的是MIO42和MIO43引脚,因此这里不做修改。

接下来,直接导出硬件,然后新建vitis文件夹,将导出的xsa文件拷贝到里面,最后打开Vitis软件,并将路径指向新建的vitis文件夹下。

软件设计

在硬件设计的最后,我们打开了Vitis开发环境,如下图所示。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_06


8.4.1 Vitis开发环境界面

下面我们在Vitis中创建应用工程,选择菜单File->New->Application Project, 新建一个应用工程。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_数据_07


8.4.2 新建应用工程

在弹出的8.4.3所示界面中,输入工程名“uart_intr_loop”,然后点击“Next >”按钮。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_串口_08


8.4.3 输入工程名

接下添加硬件平台文件,然后点击“Next”按钮,如下图所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_数据_09


8.4.4 添加硬件平台文件

在弹出的界面中,使用默认设置,然后点击“Next”。然后在接下来的界面中选择空应用工程,如下图所示,这样应用工程就搭建好了。

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_10


8.4.5 选择空应用工程

可以看到Vitis中创建了一个名为uart_intr_loop的应用工程。展开design_1_wrapper,找到platform.spr并双击,右面的界面中出现design_1_wrapper的标签页,然后找到板级支持包并点击,可以看到UART文档和导入示例,如8.4.6和图8.4.7所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_串口_11


8.4.6 板级支持包

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_12


8.4.7 uart文档和示例

如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于UART有5个示例,如下图所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_串口_13


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行,XScuGicXUartPs为程序中定义的两个结构体。如果在Vitis软件中,按住Ctrl键不放将鼠标移动到XScuGic或者XUartPs上,当鼠标变成手指状时,单击鼠标左键,会自动跳转到定义这两个结构体的地方。其中XScuGic包含了中断控制器相关的参数和数据,而XUartPs则包含了串口相关的参数和数据。

在程序的main函数中,首先对串口进行初始化(uart_init),如第101行所示。初始化完成后,函数返回初始化的结果,如果初始化失败,打印错误信息并返回;如果初始化成功,则开始执行串口中断初始化函数(uart_intr_init),如第107行所示。最后主程序会一直停留在while无限循环,如代码中第108行所示。

在代码的第15行至第40行完成了对UART的初始化。其中代码的第28XUartPs_SelfTest函数实现了UART设备自检的功能,即使用UART本地环回的模式,并验证数据是否可以正确发送和接收。XUartPs_SetOperMode函数设置串口的工作模式,这里输入的参数XUARTPS_OPER_MODE_NORMAL为正常的工作模式。XUartPs_SetBaudRate函数用于设置串口的通信波特率,这里设置的波特率为115200,如果需要修改成其它波特率,可直接在此修改输入的参数即可。XUartPs_SetFifoThreshold函数用于设置RxFIFO的中断触发等级,即触发RxFIFO中断的数据个数,这里设置的值为1,即每收到一个值就触发中断。注意,中断触发等级最大值不超过63

在代码的第65行至第94行完成了串口中断的初始化。程序首先对中断控制器进行初始化,随后设置并打开中断异常处理的功能。接下来为串口中断设置中断处理函数,通过XScuGic_Connect函数进行设置,这里设置的串口中断处理函数为uart_intr_handlerXUartPs_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文件。

软件设计部分到这里就完成了。

下载验证

首先我将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接然后使用USB连接线将USB_UART接口与电脑连接,用于口通信。最后连接开发板的电源,给开发板上电。

打开Vitis Terminal终端,设置连接串口。然后下载本次实验程序下载完成后,Terminal中点击下图箭头处的命令输入图标,打开命令输入窗口,如下图所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_14


8.5.1 打开命令输入栏

然后输入待发送的数据,这里输入“Hello Zynq”,然后按下回车键,即可在接收数据窗口中接收到数据,如下图所示:

 《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第八章  UART串口中断实验_初始化_15


8.5.2 发送数据操作界面

程序成功打印出了Hello Zynq”字符串,说本次实验在MPSOC开发板上面下载验证成功。

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

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
95kVyaJuybju