After some study, trial and error, I created a simple project for a NXP LPC1768, to test its timer interrupt. This project contains only startup_LPC17xx.s, system_LPC17xx.c, C-main.c, and timer.c.
C-main.c:
#include "lpc17xx.h" #include "timer.h" extern uint32_t SystemFrequency; int main(void) { SystemInit(); Timer_Init_n_Enable( LPC_TIM0, (SystemFrequency/8-1) ); NVIC_EnableIRQ(TIMER0_IRQn); /* LED Init */ LPC_GPIO2->FIODIR |= 0x000000FF; /* P2.0 to P2.7 LEDs defined as output */ while(1); }
timer.c:
#include "lpc17xx.h" #include "timer.h" void TIMER0_IRQHandler(void) { LPC_TIM0->IR = (1u<<0); // Reset the MR0 Interrupt; Writing a zero has no effect. LPC_GPIO2->FIOPIN ^= (1<<7); // Toggle LED. } void Timer_Init_n_Enable( LPC_TIM_TypeDef * TimerX, uint32_t TimerInterval ) { TimerX->MR0 = TimerInterval; TimerX->MCR |= (1u<<0) | (1u<<1); // Interrupt and Reset on MR0 TimerX->TCR |= (1u<<0); // Enable Timer Counter and Prescale Counter }
This project seemed to work.
But if I change the timer.c to:
void TIMER0_IRQHandler(void) { LPC_GPIO2->FIOPIN ^= (1<<7); // Toggle LED. LPC_TIM0->IR = (1u<<0); // Reset the MR0 Interrupt; Writing a zero has no effect. }
It doesn't work.
If I change the timer.c to:
void TIMER0_IRQHandler(void) { LPC_GPIO2->FIOPIN ^= (1<<7); // Toggle LED. LPC_TIM0->IR = (1u<<0); // Reset the MR0 Interrupt; Writing a zero has no effect. NVIC_ClearPendingIRQ(TIMER0_IRQn); }
It seemed to works.
I guess that, it doesn't work because I clear the LPC_TIM0->IR interrupt flag too late, so the NVIC generates a pending interrupt for LPC_TIM0->IR.
I don't have a LPC23xx now, so I can't verify my guess. I guess that, it will still work:
void Timer0Handler(void) __irq { FIO2PIN ^= (1<<7); // Toggle LED. T0IR = 1; /* Clear Interrupt Flag */ VICVectAddr = 0; /* Acknowledge Interrupt */ }
I think it is usual that, programmer needs to do something to identify which kind of interrupt was triggered, before clear the interrupt flag.
So, if my understanding is correct, how do I know that, I did NOT clear the interrupt flag too late within an ISR?
It is quite frustrated that, I can't even handle a simple timer interrupt properly. Maybe my understanding has never been correct.
> I did check the errata, it did not mention anything about Timer or "bus cycle for > accessing the interrupt flag".
As Andrew noted before, the reason is that this wouldn't be an erratum. It is a feature inherent in the architecture. I'll spare you the details, but simply put, Cortex-M3 has two independent bus interfaces for instructions and data respectively. Your memory access is using the data port (and waits for the APB bus cycle to finish), while the instruction port keeps fetching more code which gets executed. The ISR return might execute before the data bus access has completed, so NVIC thinks that the (still) active IRQ was a new one.
-- Marcus
void TIMER0_IRQHandler(void) { LPC_TIM0->IR = (1u<<0); LPC_GPIO2->FIOPIN ^= (1<<7); //=> LPC_TIM0->IR cleared already. } //===========================================> The ISR return.
void TIMER0_IRQHandler(void) { LPC_GPIO2->FIOPIN ^= (1<<7); //=> instruction port: done. LPC_TIM0->IR = (1u<<0); //=> instruction port: done; data port: not yet, LPC_TIM0->IR not cleared. } //===========================================> The ISR return. //===========================================> NVIC works on next Interrupt Handling. //===========================================> NVIC gets a LPC_TIM0->IR flag.
void TIMER0_IRQHandler(void) { LPC_GPIO2->FIOPIN ^= (1<<7); LPC_TIM0->IR = (1u<<0); NVIC_ClearPendingIRQ(TIMER0_IRQn);//=> LPC_TIM0->IR cleared already. } //===========================================> The ISR return.
Many thanks to Marcus.
I guess, If I am really trying to identify the interrupt source first, then clear the related interrupt flag right after, this symptom will not occur. Because before the ISR returns, all the interrupt flag updating has been done already.
I just joined the new company around two months. The trial period of new employee is generally three months. Somehow I can not get any available hardware for my development. (Just like in my ex-company.) So my supervisor let me use my private evaluation board, which I bought from eBay/ruten last year for personal study purpose. My supervisor asked me to do some basic practices like Key-Input and De-bounce, RTC based Date/Time Displaying. Actually most people here, don't know anything about ARM. I was told that, if I prefer ARM and I can handle ARM, I can use ARM for new projects; but it is quite doubtable. And in the Taiwanese culture, it is not wise to do something unusual, or ask for outside training of new technology, when you are the new guy.
RTC doesn't work, Strange Timer behavior, sounds like you are a quite lousy programmer.
MCU/Embedded System is very platform dependent; Yes, I know.
Good luck with the new job then.
This issue has been seen in other Cortex-M3 microcontrollers. Normally the Cortex-M3 wait until the previous write is completed before the interrupt return. However, some microcontrollers has a write buffer in the peripheral bridge. The write buffer accelerates single write transfers to peripherals so that the processor don't have to wait until the transfer is done (peripheral bus might be running at slower clock speed).
However, for clearing an interrupt request, the write buffer reply to the processor that transfer is done, while the actual write transfer actually haven't been completed in the peripheral. So the processor executed interrupt return, and found that the interrupt request from the peripheral is still high and think that it is a new interrupt request.
Adding memory barrier instruction (e.g. DSB) doesn't always help because the buffer is outside the processor. But it might introduce just enough delay to allow the interrupt to be cleared. Calling a function to clear the interrupt pending register NVIC also introduce some extra clock cycles of delay. So the interrupt request is deasserted when interrupt return is executed.
Another way to solve this problem is to carry out a dummy access to the peripheral, e.g. a read access to the same peripheral, to ensure that the previous write is completed before executing the interrupt return.
Hope this help.
Hi Joseph,
Though I often get helps from Experts via this Keil Discussion Forum. But it is surprising that I would get a reply from Joseph Yiu. I think you are not a regular visitor of this forum. Many Thanks for your help.
I would have considered that, adding a memory update is enough to avoid such a synchronization problem,
void TIMER0_IRQHandler(void) { if ( LPC_TIM0->IR & (1u<<0) ) { LPC_TIM0->IR = (1u<<0); // Reset the MR0 Interrupt; Writing a zero has no effect. Timer0_Counter++; } }
now I know that, a memory update might not be good enough. A dummy access to the peripheral register would be better.