Logging exceptions from interrupts

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
hlovatt
Posts: 12
Joined: Thu Aug 15, 2019 3:52 am

Logging exceptions from interrupts

Post by hlovatt » Tue Aug 20, 2019 7:47 am

Dear Gurus,

I want to log exceptions in a MicroPython program and then restart and report the exception after the restart.

I am having trouble with exceptions from interrupts, see example below:

Code: Select all

import micropython
import pyb

# Raise an arithmetic exception.
try:
    x = 1 / 0
except KeyboardInterrupt:
    raise
except BaseException as e:
    print('Caught arithmetic exception from main: {}.'.format(e))

# Raise an exception from user code.
exception = Exception("User's error")  # Statically allocate so that it can be used from an interrupt.
def failure(iterrupt_source):
    raise exception
try:
    failure(None)
except KeyboardInterrupt:
    raise
except BaseException as e:
    print('Caught user exception from main: {}.'.format(e))

# Raise an exception after 1/2 a second from an interrupt.
micropython.alloc_emergency_exception_buf(100)
timer = pyb.Timer(4, freq=0.5)
timer.callback(failure)
try:
    pyb.delay(1000)  # Wait for timer to interrupt and raise exception.
except KeyboardInterrupt:
    raise
except BaseException as e:
    print('Caught user exception from interrupt: {}.'.format(e))

# Check that you can still use a keyboard interrupt to terminate.
while True:
    try:
        print('Waiting for Ctrl-C!')
        pyb.delay(1000)
    except KeyboardInterrupt:
        raise
    except BaseException as e:
        print("Can't imagine how this line is ever reached: {}!".format(e))
When run I get:

Code: Select all

Caught arithmetic exception from main: divide by zero.
Caught user exception from main: User's error.
Waiting for Ctrl-C!
Waiting for Ctrl-C!
uncaught exception in Timer(4) interrupt handler
Traceback (most recent call last):
  File "main.py", line 28, in <module>
  File "main.py", line 26, in failure
Exception: User's error
Waiting for Ctrl-C!
Traceback (most recent call last):
  File "main.py", line 51, in <module>
  File "main.py", line 49, in <module>
KeyboardInterrupt: 
MicroPython v1.11-167-g331c224e0 on 2019-07-21; PYBv1.1 with STM32F405RG
As you can see the interrupt's exception isn't caught.

Any suggestions as to how this might be caught?

Thanks in advance,

-- Howard.

User avatar
jimmo
Posts: 810
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia

Re: Logging exceptions from interrupts

Post by jimmo » Tue Aug 20, 2019 11:26 am

Hi,

It sounds like what you're looking for is for the exception thrown in the interrupt handler to be caught by the main code? Unfortunately that's not how exceptions from interrupts work. In your output you can see the message "uncaught exception in Timer(4) interrupt handler" where the default handler for the interrupt context handled the exception for you.

If you want to signal something from an interrupt handler back to your main program, set some global state that your main program checks periodically. You could do something like set a global variable to an Exception object, and then in your main loop see if that variable has been set and then raise it.

Also worth taking a look at micropython.schedule if you want to make your interrupt handler able to do allocations (e.g. to create the error to send back to the main program).

hlovatt
Posts: 12
Joined: Thu Aug 15, 2019 3:52 am

Re: Logging exceptions from interrupts

Post by hlovatt » Thu Aug 22, 2019 2:13 am

Yes. The following works but is more trouble than some form of global exception handling:

Code: Select all

"""Test catching exceptions in MicroPython."""

import micropython
import pyb


# Raise an arithmetic exception.
def arithmetic_exception(_):
    print(1 / 0)


# Wrapper to catch exceptions from interrupts via schedule.
class CatchScheduledException:
    _caught_exception = None  # Static (global) store for interrupt exception.

    def __init__(self, routine):
        self._routine = routine
        self._wrapped_ref = self._wrapped

    def schedule(self, interrupt_source):
        micropython.schedule(self._wrapped_ref, interrupt_source)

    def _wrapped(self, interrupt_source):
        if CatchScheduledException._caught_exception is not None:  # Already have an exception, therefore stop.
            return
        try:
            self._routine(interrupt_source)
        except BaseException as caught_exception:
            CatchScheduledException._caught_exception = caught_exception

    @staticmethod
    def raise_if_any():
        if CatchScheduledException._caught_exception is not None:
            raise CatchScheduledException._caught_exception


# Raise an arithmetic exception after 1/2 a second from an interrupt via schedule.
arithmetic_exception_task = CatchScheduledException(arithmetic_exception)
scheduling_timer = pyb.Timer(9, freq=0.5)
scheduling_timer.callback(arithmetic_exception_task.schedule)


# Background task also needs to be scheduled.
def background(_):
    print('Waiting for Ctrl-C or exception!')


background_task = CatchScheduledException(background)


try:
    while True:
        background_task.schedule(None)
        CatchScheduledException.raise_if_any()
        pyb.delay(1000)
except KeyboardInterrupt:
    raise
except BaseException as e:
    print("Caught interrupt exception: {}!".format(e))

For the above to work you need to schedule everything including the background task as though it came from an interrupt (as shown) and you need to repeatedly check for exceptions (as shown above), hence not as convenient as a general exception handler.

Post Reply