0
votes

I am developing a bare-metal ARM project using LPC2138 soc. I have written an IRQ interrupt handler for I2C. But it does not return properly. The handler is being called repeatedly for a single interrupt.

I have done a detailed debug analysis of this issue.

In the ARM7TDMI reference manual it's clearly mentioned the following thing.

enter image description here But when I disassembled the code, I found that the code generated by GCC does not restore the CPSR resgister. Also an unknown value at last.

enter image description here

I have declared the IRQ handler like the following

void I2C0_IRQ_handler(void) __attribute__ ((interrupt("IRQ")));

Is s this a bug in GCC or have I done something wrong?

The compiler, assembler and linker flags I have used for building the project are:

CFLAGS := -mcpu=arm7tdmi-s -g3 -Wall -I. -gdwarf-2
AS_FLAGS := -mcpu=arm7tdmi-s -g3 -gdwarf-2
LD_FLAGS := -Wl,-Map,$(TARGET:%.hex=%).map -nostartfiles
1
In what way is it not working properly; what behaviour do you see compared to what you expect? Assuming VICVectAddr = 0; is clearing the interrupt, then this code is performing all 3 quoted steps appropriately (hint: read up on what subs pc,... actually does) - Notlikethat
@Notlikethat When I2C start condition is sent interrupt should be generated. Its working. But after the interrupt routine, the control does not return to the interrupted code. The step 2 is missing in the assembly code. - Sreeyesh Sreedharan
As I said, step 2 is being performed in that code, by the subs pc.... Having gone away and found the LPC213x manual, I see that that write does ack the interrupt at the VIC, but the interrupt is apparently level-triggered and you're doing nothing to deassert it at the I2C controller end (see section 13.9). Hence when you return you're just immediately taking the same interrupt again, ad infinitum. - Notlikethat
Post text as text, not images! - too honest for this site
IDK this particular device, but VICVectAddr is likely wrong (peripheral register names are typically all-uppercase) or insufficient. OP should read the reference manual. - too honest for this site

1 Answers

5
votes

Your analysis has two fatal flaws: Firstly, you're looking at it from the perspective of the CPU core, and secondly, it's wrong.

Second flaw first, I'm not sure why the ARM7TDMI manual has such an oddly overly-specific wording (you can't realistically do all those things separately anyway) but all 3 aspects are performed by the subs pc, lr, #4 line in your code. subs pc, lr is specifically an exception return instruction - it atomically restores the CPSR from the SPSR and returns to the fixed-up LR address. Since the CPU's interrupt disable flags are in the CPSR, then assuming that the I bit in that SPSR value is clear (which you kind of expect, given that you took an IRQ to get here...), IRQs will also be unmasked automatically in the process.

Thus, from the CPU core's point of view, your code is already (by virtue of its final instruction) doing everything an exception handler needs to, i.e. it resumes execution at the same point and in the same state as when the IRQ happened - there's certainly nothing wrong with the compiler. What it doesn't do, though, is anything about the fact that the peripheral way beyond the core is still asserting its interrupt, therefore the next thing you do after returning from the IRQ is to happily take the same IRQ again immediately. Repeat ad infinitum.

You have at least gone beyond the core as far as the interrupt controller; writing to VICVectAddr acknowledges the interrupt at the VIC, but that merely frees the VIC itself up to prioritise any pending interrupts and deliver the next one, which since the external source is still asserted (and assuming no higher-priority IRQ has come in from elsewhere), is still the same one.

To actually handle the interrupt and make any progress, you need to service the I2C peripheral raising it. Section 13.9 of the LPC213x manual outlines what needs to be done for each interrupt condition, but in terms of clearing the asserted interrupt, you need to write the SIC field of I2C0CONCLR.