wuyage

浅谈ARM Cortex-M0/M0+ 中的中断抢占问题

0
阅读(10697)

    Cortex-M0/M0+, M3/M4 中的中断控制器英文名叫做NVIC,Nested Vectored Interrupt Controller,翻译过来就是嵌套向量中断控制器,所谓中断嵌套是指当正在执行一个中断服务程序时,这时如果来了优先级更高的中断,新来的中断会打断原来还没有处理完的中断服务程序,等新中断处理完毕之后再回到原中断服务继续处理。

    下面让我们在Kinetis KL26芯片上来实际操作一把吧。KL26芯片为ARM Cortex M0+内核。

     硬件:FRDM_KL26Z

     软件:FRDM-KL26Z_SC_Rev_1.0 + IAR7.4

1) 先做第一个实验:程序中开启了两个定时器,一个为TPM2,周期10ms,另外一个PIT,周期1s。TPM2中断中断里打印一个字符X,PIT中断里while(1)循环,不断打印~~~~~~~。

int main (void)
{
    char ch;
    
    printf("\n\r\n\r*** Running the NVIC_Test project ***\n\r");
    
    enable_irq(INT_TPM2-16);
    init_TPM2();
    
    enable_irq(INT_PIT-16);
    init_PIT();
    
    while(1)
    {
      ch = in_char();
      out_char(ch);
    } 
}

 /*   10 ms     */
void init_TPM2(void)
{
  SIM_SCGC6 |= (SIM_SCGC6_TPM2_MASK );
  SIM_SOPT2 |= SIM_SOPT2_TPMSRC(1);           //input clock source is MCGPLL/2=24MHz
  TPM2_SC = TPM_SC_DMA_MASK | TPM_SC_PS(3);   //Divide by 3M=24M/8
  TPM2_CNT = 0;
  TPM2_MOD = 30000;                //timer interrupt triger with 100Hz
  TPM2_SC |= TPM_SC_TOIE_MASK;    // enable Interrupt
  TPM2_SC |= TPM_SC_CMOD(1);    //start the counter
}

void TPM2_IRQHandler(void)
{   
  if (TPM2_SC & TPM_SC_TOF_MASK ) 
      TPM2_SC |=  TPM_SC_TOF_MASK;   // Clear Flag     
   printf("X"); 
}

 /*   1 s     */
void init_PIT(void) 
{
    SIM_SCGC6 |= SIM_SCGC6_PIT_MASK;                                
    PIT_MCR &= ~(1 << PIT_MCR_MDIS_SHIFT);                                                                 
    PIT_LDVAL0 = 24000000;          // 1 hz                                     
    PIT_TFLG0 |= PIT_TFLG_TIF_MASK;                                    
    PIT_TCTRL0 |= PIT_TCTRL_TIE_MASK | PIT_TCTRL_TEN_MASK;              
}

void PIT_IRQHandler(void) 
{
  if(PIT_TFLG0 & PIT_TFLG_TIF_MASK) 
     PIT_TFLG0 |= PIT_TFLG_TIF_MASK;  // Clear Flag                                 
   while(1)
   {
     printf("~~~~~~~~~~~~~~~"); 
   }
}

该实验现象如下:

1.jpg

刚开始一直打印X,也就是不断进入TPM2中断服务,然后就一直打印~,即进到PIT中断里,就再也出不来了。

出现该现象的原因是:TPM2和PIT的中断优先级是一样的(默认都是0),所以当进入到PIT中断服务后,由于while(1)原因,即使TPM2中断来了也无法响应。


2)让我们做第二个实验,加入优先级设置,将TPM2 的优先级设置为1,PIT的优先级设置为2。数字越小,表示优先级越高。

    enable_irq(INT_TPM2-16);
    set_irq_priority(INT_TPM2-16,1);
    init_TPM2();
    
    enable_irq(INT_PIT-16);
    set_irq_priority(INT_PIT-16,2);
    init_PIT();

理论来说TPM2中断会打算PIT中断,可是当我们观察实验现象时却发现,还是一直在PIT中断里,和第一个试验想象没有区别。

2.jpg

这到底是怎么回事??? 理论与实际不符啊!!!!!

经过调试才发现,原来是代码里的set_irq_priority 函数压根就没起作用,有图为证:

这是调用set_irq_priority 函数之前的寄存器值:

3.jpg

这是执行完set_irq_priotiy 函数之后的寄存器值:

4.jpg

该函数为什么不起作用呢?问题出在最后两条语句上,原因在于Cortex-M0/M0+ 中的NVIC寄存器只支持字传输,不能单字节的去操作。

void set_irq_priority (int irq, int prio)
{   
    /*irq priority pointer*/
    uint8 *prio_reg;
    uint8 err = 0;
    uint8 div = 0;
    
    /* Make sure that the IRQ is an allowable number. Right now up to 32 is 
     * used.
     *
     * NOTE: If you are using the interrupt definitions from the header
     * file, you MUST SUBTRACT 16!!!
     */
    if (irq > 32)
    {
        printf("\nERR! Invalid IRQ value passed to priority irq function!\n");
        err = 1;
    }

    if (prio > 3)
    {
        printf("\nERR! Invalid priority value passed to priority irq function!\n");
        err = 1;
    }
    
    if (err != 1)
    {
        /* Determine which of the NVICIPx corresponds to the irq */
        div = irq / 4;
        prio_reg = (uint8 *)((uint32)&NVIC_IP(div));
        /* Assign priority to IRQ */
        *prio_reg = ( (prio&0x3) << (8 - ARM_INTERRUPT_LEVEL_BITS) );             
    }
}

针对这个问题,有两种途径去解决:

1) 修改set_irq_priority 函数,这个写法不唯一,有一点必须注意的是必须按字操作,也就是一下写四字节数据。

void set_irq_priority (int irq, int prio)
{   
    /*irq priority pointer*/
    //uint8 *prio_reg;
    uint32 prio_reg;  // wenxue
    uint8 err = 0;
    uint8 div = 0;
    
    /* Make sure that the IRQ is an allowable number. Right now up to 32 is 
     * used.
     *
     * NOTE: If you are using the interrupt definitions from the header
     * file, you MUST SUBTRACT 16!!!
     */
    if (irq > 32)
    {
        printf("\nERR! Invalid IRQ value passed to priority irq function!\n");
        err = 1;
    }

    if (prio > 3)
    {
        printf("\nERR! Invalid priority value passed to priority irq function!\n");
        err = 1;
    }
    
    if (err != 1)
    {
        /* Determine which of the NVICIPx corresponds to the irq */
        div = irq / 4;
      //  prio_reg = (uint8 *)((uint32)&NVIC_IP(div));
        prio_reg = *(uint32 *)((uint32)&NVIC_IP(div));
        
        prio_reg = prio_reg  & ( ~(0xFFUL <<  ((irq &  0x03UL) * 8UL)) );
          
        prio_reg =  prio_reg |  (((prio << (8 - 2)) & (uint32_t)0xFFUL) << ((irq &  0x03UL) * 8UL) );
        
        *(uint32 *)((uint32)&NVIC_IP(div)) =prio_reg ;
                     
    }
}

2) 最简单最可靠的是调用CMSIS 标准接口函数NVIC_SetPriority

void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if((int32_t)(IRQn) < 0) {
    SCB->SHP[_SHP_IDX(IRQn)] = ((uint32_t)(SCB->SHP[_SHP_IDX(IRQn)] & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
       (((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
  }
  else {
    NVIC->IP[_IP_IDX(IRQn)]  = ((uint32_t)(NVIC->IP[_IP_IDX(IRQn)]  & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
       (((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
  }
}


修改完set_irq_priority 函数之后,我们观察调试结果,这时优先级确实做了改变

ttt.jpg

再次观察最后的实验现象,结果如下:

QQ截图20160203100423.jpg

我们可以看到TPM2中断会不断打断PIT中断。


总结如下:

1) 大家在使用FRDM-KL26Z_SC_Rev_1.0 代码时,注意set_irq_priority 函数有bug的,它起不了实际作用。

2) ARM Cortex-M0/M0+中断抢占只需要设置好优先级即可,优先级高的会抢占优先级低的中断。

补充说明:

ARM Cortex-M3/M4 中断抢占问题比M0/M0+ 复杂一点,大家可以参考以下视频介绍,说的很详细。

http://pan.baidu.com/share/link?shareid=644713788&uk=2870907047&fid=351504772388557

http://pan.baidu.com/share/link?shareid=644713788&uk=2870907047&fid=221308967281612

http://pan.baidu.com/share/link?shareid=644713788&uk=2870907047&fid=845340990569219

Cortex-M3/M4里面涉及到一个优先级分组概念,在Cortex-M0/M3 里面是没有的。



上述测试代码如下:

FRDM-KL26Z_SC.rar