3

I discovered this issue when I tried to put the microcontroller to sleep and then wake it up, as an interrupt driven application. I noticed that my code did not resume from the line of code that was after my 'sleep' instruction.

When I manually trigger an interrupt while stepping through my code with a debugger it takes multiple steps (sometimes 2, sometimes 50 depending on the code) before it jumps to the ISR.

While trying to debug this issue I wrote this very simple piece of code which exhibits the issue:

#include <Arduino.h>
// Setup and Loop declared in Arduino core
void configInterrupt(void);
volatile uint32_t debug = 0;
uint32_t int_count = 0;

void EIC_Handler(void){
  int_count++;
  EIC->INTFLAG.reg = 1 << 0;
}

void configInterrupt(void){
  NVIC_DisableIRQ(EIC_IRQn);
  NVIC_ClearPendingIRQ(EIC_IRQn);
  NVIC_SetPriority(EIC_IRQn, 0);
  NVIC_EnableIRQ(EIC_IRQn);
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));
  EIC->WAKEUP.reg |= (1 << 0);
  EIC->CONFIG[0].reg |= 0x2;                    // falling edge
  pinConfig(16,INPUT,UP);                   // pin 16 as input with pullup 
  PORT->Group[0].PINCFG[16].bit.PMUXEN = 1; // enable peripheral muxing
  PORT->Group[0].PMUX[8].bit.PMUXE = 0x0;       // function A (EIC) = 0x0
  EIC->INTENSET.reg = EIC_INTENSET_EXTINT(1 << 0);
  EIC->CTRL.bit.ENABLE = 1;
}

void setup() {
  configInterrupt();                            
}

void loop() {
  for(int i = 0 ; i < 100 ; i++) debug++;   // volatile so that the compiler doesn't touch

  String debugstring = "";

  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
  __DSB();
  __WFI();
}

I'm debugging using an external interrupt that I trigger myself with a jumper wire so that I know when it should have been triggered. What I'm noticing is that while I debug the code and step through it, if I trigger the external interrupt manually then it doesn't jump to the ISR straight away. The interrupt becomes 'pending' in the NVIC but exception entry isn't carried out until later in the code.

I've read a lot about interrupts and exceptions in the SAMD21 datasheet, the Cortex M0+ Generic User Guide and the ARM Architecture manual. Supposedly the the Cortex M series has low latency interrupts with no instruction overhead and so it seems like the code should jump to the ISR relatively quickly after triggering an interrupt.

I've read 2.3.6 of the Cortex M0+ Generic Guide multiple times as well as B1.3.2 of the ARM Architecture manual, which both cover exception entry in quite a bit of detail. The SAMD21 datasheet doesn't seem to have much low level information.

I've tried to isolate the problem and identify any patterns in the device's behavior and I've noticed a few things.

It only jumps to the ISR at specific lines of code. For example in the code above, if the external interrupt is triggered at start of 'loop()' it will jump to the ISR when it reaches the String declaration, regardless of how many iterations in the 'for' loop. If I move the String declaration up above the 'for' loop then it will jump to the ISR almost immediately (after 2 or 3 debugging steps).

I've tried inserting delays, NOPs and ISBs which don't effect how long it takes or make it jump instantly. When I set a pending interrupt in software through the ISPR register in the NVIC the same problem occurs. I've kept track of the base FLASH memory in Atmel Studio and noticed that "stack" onto which the processor's current 'state' is pushed, doesn't change immediately either. It only changes when I get to the first line of code in the ISR.

Other pieces of code that I've noticed act similar to a String declaration and cause the code to jump to the ISR is the endTransmission function of the Wire library, some functions in the SD card library, the Arduino delay function.

Could it be related to the fact that I'm using a debugger in the first place which interferes / doesn't play nice with interrupts? I'm fairly sure the problem occurred before I got the debugger out though. Edit: reading through the Cortex M0+ Technical Reference Manual and ARMv6 Manual I've found a register called DHCSR which allows the debugger to mask interrupts, but I can't work out how to access these registers.

Main Question: Aside from PRIMASK and global/individual enable register bits, what else could be preventing a pending interrupt from being executed?

Edit: left out an important piece of information, although I'm working in Atmel Studio the project uses the Arduino core.

EDIT: I have noticed that after I manually trigger an interrupt, it becomes pending in the NVIC->ISPR register during my next debugging step. This leads me to believe the interrupts are being masked somewhere (I've checked PRIMASK, global enable and individual enable so far with no luck).

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • 1
    `String debugstring = "" + debugstring;` - er, is this really [tag:c]? It might be interesting to see the corresponding disassembly of `loop` to make sure it's not simply masking interrupts e.g. for atomicity (and, assuming you're _not_ generating some arbitrary pointer at an offset from wherever a string constant ends up, since it's virtually impossible to reason about C++ code in isolation when anything could be overloaded to anything...) – Notlikethat Jun 22 '16 at 09:24
  • That line should be a bug in C and C++ both. Maybe that code line writes data in la-la-land, which in turn causes a reset or exception to trigger, which in turn would explain the delay. – Lundin Jun 22 '16 at 09:56
  • When posting code, please include declarations of all necessary variables. Otherwise, posting the code isn't meaningful. – Lundin Jun 22 '16 at 09:59
  • what are you using to externally interrupt? is it debounced? – old_timer Jun 22 '16 at 13:30
  • @Notlikethat Sorry that line of code with 'debugstring' is a mistake (although it compiles?). My 'loop' is from the Arduino core, but good point I'll have a look at PRIMASK and other methods of masking interrupts to see if any of them are occurring when they shouldn't be. Sorry about the bare bones code, I thought I'd clutter the code if I included everything. – Lance Molyneaux Jun 22 '16 at 20:41
  • @dwelch During debugging I've been manually pulling the line low with a jumper wire, I haven't debounced the interrupt as I figured that I was stepping through code and wouldn't have that issue, also the digital line that I was originally using for interrupting caused the same problem, but I'll add a debounce check just to be sure. – Lance Molyneaux Jun 22 '16 at 20:52
  • 1
    If possible, add a bit of code to the ISR to toggle a GPIO and put a scope on the external interrupt line ans the GPIO. Then see what kind of latency (and latency variation) the scope shows between the external int being triggered and the GPIO being toggled by the ISR. That might give you an indication if it is the debugger causing the weird delay in recognizing the interrupt. – Michael Burr Jun 23 '16 at 00:18
  • 1
    Step one is to investigate how the debugger handles interrupts. Many debuggers have the feature to disable interrupts while single-stepping. I suppose the debugger might suppress the interrupt until you leave a certain function or translation unit. – Lundin Jun 23 '16 at 06:22
  • what does the disassembly of your vector table show? are all the addresses odd numbers? – old_timer Jun 24 '16 at 00:13

1 Answers1

0

The String debugstring = ""; is the clue that this stuff is actually C++. Thus you must declare the IRQ handler as pure C function:

extern "C" {
void EIC_Handler(void);
}
void EIC_Handler(void){
  int_count++;
  EIC->INTFLAG.reg = 1 << 0;
}

Otherwise the function is C++ an does not work as IRQ handler because it cannot be recognized by the linker as such.

Turbo J
  • 7,563
  • 1
  • 23
  • 43
  • Declaring the IRQ handler as a pure C function didn't solve my problem, but I think you've identified a problem that I really need to address. I used Arduino for prototyping and thought that it would save me time by using the libraries but I'm going to avoid them and write more of the code from scratch as it appears that the position in my code from/to which the IRQ branches is always a piece of Arduino code. – Lance Molyneaux Jun 26 '16 at 20:49