<br /> NXP的ARM7带ucos中硬中断与软中断响应详细分析<br /><br /><br /><br />一.带UCOS系统的软中断响应过程 1<br />1.第一步: 2<br />2.第二步: 2<br />二.带UCOS系统的硬中断响应过程 6<br /><br /><br /><br /><br /><br /> 下面的主要分析LPC系列ARM7的中断响应,以周立公老师带ucos移植程序为分析对象,对其他的ARM中带UCOS的项目也有参考价值。<br /><br />一.带UCOS系统的软中断响应过程<br />UCOS操作系统是以任务为单元的执行块,可以理解为linux中线程,任务需要进行切换,轮巡的执行,这种任务切换是通过ARM的软中断来实现的。ARM的软中断是在向量表中就存在,写在startup.s文件中,如:<br />;interrupt vectors<br />;中断向量表<br />Reset<br /> LDR PC, ResetAddr<br /> LDR PC, UndefinedAddr<br /> LDR PC, SWI_Addr<br /> LDR PC, PrefetchAddr<br /> LDR PC, DataAbortAddr<br /> DCD 0xb9205f80<br /> LDR PC, [PC, #-0xff0]<br /> LDR PC, FIQ_Addr<br /><br />这个向量表是ARM具备的,放在程序的开始位置,具体怎么放置和定位,要在编译时候设置分散定位表(这里不仔细讲),ARM复位启动的时候就会自动跑到开始行PC, ResetAddr。第三行LDR PC, SWI_Addr为软中断响应行。附带说明,这个向量表程序在编译后的地址(程序在运行时可以看到汇编程序显示的每行地址,也就是常说的中断向量表中所有数据32位累加和为0)累加和为0,所以可以看到程序中为什么有DCD 0xb9205f80这行,数字是手动可以调整的。<br /> 软中断和硬中断如果从程序角度分析,其实是一样的,都是响应的一种中断,响应开始要求环境和其他变量入桟保护起来,然后调用到中断响应的C程序中(就是自己写中断响应程序),执行完中断响应程序后,再进行堆栈的出桟,恢复之前保护的环境与变量,再掉转程序到开始进入中断前的位置,这样程序接着运行。其实这个过程,跟51一样,软中断与硬中断也一样。那软中断和硬中断到底有什么不同,软中断是可以人为控制的,硬中断是ARM芯片里面控制的,自动反应,如串口中断,是串口收到数据后,串口自动产生中断。软中断是通过手动调用程序后产生的中断。以我的理解,不知道是否还有其他区别。<br /> 现在我们具体可以分析软中断响应过程了:<br /> 1.第一步:<br />在UCOS中,当要进行任务切换,直接手动调用OS_TASK_SW()函数的调用,调用之后,相当执行了os_cpu.h文件中的<br />__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */<br />这是ADS编译器可以认识的软中断执行程序。这样系统会响应软中断,程序会跳到上面讲过的startup.s文件中断向量表中LDR PC, SWI_Addr,<br />2.第二步:<br />在startup.s文件中有SWI_Addr DCD SoftwareInterrupt,相当再跳到SoftwareInterrupt函数,这个函数在OS_cpu_a.s文件中:<br />;软件中断<br />SoftwareInterrupt<br /> LDR SP, StackSvc ; 重新设置堆栈指针<br /> STMFD SP!, {R0-R3, R12, LR}<br /> MOV R1, SP ; R1指向参数存储位置<br /><br /> MRS R3, SPSR<br /> TST R3, #T_bit ; 中断前是否是Thumb状态<br /> LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号<br /> BICNE R0, R0, #0xff00<br /> LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号<br /> BICEQ R0, R0, #0xFF000000<br /> ; r0 = SWI号,R1指向参数存储位置<br /> CMP R0, #1<br /> LDRLO PC, =OSIntCtxSw<br /> LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换<br /><br /> BL SWI_Exception<br /> <br /> LDMFD SP!, {R0-R3, R12, PC}^<br /> <br />StackSvc DCD (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)<br /><br />这个函数,是自己写的,可以进行改动。这其中就是上面说过的环境的一些入桟,出桟,还有查询是否有更高优先级任务的响应。软中断响应时因为有不同的中断,如关OS_ENTER_CRITICAL,开OS_EXIT_CRITICAL等很多软中断,所以程序执行上面中的SWI_Exception执行自己的外部中断C响应程序,里面实现具体的软中断:<br />void SWI_Exception(int SWI_Num, int *Regs)<br />{<br /> OS_TCB *ptcb;<br /> <br /> switch(SWI_Num)<br /> {<br /> //case 0x00: /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件 */<br /> // break;<br /> //case 0x01: /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */<br /> // break;<br /> case 0x02: /* 关中断函数OS_ENTER_CRITICAL(),参考os_cpu.h文件 */<br /> __asm<br /> {<br /> MRS R0, SPSR<br /> ORR R0, R0, #NoInt<br /> MSR SPSR_c, R0<br /> }<br /> OsEnterSum++;<br /> break;<br /> case 0x03: /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */<br /> if (--OsEnterSum == 0)<br /> {<br /> __asm<br /> {<br /> MRS R0, SPSR<br /> BIC R0, R0, #NoInt<br /> MSR SPSR_c, R0<br /> }<br /> }<br /> break;<br />#if OS_SELF_EN > 0 <br /> case 0x40:<br /> /* 返回指定系统服务函数的地址 */<br /> /* 函数地址存于数组_OSFunctionAddr中*/<br /> /* 数组_OSFunctionAddr需要另外定义 */<br /> /* Regs[0] 为第一个参数,也是返回值 */<br /> /* Regs[1] 为第二个参数 */<br /> /* Regs[2] 为第三个参数 */<br /> /* Regs[3] 为第四个参数 */<br /> /* 仅有一个参数为系统服务函数的索引 */<br /> Regs[0] = _OSFunctionAddr[Regs[0]];<br /> break;<br /> case 0x41:<br /> /* 返回指定用户的服务函数的地址 */<br /> /* 函数地址存于数组_UsrFunctionAddr中*/<br /> /* 数组_UsrFunctionAddr需要另外定义 */<br /> /* Regs[0] 为第一个参数,也是返回值 */<br /> /* Regs[1] 为第二个参数 */<br /> /* Regs[2] 为第三个参数 */<br /> /* Regs[3] 为第四个参数 */<br /> /* 仅有一个参数为用户服务函数的索引 */<br /> Regs[0] = _UsrFunctionAddr[Regs[0]];<br /> break;<br /> case 0x42: /* 中断开始处理 */<br /> OSIntNesting++;<br /> break;<br /> case 0x43: /* 判断中断是否需要切换 */<br /> if (OSTCBHighRdy == OSTCBCur)<br /> {<br /> Regs[0] = 0;<br /> }<br /> else<br /> {<br /> Regs[0] = 1;<br /> }<br /> break;<br />#endif<br /> case 0x80: /* 任务切换到系统模式 */<br /> __asm<br /> {<br /> MRS R0, SPSR<br /> BIC R0, R0, #0x1f<br /> ORR R0, R0, #SYS32Mode <br /> MSR SPSR_c, R0<br /> }<br /> break;<br /> case 0x81: /* 任务切换到用户模式 */<br /> __asm<br /> {<br /> MRS R0, SPSR<br /> BIC R0, R0, #0x1f<br /> ORR R0, R0, #USR32Mode <br /> MSR SPSR_c, R0<br /> }<br /> break;<br /> case 0x82: /* 任务是ARM代码 */<br /> if (Regs[0] <= OS_LOWEST_PRIO)<br /> {<br /> ptcb = OSTCBPrioTbl[Regs[0]];<br /> if (ptcb != NULL)<br /> {<br /> ptcb -> OSTCBStkPtr[1] &= ~(1 << 5);<br /> }<br /> }<br /> break;<br /> case 0x83: /* 任务是THUMB代码 */<br /> if (Regs[0] <= OS_LOWEST_PRIO)<br /> {<br /> ptcb = OSTCBPrioTbl[Regs[0]];<br /> if (ptcb != NULL)<br /> {<br /> ptcb -> OSTCBStkPtr[1] |= (1 << 5);<br /> }<br /> }<br /> break;<br /> default:<br /> break;<br /> }<br />}<br />这部分程序也是移植时自己写的,里面有汇编。<br />其中的switch部分程序,对应的关系可以os_cpu.h文件中:<br /> __swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */<br />__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任务 */<br />__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */<br />__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */<br /><br />__swi(0x40) void *GetOSFunctionAddr(int Index); /* 获取系统服务函数入口 */<br />__swi(0x41) void *GetUsrFunctionAddr(int Index);/* 获取自定义服务函数入口 */<br />__swi(0x42) void OSISRBegin(void); /* 中断开始处理 */<br />__swi(0x43) int OSISRNeedSwap(void); /* 判断中断是否需要切换 */<br /><br />__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */<br />__swi(0x81) void ChangeT**oid); /* 任务切换到用户模式 */<br />__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */<br />__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */<br /><br />注意:上面的程序OS_TASK_SW任务切换,_OSStartHighRdy优先级最高的任务切换,不是在void SWI_Exception(int SWI_Num, int *Regs)函数中实现,被屏蔽了,直接改在OS_cpu_a.s文件中实现的:<br />;软件中断<br />SoftwareInterrupt<br />。。。。<br /><br />为什么要调整在OS_cpu_a.s文件中实现,这个没有深入研究,我想应该都是可以的。<br /><br />到现在整个软中断过程分析完了。<br />注意:上面的各个部分,每个阶段具体安排在哪里和哪个文件中,是可以认为改动的,甚至响应的形式也可以调整,但是每一个软中断的过程和要完成的工作是一样的,因为在用lpc21xx 系列时上面的整个程序结构基本一致的,现在用LPC23XX的时候有一些变化。<br /><br />二.带UCOS系统的硬中断响应过程<br /> 硬中断,就是我们常说的中断,一般指的是外围设备中断,如串口,I2C,TCP,USB等。<br /> 硬中断一般分为普通和快速中断,对应上面所说的中断向量表中:<br /> LDR PC, [PC, #-0xff0]<br /> LDR PC, FIQ_Addr<br /><br /> 如果发生了普通的硬中断,程序会跳到LDR PC, [PC, #-0xff0]行,由于对于一个ARM7不同的硬件中断响应时的入桟和出桟以及现场保护是相同的,所以写一个汇编的宏定义就可以实现多个硬中断响应共用,这个宏在IQR.INC有:<br />MACRO<br />$IRQ_Label HANDLER $IRQ_Exception_Function<br /><br /> EXPORT $IRQ_Label ; 输出的标号<br /> IMPORT $IRQ_Exception_Function ; 引用的外部标号<br /><br />$IRQ_Label<br /> SUB LR, LR, #4 ; 计算返回地址<br /> STMFD SP!, {R0-R3, R12, LR} ; 保存任务环境<br /> MRS R3, SPSR ; 保存状态<br /> STMFD SP, {R3, SP, LR}^ ; 保存用户状态的R3,SP,LR,注意不能回写<br /> ; 如果回写的是用户的SP,所以后面要调整SP<br /> LDR R2, =OSIntNesting ; OSIntNesting++<br /> LDRB R1, [R2]<br /> ADD R1, R1, #1<br /> STRB R1, [R2]<br /><br /> SUB SP, SP, #4*3<br /> <br /> MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式<br /> CMP R1, #1<br /> LDREQ SP, =StackUsr<br /> <br /> BL $IRQ_Exception_Function ; 调用c语言的中断处理程序<br /><br /> MSR CPSR_c, #(NoInt | SYS32Mode) ; 切换到系统模式<br /> LDR R2, =OsEnterSum ; OsEnterSum,使OSIntExit退出时中断关闭<br /> MOV R1, #1<br /> STR R1, [R2]<br /><br /> BL OSIntExit<br /><br /> LDR R2, =OsEnterSum ; 因为中断服务程序要退出,所以OsEnterSum=0<br /> MOV R1, #0<br /> STR R1, [R2]<br /><br /> MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切换回irq模式<br /> LDMFD SP, {R3, SP, LR}^ ; 恢复用户状态的R3,SP,LR,注意不能回写<br /> ; 如果回写的是用户的SP,所以后面要调整SP<br /> LDR R0, =OSTCBHighRdy<br /> LDR R0, [R0]<br /> LDR R1, =OSTCBCur<br /> LDR R1, [R1]<br /> CMP R0, R1<br /><br /> ADD SP, SP, #4*3 ; <br /> MSR SPSR_cxsf, R3<br /> LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不进行任务切换<br /> LDR PC, =OSIntCtxSw ; 进行任务切换<br /> MEND<br /><br /> END<br />每个中断要申明一个宏,如:<br /> /*定时器0中断*/<br />;/*Time0 Interrupt*/<br />Timer0_Handler HANDLER Timer0_Exception<br />其中:Timer0_Exception为自己的C响应中断函数。<br /><br />说明:在上面的宏汇编程序中:<br />BL $IRQ_Exception_Function ; 调用c语言的中断处理程序<br />是每次中断响应时c语言的中断处理程序,这个是每个中断初始化时给每个中断向量地址的(对应nxp芯片的寄存器)。中断响应时会跳到中断向量表LDR PC, [PC, #-0xff0]行,然后系统根据中断响应的通道号值(NXP的arm7每个外围设备对应唯一的中断通道值,51里也是这样的,就是中断号)执行对应的宏汇编程序,在汇编里执行<br />BL $IRQ_Exception_Function ; 调用c语言的中断处理程序初始化时<br />这个IRQ_Exception_Function 对应与中断初始化时的函数,如定时器的:VICVectAddr0 = (uint32)Timer0_Handler; //定时器0中断函数地址分配<br />这样可以响应中断函数了。<br /><br />注意:上面的硬件中断过程,也有可能写法不完全一致,但是对应nxp的ARM7的响应过程是一致的,对于快速中断响应过程也是一样的。<br /><br /> <br />献给ARM的所有移植者 写于2008.10.27<br /><br /><br /> |
|