软件环境:vivado 2017.4 硬件平台:XC7Z020
内部结构大概如图所示,PL这边跟上节中断配置的基本一样,其实主要还是在SDK程序这边。
Vivado 2017.4 Create Block Design后,添加ZYNQ7 Processing system,然后自动连接就行,Generate the output products,Create a HDL wrapper,Generate Bitstream,Export Hardware(注意勾选include biestream),最后launch SDK,加入如下代码。
#include
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xuartps.h"
//timer info
#define UART_DEVICE_ID XPAR_PS7_UART_1_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define UART_IRPT_INTR XPAR_XUARTPS_1_INTR
static XScuGic Intc; //GIC
static XUartPs Uart;//uart
static void UartIntrHandler(void *CallBackRef)
{
XUartPs *InstancePtr = (XUartPs *) CallBackRef;
u32 IsrStatus;
u32 ReceivedCount=0;
u32 CsrRegister;
/*
* Read the interrupt ID register to determine which
* interrupt is active
*/
IsrStatus = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_IMR_OFFSET);//e0001000+10=regaddr=e0001010
IsrStatus &= XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_ISR_OFFSET);//e0001000+14=regaddr=e0001014
/* Dispatch an appropriate handler. */
if((IsrStatus & ((u32)XUARTPS_IXR_RXOVR | (u32)XUARTPS_IXR_RXEMPTY |
(u32)XUARTPS_IXR_RXFULL)) != (u32)0) {
CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,//判断FIFO触发标准位
XUARTPS_SR_OFFSET);//e0001000+2c=regaddr=e000102c
while((CsrRegister & XUARTPS_SR_RXEMPTY)== (u32)0){//读取FIFO中所有数据
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,XUARTPS_FIFO_OFFSET,
XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_FIFO_OFFSET));
ReceivedCount++;//计数
CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_SR_OFFSET);
}
}
printf("this time ReceivedCount=%ld\r\n",ReceivedCount);
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
IsrStatus);
}
void SetupInterruptSystem(XScuGic *GicInstancePtr,
XUartPs *UartInstancePtr, u16 UartIntrId)
{
XScuGic_Config *IntcConfig; //GIC config
Xil_ExceptionInit();
//initialise the GIC
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,//connect to the hardware
GicInstancePtr);
XScuGic_Connect(GicInstancePtr, UartIntrId,
(Xil_InterruptHandler)UartIntrHandler,//set up the timer interrupt
(void *)UartInstancePtr);
XScuGic_Enable(GicInstancePtr, UartIntrId);//enable the interrupt for the Timer at GIC
XUartPs_SetInterruptMask(UartInstancePtr, XUARTPS_IXR_RXOVR/* | XUARTPS_IXR_TXEMPTY | XUARTPS_IXR_TNFUL*/ );
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); //Enable interrupts in the Processor.
}
int main()
{
XUartPs_Config *UartConfigPtr; //uart config
UartConfigPtr = XUartPs_LookupConfig(UART_DEVICE_ID);
XUartPs_CfgInitialize(&Uart,UartConfigPtr,UartConfigPtr->BaseAddress);
SetupInterruptSystem(&Intc,&Uart,UART_IRPT_INTR);
while(1);
return 0;
}
接下来对程序中的一些函数做一些解释说明:
XUartPs_Config串口配置结构体,包括设备号、基址、输入式中频率、引脚链接方式。
typedef struct {
u16 DeviceId; /**< Unique ID of device */
u32 BaseAddress; /**< Base address of device (IPIF) */
u32 InputClockHz;/**< Input clock frequency */
s32 ModemPinsConnected; /** Specifies whether modem pins are connected
* to MIO or FMIO */
} XUartPs_Config;
XScuGic要配置中断,Gic结构体自然是不必说的啊。
typedef struct
{
XScuGic_Config *Config; /**< Configuration table entry */
u32 IsReady; /**< Device is initialized and ready */
u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;
XUartPs结构体,包括配置项、输入时钟频率、初始化状态、波特率、接收发送buffer、中断函数、中断函数参数等等。
typedef struct {
XUartPs_Config Config; /* Configuration data structure */
u32 InputClockHz; /* Input clock frequency */
u32 IsReady; /* Device is initialized and ready */
u32 BaudRate; /* Current baud rate */
XUartPsBuffer SendBuffer;
XUartPsBuffer ReceiveBuffer;
XUartPs_Handler Handler;
void *CallBackRef; /* Callback reference for event handler */
u32 Platform;
u8 is_rxbs_error;
} XUartPs;
程序最开始处宏定义的UART_DEVICE_ID是设备ID、INTC_DEVICE_ID中断的设备ID和UART_IRPT_INTR是串口的中断号。
#define UART_DEVICE_ID XPAR_PS7_UART_1_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define UART_IRPT_INTR XPAR_XUARTPS_1_INTR
不断的open declaration可以看到#define XPS_UART1_INT_ID 82U至于这个82怎么来的。查ug585。
XUartPs_LookupConfig()可以看到,是串口专用的初始化配置函数。
******************************************************************************/
XUartPs_Config *XUartPs_LookupConfig(u16 DeviceId)
{
XUartPs_Config *CfgPtr = NULL;
u32 Index;
for (Index = 0U; Index < (u32)XPAR_XUARTPS_NUM_INSTANCES; Index++) {
if (XUartPs_ConfigTable[Index].DeviceId == DeviceId) {
CfgPtr = &XUartPs_ConfigTable[Index];
break;
}
}
return (XUartPs_Config *)CfgPtr;
}
其中,XUartPs_ConfigTable表直接跟UART1挂钩,设备号、基址、时钟频率、引脚方式全通过这个结构体配置。
XUartPs_Config XUartPs_ConfigTable[XPAR_XUARTPS_NUM_INSTANCES] =
{
{
XPAR_PS7_UART_1_DEVICE_ID,
XPAR_PS7_UART_1_BASEADDR,
XPAR_PS7_UART_1_UART_CLK_FREQ_HZ,
XPAR_PS7_UART_1_HAS_MODEM
}
};
XUartPs_CfgInitialize()是串口相关参数的初始化函数,例如数据位数、停止位数、校验方式、波特率等。
s32 XUartPs_CfgInitialize(XUartPs *InstancePtr,
XUartPs_Config * Config, u32 EffectiveAddr)
{
s32 Status;
u32 ModeRegister;
u32 BaudRate;
/* Assert validates the input arguments */
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(Config != NULL);
/* Setup the driver instance using passed in parameters */
InstancePtr->Config.BaseAddress = EffectiveAddr;
InstancePtr->Config.InputClockHz = Config->InputClockHz;
InstancePtr->Config.ModemPinsConnected = Config->ModemPinsConnected;
/* Initialize other instance data to default values */
InstancePtr->Handler = XUartPs_StubHandler;
InstancePtr->SendBuffer.NextBytePtr = NULL;
InstancePtr->SendBuffer.RemainingBytes = 0U;
InstancePtr->SendBuffer.RequestedBytes = 0U;
InstancePtr->ReceiveBuffer.NextBytePtr = NULL;
InstancePtr->ReceiveBuffer.RemainingBytes = 0U;
InstancePtr->ReceiveBuffer.RequestedBytes = 0U;
/* Initialize the platform data */
InstancePtr->Platform = XGetPlatform_Info();
InstancePtr->is_rxbs_error = 0U;
/* Flag that the driver instance is ready to use */
InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
/*
* Set the default baud rate here, can be changed prior to
* starting the device
*/
BaudRate = (u32)XUARTPS_DFT_BAUDRATE;
Status = XUartPs_SetBaudRate(InstancePtr, BaudRate);
if (Status != (s32)XST_SUCCESS) {
InstancePtr->IsReady = 0U;
} else {
/*
* Set up the default data format: 8 bit data, 1 stop bit, no
* parity
*/
ModeRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_MR_OFFSET);
/* Mask off what's already there */
ModeRegister &= (~((u32)XUARTPS_MR_CHARLEN_MASK |
(u32)XUARTPS_MR_STOPMODE_MASK |
(u32)XUARTPS_MR_PARITY_MASK));
/* Set the register value to the desired data format */
ModeRegister |= ((u32)XUARTPS_MR_CHARLEN_8_BIT |
(u32)XUARTPS_MR_STOPMODE_1_BIT |
(u32)XUARTPS_MR_PARITY_NONE);
/* Write the mode register out */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_MR_OFFSET,
ModeRegister);
/* Set the RX FIFO trigger at 8 data bytes. */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,
XUARTPS_RXWM_OFFSET, 0x08U);
/* Set the RX timeout to 1, which will be 4 character time */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,
XUARTPS_RXTOUT_OFFSET, 0x01U);
/* Disable all interrupts, polled mode is the default */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_IDR_OFFSET,
XUARTPS_IXR_MASK);
Status = XST_SUCCESS;
}
return Status;
}
下图中,第一个框是默认波特率的设置,系统默认值是115200,第二个红框设置的是8个数据位数、1个停止位数、无校验方式。第三个红框是接收自带FIFO(看最顶上那张图,接收和发送自带了FIFO)多少字节触发接收中断,这三个红框的设置比较关键,一般改串口初始化配置也就改这几项,剩下要改的都在接收中断函数,就是那个Handle里面。
Xil_ExceptionInit()在这实际是个空函数,里面直接return的,据函数前的说明意思是这函数是个通用API,用于在所有支持的arm处理器上初始化异常处理程序,还放在这里主要是为了避免与之前软件版本产生意外的兼容性问题。
XScuGic_LookupConfig()和XScuGic_CfgInitialize()是与通用中断控制器相关的查找设备ID和获取设备配置表基址的函数,TIMER那一章里也有说过,设备的LookupConfig()和CfgInitialize()配设备的,中断的LookupConfig()和CfgInitialize()配中断的。
Xil_ExceptionRegisterHandler()处理特定异常时用的。通用函数。
void Xil_ExceptionRegisterHandler(u32 Exception_id,
Xil_ExceptionHandler Handler,
void *Data)
{
XExc_VectorTable[Exception_id].Handler = Handler;
XExc_VectorTable[Exception_id].Data = Data;
}
XScuGic_Connect()将串口中断的中断函数和中断控制器绑定,当串口产生中断时,找UartIntrHandler()中断处理函数。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
Xil_InterruptHandler Handler, void *CallBackRef)
{
/*
* Assert the arguments
*/
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertNonvoid(Handler != NULL);
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used as an index into the table to select the proper
* handler
*/
InstancePtr->Config->HandlerTable[Int_Id].Handler = Handler;
InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef;
return XST_SUCCESS;
}
XScuGic_Enable()使能串口中断。
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
{
u32 Mask;
/*
* Assert the arguments
*/
Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
/*
* The Int_Id is used to create the appropriate mask for the
* desired bit position. Int_Id currently limited to 0 - 31
*/
Mask = 0x00000001U << (Int_Id % 32U);
/*
* Enable the selected interrupt source by setting the
* corresponding bit in the Enable Set register.
*/
XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
((Int_Id / 32U) * 4U), Mask);
}
XUartPs_SetInterruptMask()是设置中断方式的函数,当前设置的XUARTPS_IXR_RXOVR是接收FIFO来触发中断。
void XUartPs_SetInterruptMask(XUartPs *InstancePtr, u32 Mask)
{
u32 TempMask = Mask;
/* Assert validates the input arguments */
Xil_AssertVoid(InstancePtr != NULL);
TempMask &= (u32)XUARTPS_IXR_MASK;
/* Write the mask to the IER Register */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,
XUARTPS_IER_OFFSET, TempMask);
/* Write the inverse of the Mask to the IDR register */
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,
XUARTPS_IDR_OFFSET, (~TempMask));
}
除此之外可以看到,可设置的其他中断触发方式还有很多,在这里备注如下。
#define XUARTPS_IXR_RBRK 0x00002000U /**< 接收FIFO中断探测中断(这个啥意思目前不知=。=) */
#define XUARTPS_IXR_TOVR 0x00001000U /**< 发送FIFO溢出中断 */
#define XUARTPS_IXR_TNFUL 0x00000800U /**< 发送FIFO快满时中断 */
#define XUARTPS_IXR_TTRIG 0x00000400U /**< 发送触发中断 */
#define XUARTPS_IXR_DMS 0x00000200U /**< 应该是外引脚方式改变时触发中断 */
#define XUARTPS_IXR_TOUT 0x00000100U /**< 超时错误中断 */
#define XUARTPS_IXR_PARITY 0x00000080U /**< 校验错误中断 */
#define XUARTPS_IXR_FRAMING 0x00000040U /**< 框架错误中断 */
#define XUARTPS_IXR_OVER 0x00000020U /**< 来不及处理overrun中断 */
#define XUARTPS_IXR_TXFULL 0x00000010U /**< 发送FIFO满中断 */
#define XUARTPS_IXR_TXEMPTY 0x00000008U /**< 发送FIFO空中断 */
#define XUARTPS_IXR_RXFULL 0x00000004U /**< 接收FIFO满中断 */
#define XUARTPS_IXR_RXEMPTY 0x00000002U /**< 接收FIFO空中断 */
#define XUARTPS_IXR_RXOVR 0x00000001U /**< 接收FIFO到达指定字节个数中断 */
#define XUARTPS_IXR_MASK 0x00003FFFU /**< Valid bit mask应该是个综合设置(这个啥意思目前不知=。=) */
感觉用的比较多的应该还是最后的这几个。
手册上还有更加直观的图,看一眼就明白。
Xil_ExceptionEnableMask()初始化最后当然是中断的使能函数。
#define Xil_ExceptionEnableMask(Mask) \
mtcpsr(mfcpsr() & ~ ((Mask) & XIL_EXCEPTION_ALL))
初始化到这基本就算结束了,接下来瞅瞅串口中断里都干了点啥。
第一句,看下IMR, 看看你都使能了哪些中断标志,也就是哪些中断可以被触发,可以被系统响应;第二句,看下ISR,当前产生了哪些中断;两个进行与运算,其结果决定了接下来是否进入中断处理和进入哪个中断处理。
然后判断,如果被触发的中断是接收相关的FIFO到达指定字节数、FIFO空、FIFO满其中一种,就需要继续进行相应处理。
上面那个CsrRegister存的是当前中断标志,看看哪些中断被置起来了,然后在下面的while里比较,看看被置起来的标志是不是XUARTPS_SR_RXEMPTY,这里需要说明一下,==0意味着读取FIFO不是空的,不信看E000102C寄存器。
所以整个while的意思就是,如果RX的FIFO不是空的,就把接收FIFO中的数拿出来,写到发送寄存器里去,然后接收/发送的数据计数加1,重新再读取中断状态寄存器,看看还有没有数据需要在继续往外搬,以此循环直到所有数据处理完。
这里特别需要注意两点,也是我实验以后得到的结论:
1.用SDK中自带串口终端发数,按send以后,数据末尾自动添加\n,也就是说如果你发送123,实际发出的是123\n,4个字节。
2.如果你在最开始XUartPs_CfgInitialize()中XUARTPS_RXWM_OFFSET设置的是8字节触发,那么当你接收字节数小于8时,这个while是进不去的,只有当接收字节数大于等于8时,才会依次进while(8次),所以对于FIFO虽然只有8个bit宽度,但深度应该是XUARTPS_RXWM_OFFSET设置的最大64(我猜的。。。)。
3.读取数据和写入数据都是通过操作XUARTPS_FIFO_OFFSET这个偏移量的寄存器来完成的。
当所有数据处理完,最后往XUARTPS_ISR_OFFSET写入之前的ISR状态标志,将相应的中断状态写1清0。
最后小声哔哔一下,本来我以为串口中断两三行就写完了,结果没想到这么多东西。。。
所以在最后总结一下UART,在SDK中大致的流程:
通过XUartPs_LookupConfig函数,找到UART的基址------------>
通过XUartPs_CfgInitialize函数,初始化UART配置------------>
通过Xil_ExceptionInit函数,避免与前版本有兼容性问题------------>
通过XScuGic_LookupConfig函数,找到通用中断控制器基址------------>
通过XScuGic_CfgInitialize函数,初始化通用中断控制器------------>
通过Xil_ExceptionRegisterHandler函数,使能异常中断处理------------>
通过XScuGic_Connect函数,绑定UART中断处理函数------------>
通过XScuGic_Enable函数,在通用中断控制器中使能UART中断------------>
通过XUartPs_SetInterruptMask函数,设置UART的中断触发方式------------>
通过Xil_ExceptionEnableMask函数,使能中断。