Zynq中的UART支持轮询和中断驱动两种模式。本文给出使用中断驱动模式的例子,完成与26篇中轮询模式下相同的功能,即UART收到8字节数据后执行某项操作。对比之下,体会中断驱动模式的特点。
由于要使用中断系统,我们翻出两个“老伙计”,第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自带的串口终端发送数据时会自动加个换行符,为了观察方便,用串口调试助手进行测试,结果如下。
本例中我们在UART中断处理函数中处理接收到的数据,同时主循环中每隔1s发送一个“Hello World!”。从上述测试结果可以看到,中断处理和主程序中的处理并不冲突,两者可以同时运行。
然而25篇中的轮询模式就不行,它必须有一个挂起等待的过程。对于裸机环境下的嵌入式开发,我们只能通过各种中断机制来实现一些“伪”并行处理。这也是为什么UART的中断驱动模式要使用的更多。
UART初始化时中使用XUartPs_SetFifoThreshold函数设置RxFIFO的触发等级。RxFIFO中的字节数超过这个值时,会产生一个接收数据中断。
void XUartPs_SetFifoThreshold(XUartPs *InstancePtr, u8 TriggerLevel)
第二个参数的取值应在1~64,因为RxFIFO最大只能存储64个字节。
初始化部分使用XScuGic_Connect和XScuGic_Enable函数设置UART中断,和前面的PL中断和定时器中断类似,不再详述。串口中断也是一种SPI,查看第7篇中的表格。
其中断号为82,在头文件中宏定义。然后使用XUartPs_SetInterruptMask函数设置串口中断的触发方式。这是设置了RxFIFO触发器中断。
XUartPs_SetInterruptMask(Uart_Ps, XUARTPS_IXR_RXOVR);
UART可以提供多种中断触发方式。我们无需关心底层的中断寄存器的控制,直接使用次函数设置即可。如果要用到多种触发方式,使用逻辑或运算符将其拼在一起即可。下表给出了各种中断触发方式的宏定义(参考第24篇)。
宏定义 | 说明 |
---|---|
XUARTPS_IXR_RBRK | RxFIFO break detect interrupt |
XUARTPS_IXR_TOVR | TxFIFO溢出中断 |
XUARTPS_IXR_TNFUL | TxFIFO几乎满中断 |
XUARTPS_IXR_TTRIG | Tx触发中断 |
XUARTPS_IXR_DMS | 调制解调状态改变中断 |
XUARTPS_IXR_TOUT | 超时错误中断 |
XUARTPS_IXR_PARITY | 奇偶校验错误中断 |
XUARTPS_IXR_FRAMING | 帧错误中断 |
XUARTPS_IXR_OVER | 溢出错误中断 |
XUARTPS_IXR_TXFULL | TxFIFO满中断 |
XUARTPS_IXR_TXEMPTY | TxFIFO空中断 |
XUARTPS_IXR_RXFULL | RxFIFO满中断 |
XUARTPS_IXR_RXEMPTY | RxFIFO空中断 |
XUARTPS_IXR_RXOVR | RxFIFO触发中断 |
XUARTPS_IXR_MASK | 将所有中断都设为有效 |
我们第一眼看到这个中断处理函数可能头都大了,怎么还用到了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);
}
根据这些官方例程,我们完全可以编写自己的中断处理函数。总的来说大致流程如下:
希望大家学习时尽量多利用官方这些最权威的资料(我的博客都可能会存在错误)。
本文介绍了UART的中断驱动模式。这里只给出了RxFIFO触发中断的示例。但相信你可以根据自己的设计目标,做到灵活使用UART的各种中断信号。