The Pyboard's External Interrupts

Contents

What are External Interrupts?

Hardware interrupts are specialized electrical signals that allow external or internal hardware events to immediately pause a microcontroller's main program and execute a dedicated task. They are a fundamental mechanism for real-time responsiveness and efficient multitasking.

A common signal that triggers a hardware interrupt occurs when a given digital pin changes value. Such an interrupt will fire when a rising edge (voltage) or a falling edge (voltage) on the pin is detected. The signal is generated by an external source such as a button press or sensor pin changing logic level.

Another common signal source that produces an interrupt are timers that have reached their end of count value.

When an interrupt occurs the processor completes its current instruction then saves its state (registers, program counters, etc). The CPU looks up the address of a specific function called an Interrupt Service Routine  (Callback function  in MicroPython speak) and jumps to that code.

Once the ISR is finished the processor restores the main program state and resumes from where it was interrupted.

External Interrupts on the Pyboard

The Pyboard uses the STM32F405RG's onboard EXTI  (External Hardware/Event Controller) hardware. When a voltage change (rising, falling, or either) is detected on a physical pin, the microcontroller's hardware pauses after the current machine instruction completes.

The hardware saves the current program state to the stack and jumps to a specific Interrupt Service Routine (ISR). MicroPython then executes the callback function provided in the MicroPython program.

There are a couple of important points to make with reference to the callback function.

  • New objects (lists, dictionaries, or floats) cannot be created because the heap is locked during an interrupt. If required they must already be allocated as global objects.

    Integers between -230 and 230 - 1 do not require heap allocation so can be declared locally in a callback.
  • Speed is critical. A callback must be as short as possible to avoid blocking other important system interrupts.

The Pyboard has 16 external hardware interrupts (labelled 0 through to 15) that can be used to respond to falling edge, rising edge or either on the board's digital pins. There are some rules as to how these interrupts are allocated to pins.

These 16 hardware interrupts are allocated by bit number on the microcontroller's ports. For example:

  • Port pins PA0 (X1), PB0 (Y11), PC0 (X19) are allocated to Interrupt line 0
  • Port pins PA1 (X2), PB1 (Y12), PC1 (X20) are allocated to Interrupt line 1
  • ... and so on.

Each line can only be assigned to one pin at a time. For example separate external interrupts could be configured for pins A1 and A2 as two different lines (1 and 2) are being used.

However it would cause an exception to be raised if external interrupts were configured at the same time on pins A1 and B1. These two interrupts would be attempting to use the same line (1) and that is not possible.

The equivalent Pyboard pin names (shown in brackets above) are found by referring to the Pyboard V1.1 pinout.

MicroPython pyb.ExtInt Class

This section will highlight the most used methods of the class. A comprehensive, full MicroPython program example follows in the next section.


Syntax:
pyb.ExtInt(pin, mode, pull, callback)
Creates and returns an ExtInt object.

Where:
  pin: The pin on which to enable the interrupt.
       Can be any valid pin name or pin object.

  mode : One of:
         ExtInt.IRQ_RISING
         ExtInt.IRQ_FALLING
         ExtInt.IRQ_RISING_FALLING

  pull : One of:
         Pin.PULL_NONE
         Pin.PULL_UP
         Pin.PULL_DOWN

  callback : Sets the function to be called when the
             timer triggers. The callback function must
             accept exactly 1 argument, which is the line
             that triggered the interrupt.


Syntax:            
ExtInt.disable()
Disable the interrupt.           


Syntax:            
ExtInt.enable()
Enable a disabled interrupt.

Syntax:            
ExtInt.line()
Return the line number that the pin is mapped to.

Example:
from pyb import ExtInt, Pin

# Interrupt callback function
def fun(line):
    pass

# Create an ExtInt object
extint = ExtInt(Pin('X1'),
                ExtInt.IRQ_RISING,
                Pin.PULL_NONE,
                fun)

# Report on the ExtInt object
print('External Interrupt description:')
print('External Interrupt:', extint)
print('Interrupt line:', extint.line())

Output:
External Interrupt description:
External Interrupt: <ExtInt line=0>
Interrupt line: 0
          

Pyboard External Interrupts Demonstration

This example will use an interrupt assigned to pin Y12 to manipulate the onboard LEDs while the main program independently performs the lengthy mathematical calculation of summing up the series of the first 3,000,000 integers.

Pin Y12 is pulled High through an external 4.7kΩ and pulled Low when the user presses a pushbutton switch. The simple circuit show below was built and connected to pin Y12.

Pullup/pulldown with resistor and switch.
Fig 1 - Pullup/pulldown on pin Y12 with resistor and switch

Initially the onboard red LED is on, the blue LED is off. The LEDs are toggled each time a rising-edge or a falling-edge interrupt is generated by the user pushing/releasing the switch.


Code:
# Pin Y12 is pulled high by an external 4.7K pullup
# resistor. The same pin is pulled low with an external
# switch when it is pushed down i.e. closed contacts.

# An external interrupt is defined on the Y12 pin that
# triggers on both rising edge and falling edge.

# The onboard blue LED is turned on when the switch
# is pushed. When the switch is released the blue LED
# is turned off and the onboard red LED is turned on.

from pyb import Pin, ExtInt, LED, delay

# Declare global variable
startInt = startSum = pyb.millis()

# Callback function called whenever pin Y12 changes value.
# The LEDs are only toggled if the function has not been
# called in the last 50 ms. This allows for switch bounce.
def CallFalling(line):
    global startInt, pin
    if pyb.elapsed_millis(startInt) >= 50:
        red.toggle()
        blue.toggle()
        startInt = pyb.millis()

# Define the interrupt on pin Y12
intFalling = ExtInt(Pin('Y12'),
                    ExtInt.IRQ_RISING_FALLING,
                    Pin.PULL_NONE,
                    CallFalling)


# Define and set the initial state of the two LEDs.
red = LED(1)
blue = LED(4)
red.on()
blue.off()

# Do an intense mathematical calculation.
# The first 3,000,000 integers are summed.
sum = 0
for i in range(1, 3000001):
    sum += i
print('Sum of first 3,000,000 integers:', sum)
print('Time taken:',
       int(pyb.elapsed_millis(startSum)/1000),
       'Sec')

Output:
Sum of first 3,000,000 integers: 4500001500000
Time taken: 57 Sec