Chinaunix首页 | 论坛 | 博客
  • 博客访问: 46278
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-06 15:00
文章分类

全部博文(15)

文章存档

2013年(15)

我的朋友

分类: 嵌入式

2013-10-15 15:12:11

上一篇说了MQX中断的大致流程,本文我们重点关注一下细节问题,首先一个是中断优先级的问题。

在此我想简单说说cortex m4的中断异常的机制。由于这个部分是arm统一控制的,freescale的k60手册里没有提及,具体内容可以参考“Cortex-M4 Devices Generic User Guide”,里面说的已经比较详细了。

cortex m4的中断由NVIC(Nested Vectored Interrupt Controller)控制,最多255个中断(加上异常),其中arm定义了前15个(刚刚好vectors_ram里的15个内容),剩下的就是芯片公司自定义了。而对于k60,freescale自己定义了95个中断。每个中断(异常)都有一个对应优先级,越小优先级越高。除了前三个中断Reset、NMI、HardFault之外,其他都可以用户自己定义,当然由于前三个优先级分别是-3~-1,想定义比它们高的优先级是不可能的了。至于优先级,NVIC设计了两个优先级,一个是主优先级、一个是从优先级。主优先级比较厉害,当中断触发时,高优先级的中断可以立刻抢占低优先级的中断。至于从优先级就比较弱了,只有在两个中断主优先级相同的时候,它才起作用。在这种情况下,从优先级高的中断虽然不能抢占正在运行的中断服务(当然大家主优先级一样),但是当这个中断服务结束的时候,它能够排到从优先级低的中断前面先执行,总之还是有点作用的。arm规定主优先级和从优先级一共占一个字节,也就是8个位,具体谁占几个位就是芯片厂商自己定义了。对于k60,我们没有找到对应的相关资料,只是在程序里发现了这么一段:

点击(此处)折叠或打开

  1. #ifndef CORTEX_PRIOR_IMPL
  2.     #if PSP_MQX_CPU_IS_ARM_CORTEX_M0P
  3.         #define CORTEX_PRIOR_IMPL (1)
  4.     #elif PSP_MQX_CPU_IS_ARM_CORTEX_M4
  5.         #define CORTEX_PRIOR_IMPL (3)
  6.     #endif
  7. #endif /* CORTEX_PRIOR_IMPL */

根据后面看源码的分析,我推断k60的主优先级占3个位,从优先级占5个位。中断的优先级是由NVIC_IPR0~NVIC_IPR59,这60个32位的寄存器控制的,算一下(32/8)×60=250是不是正好等于256-16呢?而前12(15个里面前3个不能改的)个异常的优先级是SHPR1-SHPR3控制的,内容基本一样。

再说一下中断(异常)的开启和禁用。全局上设置中断(异常)的开启和禁用主要是三大寄存器PRIMASK、FAULTMASK和BASEPRI。PRIMASK是开关全局的中断(16~255)、FAULTMASK是开关全局异常(15-1,除了NMI),而BASEPRI设置一个优先级,只有比这个优先级高的中断(异常)才能响应。而关于单个中断的开关是由NVIC_ISER0-7和NVIC_ICER0-7控制的。在这里要说一下,程序里经常使用的CPSID I指令关的是PRIMASK,没有涉及FAULTMASK,所以keil是能够在CPSID I之后正常调试的,因为Debug Monitor是异常,不受PRIMASK控制,记得当时我还纳闷了半天,现在想想真是概念不清。

回到MQX,MQX没有使用5个位的从优先级,它只使用了前3个主优先级,所以MQX的可设置的中断优先级一共是从0~7。至于为什么没有使用从优先级,我个人猜测是为了简化代码,因为MQX的中断优先级是和实时性任务相关的(这个机制我真的是第一次见到,包括ucos、rt thread,甚至linux里都没见过,虽然linux现在有那么一个中断线程的说法。),正在运行的任务会对当前中断的触发造成影响。这个机制只考虑了主优先级,如果连从优先级都考虑进去的话,那真的是过于复杂了,毕竟MQX不是linux那样的庞然大物。而MQX第一次关于优先级的操作是在_psp_set_kernel_disable_level里:

点击(此处)折叠或打开

  1. init_ptr = (MQX_INITIALIZATION_STRUCT_PTR)&kernel_data->INIT;

  2.     // Compute the enable and disable interrupt values for the kernel.
  3.     temp = init_ptr->MQX_HARDWARE_INTERRUPT_LEVEL_MAX;
  4.     if (temp > 7) {
  5.         temp = 7;
  6.         init_ptr->MQX_HARDWARE_INTERRUPT_LEVEL_MAX = 7;
  7.     } else if (temp == 0) {
  8.         temp = 1;
  9.         init_ptr->MQX_HARDWARE_INTERRUPT_LEVEL_MAX = 1;
  10.     }

  11.     kernel_data->DISABLE_SR = CORTEX_PRIOR(temp);

  12.     /* Set all (till now unused) interrupts level to the disable level */
  13.     for (i = 0; i < sizeof(NVIC_BASE_PTR->IP) / sizeof(NVIC_BASE_PTR->IP[0]); i++)
  14.         NVIC_BASE_PTR->IP[i] = CORTEX_PRIOR((1 << CORTEX_PRIOR_IMPL) - 2);
  15.     /* Disable interrupts by default */
  16.     {
  17.         uint32_t * icer_ptr = (uint32_t *)&NVIC_BASE_PTR->ICER;

  18.         for (i = 0; i < sizeof(NVIC_BASE_PTR->ICER) / sizeof(uint32_t); i++) {
  19.             *(icer_ptr + i) = 0xFFFFFFFF; /* disable 32 interrupts in a row */
  20.         }
  21.     }

这里所谓的MQX_HARDWARE_INTERRUPT_LEVEL_MAX是被预先设置好的,其值为2。从这个值名字上我们可以知道,我们用户定义的中断的优先级最高也就是2了。DISABLE_SR也很好计算为2<<5=0x40,不过我们暂时不去管它。看下面的部分,MQX把0-105个中断(不包括异常哦)的优先级全部设为(8-2)<<5,就是0xC0作为默认中断的优先级,然后再把全部的中断都给禁用了。通过这个操作,MQX保证了一件事情,就是没有注册的中断,是绝对不会触发的。在这里我对这个106的值很是纳闷,参照k60的用户手册,总共的中断总数应为95个,真不知道106的值是怎么来的,不过就算多出来也没有问题,反正ICER也足够写了,油多不坏菜嘛。

MQX还设置了三个异常的优先级(其他的都是默认设置),分别是SVCall、PendableSrvReq和SysTick。SVCall和SysTick是在systick_init里设置的:


点击(此处)折叠或打开

  1. SCB_SHPR2 |= 0x10000000;
  2.     /* SysTick priority*/
  3.     SCB_SHPR3 |= SCB_SHPR3_PRI_15(CORTEX_PRIOR(BSP_TIMER_INTERRUPT_PRIORITY));


也就是把SVCall主优先级设置成了0,SysTick的主优先级设置成了2。至于PendableSrvReq,目前是被设置为2,是通过_int_disable和_int_enable来设置的。这个比较复杂,涉及了上面提到的任务影响中断的机制,先按下不表,大家留一个意。

现在MQX的中断是全部禁用了,那怎么开启中断呢?MQX采取的是谁用中断谁开启的原则,在设备驱动中使用_bsp_int_init、_bsp_int_enable和_bsp_int_disable来管理某个中断。举个例子,_bsp_int_init可以使能一个中断,设置它的优先级,虽然这里优先级设置没有什么限制,不过推荐还是不高于2为好。


点击(此处)折叠或打开

  1. _mqx_uint _nvic_int_init
  2.    (
  3.       // [IN} Interrupt number
  4.       _mqx_uint irq,

  5.       // [IN} Interrupt priority
  6.       _mqx_uint prior,

  7.       // [IN] enable the interrupt now?
  8.       boolean enable
  9.    )
  10. {
  11.     VCORTEX_NVIC_STRUCT_PTR nvic = (VCORTEX_NVIC_STRUCT_PTR)&(((CORTEX_SCS_STRUCT_PTR)CORTEX_PRI_PERIPH_IN_BASE)->NVIC);
  12.     _mqx_uint ext_irq_no = irq - 16;

  13.     if (irq >= PSP_INT_FIRST_INTERNAL && irq <= PSP_INT_LAST_INTERNAL) {
  14.         nvic->PRIORITY[ext_irq_no >> 2] = (nvic->PRIORITY[ext_irq_no >> 2] & ~(0xff << ((ext_irq_no & 3) * 8))) | (((prior << CORTEX_PRIOR_SHIFT) & CORTEX_PRIOR_MASK) << ((ext_irq_no & 3) * 8));

  15.         if (enable)
  16.             _nvic_int_enable(irq);
  17.         else
  18.             _nvic_int_disable(irq);

  19.     }
  20.     else
  21.         return MQX_INVALID_PARAMETER;

  22.     return MQX_OK;


本来想把堆栈的设置放在下一篇的,可是想想这会导致下一篇的内容太多了,就在这篇里写吧。

     中断的堆栈是一个非常重要的细节,由于普通的裸奔程序的程序堆栈和中断堆栈共有一个内存区域,所以只需要注意不要溢出就可以了,但是对于MQX由于采用了系统堆栈和任务堆栈两套机制,所以相对而言就比较复杂了。在这先插入cortex m4的堆栈机制,虽然arm的堆栈寄存器sp(R13)只有一个,但是它在不同状态下可以对应不同的堆栈指针。cortex一个有两个堆栈寄存器:系统堆栈MSP(Main Stack Pointer)和任务堆栈PSP(Process Stack Pointer)。就是说,SP可能对应的是MSP,也有可能对应的是PSP。控制当前SP堆栈的是CONTROL寄存器的CONTROL[1]——SPSEL。若SPSEL=0则SP=MSP;若SPSEL=1则SP=PSP。不过在中断的情况下,系统默认的SP始终是MSP。arm采用双堆栈的目的是更好的配合嵌入式系统的多任务机制,x86下也有类似的tss寄存器切换堆栈的机制,具体的优点我就不说了,毕竟这是操作系统里面的东西。


     顺带一提的是cortex还规定了cpu的两个状态:Thread mode和Handler mode。顾名思义cpu执行任务的时候是在Thread mode里,执行中断(异常)的时候是在Handler mode。此外还有两个特权级Unprivileged和Privileged,在Privileged特权级下芯片所有的硬件资源都是可以访问的。但是在Unprivileged下面,就有诸多的限制了:

1.has limited access to the MSR and MRS instructions, and cannot use the CPS instruction
2.cannot access the system timer, NVIC, or system control block
3.might have restricted access to memory or peripherals

虽然arm建议一般任务在Unprivileged下执行,系统任务在privileged下执行(中断服务默认在privileged下执行),但是MQX似乎没有考虑这么多,目前为止我看到的一般任务都是在privileged下的。

继续我们的中断堆栈,一旦系统发生中断,在进入中断向量表的跳转程序之前,cortex内核会先进行自动的堆栈,将xPSR、PC、LR、R12、R0~R3八个寄存器压入栈中。如果M4内核有FPU(浮点数单元),那压入的东西更多,这里就忽略了。至于压入什么堆栈,这就要看系统在进入中断之前是在使用哪一个堆栈了,如果是用PSP就把8个寄存器压倒PSP里,如果是MSP就压倒MSP里,最后在中断里,都是用的MSP。



       最后说一下MQX的整体部分堆栈的使用。MQX除了在中断中是使用MSP之外,其他任务包括系统任务都是使用PSP的。由于系统复位是使用MSP,在boot.S就把当前堆栈改成了PSP。

点击(此处)折叠或打开

  1.         mrs r0, MSP
  2.         msr PSP, r0

  3.         /* switch to proccess stack (PSP) */
  4.         mrs r0, CONTROL
  5.         orr r0, r0, #2
  6.         msr CONTROL, r0
  7.         isb #15

虽然这里把当前堆栈改成了PSP,但实际的堆栈值没有变,系统在初始化配置的时候使用的是一个临时的堆栈,就是我们之前在“代码之始”提及的BOOT_STACK_ADDR=0x2000FEF0。细心的同学会发现这里和我们动态内存的分配区域有所重复,当然没有关系,系统在启动任务时候就会放弃这个堆栈。作为替代,每个任务都有自己的独立堆栈,所以我们在启动一个任务的时候,必须先考虑到它的堆栈,MQX把系统的管理操作也设置为一个特殊的任务:系统任务。这个任务的堆栈是调用_mem_alloc_system动态分配的。而系统中断对于的堆栈也是由这个函数动态分配的,不过这个堆栈的寄存器的是MSP,被全体中断(异常)服务程序共用。



阅读(1498) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~