下面让我们在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("nrnr*** Running the NVIC_Test project ***nr"); 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("~~~~~~~~~~~~~~~"); }}
该实验现象如下:
刚开始一直打印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中断里,和第一个试验想象没有区别。
这到底是怎么回事??? 理论与实际不符啊!!!!!
经过调试才发现,原来是代码里的set_irq_priority 函数压根就没起作用,有图为证:
这是调用set_irq_priority 函数之前的寄存器值:
这是执行完set_irq_priotiy 函数之后的寄存器值:
该函数为什么不起作用呢?问题出在最后两条语句上,原因在于Cortex-M0/M0+ 中的NVIC寄存器只支持字传输,不能单字节的去操作。
void set_irq_priority (int irq, int prio){ /*irq priority pointer*/ uint8 *prio_reg; uint8 err = 0; uint8 p = 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 */ p = irq / 4; prio_reg = (uint8 *)((uint32)&NVIC_IP(p)); /* 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 p = 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 */ p = irq / 4; // prio_reg = (uint8 *)((uint32)&NVIC_IP(p)); prio_reg = *(uint32 *)((uint32)&NVIC_IP(p)); 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(p)) =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 函数之后,我们观察调试结果,这时优先级确实做了改变
再次观察最后的实验现象,结果如下:
我们可以看到TPM2中断会不断打断PIT中断。
总结如下:
1) 大家在使用FRDM-KL26Z_SC_Rev_1.0 代码时,注意set_irq_priority 函数有bug的,它起不了实际作用。
2) ARM Cortex-M0/M0+中断抢占只需要设置好优先级即可,优先级高的会抢占优先级低的中断。
补充说明:
Cortex-M3/M4里面涉及到一个优先级分组概念,在Cortex-M0/M3 里面是没有的。
上述测试代码如下:http://www.ippipp.com/file/id/40480