How do you properly handle external interrupts?

All ESP8266 boards running MicroPython.
Official boards are the Adafruit Huzzah and Feather boards.
Target audience: MicroPython users with an ESP8266 board.
Rf3w8
Posts: 14
Joined: Tue Feb 21, 2017 4:51 pm

How do you properly handle external interrupts?

Post by Rf3w8 » Thu Feb 23, 2017 6:12 pm

I'm trying to understand how external interrupt request should be handled. I own an ESP-01 whose GPIO2 is connected to ground through a button switch. What I'm trying to achieve here is run a callback function when I press the button.
Here's what I've written so far:

[code]import micropython
from machine import Pin
from time import sleep


micropython.alloc_emergency_exception_buf(100)


def call_this(change):
print(change)

p2 = Pin(2, Pin.IN)

p2.irq(trigger=Pin.IRQ_FALLING, handler=call_this)

while 1:
pass
[/code]

However, the callback above is called only the first time I press the button, if I try more than once nothing happens.
What is wrong?
Last edited by Rf3w8 on Fri Feb 24, 2017 10:19 am, edited 1 time in total.

tknp
Posts: 5
Joined: Sun Feb 19, 2017 6:26 am

Re: How do you properly handle external interrupts?

Post by tknp » Thu Feb 23, 2017 10:54 pm

I was just dabbling with this the other day so my incomplete but working example may help. I would also recommend reading dhyland's post at viewtopic.php?p=17995#p17995

Code: Select all

import machine
import micropython
import network
from time import sleep, sleep_ms, ticks_ms, ticks_diff

button = machine.Pin(14, machine.Pin.IN)
led = machine.Pin(4, machine.Pin.OUT)

ap = network.WLAN(network.AP_IF)
ap.active(False)

time_stamp = ticks_ms()
micropython.alloc_emergency_exception_buf(100)

# Bad example since enable_irq is being set in this every time
# We would want to have enable_irq triggered by a.. IRQ via Timer?
def callback_rising(p):
    state = machine.disable_irq()

    global time_stamp
    current_time = ticks_ms()

    if ticks_diff(current_time, time_stamp) >= 500:
        print('rising edge detected')
        led.value(not led.value())
    time_stamp = current_time

    machine.enable_irq(state)


button.irq(trigger=machine.Pin.IRQ_RISING, handler=callback_rising)

while True:
    sleep_ms(1000)

Rf3w8
Posts: 14
Joined: Tue Feb 21, 2017 4:51 pm

Re: How do you properly handle external interrupts?

Post by Rf3w8 » Thu Feb 23, 2017 11:30 pm

Thank you, I'll test your code and see how it does!
I'm aware of bouncing issues but right now I'm focusing on getting the basics work. I imagine the switch off -> on of the IRQ is done to avoid bouncing..
Do you know if the IRQ is turned off each time we enter the callback? I was thinking my code is not working because maybe the IRQ is turned off each time you enter the callback..

Rf3w8
Posts: 14
Joined: Tue Feb 21, 2017 4:51 pm

Re: How do you properly handle external interrupts?

Post by Rf3w8 » Fri Feb 24, 2017 11:17 am

Your code has the same issue as mine, BUT... I was finally able to make it work properly. The needed fix to my code was to set up pin 2 as pull_up explicitly rather than leave it on None (default value).
Here's the updated code:

Code: Select all

import micropython
import machine
from time import sleep


micropython.alloc_emergency_exception_buf(100)


def call_this(change):
    state = machine.disable_irq()
    print(change)
    machine.enable_irq(state)


p2 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)

p2.irq(trigger=machine.Pin.IRQ_FALLING, handler=call_this)

while 1:
    print("P2 value {}".format(p2.value()))
    sleep(1)
In particular, this was the line that was fixed:

Code: Select all

p2 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
Note that this IRQ has bouncing issues, so probably an approach as the one used by tknp should be adopted.

Hope it can help other users who are starting with esp-01!

mr_exit
Posts: 8
Joined: Sun Feb 19, 2017 10:07 pm

Re: How do you properly handle external interrupts?

Post by mr_exit » Mon Feb 27, 2017 6:45 am

Here's My first script! It sends a MQTT signal when a button is pressed. It's set up to have 3 buttons, the button callback only sets a global variable. Then the main loop checks if that variable is set and sends the command. This means that slow network code stays out of the callback, makes sure the MQTT publishing sends one command at a time, and has a 1 sec pause between sending messages to account for bounce and avoid flooding the broker.

Code: Select all

import time
import ubinascii
import machine
from simple import MQTTClient
from machine import Pin

#some dummy variables that are set by the callback, and read by the main loop
pinSetUp = 0 # pin D3, pin 0
pinSetDown = 0 # pin D2, pin 4
pinSetToggle = 0 # pin D4, pin 2

# this is called when a button is pressed
# it sets a global variable to 1 to indicate a button has been activated
def callbackUp(p):
    #print('pin changed', p)
    global pinSetUp
    pinSetUp= 1
    print("button up pressed")

def callbackDn(p):
    #print('pin changed', p)
    global pinSetDown
    pinSetDown = 1
    print("button down pressed")

def callbackTog(p):
    #print('pin changed', p)
    global pinSetToggle
    pinSetToggle = 1
    print("button toggle pressed")

#setup the pins connected to the buttons
pUp = Pin(0, Pin.IN)
pDn = Pin(4, Pin.IN)
pTog = Pin(2, Pin.IN)
pUp.irq(trigger=Pin.IRQ_RISING, handler=callbackUp)
pDn.irq(trigger=Pin.IRQ_RISING, handler=callbackDn)
pTog.irq(trigger=Pin.IRQ_RISING, handler=callbackTog)

# Default MQTT server to connect to
SERVER = b"192.168.1.1"
CLIENT_ID = b"bedroomButton"
TOPIC = b"home/bedroom/button"

# Main loop checks the dummy variables to see if the button has been pressed
def mainLoop(server=SERVER):
    c = MQTTClient(CLIENT_ID, server, port=1883, user=b"USER", password=b"PW")
    c.connect()
    print("Connected to %s, waiting for button presses" % server)
    c.publish(TOPIC, b"Bedroom button connected")
    global pinSetUp, pinSetDown, pinSetToggle

    while True:
        while True:
            if pinSetUp == 1:
                print("sending up pressed")
                c.publish(TOPIC, b"up")
                time.sleep_ms(1000) # enforces a 1 sec pause after sending command, eliminates bounce 
                pinSetUp = 0
            if pinSetDown == 1:
                print("sending down pressed")
                c.publish(TOPIC, b"down")
                time.sleep_ms(1000)
                pinSetDown = 0
            if pinSetToggle == 1:
                print("sending toggle pressed")
                c.publish(TOPIC, b"toggle")
                time.sleep_ms(1000)
                pinSetToggle = 0
            time.sleep_ms(80)
    c.disconnect()

time.sleep_ms(5000) #required pause after boot before trying to connect to MQTT broker
mainLoop()

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

Re: How do you properly handle external interrupts?

Post by pythoncoder » Mon Feb 27, 2017 8:09 am

I appreciate the use of interrupts might be as a learning exercise. But in this context it's worth bearing in mind that the purpose of an interrupt is to cater for situations where hardware needs to be serviced in a short period of time; where "short" might be measured in microseconds.

Where a response time of the order of one second is involved it's valid to poll the state of the buttons in the main loop.
Peter Hinch

Rf3w8
Posts: 14
Joined: Tue Feb 21, 2017 4:51 pm

Re: How do you properly handle external interrupts?

Post by Rf3w8 » Mon Feb 27, 2017 4:48 pm

pythoncoder wrote:I appreciate the use of interrupts might be as a learning exercise. But in this context it's worth bearing in mind that the purpose of an interrupt is to cater for situations where hardware needs to be serviced in a short period of time; where "short" might be measured in microseconds.

Where a response time of the order of one second is involved it's valid to poll the state of the buttons in the main loop.
Is it a bad idea an approach like the one proposed by mr_exit if you need to handle http request? I'd do the same honestly..
Isn't continuous polling power expensive?

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

Re: How do you properly handle external interrupts?

Post by pythoncoder » Tue Feb 28, 2017 11:20 am

On any platform with a low power sleep mode you could poll at (say) 100ms intervals using such a mode for the 100ms delays. This should be very nearly as power efficient as using a hardware interrupt.

I'm not saying there's anything wrong with the interrupt approach - merely outlining alternatives.
Peter Hinch

Rf3w8
Posts: 14
Joined: Tue Feb 21, 2017 4:51 pm

Re: How do you properly handle external interrupts?

Post by Rf3w8 » Tue Feb 28, 2017 5:04 pm

pythoncoder wrote:On any platform with a low power sleep mode you could poll at (say) 100ms intervals using such a mode for the 100ms delays. This should be very nearly as power efficient as using a hardware interrupt.

I'm not saying there's anything wrong with the interrupt approach - merely outlining alternatives.
Thank you!
I'm very interested in possible alternatives as I'd like to build a battery powered "wifi remote", so I need a super low power consumption.

Would a polling solution like the following work fine?

Code: Select all

while True:
    # Check if GPIO(0 or 2) is high/low
    # Act accordingly
    time.sleep_ms(100)

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

Re: How do you properly handle external interrupts?

Post by pythoncoder » Tue Feb 28, 2017 5:14 pm

I suggest you look at the ESP8266 low power modes e.g. the esp library http://docs.micropython.org/en/latest/e ... y/esp.html and search this forum to see what others have done. It seems that you set the sleep mode and everything works (with code such as you've outlined) auto-magically but I haven't tried this in practice.
Peter Hinch

Post Reply