UART串口中断实验
我们在使用PS的时候,通常会添加UART控制器,用于打印信息和调试代码。除此之外,PS在和外部设备通信时,也会经常使用串口进行通信。在“Hello World实验”章节中,我们在PS中已经添加了UART控制器,本章我们进一步向大家介绍UART控制器以及UART控制器利用中断进行通信的方法。
本章包括以下几个部分:
- 简介
- 实验任务
- 硬件设计
- 软件设计
- 下载验证
简介
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控制器内部框图如下图所示:
图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)。
模式切换的功能示意图如所示:
图8.1.2 模式切换功能示意图
从上图中可以清晰的看出UART不同模式下所实现的功能。正常模式是标准的UART操作模式;自动回音模式下,RxD连接至TxD,控制器可以接收数据,但是不能发送数据;本地环回模式没有连接RxD和TxD的引脚,用于本地程序的环回测试;远程环回模式下,RxD连接至TxD,但是并没有和控制器连接,因此控制器在此模式下无法发送数据和接收数据。当然在实际应用中,最常用的就是UART的正常模式。
在讲解完UART控制器之后,接下来我们向大家介绍程序中UART控制器的设计方法。如果我们只是用串口来打印信息的话,那么可以直接使用print()或者xil_printf()函数就可以了,无需在程序中对串口做配置。但是如果我们需要使用UART来完成某些特定功能的话,如串口接收中断,那么就要了解UART控制器初始化、UART中断初始化以及UART常用的API函数等相关内容了。
UART的启动顺序
UART的启动顺序如下:
- 复位控制器(在PS系统复位时);
- 配置IO引脚信号。RxD和TxD可以连接至MIO或者EMIO,只有EMIO可以使用串口的流控制。
- 配置UART参考时钟;
- 配置控制器功能(UART控制器初始化);
- 配置中断,通过中断来管理RxFIFO和TxFIFO;
- 配置串口流控制(可选);
- 管理发送和接收的数据,可以采用轮询或中断驱动处理两种方式。
配置控制器功能
控制器功能主要配置字符帧、波特率、FIFO触发器等级、Rx超时机制,并启用控制器。重置控制器后必须要配置所有这些参数。步骤如下:
- 配置UART数据帧格式。如:数据位长度、停止位、校验方式、IO模式等;
- 设置波特率;
- 设置RxFIFO触发器等级,可以选择启用或禁用该功能;
- 使能UART控制器;
- 配置接收器的超时机制,可以选择启用或禁用该功能。
发送数据
我们可以使用轮询或者中断两种方式控制TxFIFO和RxFIFO的数据流。这两个FIFO大小均为64个字节,因此当TxFIFO的空标志有效时,我们可以直接向其写入64个字节,无需检查TxFIFO的状态。实际上当发送器处于活跃状态时,可写入的字节数要超过64个字节,因为控制器同时也在移出数据,将其串行化转移到TxD信号上。
采用轮询方法发送数据的顺序如下:
- 检查TxFIFO是否为空;
- 向TxFIFO写入数据,可以写入64个字节;
- 向TxFIFO中写入更多数据。我们可以等待TxFIFO为空之后再写入64个字节,即执行第2步;也可以检测TxFIFO是否写满,即不停的读取TFUL标志和写单个字节的数据。
采用中断方法发送数据的顺序如下:
- 禁用TxFIFO空中断;
- 向TxFIFO写数据,可以写入64个字节的数据;
- 检测TxFIFO是否为满状态,不停的读取TFUL标志和写单个字节的数据;
- 重复步骤2和3,直到TxFIFO已满;
- 使能TxFIFO空中断;
- 等待,直到TxFIFO为空,然后从步骤1重新开始;
接收数据
采用轮询方法接收数据的顺序如下:
- 等待,直到RxFIFO中的数据数量达到触发等级;
- 从RxFIFO中读取数据;
- 重复步骤2直到FIFO空;
- 发生Rx超时中断时将其重置。
采用中断方法接收数据的顺序如下:
- 使能中断;
- 等待,直到RxFIFO中的数据数量达到触发等级或者发生超时;
- 从RxFIFO中读取数据;
- 重复步骤2和3,直到RxFIFO为空;
- 清除中断标志。
实验任务
本章的实验任务是使用UART控制器,完成串口中断数据环回的功能。
硬件设计
从实验任务我们可以画出如下的系统框图,DDR4中存放和运行程序、UART实现串口通信。
图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接口不支持此功能。
图8.3.2 UART MIO配置界面
如果想要把UART控制器的引脚映射到EMIO接口,只需要将UART的引脚改为EMIO,如图8.3.3所示。然后在Vivado工程中添加对应的管脚约束,生成Bitstream文件并导出Hardware即可。
图8.3.3 UART控制器EMIO接口配置
本次实验使用的是板载的USB UART接口,连接的是MIO42和MIO43引脚,因此这里不做修改。
接下来,直接导出硬件,然后新建vitis文件夹,将导出的xsa文件拷贝到里面,最后打开Vitis软件,并将路径指向新建的vitis文件夹下。
软件设计
在硬件设计的最后,我们打开了Vitis开发环境,如下图所示。
图8.4.1 Vitis开发环境界面
下面我们在Vitis中创建应用工程,选择菜单File->New->Application Project, 新建一个应用工程。
图8.4.2 新建应用工程
在弹出的图8.4.3所示界面中,输入工程名“uart_intr_loop”,然后点击“Next >”按钮。
图8.4.3 输入工程名
接下添加硬件平台文件,然后点击“Next”按钮,如下图所示:
图8.4.4 添加硬件平台文件
在弹出的界面中,使用默认设置,然后点击“Next”。然后在接下来的界面中选择空应用工程,如下图所示,这样应用工程就搭建好了。
图8.4.5 选择空应用工程
可以看到Vitis中创建了一个名为uart_intr_loop的应用工程。展开design_1_wrapper,找到platform.spr并双击,右面的界面中出现design_1_wrapper的标签页,然后找到板级支持包并点击,可以看到UART文档和导入示例,如图8.4.6和图8.4.7所示:
图8.4.6 板级支持包
图8.4.7 uart文档和示例
如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于UART有5个示例,如下图所示:
图8.4.8 导入示例
感兴趣的朋友可以参考下官方提供的UART例程,其中xuartps_intr_example是串口中断的示例。
这里我们不导入官方的例程,而是新建一个源文件。在uart_intr_loop/src目录上右键,选择New->File一栏。在弹出的对话框中File name一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧uart/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
在代码的第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文件。
软件设计部分到这里就完成了。
下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。
打开Vitis Terminal终端,设置并连接串口。然后下载本次实验的程序。下载完成后,在Terminal中点击下图箭头处的命令输入图标,打开命令输入窗口,如下图所示:
图8.5.1 打开命令输入栏
然后输入待发送的数据,这里输入“Hello Zynq”,然后按下回车键,即可在接收数据窗口中接收到数据,如下图所示:
图8.5.2 发送数据操作界面
程序成功打印出了“Hello Zynq”字符串,说明本次实验在MPSOC开发板上面下载验证成功。
最后这里强调几点程序需要注意的地方。首先程序会对UART串口进行初始化,我们知道,当使用一些打印函数的时候(如:xil_printf()),实际上调用的还是UART相关的API函数,如果在初始化的过程中,使用打印函数,或者在打印的过程中对串口进行初始化,都会导致串口助手打印信息出错。其次当需要Debug在线调试的时候(定时器中断实验的下载验证部分有Debug调试教程),单步执行或者当程序停留在打断点的地方,都会使程序暂停执行,此时如果在串口助手中发送数据,会导致程序收不到串口数据或者接收到的数据异常。