学习内容
本文主要介绍关于ZYNQ芯片的串口中断功能,并编写相关读写测试代码,完成串口中断的读写测试。
开发环境
vivado 18.3&SDK,PYNQ-Z2开发板。
UART控制器
简介
UART控制器是一个全双工异步接收和发送,支持可编程波特率和I/O信号格式。 该控制器可实现奇偶校验自动生成和多主检测模式。UART操作由配置和模式寄存器控制。
FIFO、调制解调器信号和其他控制器功能的状态是使用状态、中断状态和调制解调器状态寄存器读取的。UART控制器有独立的RX和TX数据路径。每个路径包括一个64字节的FIFO。 控制器TX和RX FIFO中的数据进行串行化和反串行化,并包括一个模式开关,以支持RXD和TXD信号的各种环回配置。(RXD和TXD使用模式:正常模式、各种环回诊断测试模式)
FIFO中断状态位支持轮询或中断驱动的处理程序。 软件使用RX和TX数据端口寄存器读取和写入数据字节。当在类似于调制解调器的
应用程序中使用UART时,调制解调器控制模块检测并生成调制解调器握手信号,并且还根据握手协议控制接收和发送器路径。(调制解调器控制信号:CTS, RTS, DSR, DTR, RI和DCD只有在EMIO接口可用)
系统框图
UART控制器的系统框图如下图所示:
在图中,SLCR寄存器(系统级控制寄存器(SLCR)由用于控制PS行为的各种寄存器组成。这些寄存器可以通过中央互连使用加载和存储指令访问。)包括控制位用于UART时钟,复位和MIO-EMIO信号映射。软件可以使用APB 32位的从接口访问UART控制器寄存器。每个控制器的IRQ(中断号为59,82)连接到PS中断控制器,并连接到PL。UART控制器由参考时钟( UART REF_CLK)驱动,同时控制器也需要连接APB 总线时钟( CPU_1x clock), UART REF_CLK 和 CPU_1x clock 都是来自于 PS 时钟子系统。
内部框图
UART控制器内部框图如下图所示。
由上图可知,UART控制器使用PS AXI interconnect 进行数据交互,通过APB Slave接口来接收PS端口的配置信息和一些数据信息。假设要发送数据,则系统先将发生的字符串缓存到TxFIFO下,然后经Transmitter模块实现并转串,如果工作在正常模式下,则数据之间接到MIO/EMIO引脚上,正常向接收设备发送;假设要接收其他设备传来的串口信息,则首先通过串口接收引脚接收串行数据,然后经过receiver模块实现串转并,转换过后存入到RxFIFO下,经过APB从接口传输到PS端,即可对接收数据进行处理。
通过控制状态寄存器可以对UART控制器进行控制,而这些引脚只能连接到EMIO。UART控制器内部包括一个中断模块,所以可以和其他模块一样正常接收到来自系统的中断信号。对于UART的参考时钟在控制器内部首先进行了一个八分频,接着再产生波特率时钟。
Transmit FIFO
Transmit FIFO (Tx FIFO)存储由APB从接口写入的数据,发送模块收到FIFO中的数据后删除FIFO的数据然后进行串并转换,并装入其移位寄存器。TxFIFO的最大数据宽度为8位。数据通过写入TxFIFO寄存器加载到TxFIFO。
当数据加载到TxFIFO时,TxFIFO空标志将被清除并保持在这个Low状态,直到TxFIFO中的最后一个字符被删除并加载到发送器移位寄存器中。TxFIFO满中断状态(TFULL)表明TxFIFO已经完全写满了,并且阻止数据被写入到TxFIFO中。如果对TxFIFO执行另一个APB写入操作,则触发溢出,写入数据不会加载到TxFIFO中。
发送 FIFO接近满标志(TNFULL)表明在FIFO中没有足够的空间来进行一次程序大小的写入,这是由模式寄存器的WSIZE位控制的。TxFIFO接近满标志(TNFULL)表示TxFIFO中只有字节空闲。
可以在TxFIFO填充级别上设置一个阈值触发器(TTRIG)。发射器触发寄存器可以用来设置这个值,这样当TxFIFO填充深度达到设定的阈值时触发可以设置。
Receiver FIFO
Receiver FIFO和Transmit FIFO类似。RxFIFO存储由接收器串行移位寄存器接收的数据。RxFIFO的最大数据宽度是8位。当数据加载到RxFIFO时,RxFIFO空标志被清除,并且这种状态保持为低,直到RxFIFO中的所有数据通过APB接口传输完毕。空标志重新置位为高,如果接着读FIFO的话,则会从空的RxFIFO读取返回0。
RxFIFO满状态(Chnl_int_sts_reg0 [RFUL]和Channel_sts_reg0 [RFUL]位)表明RxFIFO满了,阻止数据被加载到RxFIFO。同时也可以在RxFIFO上设置一个阈值触发器(RTRIG)。接收器触发级别寄存器(Rcvr_FIFO_trigger_level0)可以用来设置这个值,取值范围是1 ~ 63。
I / O模式切换
这里的模式切换即为内部框图中的 Mode Switch 模块,如下图所示。
该模式由mode_reg0 [CHMODE]寄存器设置控制,总共分为四种模式,分别
为:正常模式( Normal Mode)、自动回音模式( Automatic Echo Mode)、本地环回模式( Local Loopback Mode)和远程环回模式( Remote Loopback Mode)。
正常模式( Normal Mode) :用于标准UART操作,就是发送接收功能。
自动回音模式( Automatic Echo Mode) :Automatic Echo Mode模式在RxD上接收数据,模式开关将数据连接到接收端和UARTx_TxD。而PS的TXD端口的数据无法正常发出。
本地环回模式( Local Loopback Mode) :本地环回模式不连接到RxD或TxD引脚,而是直接把PS发送的数据传输到PS的接收端。
远程环回模式( Remote Loopback Mode) :远程环回模式将RxD信号连接到TxD信号。在这种模式下,控制器不能在TxD上发送任何内容,也不能在RxD上接收任何内容。
UART启动顺序
UART 的启动顺序如下:
- 复位UART控制器,在PS进行系统复位时进行控制器的复位。
- 配置 IO 引脚信号。
- 配置 UART 参考时钟(可以保护默认)。
- 配置控制器功能( UART 控制器初始化)。
- 配置中断,通过中断来管理 RxFIFO 和 TxFIFO。
- 配置串口模式控制(可选)。
- 管理发送和接收的数据,采用轮询或中断驱动处理两种方式。
配置控制器功能步骤
在UART控制器中,控制器可以配置字符帧、波特率、FIFO触发级别、Rx超时机制,并使能控制器。所有步骤都必须在复位之后。
配置控制器的功能步骤如下:
- 配置 UART 数据帧格式。 数据位长度、停止位、校验方式、 IO 模式等。
- 设置波特率。
- 设置 RxFIFO 触发器等级,可以选择启用或禁用该功能。
- 使能 UART 控制器。
- 配置接收器的超时机制,可以选择启用或禁用该功能。
发送数据步骤
编写软件程序时,可以通过使用轮训和中断的方式控制RxFIFO和TxFIFO中的数据流。
使用轮询方式发送数据顺序
- 检查TxFIFO是否为空。直到uart.Channel sts rego[TEMPTY] =1,执行后续步骤。
- 用数据填充TxFIFO。向uart.TX_RX_FIFO0寄存器写入64字节的数据。
- 向TxFIFO写入数据。有两种方法:方法一: 可以等待 TxFIFO 为空之后再写入 64 个字节,即执行第 2 步;方法一: 可以检测 TxFIFO 是否写满,即不停的读取 TFUL 标志和写单个字节的数据。
使用中断方法发送数据的顺序
- 禁用 TxFIFO 空状态中断。
- 写入数据到TxFIFO中。
- 检测TxFIFO是否还有足够的空间容纳数据。
- 重复2和3操作。
- 使能中断。
- 等待TxFIFO为空,然后从步骤 1 重新开始。
接收数据步骤
使用轮询方式发送数据顺序
- 等待RxFIFO被填满到触发器级别。
- 从RxFIFO读取数据。
- 重复步骤2,直到FIFO为空。
- 设置Rx超时中断状态位时清除。
使用中断方法发送数据顺序
- 使能中断。
- 等待RxFIFO被填满到触发级别或Rx超时。
- 从RxFIFO读取数据。
- 重复步骤2和3,直到FIFO为空。
- 如果设置了中断状态位,则清除中断状态位。
系统框图
这里仅仅使用了UART部分,所以可以利用前文的helloworld工程,不需要进行特殊的配置。通过串口的中断功能把发送的数据再接收到PS端进行一个回环显示。
硬件平台搭建
新建工程,创建 block design。添加ZYNQ7 ip,根据本次工程需要对IP进行配置。勾选本次工程使用的uart资源
将ZYNQ无用资源进行取消勾选:
硬件系统构建完成如下:
然后我们进行generate output product 然后生成HDL封装。这里只用到了UART,是MIO引脚,所以不需要进行管脚分配。点击导出硬件资源(可以不包含bit流文件,因为只用到了PS资源),接着launch SDK。
SDK软件部分
打开SDK后,新建application project。
在system.mss中可以打开相关参考文档辅助设计。
这里使用串口中断可以参考赛灵思提供的例程代码进行修改设计:
这里导入uart_intr_example例程模板在main.c中输入以下代码:
#include "xparameters.h" #include "stdio.h" #include "xuartps.h" #include "xuartps_hw.h" #include "xscugic.h" #define UART_0_DEVICE_ID XPAR_PS7_UART_0_DEVICE_ID #define INTR_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID #define UART_INT_IRQ_ID XPAR_XUARTPS_0_INTR XUartPs Uart_Inst; XScuGic ScuGic_Inst; int uart_init(); void intr_init(XScuGic *intr, XUartPs *uart); void UartIntr_Handler(void *call_back_ref); int main(){ //uart初试化函数 uart_init(); xil_printf("intr\n"); //中断初始化 intr_init(&ScuGic_Inst,&Uart_Inst); while(1); return 0; } //uart初始化 int uart_init(){ XUartPs_Config *UartPs_Cfg; int Status; //查找配置信息 UartPs_Cfg= XUartPs_LookupConfig(UART_0_DEVICE_ID); //对uart控制器进行初始化 XUartPs_CfgInitialize(&Uart_Inst, UartPs_Cfg, UartPs_Cfg->BaseAddress); //检测硬件搭建是否正确 Status = XUartPs_SelfTest(&Uart_Inst); if (Status != XST_SUCCESS) { return XST_FAILURE; } //设置波特率 XUartPs_SetBaudRate(&Uart_Inst,115200); //设置RXFIFO触发阈值 XUartPs_SetFifoThreshold(&Uart_Inst,1); //设置操作模式 XUartPs_SetOperMode(&Uart_Inst, XUARTPS_OPER_MODE_NORMAL); return XST_SUCCESS; } //中断初始化 void intr_init(XScuGic *intr, XUartPs *uart){ XScuGic_Config *IntcConfig; //中断控制器初始化 IntcConfig = XScuGic_LookupConfig(INTR_DEVICE_ID); XScuGic_CfgInitialize(intr,IntcConfig,IntcConfig->CpuBaseAddress); Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, (void *)intr); Xil_ExceptionEnable(); //为中断设置中断处理函数 XScuGic_Connect(intr, UART_INT_IRQ_ID, (Xil_ExceptionHandler) UartIntr_Handler, (void *) uart); //设置触发类型 XUartPs_SetInterruptMask(uart, XUARTPS_IXR_RXOVR); //使能中断 XScuGic_Enable(intr, UART_INT_IRQ_ID); } //中断处理函数 void UartIntr_Handler(void *call_back_ref){ XUartPs *uartinst =(XUartPs *)call_back_ref; u32 read_data = 0; u32 intr_status; //读取中断ID寄存器 intr_status = XUartPs_ReadReg(uartinst->Config.BaseAddress, XUARTPS_IMR_OFFSET);//读取掩码 intr_status &= XUartPs_ReadReg(uartinst->Config.BaseAddress, XUARTPS_ISR_OFFSET);//读取状态 if(intr_status & (u32)XUARTPS_IXR_RXOVR){ read_data = XUartPs_RecvByte(XPAR_PS7_UART_0_BASEADDR);//接收发送的字节 XUartPs_WriteReg(uartinst->Config.BaseAddress,XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR);//清除中断状态 } //设置发送 XUartPs_SendByte(XPAR_PS7_UART_0_BASEADDR,read_data); }
部分代码讲解
在整体的代码设计中,代码思路如下:
- 初始化UART控制器
- 初始化UART中断
- 编写中断服务函数
对于初始化UART部分,在#include "xuartps.h"
头文件中可以找到很多配置的函数,调用即可对uart的波特率,中断触发阈值等参数进行设置。
对于中断的配置可以类比GPIO的中断配置函数,先在初始化SGIC,然后进行异常初始化,完成注册异常并使能,接着需要连接SGIC和UART,最后设置中断类型并使能完成中断整体操作配置。
在中断服务函数中,实现的功能为回环读写,所以要在中断函数中进行检测中断类型,当进入相应的中断时进行数据的读取,读取到上位机的发送数据,然后清除中断标志,最后把读到的数据进行发送。
Reference
- 正点原子视频教程
- UG585