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的各种中断信号。