学会Zynq(27)UART中断驱动模式示例

Zynq中的UART支持轮询和中断驱动两种模式。本文给出使用中断驱动模式的例子,完成与26篇中轮询模式下相同的功能,即UART收到8字节数据后执行某项操作。对比之下,体会中断驱动模式的特点。

SDK程序设计

由于要使用中断系统,我们翻出两个“老伙计”,第14篇中的sys_intr.h和sys_intr.c。将GIC初始化和串口中断初始化分开,这样当设计中有多个中断源时,编写代码会更方便。

user_uart.h文件的代码如下:

#ifndef SRC_USER_UART_H_
#define SRC_USER_UART_H_

#include "xparameters.h"
#include "xuartps.h"
#include "xil_printf.h"
#include "sleep.h"
#include "xscugic.h"

#define UART_DEVICE_ID     XPAR_PS7_UART_1_DEVICE_ID //设备ID
#define UART_INT_IRQ_ID    XPAR_XUARTPS_1_INTR  //中断号
#define BUFFER_SIZE 8

int Uart_Send(XUartPs* Uart_Ps, u8 *RecvBuffer, int length);
int Uart_Init(XUartPs* Uart_Ps, u16 DeviceId);
void Uart_Intr_System(XScuGic *Intc, XUartPs *Uart_Ps, u16 UartIntrId);
void Handler(void *CallBackRef);

#endif /* SRC_USER_UART_H_ */

user_uart.c文件的代码如下,设计UART中断初始化函数和中断处理函数:

#include "user_uart.h"

static u8 RecvBuffer[BUFFER_SIZE];
u8 *RecvBufferPtr;
volatile u32 TotalRecvCnt;

XUartPsFormat uart_format =
{
	//9600,
	XUARTPS_DFT_BAUDRATE,   //默认波特率 115200
	XUARTPS_FORMAT_8_BITS,
	XUARTPS_FORMAT_NO_PARITY,
	XUARTPS_FORMAT_1_STOP_BIT,
};

//--------------------------------------------------------------
//                     UART初始化函数
//--------------------------------------------------------------
int Uart_Init(XUartPs* Uart_Ps, u16 DeviceId)
{
	int Status;
	XUartPs_Config *Config;

	/*  数据初始化  */
	RecvBufferPtr = RecvBuffer;
	TotalRecvCnt = 0;

	/*  初始化UART设备    */
	Config = XUartPs_LookupConfig(DeviceId);
	if (NULL == Config) {
		return XST_FAILURE;
	}
	Status = XUartPs_CfgInitialize(Uart_Ps, Config, Config->BaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*  UART设备自检  */
	Status = XUartPs_SelfTest(Uart_Ps);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*  设置UART模式与参数   */
	XUartPs_SetOperMode(Uart_Ps, XUARTPS_OPER_MODE_NORMAL); //正常模式
	XUartPs_SetDataFormat(Uart_Ps, &uart_format);    //设置UART格式
	XUartPs_SetFifoThreshold(Uart_Ps, 8);  //设置RxFIFO的中断触发等级

	return XST_SUCCESS;
}

//--------------------------------------------------------------
//                  UART中断系统初始化函数
//--------------------------------------------------------------
void Uart_Intr_System(XScuGic *Intc, XUartPs *Uart_Ps, u16 UartIntrId)
{
	XScuGic_Connect(Intc, UartIntrId,
			(Xil_ExceptionHandler) Handler,
			(void *) Uart_Ps);
	XScuGic_Enable(Intc, UartIntrId);
	// 设置UART的中断触发方式
	XUartPs_SetInterruptMask(Uart_Ps, XUARTPS_IXR_RXOVR);
}

//--------------------------------------------------------------
//                     UART中断处理函数
//--------------------------------------------------------------
void Handler(void *CallBackRef)
{
	XUartPs *UartInstancePtr = (XUartPs *) CallBackRef ;
	u32 ReceivedCount = 0 ;
	u32 IsrStatus ;

	//读取中断ID寄存器,判断触发的是哪种中断
	IsrStatus = XUartPs_ReadReg(UartInstancePtr->Config.BaseAddress,
				   XUARTPS_IMR_OFFSET);
	IsrStatus &= XUartPs_ReadReg(UartInstancePtr->Config.BaseAddress,
				   XUARTPS_ISR_OFFSET);

	if (IsrStatus & (u32)XUARTPS_IXR_RXOVR)   /* 检查RxFIFO是否触发 */
	{
		ReceivedCount = XUartPs_Recv(UartInstancePtr, RecvBufferPtr, (BUFFER_SIZE-TotalRecvCnt)) ;
		TotalRecvCnt += ReceivedCount ;
		RecvBufferPtr += ReceivedCount ;
		/* 清除中断标志 */
		XUartPs_WriteReg(UartInstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR) ;
	}

	xil_printf("Enter INTR\r\n");
	/* 数据处理 */
	if (TotalRecvCnt >= BUFFER_SIZE) {
		xil_printf("%s", RecvBuffer);
		xil_printf("\r\nI have received %d bytes.\r\n", TotalRecvCnt);
		RecvBufferPtr = RecvBuffer;
		TotalRecvCnt = 0;
	}
}

main.c文件的代码如下

//---------------------------------------------------------------
//            Writen by CUIT 刘奇 2019.3.28
//             此程序为UART中断驱动模式示例
//---------------------------------------------------------------

#include "sys_intr.h"
#include "user_uart.h"

XScuGic Intc;       //GIC
XUartPs Uart_Ps;	//UART

void System_Init(void)
{
	Init_Intr_System(&Intc);
	Setup_Intr_Exception(&Intc);
	Uart_Intr_System(&Intc, &Uart_Ps, UART_INT_IRQ_ID);
}

int main(void)
{
	int Status;

	/* 串口初始化 */
	Status = Uart_Init(&Uart_Ps, UART_DEVICE_ID);
	if (Status == XST_FAILURE) {
		xil_printf("Uartps Failed\r\n");
		return XST_FAILURE;
	}
	System_Init();   //中断初始化
	while (1){
		sleep(1);
		xil_printf("Hello World!\r\n");
	}
	return Status;
}

SDK自带的串口终端发送数据时会自动加个换行符,为了观察方便,用串口调试助手进行测试,结果如下。


相关问题解答

1. 中断驱动模式的特点

本例中我们在UART中断处理函数中处理接收到的数据,同时主循环中每隔1s发送一个“Hello World!”。从上述测试结果可以看到,中断处理和主程序中的处理并不冲突,两者可以同时运行。

然而25篇中的轮询模式就不行,它必须有一个挂起等待的过程。对于裸机环境下的嵌入式开发,我们只能通过各种中断机制来实现一些“伪”并行处理。这也是为什么UART的中断驱动模式要使用的更多。

2. RxFIFO触发等级设置

UART初始化时中使用XUartPs_SetFifoThreshold函数设置RxFIFO的触发等级。RxFIFO中的字节数超过这个值时,会产生一个接收数据中断。

void XUartPs_SetFifoThreshold(XUartPs *InstancePtr, u8 TriggerLevel)

第二个参数的取值应在1~64,因为RxFIFO最大只能存储64个字节。

3. UART中断初始化

初始化部分使用XScuGic_Connect和XScuGic_Enable函数设置UART中断,和前面的PL中断和定时器中断类似,不再详述。串口中断也是一种SPI,查看第7篇中的表格。


其中断号为82,在头文件中宏定义。然后使用XUartPs_SetInterruptMask函数设置串口中断的触发方式。这是设置了RxFIFO触发器中断。

XUartPs_SetInterruptMask(Uart_Ps, XUARTPS_IXR_RXOVR);

UART可以提供多种中断触发方式。我们无需关心底层的中断寄存器的控制,直接使用次函数设置即可。如果要用到多种触发方式,使用逻辑或运算符将其拼在一起即可。下表给出了各种中断触发方式的宏定义(参考第24篇)。


4. 中断处理函数该怎么写

我们第一眼看到这个中断处理函数可能头都大了,怎么还用到了XUartPs_ReadReg函数直接操纵UART的寄存器,难道还是要对UART的底层寄存器结构有所了解吗?博主是怎么写出这个中断处理函数的呢?

其实并不是的。xuartps_intr.c文件中就已经提供了各种UART中断处理函数,供我们参考(如果你找不到这个文件,在XUartPs_SetInterruptMask函数上右键->Open Declaration)。包括UART中断处理、接收错误处理、接收超时处理、接收数据处理、发送数据处理、调制解调处理多个函数。比如通用的UART中断处理函数如下:

void XUartPs_InterruptHandler(XUartPs *InstancePtr)
{
	u32 IsrStatus;

	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

	/*
	 * Read the interrupt ID register to determine which
	 * interrupt is active
	 */
	IsrStatus = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
				   XUARTPS_IMR_OFFSET);

	IsrStatus &= XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
				   XUARTPS_ISR_OFFSET);

	/* Dispatch an appropriate handler. */
	if((IsrStatus & ((u32)XUARTPS_IXR_RXOVR | (u32)XUARTPS_IXR_RXEMPTY |
			(u32)XUARTPS_IXR_RXFULL)) != (u32)0) {
		/* Received data interrupt */
		ReceiveDataHandler(InstancePtr);
	}

	if((IsrStatus & ((u32)XUARTPS_IXR_TXEMPTY | (u32)XUARTPS_IXR_TXFULL))
									 != (u32)0) {
		/* Transmit data interrupt */
		SendDataHandler(InstancePtr, IsrStatus);
	}

	/* XUARTPS_IXR_RBRK is applicable only for Zynq Ultrascale+ MP */
	if ((IsrStatus & ((u32)XUARTPS_IXR_OVER | (u32)XUARTPS_IXR_FRAMING |
			(u32)XUARTPS_IXR_PARITY | (u32)XUARTPS_IXR_RBRK)) != (u32)0) {
		/* Received Error Status interrupt */
		ReceiveErrorHandler(InstancePtr, IsrStatus);
	}

	if((IsrStatus & ((u32)XUARTPS_IXR_TOUT)) != (u32)0) {
		/* Received Timeout interrupt */
		ReceiveTimeoutHandler(InstancePtr);
	}

	if((IsrStatus & ((u32)XUARTPS_IXR_DMS)) != (u32)0) {
		/* Modem status interrupt */
		ModemHandler(InstancePtr);
	}

	/* Clear the interrupt status. */
	XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
		IsrStatus);
}

根据这些官方例程,我们完全可以编写自己的中断处理函数。总的来说大致流程如下:

  • 读取中断ID寄存器,检查触发的是哪种中断;
  • 根据不同的中断类型,执行不同的操作;
  • 清除中断标志状态;
  • 用户自定义的数据处理部分。

希望大家学习时尽量多利用官方这些最权威的资料(我的博客都可能会存在错误)。

总结

本文介绍了UART的中断驱动模式。这里只给出了RxFIFO触发中断的示例。但相信你可以根据自己的设计目标,做到灵活使用UART的各种中断信号。


文章来源:FPGADesigner的博客
*本文由作者授权转发,如需转载请联系作者本人

推荐阅读