Skip to content

Provide external-irq functionality directly in Pin class #1118

Description

@dpgeorge

Now that we have a few ports to different MCUs, we can start to see some ways of making the functionality in the pyb module more MCU agnostic / more general. Eg see issue #1085.

I'd like to propose that the bulk of the ExtInt functionality be incorporated into the Pin class. In a general MCU a Pin object can generate an interrupt when the level changes. The pyboard supports this feature in a bit of a round-about way using ExtInt to configure the level changing external interrupt on a pin. This way of doing things does not map easily to the cc3200 or esp8266 ports, where it would make more sense to register the irq directly with a Pin object.

From a users point of view, I think being able to configure a pin directly for irq on level change is much easier that having to create a separate ExtInt object. Eg, new scheme would provide something like:

pin = Pin('X1', Pin.IN, Pin.PULL_DOWN)
pin.callback(pin.IRQ_RISING, lambda p: print("level change on pin:", p))

and/or you can set it in the init function directly:

pin = Pin('X1', Pin.IN, Pin.PULL_DOWN, callback=(Pin.IRQ_RISING, callback_fun))
pin.init(Pin.IN, Pin.PULL_UP, callback=(Pin.IRQ_FALLING, callback_fun))

Could also provide aux control such as pin.trigger_callback for software irq trigger, pin.disable_callback and pin.enable_callback.

Main benefits are:

  • conceptually simpler for end user
  • easier to map to other MCUs (cases in point: cc3200 and esp8266)

The stmhal port can still provide ExtInt for finer control, but this will be mostly unnecessary I think. It has support for non-pin irqs (eg RTC alarm), but actually this is not working properly and when I've been trying to implement proper RTC wakeup, I've anyway needed to rewrite specific ext-int support. Also, the fact that ExtInt actually calls HAL_GPIO to configure the irq means that it's much more suited to be part of the Pin class.

Any thoughts? Any suggestions for the API to setup and control the pin irq?

Activity

  1. added
    rfcRequest for Comment
    portsRelates to multiple ports, or a new/proposed port
    on Feb 14, 2015
  2. danicampora commented on Feb 14, 2015

    @danicampora
    Member

    I like this proposal, I can already see how it will reduce the code size of the CC3200 and I am always in the mood for that ;-)
    One thing that I suggest is to pass to the callback function the pin object for which the callback is being triggered (that's how it is now implemented for the CC3200) instead of a "line number" like done on stmhal.
    For the rest, the proposed API looks OK to me.

  3. dhylands commented on Feb 15, 2015

    @dhylands
    Contributor

    I'm all in favor of this. The whole ExtInt thing seems a little unusual (as you mentioned most processors have some type of pin change interrupt).

    I think for stmhal, the only thing I would do is to detect conflicts (so if you try to setup say pin B3 ad C3) and report them.

  4. danicampora commented on Feb 15, 2015

    @danicampora
    Member

    @dpgeorge should we give this a go? Using your proposed API and passing a Pin object to the callback?

  5. pfalcon commented on Feb 15, 2015

    @pfalcon
    Contributor

    My concern is that this tightly couples GPIO and IRQ handling. But IRQ handling is cross-device-block matter, how IRQs will be (are) handled for other components?

  6. dhylands commented on Feb 15, 2015

    @dhylands
    Contributor

    My inclination would be to make callback= just assign the callback and have the rising/falling attriute be separate (probably part of mode, or a separate parameter). I've seen cases where you want to change the riing/falling portion on the fly, and using a tuple for callback feels wrong to me.

  7. dpgeorge commented on Feb 15, 2015

    @dpgeorge
    MemberAuthor

    @dhylands glad you are on board with this!

    @danicampora yes, definitely we should pass the pin object to the callback. There is no concept of "line" anymore.

  8. dpgeorge commented on Feb 15, 2015

    @dpgeorge
    MemberAuthor

    how IRQs will be (are) handled for other components?

    In all cases they use the callback API proposed here: switch.callback, timer.callback, can.rxcallback. IRQ=callback and IRQs are, at least for stmhal, almost 1-1 correspondence with peripherals.

  9. dhylands commented on Feb 15, 2015

    @dhylands
    Contributor

    My concern is that this tightly couples GPIO and IRQ handling. But IRQ handling is cross-device-block matter, how IRQs will be (are) handled for other components?

    It only couples irq handing for pin change to gpio. In the end, I think we want IRQ handling (in the form of callbacks) for pretty much every peripheral - uart, spi, can, i2c, timer, gpio. The thing that triggers the interrupt is often peripheral specific.

  10. dpgeorge commented on Feb 15, 2015

    @dpgeorge
    MemberAuthor

    It only couples irq handing for pin change to gpio.

    In fact, the way the ST HAL library is implemented, EXTI control is actually part of the GPIO module and there is no real way to decouple them in this case.

  11. danicampora commented on Feb 15, 2015

    @danicampora
    Member

    My inclination would be to make callback= just assign the callback and have the rising/falling attriute be separate (probably part of mode, or a separate parameter)

    I think @dhylands has a good point there. Probably better to make the rising/falling (or other IRQ triggering mdoes, this is hardware specific) it part of the init, and also make it possible to change it on the fly.

  12. dpgeorge commented on Feb 15, 2015

    @dpgeorge
    MemberAuthor

    have the rising/falling attriute be separate (probably part of mode, or a separate parameter)

    Could do this. For a GPIO pin to be in EXTI mode it must be an input pin. So we can just add a Pin.INT_RISING mode.

    @danicampora how does the CC3200 do it? Is EXTI exclusively input?

  13. danicampora commented on Feb 15, 2015

    @danicampora
    Member

    CC3200 does not have a separate EXTI module like the stm32. GPIO pins simply have the option to generate interrupts on level changes. The Pin needs to be an input of course.

    Pin.INT_RISING

    Maybe that is a good option. These are the modes the CC3200 has:

    • Falling edge.
    • Rising edge.
    • Falling or rising.
    • Low level.
    • High Level.

    In cases where you use the "level" modes instead of the "edges" then is important to be able to change the mode on the fly as @dhylands suggested.

  14. dpgeorge commented on Feb 15, 2015

    @dpgeorge
    MemberAuthor

    We can make a branch with the new pin irq behaviour, code it all up to see how it works for the different ports, fine tune, then merge. Sound ok?

  15. 14 remaining items

  16. dpgeorge commented on Feb 21, 2015

    @dpgeorge
    MemberAuthor
  17. danicampora commented on Feb 21, 2015

    @danicampora
    Member

    Ok, good idea. Lets see how that turns out.

  18. pfalcon commented on Mar 7, 2015

    @pfalcon
    Contributor

    Sorry, I missed to comment previously:

    To open up the discussion more, here is an alternative suggestion: a callback (irq line) is an object in its own right
    To provide backwards compatibility with existing API could make the callback object accept the call method to get:

    So, what we have here is that trying to make it "simple" ("each object allows to set up callback"), it actually gets complex - "callback is an object", having its own state, usable in either "backwards compatible" or real way, etc. Besides being confusing, all this stuff is not going to fly with 16K heap. So, if there's interest in supporting such targets, that should be taken into account. The most simple and efficient abstraction is usually the one which captures target domain faithfully. If there's an interrupt controller in every microprocessor system, then exposing its functionality is the obvious way to let it be used, with further simplifications and user-friendly-helpers can be built on top of that. Btw, IRQController idea above is pretty much ExtInt generalized, cleaned up (ExtInt somehow deals with pull up/pull down) and with any state held in IRQController itself (no "ExtInt" objects).

  19. danicampora commented on Mar 7, 2015

    @danicampora
    Member

    Interesting... I am almost finished implementing the module to handle the CC3200's low power modes, and at the same time I took the chance to implement interrupt handling using the callback object approach that @dpgeorge proposed. I find it quite intuitive. It's also implemented in a way that there is only one "C interrupt helper" that takes care of calling all Python callbacks.
    It works like this:

    sw = pyb.Pin('GPIO22', af=0, mode=pyb.Pin.IN, type=pyb.Pin.STD_PD)
    sw.callback(handler=swcallback, intmode=pyb.Pin.INT_RISING, priority=pyb.callback.LEVEL_2)

    and of course:

    cb=sw.callback()
    cb.init(handler=swcallback, intmode=pyb.Pin.INT_RISING)

    to trigger the callback manually:

    sw.callback()()

    enable and disable:

    sw.callback().enable()
    sw.callback().disable()

    Of course the final naming has to be decided, but I think is a good approach. The way I implemented it every callback object takes 24 bytes of RAM. Is that too much?

  20. dpgeorge commented on Mar 9, 2015

    @dpgeorge
    MemberAuthor

    "callback is an object", having its own state, usable in either "backwards compatible" or real way, etc. Besides being confusing, all this stuff is not going to fly with 16K heap

    I did not write it above, but my plan was to make these callback objects all static/const/ROMable. Thus pin.callback does nothing more than get the address of the object. The only state you need in RAM (and you need it no matter how you implement it) is a pointer to the Python callback object (which ExtInt already has as a big array). All other configuration state (IRQ priority level, enabled/disabled, etc) is in the registers of the MCU.

    ExtInt somehow deals with pull up/pull down

    But not in a clean way. ExtInt only works correctly for Pin objects. Even though it was intended for controlling other ExtInt sources (eg RTC alarm) it doesn't work, and this is because they don't all fit the same pattern.

  21. danicampora commented on Mar 10, 2015

    @danicampora
    Member

    The way I implemented it every callback object takes 24 bytes of RAM.

    Sorry, I meant 8 bytes, not 24 ;-)

  22. dpgeorge commented on Jun 15, 2017

    @dpgeorge
    MemberAuthor

    stmhal, esp8266 and cc3200 now have a Pin.irq() method, and it's documented in the docs (stmhal was added in 4abe373). Some tidying up is needed but that can happen later.

  23. nevercast commented on Jan 25, 2018

    @nevercast
    Contributor

    I couldn't immediately see any documentation about the IRQ internals in MicroPython. So I'm asking here, hope this isn't a bother.

    I've written a custom MicroPython BuiltIn module for some hardware we are working with and I'd like to be able to invoke a MP function out of turn (essentially an IRQ, though it's not from a hardware IRQ). Should I use mp_irq_queue_interrupt to schedule the function to fire. Or can I call it directly with mp_call_function_* without expecting issues?

    Cheers.

  24. peterhinch commented on Jan 25, 2018

    @peterhinch
    Contributor

    You might want to consider micropython.schedule docs.

  25. nevercast commented on Jan 25, 2018

    @nevercast
    Contributor

    Thanks for the reply @peterhinch

    I did see the issues leading up to the implementation to micropython.schedule, and while it looks promising, I'm not trying to invoke the function from Python space, but C space. Is this still the correct approach?

    I've a module and C code that runs in the Servers task. To update, I've tried both approaches I mentioned above. Both of which cause a LoadProhibited error, though at different times. mp_call_function_1 fails to invoke the python function at all. mp_irq_queue_interrupt actually succeeds the first time, calling the python callback and having control return to my C code. On the second invocation the python callback is fired but then during the return it all falls down. Another LoadProhibited.

    Though I can't be sure yet, it looks like the stack perhaps gets corrupted, as the BackTrace didn't seem to look as I expected it.

    If there is a better place to discuss this, I'll oblige.

  26. peterhinch commented on Jan 26, 2018

    @peterhinch
    Contributor

    I have no experience of doing this from C. It might be worth raising a query in the forum.

  27. nevercast commented on Jan 26, 2018

    @nevercast
    Contributor

    Thanks @peterhinch for your reply. Actually worked it out about half an hour ago. I'm tackling the Garbage Collector now. Unfortunately the memory is too fragmented for my use-case (We are running very close to the memory limits of our device, WiPy2). So I'll be attempting a Best-Fit/Worst-Fit GC implementation, hopefully that reduces fragmentation over the First-Fit implementation MicroPython uses now.

  28. tannewt commented on Jan 26, 2018

    @tannewt
    Sponsor

    @nevercast I just redid allocations in CircuitPython to allocate qstrs, imports and types at the top of the heap. It appears to really help heap fragmentation (one test I did went had the longest free space go from 432 bytes to over 3400 bytes.) There is a before/after video of it here and the commit is here. It should be simple to patch into MicroPython and I'm happy to help with it. Want to open a new issue for it and we can discuss more there?

  29. nevercast commented on Jan 28, 2018

    @nevercast
    Contributor

    @tannewt That's an excellent visualization of the commit. Yes I'll read over the commit and see about opening an issue for it. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    portsRelates to multiple ports, or a new/proposed portrfcRequest for Comment

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions