How to Debug the nRF52 Interrupts — Useful Tips
While developing Jumper’s emulator, we often find ourselves digging into how things work on the actual hardware. These debug sessions often end up getting down to the assembly level. While we’re not aiming for full timing parity with the device, it’s important for us that the logical coherence of applications is preserved. That means that the order by which interrupts are executed should be identical to that of the device.
Step by step debugging suppresses interrupts
This one is rather annoying. Most out of the box debuggers suppress interrupts when doing step by step debugging. Even directly setting an IRQ to pending in the NVIC and making sure it has higher priority than the current handler/thread will not cause it to occur unless you hit the play button. It’s highly annoying as even though I’m aware of this problem, it’s hard to have it top of mind 100% of the time. It only takes one time of forgetting about it to waste hours of precious debugging time. So how do you get around that?
If you’re not looking for fancy configuration solutions, the only thing you can do is use breakpoints and play instead of doing step-by-step debugging. I found that to be rather annoying, but it works (and it’s actually the response on ARM’s forum).
There’s another way of getting around this. Checkout this post on the nRF devzone to learn how to use SEGGER JLINK + monitoring code. I haven’t tried it but it sounds promising. One downside though is that it means you’re adding code to your application that has some probability of changing the way your application works as it uses the monitoring IRQ.
Given how unnatural this feels, we decided to design the emulator so that it behaves how you would expect — if there’s a pending interrupt that preempts the current handler or thread, it will be executed immediately. The debugger will jump to the right line and you’ll be able to continue from there. We’ll make this behavior configurable in real time so that users will be able to debug with ease and make the choice themselves.
One other tricky thing to figuring out is the interrupt order. Getting a report of the order by which interrupt handlers got executed is great when you’re using a handful of handlers with timers and real time counters.
Unfortunately, our tip is only useful for the first few interrupt raises. What you can do is head over to the vector table (typically on address 0x0) and addresses of the handlers you’re expecting to be raised. Then, put a breakpoint on each of those. Next, hit reset and play your code. Once you hit the first breakpoint, write the address down, and hit play again. If the debugger halts on the same breakpoint, remove it and restart your application. You then go one by one to understand the interrupts order.
As I mentioned, this is not super handy. That’s why we decided to have an interrupts report, fully with the order and CPU cycle of which a handler has been set to pending and actually executed.
Call for input
As we’re working on more ways of making Jumper’s emulator useful, we’re adding more features and debug capabilities that are not available on the actual hardware. For example: memory read/write breakpoints that will work on ALL addresses, including all the MCU/CPU peripherals.
If you find this interesting — signup to get updates from Jumper and join our closed beta.