在arm目录里搜带"__irq"的c代码,然后打开相应的project看看。如: D:KeilARMRV30BoardsSamsungS3C44001Hello
要点: 1.在启动文件里设置中断向量,比如s3c44b0x.s里有一段: IF VIM_SETUP <> 0
MACRO $IRQ_Vector IRQ_Vec $Num, $HandlerName $IRQ_Vector IF (VIM_CFG:AND:(1:SHL:$Num)) <> 0 IMPORT $HandlerName LDR PC, =$HandlerName ELSE B . ENDIF MEND
IRQ_Vec 25, HandlerEINT0 IRQ_Vec 24, HandlerEINT1 IRQ_Vec 23, HandlerEINT2 IRQ_Vec 22, HandlerEINT3 IRQ_Vec 21, HandlerINT4567 IRQ_Vec 20, HandlerTICK B . B . IRQ_Vec 19, HandlerZDMA0 IRQ_Vec 18, HandlerZDMA1 IRQ_Vec 17, HandlerBDMA0 IRQ_Vec 16, HandlerBDMA1 IRQ_Vec 15, HandlerWDT IRQ_Vec 14, HandlerUERR01 B . B . IRQ_Vec 13, HandlerTIMER0 IRQ_Vec 12, HandlerTIMER1 IRQ_Vec 11, HandlerTIMER2 IRQ_Vec 10, HandlerTIMER3 IRQ_Vec 9, HandlerTIMER4 IRQ_Vec 8, HandlerTIMER5 .................
ENDIF
如果要用HandlerTIMER5,将VIM_SETUP设为1,VIM_CFG中与TIMER5相关的bit置1。这样在中断向量表中在TIMER5中断位置会生成一句: LDR PC, =HandlerTIMER5 其中HandlerTIMER5为中断服务程序的函数地址
2。在C程序中为中断5写服务程序: __irq void HandlerTIMER5 (void) { /* Timer 5 Interrupt Handler */
timeval++; /* Increment Time Tick */ pIC->I_ISPC = INT_TIMER5; /* Clear Interrupt Flag */ }
函数定义前加上__irq,函数名与启动文件中的一致。
3。在scatter文件中将中断向量表放到0地址,这个不用解释了吧?
4。如果你调试时程序加载到ram,最后程序是烧到flash中的,如果arm不支持remap(如s3c44b0x),那么用仿真器在ram中程序时发生中断,会到flash中找中断向量表,因此flash中的向量表要与ram程序的中断向量表一致。解决至少有两种,这里介绍中断调用比软件快的一种。启动文件仍然如上,因此只要保证HandlerTIMER5函数的地址在flash中运行与仿真器加载到ram中运行一样。步骤: a.给中断服务程序的程序段起个名字,加上#pragma arm section编译指令: #pragma arm section code = "ISR_TIMER5" __irq void HandlerTIMER5(void) { if(RefreshBatt!=0)RefreshBatt--; rI_ISPC |= (1<<8); } #pragma arm section code //表示恢复原来的程序段名 b.两个运行版本本给ISR_TIMER5段分配相同的地址,如: ISR_SECT 0x0C1F0000 { isr.o (ISR_TIMER5, +first) } 这样就保证了HandlerTIMER5在两个运行版本中的地址是一样的,中断向量表当然也是一样的了。
如果有多个中断服务程序,最老实的办法是每个服务程序命名一个段,然后在scatter文件中分配地址。如果你能确认编译参数和链接循序是一样的,产生代码位置也是一样,分配到同一个段中也行,不过总有点隐患。
另外一个方案里改启动文件,思路是中断服务地址在程序运行时存到一个地址中,而中断服务程序先跳到一个取地址的小代码里。这样的好处是中断服务程序的地址随意,但增加了调用中断的时间。因为不仅要多跳转一次,而且取地址要用到寄存器又得压栈。因此个人倾向于前一种方案。 |