ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ZYNQ学习笔记2-ZYNQ的UART控制器5

ZYNQ学习笔记2-ZYNQ的UART控制器5 本文主要以UART测试程序为例分析ZYNQ的中断流程。一、中断流程整体分析1.中断产生ZYNQ的UART中断分为FIFO中断和非FIFO中断两种。每种又有很多种类比FIFO中断有FIFO空FIFO满到达触发值等非FIFO中断有超市、校验错误等。不过无论具体原因是什么查阅UG585可知所有UART中断都属于IRQ中断如下图查看ARM手册《ARM® Architecture Reference Manual》DDI0406CIRQ中断偏移量为0x18也就是说当IRQ中断发生时CPU会去vect_table偏移0x18位置执行程序。因此IRQ的中断程序必须放在vect_table偏移量为0x18的位置。根据《ARM® Architecture Reference Manual》DDI0406C当出现有效的UART中断后IRQ信号有效。可知IRQ处理流程如下根据上图可知当IRQ中断发生时系统首先暂存寄存器当前值然后给出向量表偏移量240x18vect_table然后检测当前是否是安全模式或者虚拟模式裸机情况下都不是直接进入IRQ处理模式默认模式。在跳转前会对系统进行一些配置如切换到 IRQ 模式CPSR.M 10010。将 new_spsr_value 写入 SPSR_irq即 SPSR[]new_lr_value 写入 LR_irq即 R[14]。CPSR.I 1禁用 IRQ禁止嵌套。如果满足特定条件还会禁用异步中止CPSR.A 1。重置 IT 状态Thumb-2 条件执行并根据 SCTLR.TE 设置 CPSR.T决定后续指令是 ARM 还是 Thumb最后根据 SCTLR.EE 设置大小端。如果启用了向量中断SCTLR.VE 1则由实现定义的方式跳转通常由中断控制器提供地址。否则跳转到常规 IRQ 向量ExcVectorBase() 0x18。ExcVectorBase() 由 SCTLR.V 决定V0 时为 0x00000000V1 时为 0xFFFF0000。ZYNQ不具备向量中断模式所以直接执行的就是ExcVectorBase() 0x18。如果把整个CPU比作一个大型集团公司的话vector_table可以说是整个集团公司的董事长掌管整个集团的事务把握大方向。当发现出现需要IRQ这个分公司需要处理的业务的时候一个电话打给IRQ的董事长让他去处理。说了一堆总结也是一句话就是IRQ中断发生时系统回到中断向量表偏移0x18的位置去执行程序。2.中断向量表打开测试工程bsp的asm_vector.S文件可以看到ZYNQ中断向量表的分配情况如下vector_table: B _boot B Undefined B SVCHandler B PrefetchAbortHandler B DataAbortHandler NOP /* Placeholder for address exception vector*/ B IRQHandler B FIQHandlerZYNQ一个向量占据四个字节的空间可以看到IRQHandler正好处于是第7个向量。因此地址是240x18。也就是IRQ中断发生时系统到0x18处执行指令执行的指令只有一条那就是跳转到IRQHandler处B IRQHandler。那么继续往下看IRQHandler处的代码也在asm_vector.S文件中IRQHandler: /* IRQ vector handler */ stmdb sp!,{r0-r3,r12,lr} /* state save from compiled code*/ #if FPU_HARD_FLOAT_ABI_ENABLED vpush {d0-d7} vpush {d16-d31} vmrs r1, FPSCR push {r1} vmrs r1, FPEXC push {r1} #endif #ifdef PROFILING ldr r2, prof_pc subs r3, lr, #0 str r3, [r2] #endif bl IRQInterrupt /* IRQ vector */ #if FPU_HARD_FLOAT_ABI_ENABLED pop {r1} vmsr FPEXC, r1 pop {r1} vmsr FPSCR, r1 vpop {d16-d31} vpop {d0-d7} #endif ldmia sp!,{r0-r3,r12,lr} /* state restore from compiled code */ subs pc, lr, #4 /* adjust return */IRQHandler代码中主要内容可以分为三部分跳转前跳转和跳转后。跳转前主要工作就是保存当前的工作状态代码如下stmdb sp!,{r0-r3,r12,lr} //保存工作寄存器r0-r3函数参数/临时变量、r12内部调用暂存、lr当前模式的链接寄存器 #if FPU_HARD_FLOAT_ABI_ENABLED //如果定义了硬件浮点FPU_HARD_FLOAT_ABI_ENABLED vpush {d0-d7} //入栈d0-d7 vpush {d16-d31} //入栈d16-d31 vmrs r1, FPSCR push {r1} //入栈浮点状态控制寄存器FPSCR vmrs r1, FPEXC push {r1} //入栈浮点异常寄存器FPEXC #endif #ifdef PROFILING //如果定义了性能分析宏PROFILING ldr r2, prof_pc //返回地址lr存到一个全局变量 prof_pc subs r3, lr, #0 str r3, [r2] #endif跳转保存完之后开始执行长跳转指令跳转到中断服务函数这是跳转真正发生的地方。代码如下bl IRQInterrupt跳转后执行完中断服务程序返回当前位置开始恢复现场。#if FPU_HARD_FLOAT_ABI_ENABLED //如果启用了硬件浮点FPU_HARD_FLOAT_ABI_ENABLED pop {r1} //FPEXC出栈 vmsr FPEXC, r1 pop {r1} //FPSCR出栈 vmsr FPSCR, r1 vpop {d16-d31} //d16-d31出栈 vpop {d0-d7} //d0-d7出栈 #endif ldmia sp!,{r0-r3,r12,lr} //r0-r3,r12,lr出栈 subs pc, lr, #4 //返回程序原位置这里拓展说下为什么pc-4就能回中断位置执行程序。ARM手册DDI0406C表述如下中断发生时ARM可以保证把当前正在执行的指令完整执行然后指针指向下一条指令然后硬件在进入 IRQ 时LR_irq被设置为PC 4即被中断指令的下一条再下一条实际上多加了 4所以返回时需要减 4 才能正确指向被中断指令的下一条指令。至此可以发现系统进入中断向量表后通过长跳转指令跳转到IRQInterrupt。3.IRQInterrupt函数IRQInterrupt函数很简单就是一个函数调用void IRQInterrupt(void) { XExc_VectorTable[XIL_EXCEPTION_ID_IRQ_INT].Handler(XExc_VectorTable[ XIL_EXCEPTION_ID_IRQ_INT].Data); }其中XIL_EXCEPTION_ID_IRQ_INT值为5所以简化一下这个函数可以这样等效void IRQInterrupt(void) { XExc_VectorTable[5].Handler(XExc_VectorTable[5].Data); }也就是调用XExc_VectorTable数组第六个元素的Handler函数第六个元素Data为参数。找到定义如下。可见默认情况下第六个元素为空需要用户注册。XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST 1] { {Xil_ExceptionNullHandler, NULL}, {Xil_UndefinedExceptionHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, {Xil_PrefetchAbortHandler, NULL}, {Xil_DataAbortHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, };注册在Xil_ExceptionRegisterHandler函数实现相关程序片段如下//调用语句其中XIL_EXCEPTION_ID_INT 5 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,XScuGic_inst); //函数主体 void Xil_ExceptionRegisterHandler(u32 Exception_id, Xil_ExceptionHandler Handler, void *Data) { #if defined (versal) !defined(ARMR5) EL3 /* * Cortexa72 processor in versal is coupled with GIC-500, and GIC-500 supports * only FIQ at EL3. Hence, tweaking this API to always act on FIQ, * ignoring argument passed by user. */ Exception_id XIL_EXCEPTION_ID_FIQ_INT; #endif XExc_VectorTable[Exception_id].Handler Handler; XExc_VectorTable[Exception_id].Data Data; }通过Xil_ExceptionRegisterHandler函数的函数体可以发现此函数的功能很简单就是向XExc_VectorTable数组写入数据。分别向Handler成员和Data成员赋值。根据程序的调用语句调用内容可知此程序是将向XExc_VectorTable的元素5中的Handler赋值为XScuGic_InterruptHandlerData成员赋值为XScuGic_inst变量地址。赋值后再调用IRQInterrupt实际上应该调用的是void IRQInterrupt(void) { XScuGic_InterruptHandler(XScuGic_inst); }至此可总结中断流程如下UART满足中断条件后会申请IRQ异常CPU接受到此异常后硬件自动到向量表0x18位置执行程序。0x18出只有一句话即 B IRQHandler,CPU执行此指令跳转到 IRQHandler处。在IRQHandler处CPU将当前环境入栈暂存然后执行长跳转到IRQInterrupt函数IRQInterrupt功能是主要是执行XExc_VectorTableEntry 数据中元素5的的函数。由于例程在主函数中已通过Xil_ExceptionRegisterHandler函数将元素5注册为XScuGic_InterruptHandler函数因此CPU会执行XScuGic_InterruptHandler函数。执行完成后CPU回到asm_vector.S将之前入栈的变量出栈然后回到中断前指令的下一条指令继续执行新指令。4.XScuGic_InterruptHandler函数XScuGic_InterruptHandler主要内容如下void XScuGic_InterruptHandler(XScuGic *InstancePtr) { //……省略非必要内容 IntIDFull XScuGic_CPUReadReg(InstancePtr, XSCUGIC_INT_ACK_OFFSET); InterruptID IntIDFull XSCUGIC_ACK_INTID_MASK; //……省略非必要内容 //此处为主要功能 TablePtr (InstancePtr-Config-HandlerTable[InterruptID]); if (TablePtr ! NULL) { TablePtr-Handler(TablePtr-CallBackRef); //……省略非必要内容 }根据函数体内容可见此函数先通过读取InterruptID 确定是发生了什么中断然后根据InterruptID从InstancePtr中加载特定函数。因此CPU执行到XScuGic_InterruptHandler后会执行什么函数由InstancePtr的内容决定。InstancePtr中到底是什么内容由主程序函数在执行XScuGic_Connect函数时决定XScuGic_Connect相关的主要内容如下//下为调用语句 XScuGic_Connect(XScuGic_inst,UART_INTR_S_ID, (Xil_InterruptHandler)XUartPs_InterruptHandler, (void *)XUartPs_0); //下为函数体 s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id, Xil_InterruptHandler Handler, void *CallBackRef) { //……此处省略主要内容 InstancePtr-Config-HandlerTable[Int_Id].Handler (Xil_InterruptHandler)Handler; InstancePtr-Config-HandlerTable[Int_Id].CallBackRef CallBackRef; }根据函数体内容可知执行完此函数后UART的ID对应的元素的Handler成员被注册为XUartPs_InterruptHandler函数CallBackRef元素被注册为XUartPs_0变量。还是把CPU比作一个集团XScuGic_InterruptHandler就是IRQ分公司的老大掌管所有IRQ中断。总公司把活分下来之后XScuGic_InterruptHandler分析分析任务的主要内容最后发现这个需求需要UART这条“产品线”处理便把任务再次分给UART产品线。至此可以再次总结中断流程如下UART满足中断条件后会申请IRQ异常CPU接受到此异常后硬件自动到向量表0x18位置执行程序。0x18出只有一句话即 B IRQHandler,CPU执行此指令跳转到 IRQHandler处。在IRQHandler处CPU将当前环境入栈暂存然后执行长跳转到IRQInterrupt函数IRQInterrupt功能是主要是执行XExc_VectorTableEntry 数据中元素5的的函数。由于例程在主函数中已通过Xil_ExceptionRegisterHandler函数将元素5注册为XScuGic_InterruptHandler函数因此CPU会执行XScuGic_InterruptHandler函数。XScuGic_InterruptHandler主要功能是根据读到InterruptID 调用InstancePtr对应位置的函数和参数。InstancePtr变量中的函数注册在XScuGic_Connect执行通过XScuGic_Connect函数已经将InstancePtr函数注册为XUartPs_InterruptHandler因此系统会执行XUartPs_InterruptHandler函数。并在执行完成后回到asm_vector.S将之前入栈的变量出栈然后回到中断前指令的下一条指令继续执行新指令。5.XUartPs_InterruptHandler函数XUartPs_InterruptHandler函数非常复杂分析函数的具体内容不是本文的主要任务本文主要是要简明扼要的讲述中断执行的整个流程力求简洁清晰。在这里仅做XUartPs_InterruptHandler功能简述。简单来说XUartPs_InterruptHandler是通过读取中断源与中断使能来判断要执行什么中断服务程序。以UART举例中断发生后经过前面一系列的跳转来到了XUartPs_InterruptHandler函数CPU读完寄存器来判断当前是应该执行FIFO空的中断呐还是接受满的中断还是超时中断还是校验错误的中断等等。一旦判断完成便进入子服务函数。换句话说XUartPs_InterruptHandler是起到一个进一步分发的作用。相当于把IRQ进行进一步细分。IRQ区分设备级比如UART会引发IRQ中断CAN也能引发IRQ中断USB也会引发IRQ中断XScuGic_InterruptHandler就是为了分清楚到底是哪个设备引发的IRQ。但XUartPs_InterruptHandler是已经确认是UART引发的但是要进一步分清楚UART为什么会引发中断。如果还是把CPU比作一个集团XUartPs_InterruptHandler就是UART这个“产品线”的负责人了或者说某个具体部门的经理负责分一分这个活到底是谁来干。换句话说XUartPs_InterruptHandler其实还是没有干具体的活还是把活分下去了。XUartPs_InterruptHandler具体内容详见之前两篇文章这里不赘述了。打开XUartPs_InterruptHandler中断的具体子函数可以发现他其实调用的都是同一个函数以SendDataHandler函数为例其主要内容如下static void SendDataHandler(XUartPs *InstancePtr, u32 IsrStatus) { //……省略非主要内容 InstancePtr-Handler(InstancePtr-CallBackRef, XUARTPS_EVENT_SENT_DATA, InstancePtr-SendBuffer.RequestedBytes - InstancePtr-SendBuffer.RemainingBytes); //……省略非主要内容 }可见最后都是调用InstancePtr-Handler指向的函数不同的是参数不一样给这个函数传入的参数不一样。很不幸如果还是把一个CPU比作一个集团这个函数怕就是我与在座的诸位了就是不管是搬桌子还是擦凳子干活的永远是这个人只是上级给的指令不一样他干的活不一样简直苦逼。那么是谁给他找的这个苦逼的工作呐。这个承担了HR角色的函数就是XUartPs_SetHandler函数。主要内容如下//调用语句 XUartPs_SetHandler(XUartPs_0, (XUartPs_Handler)uart_intr_Handler, XUartPs_0); //函数体 void XUartPs_SetHandler(XUartPs *InstancePtr, XUartPs_Handler FuncPtr, void *CallBackRef) { //……省略非必要部分 InstancePtr-Handler (XUartPs_Handler)FuncPtr; InstancePtr-CallBackRef CallBackRef; }可见通过该函数系统将uart_intr_Handler函数注册给了InstancePtr-Handler。最后系统将执行uart_intr_Handler。uart_intr_Handler这个牛马就开始苦哈哈干活了。至此整个中断流程分析结束总结如下UART满足中断条件后会申请IRQ异常CPU接受到此异常后硬件自动到向量表0x18位置执行程序。0x18出只有一句话即 B IRQHandler,CPU执行此指令跳转到 IRQHandler处。在IRQHandler处CPU将当前环境入栈暂存然后执行长跳转到IRQInterrupt函数IRQInterrupt功能是主要是执行XExc_VectorTableEntry 数据中元素5的的函数。由于例程在主函数中已通过Xil_ExceptionRegisterHandler函数将元素5注册为XScuGic_InterruptHandler函数因此CPU会执行XScuGic_InterruptHandler函数。XScuGic_InterruptHandler主要功能是根据读到InterruptID 调用InstancePtr对应位置的函数和参数。InstancePtr变量中的函数注册在XScuGic_Connect执行通过XScuGic_Connect函数已经将InstancePtr函数注册为XUartPs_InterruptHandler因此系统会执行XUartPs_InterruptHandler函数在XUartPs_InterruptHandler函数中CPU读取UART的中断源和使能情况判断应该响应那个子服务程序。不过不管是哪个子服务程序系统都将统一执行InstancePtr-Handler所指向的函数。此函数是通过XUartPs_SetHandler来注册的。区别在于每个子服务都有自己独特编号事件码用于区分到底是什么中断。此外不同的服务函数还会根据中断源进行少量的不要操作。但InstancePtr-Handler所指向的函数实际上才是是最后由用户来编辑的具有实际功能的函数。在执行完该函数后CPU回到asm_vector.S将之前入栈的变量出栈然后回到中断前指令的下一条指令继续执行新指令。二、中断流程图整体程序流程如下图所示
返回列表