1

I've spent the last several days debugging a small program that would not interrupt on SMBUS characters. I finally tracked it down to an unimplemented interrupt handler. It's not that I needed the handler, rather, the 8051F330 had been wrongly configured for a hardware interrupt, and there was no interrupt handler to catch the interrupt.

Now, this is kind of surprising to me. If there's no handler, I would have expected a NOP, but I guess a missing RETI causes IRQ's to lock up? The main loop seemed to work while in this condition. I know one needs to reset the interrupt flag, but the timer0 IRQ had nothing in common with my SMBUS IRQ.

Am I interpreting this correctly? Will an inadvertent IRQ enable cause the IRQ's to stop working?

2 Answers2

0

Reference the C8051F330 datasheet, Table 15.1.

Each interrupt source (Reset, Timer 0 overflow, SMB0, etc) has a hardcoded interrupt vector address. When the interrupt is asserted, the micro generates an LCALL instruction to that vector address. SMB0 is 0x003B, Timer 0 overflow is 0x000B for example. When you code an interrupt handler (interrupt keyword in Keil, see this question for other examples), your linker usually puts some kind of JMP instruction at the vector address to get to your handler.

If you neglect to write a handler for an interrupt that is enabled, the micro will still vector to that address. What happens then depends on what the linker put where in code space. I find that small functions tend to get stuffed in between the vector addresses, especially if you're not using many interrupts and there are several contiguous vector addresses not in use. You can find out by looking at Code space at 0x003B (the SMB0 vector address) in a disassembler.

If you accidentally vector into the middle of some such function located at the wrong place at right time, you'll pick up any side effects of the portion of that function that is run and then the RET at the end of the function will put you right back where you were before the interrupt. Except that since you didn't go through RETI, the interrupt is not cleared. Any future interrupts of the same or lower priority will be blocked, because the micro thinks you're still servicing the SMB0 interrupt.

You suggested that a NOP might replace unimplemented handlers, but this cannot be done since your toolchain doesn't know what interrupts are enabled at compile time. If you don't write a handler and locate it at a particular vector address, it presumes that that portion of code space is fair game and will put something else there.

Community
  • 1
  • 1
Phil Wetzel
  • 135
  • 4
0

CPU Vendor assumes you will never forget to write a handler and I think I never saw a piece of driver with all possible handlers written.

I just had the problem of using the wrong mask while re-enabling Timer4 IT so that Timer5 IT was enabled instead (C8051F580 in my case)

So I end-up writing a file like the one below.

This is no sufficient as said in the other answers, because I do not reset the specific interrupt in each handler ( e.g. clearing TF5 for Timer5 interrupt ), but it let you quickly find what's wrong.

The macro SPEC_INT(x) let me create a handler for each interrupt in one line. Commented lines corresponds to the interrupt I do really use in my application code. Each handler call a single function int_general_function( )

Take care, the 8051 does not like reentrant function (no stack in the usual sense). As every handler call the same function the linker ( Keil C51/BL51) will warn you with a L15 warning. You must add to the Linker option, category Overlay, the following statement

int_general_function !*

in order to prevent the (potential) local variables and (potential) arguments not to shared their places in memory.

#define ALLINTERRUPT_C

#include <my_types.h>
#include <my_platform.h> //contains #include "C8051F580.h"

//A global var
u8 global_unexp_int = 0;

void int_general_function(u8 int_nb)
{
    global_unexp_int = int_nb;

    //Do here other useful things 
    //at least place a breakpoint 
    //if you are in debug
}

#define SPEC_INT(x) \
void i##x (void) interrupt x                \
{                                           \
    int_general_function(x);                \
}

//C8051F580
SPEC_INT(INTERRUPT_INT0)
SPEC_INT(INTERRUPT_TIMER0)
SPEC_INT(INTERRUPT_INT1)
SPEC_INT(INTERRUPT_TIMER1)
//SPEC_INT(INTERRUPT_UART0)
SPEC_INT(INTERRUPT_TIMER2)
SPEC_INT(INTERRUPT_SPI0)
SPEC_INT(INTERRUPT_SMBUS0)
SPEC_INT(INTERRUPT_ADC0_WINDOW)
SPEC_INT(INTERRUPT_ADC0_EOC)
//SPEC_INT(INTERRUPT_PCA0)
SPEC_INT(INTERRUPT_COMPARATOR0)
SPEC_INT(INTERRUPT_COMPARATOR1)
SPEC_INT(INTERRUPT_TIMER3)
SPEC_INT(INTERRUPT_LIN0)
SPEC_INT(INTERRUPT_VREG)
//SPEC_INT(INTERRUPT_CAN0)
SPEC_INT(INTERRUPT_PORT_MATCH)
SPEC_INT(INTERRUPT_UART1)
SPEC_INT(INTERRUPT_PCA1)
SPEC_INT(INTERRUPT_COMPARATOR2)
//SPEC_INT(INTERRUPT_TIMER4)
SPEC_INT(INTERRUPT_TIMER5)

Hope it helps ...

NGI
  • 852
  • 1
  • 12
  • 31