比如你没有开中断源,即使使能中断也没用,看看VxWorks移植和万能中断操作模板方面的文档吧。
********************************************** * VxWorks在EasyARM2200和SmartARM2200上的移植 * ********************************************** ------ 浅谈《ecos增值包》辅助开发VxWorks BSP 2007/04/07 asdjf@163.com www.armecos.com 。。。。。 ====== 中断 ====== 中断需要提供:初始化、返回向量号、中断使能、中断禁止函数。 中断初始化真是妙不可言,因为LPC2210提供VIC映射,所以一步就可以得到向量号,不必查询,所以初始化做得好,中断响应效率可以很高。中断使能只要对LPC2XXX_VIC_INT_ENABLE寄存器对应位置1即可;中断禁止时对LPC2XXX_VIC_INT_ENABLE_CLR寄存器对应位置1。 在调试BSP时,运行后总是没有动静,通过内存打印技术,发现程序死在了excVecInit()函数处。见名知意,这个函数肯定和中断向量初始化有关,但它到底是如何工作的呢?虽然有源码,但那个是for X86的,对于ARM体系相关的函数部分没有指导意义啊,怎么办呢?此时,需要祭出战无不胜,攻无不克,见神杀神,见鬼杀鬼的利器---反汇编调试。 《ecos增值包》提供了GNU开发环境,使用“arm-elf-objdump -d vxWorks_romResident > 1.txt”就可以得到VxWorks的汇编列表文件1.txt,里面有地址和汇编信息,可用于和AXD汇编对照调试。 8101a644 <excVecInit>: 8101a644: e92d4800 stmdb sp!, {fp, lr} 8101a648: e24dd008 sub sp, sp, #8 ; 0x8 8101a64c: eb002d4e bl 81025b8c <armInitExceptionModes> 8101a650: e59fb4d4 ldr fp, [pc, #1236] ; 8101ab2c <$d> fp=81301524 excEnterTbl 8101a654: e3a01005 mov r1, #5 ; 0x5 8101a658: e59f04d0 ldr r0, [pc, #1232] ; 8101ab30 <$d+0x4> r0 = e59ff0f4 8101a65c: e24bb008 sub fp, fp, #8 ; 0x8
8101a660: e59b3008 ldr r3, [fp, #8] 8101a664: e2511001 subs r1, r1, #1 ; 0x1 8101a668: e5830000 str r0, [r3] 8101a66c: e59bc008 ldr ip, [fp, #8] 8101a670: e59b300c ldr r3, [fp, #12] 8101a674: e28bb008 add fp, fp, #8 ; 0x8 8101a678: e58c30fc str r3, [ip, #252] 8101a67c: 1afffff7 bne 8101a660 <excVecInit+0x1c> 8101a660 8101a680: e3a0c000 mov ip, #0 ; 0x0 8101a684: e59f34a8 ldr r3, [pc, #1192] ; 8101ab34 <$d+0x8> r3 = e7fddefe 8101a688: e58c3000 str r3, [ip] 8101a68c: e59fc4a4 ldr ip, [pc, #1188] ; 8101ab38 <$d+0xc> ip = 813087C8 8101a690: e59cb01c ldr fp, [ip, #28] fp = [813087E4] 8101a694: e35b0000 cmp fp, #0 ; 0x0 8101a698: 0a000003 beq 8101a6ac <excVecInit+0x68> 8101a6ac 8101a69c: e3a00000 mov r0, #0 ; 0x0 8101a6a0: e3a0101c mov r1, #28 ; 0x1c 8101a6a4: e1a0e00f mov lr, pc 8101a6a8: e1a0f00b mov pc, fp
8101a6ac: e59f3488 ldr r3, [pc, #1160] ; 8101ab3c <$d+0x10> r3 = 8101a6dc 8101a6b0: e59fc488 ldr ip, [pc, #1160] ; 8101ab40 <$d+0x14> ip = 813102a4 _func_armIrqHandler 8101a6b4: e58c3000 str r3, [ip] _func_armIrqHandler = 8101a6dc excIntHandle 8101a6b8: e3a00000 mov r0, #0 ; 0x0 8101a6bc: e28dd008 add sp, sp, #8 ; 0x8 8101a6c0: e8bd8800 ldmia sp!, {fp, pc}
8101b494 <intVecBaseSet>: 8101b494: e1a0f00e mov pc, lr
通过反复分析,intVecBaseSet在ARM体系上屁用也没有,是空的,根本不能指望通过它改变中断向量基址VEC_BASE_ADRS。这段汇编的大概意思是:在0地址开始处填写中断向量表跳转语句,在100H处写跳转地址,没有中断服务子程序的入口填写0xE59FF9F4(未定义指令,用于引发异常)。怪不得死机,LPC2210重映射到0地址的RAM空间只有64字节,向100H只读地址写数据会引发异常(44B0向ROM里写数据不会引发异常,顶多写不进去就是了,看来LPC2210在地址空间防护上做了一些工作,能识别出向只读空间里写数据的错误。虽然是好事,但给我们移植BSP带来了困难,怎么办呢?)。VxWorks考虑得真是周到,这部分是用源码提供的,那就咔嚓了excVecInit(),换成自己的myExcVecInit(),齐活。 VxWorks提供的中断处理函数不能动,因为要使用VxWorks的中断体系,由它来调用我们提供的处理函数,这样,就把LPC2210的中断体系映射到了VxWorks上。 既然可以替换成自己的代码,那就不用仿照VxWorks向量表原来的构造了,把它推翻,换个和LPC2210匹配更好的结构。我用LPC2210内部IRAM保存向量表和ISR服务程序入口地址,因为内部IRAM快,还可节省一些外部XRAM空间,然后把它映射到0地址即可(真是绝配啊!)。汇编源程序如下:(targetconfigzlgarmsysALib.s) .globl FUNC(myExcVecInit) /* own code for armInitExceptionModes()---zk */ .extern FUNC(excEnterUndef) .extern FUNC(excEnterSwi) .extern FUNC(excEnterPrefetchAbort) .extern FUNC(excEnterDataAbort) .extern FUNC(intEnt) .extern FUNC(armInitExceptionModes) .extern FUNC(_func_armIrqHandler) .extern FUNC(excIntHandle)
_ARM_FUNCTION(myExcVecInit)
stmfd sp!, {r0-r10,lr} bl FUNC(armInitExceptionModes) copy_vector: adr r0, real_vectors add r2, r0, #64 ldr r1, =0x40000000 /*前面的初始化程序已经把此RAM的前64字节重映射到了0地址*/ /*add r1, r1, #0x08*/ vector_copy_loop: ldmia r0!, {r3-r10} stmia r1!, {r3-r10} cmp r0, r2 ble vector_copy_loop
/*反汇编的程序在此处判断了一个内存中的变量,但我没找到该变量名,没判断变量是否为0就直接赋值了。*/ /*看效果没有任何不良影响。*/ ldr r0, L$__func_armIrqHandler ldr r1, L$_excIntHandle str r1, [r0]
nop ldmfd sp!, {r0-r10,pc}
/*************************************************/ /* interrupt vectors */ /*************************************************/ real_vectors: ldr pc,.reset //0x00 ldr pc,.undefined_instruction //0x04 ldr pc,.software_interrupt //0x08 ldr pc,.prefetch_abort //0x0C ldr pc,.data_abort //0x10 ldr pc,.not_used //0x14 ldr pc,.irq //0x18 ldr pc,.fiq //0x1C
/*************************************************/
.reset: .word 0xE59FF9F4 .undefined_instruction: .word FUNC(excEnterUndef) .software_interrupt: .word FUNC(excEnterSwi) .prefetch_abort: .word FUNC(excEnterPrefetchAbort) .data_abort: .word FUNC(excEnterDataAbort) .not_used: .word 0xE59FF9F4 /* not use */ .irq: .word FUNC(intEnt) .fiq: .word 0xE59FF9F4 /* fiq */
注意噢:子程序要压栈保存所有改变的寄存器(除非明确需要改变,如传参数值,才不需要保存恢复),不然不要怪我没有提醒你ARM编译器会优化程序,使用寄存器传值,如果你的子程序内部改变了寄存器值又没有恢复原先的值,那么插入你自编的函数,会发生很多奇妙的事哦! stmfd sp!, {r0-r10,lr} ldmfd sp!, {r0-r10,pc} 。。。。。。
*********************** * 第三讲 ecos中断操作 * *********************** 2006/12/30 asdjf@163.com www.armecos.com
ecos的中断处理到底比我们自己写的高明在哪里呢? 如果我们自己写中断处理程序,那么,针对lpc2210、s3c44b0x、2410、2440等具体硬件,每一个片子都要重写ISR。对于不同的体系结构(如ARM、MIPS、X86、POWERPC......)、变种(同体系结构不同型号)、硬件平台(不同公司的开发板),中断机制不尽相同,程序员要花费大量的时间纠缠于细微的差异,心情不会愉快,代码复用性也不强。 ecos采用了一种通用的中断处理机制,抽象出中断最本质的特点,用硬件抽象层HAL来抹平各种中断硬件体系实现的细节,程序员只要针对ecos抽象出来的通用虚拟中断系统编程就可以了。 那么ecos到底是如何“抽象”中断的呢?暂且不表,先来说说中断响应时间。 如果你写过汇编中断处理程序,或者用过ucos,那么你对ISR的实现方式一定不会陌生,先填好中断向量表,再写好ISR处理程序,一旦中断就通过向量表找到ISR函数地址,跳到那里执行。 而ecos的虚拟中断系统是ISR+DSR,当然Linux也是ISR+tasklet。很明显,中断分成了两个部分,一部分是中断服务程序ISR,关中断执行,尽可能短,很多内核函数用不了;另一部分是延迟服务程序DSR,不关中断,可以进行比较耗时的处理,比ISR能用更多的内核函数。 为什么这样做?有什么好处呢? 我们知道,中断是随时都有可能异步发生的,中断处理程序和被中断程序的“并发”执行,会引起重入、同步、临界资源保护等诸多问题。中断发生时会保存现场,返回时恢复,故不会对被中断的无临界资源的应用线程产生影响,但如果被中断的线程正在执行内核调用,则有可能破坏内核数据,因为内核函数需要原子操作,不能被打断。避免这一问题的方法之一是在处于临界区的内核函数中禁止中断。例如ucos的内核函数就经常开关中断。不过有时这种原子操作的时间可能相当长,对于中断响应时间这个指标有不良影响。改善性能的方法就是不使用开关中断的方法避免冲突,而是使用锁、信号量等方式实现内核数据保护。这样就约定在ISR里限制使用不安全的绝大部分内核API,内核API也不再频繁开关中断,代之以锁、信号量。当我们的ISR可以写得很短,内核API又不会关中断的情况下,中断响应时间就会变小,RTOS的实时性指标就会更好看。因此,采用ISR+DSR的ecos中断响应比ucos快。可能您现在还不太习惯这种把ISR分成两半儿的写法,没关系,我们下面提供一个万能中断处理模板,您只要套用就可以了。把精力集中在中断事件处理上,再不用为搭架子发愁。 想看中断如何被抽象吗?我们结合一个题目边看边讲。 总的思路是:创建中断句柄,挂接中断,使能中断,正常工作后一旦发生中断,先调用ISR,再调用DSR(可选)。其中涉及到中断应答,中断清除、外部中断清除、中断触发方式配置、中断禁止/使能、中断创建/删除、中断挂接/解挂等概念。 题目:用KEY1按键控制蜂鸣器鸣响。低电平有效,按下发声,再按停止,再按又发声,周而复始。(别忘了先装好跳线再实验) 全部源码如下所示。多任务编程第一讲已经讲过了,I/O寄存器读写在第二讲也已经讲过了,现在来看看中断编程。 我在创建taska线程时传递了一个参数CYGNUM_HAL_INTERRUPT_EXT3,这是KEY1对应的外部中断号,没什么特别的目的,只是想用线程参数存放我的私有数据(priv_data),总得找个地儿放我自己的东西不是,得,就放这儿了。CYGNUM_HAL_INTERRUPT_EXT3的意思是符合CYG公司标准的数字---位于硬件抽象层---中断用途---外部中断3号。 线程taska中首先设置引脚,虽然ecos抽象了中断,但中断毕竟还是和硬件紧密相关,有些芯片为了引脚复用或低功耗等目的,多种功能对应在同一个引脚上/多个引脚对应在同一个功能上,使用时必须根据需要选择。例如:SMARTARM2200平台的KEY1使用的是EINT3中断,该中断由P0.9、P0.20、P0.30复用,要屏蔽P0.9(网卡8019中断)和P0.30(ZLG7290中断),以免冲突。感兴趣的读者可以试试允许P0.9和P0.30对应EINT3会发生什么现象。(低电平触发时,中断信号线相与;高电平触发时,中断信号线相或。) 设置BEEP控制引脚方向为输出,禁止刚一开始就BEEP鸣响,上电缺省输出是低电平,导致一开始就鸣叫,此处明确关掉声音。 中断触发方式有4种:高电平、低电平、上升沿、下降沿。ecos使用cyg_interrupt_configure函数设置中断触发方式。 cyg_interrupt_configure(中断号,电平/边沿选择,上下/高低选择) 其中: 电平/边沿选择:0---电平;1---边沿 上下/高低选择:0---低下;1---高上 此处设置为(priv_data,0,0),即低电平触发方式。priv_data是线程参数传递过来的EINT3中断号。 用中断触发配置函数是不是比直接写EMODE,EPOLAR寄存器要方便很多呢! 边沿触发的中断不用清中断源,但可能会丢中断;电平触发的中断需要清除外部中断源,不会丢失中断指示,而且可以共享同一根中断线。 清除priv_data(即EINT3)中断标志,等效于1<<3。这句要放在cyg_interrupt_configure后面,好象改EMODE和EPOLAR会影响EINT。最好在使用中断前明确清除以前的中断指示,以免发生不能预料的随机事件,这是好习惯。 ecos的虚拟中断体系要求把需要的中断处理函数挂接到对应的中断号上,而不必关心具体的硬件映射细节。中断句柄可以创建多个,但只能挂接其中的一个。 cyg_interrupt_create( 中断号, 中断优先级, 传递的中断参数, ISR函数, DSR函数, 被返回的中断句柄, 存放与此中断相关的内核数据的变量空间); cyg_interrupt_attach(中断句柄); 创建了中断句柄并且挂接后,中断服务程序就和中断向量建立了联系,每当中断后,先执行向量服务程序VSR,再调用已注册的中断服务程序ISR,然后执行延迟服务程序DSR。至此,中断已经准备就绪。 刚挂接的中断是被屏蔽的,要调用解除屏蔽函数,中断才能正式开始工作。这么做是为了在中断注册后,还能有一段时间继续准备其他需要做的工作,而这些工作不希望被中断干扰。如果都准备好了,就可以解除屏蔽,让中断正常工作。 cyg_interrupt_unmask(中断号);//使能中断 “while(1);”是为了不退出taska线程,你可以试试去掉这一句会发生什么现象。 ISR程序非常短小,也不调用绝大部分内核函数,不过中断应答和返回值是必须的。 cyg_interrupt_acknowledge(中断号); 中断应答应该在ISR快结束时执行一次写操作(写入的值一般为0),以便更新优先级硬件。如果不应答,硬件不能正常工作,而且一定要在ISR里应答。 如果ISR返回CYG_ISR_HANDLED,则不再调用DSR,如果返回CYG_ISR_CALL_DSR,则还要调用DSR。 此处屏蔽中断的目的是避免重入,因为我不希望中断嵌套,那样会搞乱数据。如果没有冲突的数据,允许嵌套,就可以不用此语句屏蔽中断。注意不要在ISR里用printf函数。 DSR里执行了大部分的处理工作,可以使用更多的内核函数。这段程序的主要作用是取反BEEP控制,让蜂鸣器随着KEY1按键中断控制在响与不响之间切换。解除中断屏蔽是对应ISR里的中断屏蔽,此时中断已经执行完毕,可以使能新的中断。 需要注意的是:电平中断的处理过程稍微烦琐一些。先撤消外部中断源指示(例如等待KEY1按键抬起/读空8019网卡输出缓冲区),然后撤消内部EINT中断标志位(对应位写1就可以清除)。如果不先撤消外部指示就清除内部标志/不清除内部标志,那么会反复不断地陷入中断,直至出错死机。感兴趣的读者可以试试注释掉“HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0));”看看中断能否退出,是否死机。去掉while中的KEY1按键抬起判断,看看按下去时的现象有何差别。 以上是面向ecos虚拟中断体系编程的讲解,可以看出,ecos的中断更加抽象,再也不用关心什么VIC,一大堆中断寄存器了,套用这个模板就可以了。 如果你想改成KEY1键高电平触发,只要写成“cyg_interrupt_configure(priv_data,0,1);”即可。此时,一运行就鸣叫,按一下停止,再按一下又鸣叫......结果正确。DSR里同低电平触发时一样等待外部中断源指示失效才退出,此时若信号保持为高电平,中断标志会一直置1。 如果想改成边沿触发,要将以下语句: HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag); while((flag&1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)) != 0) { HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)); HAL_READ_UINT8(LPC2XXX_SYSCON_EINT,flag); } 改为一句: HAL_WRITE_UINT8(LPC2XXX_SYSCON_EINT,1<<(priv_data-CYGNUM_HAL_INTERRUPT_EXT0)); 不用再判断外部中断源的情况,边沿触发不需要清除外部中断源,因为边沿不会一直保持。EINT写“1”清除还是必须的,不然还会反复陷入中断。 写成“cyg_interrupt_configure(priv_data,1,0);”就是下降沿触发,按下KEY1键会响,再按下停止...... 写成“cyg_interrupt_configure(priv_data,1,1);”就是上升沿触发,按下后抬起KEY1键,会响,再按下抬起停止...... 套用这个摸板,写基于中断的8019网卡驱动程序十分方便,8019中断也是用的EINT3,高电平触发,感兴趣的读者可以试试。(提示:改引脚分配PINSEL0和PINSEL1,ecos中有dp83902a参考源码) 很多人可能不用ARM或者ecos,那也没有关系,这里介绍的主要是编程思路和中断本质抽象,有了思路,细节的解决只是时间问题,您完全可以把这里的中断处理思路用在2410、X86、MIPS......上。初学者如果一上来就接触大量硬件细节,难免会晕掉,只见树木不见森林,ecos增值软件包使你能集中精力掌握思路,花最少的时间,取得最大的效果。 。。。。。。
上述文档可能不完整或已被更新,想获得该资料的最新最全版本,请访问:www.armecos.com
更多内容,详见: 我的培训中心 我的研发团队 我的技术顾问 文件系统整体解决方案咨询套餐 IP协议栈整体解决方案咨询套餐 USB整体解决方案咨询套餐 BootLoader整体解决方案咨询套餐 |