ExtInt and i2c Memory Error

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
Post Reply
rhubarbdog
Posts: 64
Joined: Tue Nov 07, 2017 11:45 pm

ExtInt and i2c Memory Error

Post by rhubarbdog » Wed Oct 10, 2018 2:35 pm

why am i getting this exception?

Code: Select all

Uncaught exception in ExtInt interrupt handler line 1
MemoryError:
this is my callback, it's part of an object

Code: Select all

    def _read_keys(self, pin):
        self._i2c.send(b'\x00', self._address)
        #keys = bytearray(self._i2c.recv(2, self._address))                     
	self._buffer = self._i2c.recv(2, self._address)
        keycode = (self._buffer[1] & 0x1f) << 8 | self._buffer[0]
        #keycode = (keys[1] & 0x1f) << 8 | keys[0]                              

        # keys 0 to 9 and # & *                                                 
        for i in range(12):
            mask = 0x01 << i

            if mask & keycode:
                self._pads[i]._touch()
            else:
                self._pads[i]._release()
it appears to be happening at the i2c.recv

jickster
Posts: 511
Joined: Thu Sep 07, 2017 8:57 pm

Re: ExtInt and i2c Memory Error

Post by jickster » Wed Oct 10, 2018 2:36 pm

Is this in an interrupt?

jickster
Posts: 511
Joined: Thu Sep 07, 2017 8:57 pm

Re: ExtInt and i2c Memory Error

Post by jickster » Wed Oct 10, 2018 2:42 pm

rhubarbdog wrote:
Wed Oct 10, 2018 2:35 pm
why am i getting this exception?

Code: Select all

Uncaught exception in ExtInt interrupt handler line 1
MemoryError:
this is my callback, it's part of an object

Code: Select all

    def _read_keys(self, pin):
        self._i2c.send(b'\x00', self._address)
        #keys = bytearray(self._i2c.recv(2, self._address))                     
	self._buffer = self._i2c.recv(2, self._address)
        keycode = (self._buffer[1] & 0x1f) << 8 | self._buffer[0]
        #keycode = (keys[1] & 0x1f) << 8 | keys[0]                              

        # keys 0 to 9 and # & *                                                 
        for i in range(12):
            mask = 0x01 << i

            if mask & keycode:
                self._pads[i]._touch()
            else:
                self._pads[i]._release()
it appears to be happening at the i2c.recv
You cannot do anything that allocates memory in an interrupt.

https://docs.micropython.org/en/latest/ ... -practices

rhubarbdog
Posts: 64
Joined: Tue Nov 07, 2017 11:45 pm

Re: ExtInt and i2c Memory Error

Post by rhubarbdog » Wed Oct 10, 2018 2:45 pm

So is the i2c.recv() allocating memory internally

jickster
Posts: 511
Joined: Thu Sep 07, 2017 8:57 pm

Re: ExtInt and i2c Memory Error

Post by jickster » Wed Oct 10, 2018 2:46 pm

Code: Select all

line 1
Doesn't mean line in python code

Code: Select all

/**
  * @brief  These functions handle the EXTI interrupt requests.
  * @param  None
  * @retval None
  */
void EXTI0_IRQHandler(void) {
    IRQ_ENTER(EXTI0_IRQn);
    Handle_EXTI_Irq(0);
    IRQ_EXIT(EXTI0_IRQn);
}

void EXTI1_IRQHandler(void) {
    IRQ_ENTER(EXTI1_IRQn);
    Handle_EXTI_Irq(1);
    IRQ_EXIT(EXTI1_IRQn);
}

void EXTI2_IRQHandler(void) {
    IRQ_ENTER(EXTI2_IRQn);
    Handle_EXTI_Irq(2);
    IRQ_EXIT(EXTI2_IRQn);
}

Code: Select all

// Interrupt handler
void Handle_EXTI_Irq(uint32_t line) {
    if (__HAL_GPIO_EXTI_GET_FLAG(1 << line)) {
        __HAL_GPIO_EXTI_CLEAR_FLAG(1 << line);
        if (line < EXTI_NUM_VECTORS) {
            mp_obj_t *cb = &MP_STATE_PORT(pyb_extint_callback)[line];
            if (*cb != mp_const_none) {
                // If it's a soft IRQ handler then just schedule callback for later
                if (!pyb_extint_hard_irq[line]) {
                    mp_sched_schedule(*cb, pyb_extint_callback_arg[line]);
                    return;
                }

                mp_sched_lock();
                // When executing code within a handler we must lock the GC to prevent
                // any memory allocations.  We must also catch any exceptions.
                gc_lock();
                nlr_buf_t nlr;
                if (nlr_push(&nlr) == 0) {
                    mp_call_function_1(*cb, pyb_extint_callback_arg[line]);
                    nlr_pop();
                } else {
                    // Uncaught exception; disable the callback so it doesn't run again.
                    *cb = mp_const_none;
                    extint_disable(line);
                    printf("Uncaught exception in ExtInt interrupt handler line %u\n", (unsigned int)line);
                    mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
                }
                gc_unlock();
                mp_sched_unlock();
            }
        }
    }
}

jickster
Posts: 511
Joined: Thu Sep 07, 2017 8:57 pm

Re: ExtInt and i2c Memory Error

Post by jickster » Wed Oct 10, 2018 2:50 pm

rhubarbdog wrote:
Wed Oct 10, 2018 2:45 pm
So is the i2c.recv() allocating memory internally
Lots of things could be. You'd have to use C-debugger to step through code to discover which functions are allocating memory.
This is not recommended route.

In a Python interrupt, you're supposed to use micropython.schedule to schedule a function to be executed later in non-interrupt context.

User avatar
pythoncoder
Posts: 2980
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ExtInt and i2c Memory Error

Post by pythoncoder » Wed Oct 10, 2018 5:01 pm

The line

Code: Select all

keys = bytearray(self._i2c.recv(2, self._address))
would allocate memory. First recv(2,...) allocates a 2-byte bytes instance. Then you allocate a bytearray. Solutions are either
  • Pre-allocate the array (can be done as a default argument to the callback) and use the form of recv(my_array, ...).
  • Or, simpler, use micropython.schedule(). Then you don't need to worry about allocation.
Peter Hinch

SpotlightKid
Posts: 377
Joined: Wed Apr 08, 2015 5:19 am

Re: ExtInt and i2c Memory Error

Post by SpotlightKid » Wed Oct 10, 2018 5:10 pm

rhubarbdog wrote:
Wed Oct 10, 2018 2:45 pm
So is the i2c.recv() allocating memory internally
You are allocating a bytearray object, when you are assigning the return value of I2C.recv(). Instead of passing an integer for the number of bytes to read, you should pass a pre-allocated buffer (bytearray or memory view) of the length you wish to read.

http://docs.micropython.org/en/latest/l ... b.I2C.recv

User avatar
dhylands
Posts: 2856
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: ExtInt and i2c Memory Error

Post by dhylands » Wed Oct 10, 2018 5:47 pm

This page covers writing interrupt handlers:
https://docs.micropython.org/en/latest/ ... rules.html

In particular, if you allocate an emergency exception buffer:
https://docs.micropython.org/en/latest/ ... ion-buffer
then you'll get more detailed information about where the error actually originates from.

The emergency exception buffer is essentially a pre-allocated buffer that the exception handler can use to fill in the information about where the exception occurred and the actual type of exception.

Post Reply