Interrupt-handlers schrijven

Op geschikte hardware biedt MicroPython de mogelijkheid om interrupt-handlers in Python te schrijven. Interrupt-handlers - ook bekend als interrupt service routines (ISR’s) - worden gedefinieerd als callback-functies. Deze worden uitgevoerd als reactie op een gebeurtenis zoals een timertrigger of een spanningsverandering op een pin. Dergelijke gebeurtenissen kunnen op elk moment tijdens de uitvoering van de programmacode optreden. Dit heeft aanzienlijke gevolgen, sommige specifiek voor de MicroPython-taal. Andere zijn gemeenschappelijk voor alle systemen die op realtime gebeurtenissen kunnen reageren. Dit document behandelt eerst de taalspecifieke kwesties, gevolgd door een korte inleiding tot realtime programmeren voor wie er nieuw in is.

Deze inleiding gebruikt vage termen als “traag” of “zo snel mogelijk”. Dit is bewust, aangezien snelheden afhankelijk zijn van de toepassing. Acceptabele duren voor een ISR zijn afhankelijk van de snelheid waarmee interrupts optreden, de aard van het hoofdprogramma en de aanwezigheid van andere gelijktijdige gebeurtenissen.

MicroPython-kwesties

De noodbuffer voor uitzonderingen

Als er een fout optreedt in een ISR, kan MicroPython geen foutrapport produceren tenzij hiervoor een speciale buffer is aangemaakt. Het debuggen wordt vereenvoudigd als de volgende code wordt opgenomen in elk programma dat interrupts gebruikt.

import micropython

micropython.alloc_emergency_exception_buf(100)

De noodbuffer voor uitzonderingen kan slechts één uitzonderings-stacktrace bevatten. Dit betekent dat als er een tweede uitzondering wordt opgeworpen tijdens de afhandeling van een uitzondering terwijl de heap vergrendeld is, de stacktrace van die tweede uitzondering de oorspronkelijke vervangt - zelfs als de tweede uitzondering netjes wordt afgehandeld. Dit kan leiden tot verwarrende uitzonderingsmeldingen als de buffer later wordt afgedrukt.

Eenvoud

Om diverse redenen is het belangrijk om ISR-code zo kort en eenvoudig mogelijk te houden. Het moet alleen doen wat onmiddellijk na de gebeurtenis die het veroorzaakte moet gebeuren: bewerkingen die uitgesteld kunnen worden, moeten aan de hoofdprogrammalus worden gedelegeerd. Doorgaans handelt een ISR het hardwareapparaat af dat de interrupt veroorzaakte, waardoor het klaar wordt gemaakt voor de volgende interrupt. Het communiceert met de hoofdlus door gedeelde gegevens bij te werken om aan te geven dat de interrupt heeft plaatsgevonden, en het keert terug. Een ISR moet de controle zo snel mogelijk teruggeven aan de hoofdlus. Dit is geen specifieke MicroPython-kwestie en wordt daarom meer in detail behandeld hieronder.

Communicatie tussen een ISR en het hoofdprogramma

Normaal gesproken moet een ISR communiceren met het hoofdprogramma. De eenvoudigste manier om dit te doen is via een of meer gedeelde gegevensobjecten, ofwel gedeclareerd als globaal ofwel gedeeld via een class (zie hieronder). Er zijn diverse beperkingen en gevaren rond het doen hiervan, die hieronder meer in detail worden behandeld. Gehele getallen, bytes- en bytearray-objecten worden hier vaak voor gebruikt, samen met arrays (uit de array-module) die diverse gegevenstypen kunnen opslaan.

Het gebruik van objectmethoden als callbacks

MicroPython ondersteunt deze krachtige techniek die het mogelijk maakt dat een ISR instantievariabelen deelt met de onderliggende code. Het stelt een class die een apparaatstuurprogramma implementeert ook in staat om meerdere apparaatinstanties te ondersteunen. Het volgende voorbeeld laat twee LED’s met verschillende snelheden knipperen.

import machine
import micropython

micropython.alloc_emergency_exception_buf(100)


class Foo(object):
    def __init__(self, freq, led):
        self.led = led
        self.timer = machine.Timer(-1, freq=freq, callback=self.cb, hard=True)

    def cb(self, tim):
        self.led.toggle()


red = Foo(1, machine.LED("LED_RED"))
green = Foo(0.8, machine.LED("LED_GREEN"))

In dit voorbeeld stuurt de instantie red de rode LED aan vanaf een virtuele timer van 1 Hz: telkens wanneer de timer afgaat wordt red.cb() aangeroepen, wat de rode LED omschakelt. De instantie green werkt op vergelijkbare wijze met een timer van 0,8 Hz die de groene LED omschakelt. Het gebruik van instantiemethoden levert twee voordelen op. Ten eerste stelt één enkele class code in staat om te worden gedeeld tussen meerdere hardware-instanties. Ten tweede is, als gebonden methode, het eerste argument van de callback-functie self. Dit stelt de callback in staat om instantiegegevens te benaderen en de toestand tussen opeenvolgende aanroepen te bewaren. Als de bovenstaande class bijvoorbeeld een variabele self.count had die in de constructor op nul is gezet, dan zou cb() de teller kunnen verhogen. De instanties red en green zouden dan onafhankelijke tellingen bijhouden van het aantal keren dat elke LED van toestand is veranderd.

Aanmaken van Python-objecten

ISR’s kunnen geen instanties van Python-objecten aanmaken. Dit komt doordat MicroPython geheugen voor het object moet toewijzen uit een voorraad vrije geheugenblokken die de heap wordt genoemd. Dit is niet toegestaan in een interrupt-handler omdat heap-toewijzing niet re-entrant is. Met andere woorden, de interrupt kan optreden terwijl het hoofdprogramma halverwege een toewijzing is - om de integriteit van de heap te behouden, staat de interpreter geen geheugentoewijzingen toe in ISR-code.

Een gevolg hiervan is dat ISR’s geen drijvende-kommaberekeningen kunnen gebruiken; dit komt doordat floats Python-objecten zijn. Evenzo kan een ISR geen item aan een lijst toevoegen. In de praktijk kan het lastig zijn om precies te bepalen welke codeconstructies geheugentoewijzing zullen proberen uit te voeren en een foutmelding zullen veroorzaken: nog een reden om ISR-code kort en eenvoudig te houden.

Een manier om deze kwestie te vermijden is dat de ISR vooraf toegewezen buffers gebruikt. Een class-constructor maakt bijvoorbeeld een bytearray-instantie en een booleaanse vlag aan. De ISR-methode wijst gegevens toe aan locaties in de buffer en zet de vlag. De geheugentoewijzing vindt plaats in de hoofdprogrammacode wanneer het object wordt geïnstantieerd in plaats van in de ISR.

De I/O-methoden van de MicroPython-bibliotheek bieden gewoonlijk een optie om een vooraf toegewezen buffer te gebruiken. machine.I2C.readfrom_into() leest bijvoorbeeld in een door de aanroeper geleverde wijzigbare buffer: dit maakt het gebruik ervan in een ISR mogelijk.

Een manier om een object aan te maken zonder een class of globals te gebruiken is als volgt:

def set_volume(t, buf=bytearray(3)):
    buf[0] = 0xa5
    buf[1] = t >> 4
    buf[2] = 0x5a
    return buf

De compiler instantieert het standaardargument buf wanneer de functie voor het eerst wordt geladen (gewoonlijk wanneer de module waarin het zit wordt geïmporteerd).

Een geval van objectaanmaak treedt op wanneer een verwijzing naar een gebonden methode wordt aangemaakt. Dit betekent dat een ISR geen gebonden methode aan een functie kan doorgeven. Eén oplossing is om in de class-constructor een verwijzing naar de gebonden methode aan te maken en die verwijzing in de ISR door te geven. Bijvoorbeeld:

class Foo():
    def __init__(self):
        self.bar_ref = self.bar  # Allocation occurs here
        self.x = 0.1
        self.tim = machine.Timer(-1, freq=2, callback=self.cb, hard=True)

    def bar(self, _):
        self.x *= 1.2
        print(self.x)

    def cb(self, t):
        # Passing self.bar would cause allocation.
        micropython.schedule(self.bar_ref, 0)

Andere technieken zijn het definiëren en instantiëren van de methode in de constructor, of het doorgeven van Foo.bar() met het argument self.

Gebruik van Python-objecten

Een verdere beperking op objecten ontstaat door de manier waarop Python werkt. Wanneer een import-statement wordt uitgevoerd, wordt de Python-code gecompileerd naar bytecode, waarbij één regel code doorgaans op meerdere bytecodes wordt afgebeeld. Wanneer de code draait, leest de interpreter elke bytecode en voert deze uit als een reeks machinecode-instructies. Aangezien een interrupt op elk moment tussen machinecode-instructies kan optreden, kan de oorspronkelijke regel Python-code slechts gedeeltelijk zijn uitgevoerd. Bijgevolg kan een Python-object zoals een set, lijst of dictionary dat in de hoofdlus wordt gewijzigd interne consistentie missen op het moment dat de interrupt optreedt.

Een typische uitkomst is als volgt. In zeldzame gevallen zal de ISR precies op het moment draaien dat het object gedeeltelijk is bijgewerkt. Wanneer de ISR het object probeert te lezen, ontstaat er een crash. Omdat dergelijke problemen doorgaans op zeldzame, willekeurige momenten optreden, kunnen ze moeilijk te diagnosticeren zijn. Er zijn manieren om deze kwestie te omzeilen, beschreven in Kritieke secties hieronder.

Het is belangrijk om duidelijk te zijn over wat de wijziging van een object inhoudt. Het wijzigen van de inhoud van een array of bytearray is veilig. Dit komt doordat bytes of words worden geschreven als één enkele machinecode-instructie die niet onderbreekbaar is: in de terminologie van realtime programmeren is het schrijven atomair. Hetzelfde geldt voor het bijwerken van een dictionary-item, omdat items machinewoorden zijn, namelijk gehele getallen of pointers naar objecten. Een door de gebruiker gedefinieerd object kan een array of bytearray instantiëren. Het is geldig dat zowel de hoofdlus als de ISR de inhoud hiervan wijzigen.

Het gevaar ontstaat wanneer de structuur van een object wordt gewijzigd, met name in het geval van dictionaries. Het toevoegen of verwijderen van sleutels kan een rehash veroorzaken. Als een harde ISR draait terwijl een rehash bezig is en probeert een item te benaderen, kan er een crash optreden. Intern worden globals geïmplementeerd als een dictionary. Bijgevolg moet het hoofdprogramma alle benodigde globals aanmaken voordat een proces wordt gestart dat harde interrupts genereert. Toepassingscode moet ook het verwijderen van globals vermijden.

MicroPython ondersteunt gehele getallen van willekeurige precisie. Waarden tussen 230 -1 en -230 worden opgeslagen in één enkel machinewoord. Grotere waarden worden opgeslagen als Python-objecten. Bijgevolg kunnen wijzigingen aan lange gehele getallen niet als atomair worden beschouwd. Het gebruik van lange gehele getallen in ISR’s is onveilig, omdat geheugentoewijzing kan worden geprobeerd terwijl de waarde van de variabele verandert.

De float-beperking overwinnen

Over het algemeen is het het beste om het gebruik van floats in ISR-code te vermijden: hardwareapparaten werken normaal gesproken met gehele getallen en conversie naar floats wordt normaal gesproken in de hoofdlus gedaan. Er zijn echter enkele DSP-algoritmen die drijvende komma vereisen. Op platforms met hardwarematige drijvende komma (zoals de op STM32 gebaseerde OpenMV Cams) kan de inline ARM Thumb-assembler worden gebruikt om deze beperking te omzeilen. Dit komt doordat de processor float-waarden in een machinewoord opslaat; waarden kunnen tussen de ISR en de hoofdprogrammacode worden gedeeld via een array van floats.

micropython.schedule gebruiken

Deze functie stelt een ISR in staat om “zeer binnenkort” een callback voor uitvoering in te plannen. De callback wordt in een wachtrij geplaatst voor uitvoering die plaatsvindt op een moment dat de heap niet vergrendeld is. Daardoor kan het Python-objecten aanmaken en floats gebruiken. De callback wordt ook gegarandeerd uitgevoerd op een moment dat het hoofdprogramma elke update van Python-objecten heeft voltooid, dus de callback zal geen gedeeltelijk bijgewerkte objecten tegenkomen.

Typisch gebruik is het afhandelen van sensorhardware. De ISR verkrijgt gegevens van de hardware en stelt deze in staat om een verdere interrupt uit te geven. Vervolgens plant het een callback in om de gegevens te verwerken.

Ingeplande callbacks moeten voldoen aan de hieronder geschetste principes voor het ontwerp van interrupt-handlers. Dit om problemen te voorkomen die voortvloeien uit I/O-activiteit en de wijziging van gedeelde gegevens, die kunnen ontstaan in elke code die de hoofdprogrammalus onderbreekt.

De uitvoeringstijd moet worden beschouwd in relatie tot de frequentie waarmee interrupts kunnen optreden. Als er een interrupt optreedt terwijl de vorige callback wordt uitgevoerd, wordt er een verdere instantie van de callback in de wachtrij geplaatst voor uitvoering; deze draait nadat de huidige instantie is voltooid. Een aanhoudend hoge herhalingssnelheid van interrupts brengt daarom een risico van ongecontroleerde groei van de wachtrij en uiteindelijke storing met een RuntimeError met zich mee.

Als de callback die aan schedule() moet worden doorgegeven een gebonden methode is, raadpleeg dan de opmerking in “Aanmaken van Python-objecten”.

Uitzonderingen

Als een ISR een uitzondering opwerpt, zal deze zich niet voortplanten naar de hoofdlus. De interrupt wordt uitgeschakeld tenzij de uitzondering door de ISR-code wordt afgehandeld.

Koppelen aan asyncio

Wanneer een ISR draait, kan deze de asyncio-scheduler onderbreken. Als de ISR een asyncio-bewerking uitvoert, kan de werking van de scheduler worden verstoord. Dit geldt ongeacht of de interrupt hard of zacht is en geldt ook als de ISR de uitvoering via micropython.schedule aan een andere functie heeft doorgegeven. In het bijzonder is het aanmaken of annuleren van taken ongeldig in een ISR-context. De veilige manier om met asyncio te interageren is door een coroutine te implementeren met synchronisatie uitgevoerd door asyncio.ThreadSafeFlag. Het volgende fragment illustreert het aanmaken van een taak als reactie op een interrupt:

tsf = asyncio.ThreadSafeFlag()


def isr(_):  # Interrupt handler
    tsf.set()


async def foo():
    while True:
        await tsf.wait()
        asyncio.create_task(bar())

In dit voorbeeld zal er een variabele hoeveelheid latentie zijn tussen de uitvoering van de ISR en de uitvoering van foo(). Dit is inherent aan coöperatieve scheduling. De maximale latentie is afhankelijk van de toepassing en het platform, maar wordt doorgaans gemeten in tientallen ms.

Algemene kwesties

Dit is slechts een korte inleiding tot het onderwerp realtime programmeren. Beginners moeten er rekening mee houden dat ontwerpfouten in realtime programma’s kunnen leiden tot storingen die bijzonder moeilijk te diagnosticeren zijn. Dit komt doordat ze zelden en met essentieel willekeurige tussenpozen kunnen optreden. Het is cruciaal om het oorspronkelijke ontwerp goed te krijgen en problemen te anticiperen voordat ze ontstaan. Zowel interrupt-handlers als het hoofdprogramma moeten worden ontworpen met begrip van de volgende kwesties.

Ontwerp van interrupt-handlers

Zoals hierboven vermeld, moeten ISR’s zo eenvoudig mogelijk worden ontworpen. Ze moeten altijd binnen een korte, voorspelbare periode terugkeren. Dit is belangrijk omdat wanneer de ISR draait, de hoofdlus dat niet doet: de hoofdlus ondervindt onvermijdelijk pauzes in zijn uitvoering op willekeurige punten in de code. Dergelijke pauzes kunnen een bron zijn van moeilijk te diagnosticeren bugs, vooral als hun duur lang of variabel is. Om de implicaties van de looptijd van een ISR te begrijpen, is een basaal begrip van interruptprioriteiten vereist.

Interrupts zijn georganiseerd volgens een prioriteitsschema. ISR-code kan zelf worden onderbroken door een interrupt met hogere prioriteit. Dit heeft implicaties als de twee interrupts gegevens delen (zie Kritieke secties hieronder). Als zo’n interrupt optreedt, introduceert deze een vertraging in de ISR-code. Als een interrupt met lagere prioriteit optreedt terwijl de ISR draait, wordt deze vertraagd tot de ISR is voltooid: als de vertraging te lang is, kan de interrupt met lagere prioriteit mislukken. Een verdere kwestie met trage ISR’s is het geval waarin een tweede interrupt van hetzelfde type optreedt tijdens de uitvoering ervan. De tweede interrupt wordt afgehandeld bij beëindiging van de eerste. Echter, als de snelheid van binnenkomende interrupts consequent de capaciteit van de ISR om ze af te handelen overschrijdt, zal de uitkomst geen gelukkige zijn.

Bijgevolg moeten lusconstructies worden vermeden of geminimaliseerd. I/O naar andere apparaten dan het onderbrekende apparaat moet normaal gesproken worden vermeden: I/O zoals schijftoegang, print-statements en UART-toegang is relatief traag en de duur ervan kan variëren. Een verdere kwestie hierbij is dat bestandssysteemfuncties niet re-entrant zijn: het gebruik van bestandssysteem-I/O in een ISR en het hoofdprogramma zou gevaarlijk zijn. Cruciaal is dat ISR-code niet op een gebeurtenis mag wachten. I/O is acceptabel als gegarandeerd kan worden dat de code binnen een voorspelbare periode terugkeert, bijvoorbeeld het omschakelen van een pin of LED. Het benaderen van het onderbrekende apparaat via I2C of SPI kan nodig zijn, maar de tijd die voor dergelijke toegangen nodig is, moet worden berekend of gemeten en de impact ervan op de toepassing moet worden beoordeeld.

Er is gewoonlijk een behoefte om gegevens te delen tussen de ISR en de hoofdlus. Dit kan worden gedaan ofwel via globale variabelen ofwel via class- of instantievariabelen. Variabelen zijn doorgaans integer- of booleaanse typen, of integer- of byte-arrays (een vooraf toegewezen integer-array biedt snellere toegang dan een lijst). Wanneer meerdere waarden door de ISR worden gewijzigd, is het nodig om het geval te overwegen waarin de interrupt optreedt op een moment dat het hoofdprogramma sommige, maar niet alle, van de waarden heeft benaderd. Dit kan tot inconsistenties leiden.

Beschouw het volgende ontwerp. Een ISR slaat binnenkomende gegevens op in een bytearray en telt vervolgens het aantal ontvangen bytes op bij een geheel getal dat het totale aantal voor verwerking gereed staande bytes vertegenwoordigt. Het hoofdprogramma leest het aantal bytes, verwerkt de bytes en wist vervolgens het aantal gereed staande bytes. Dit werkt totdat een interrupt optreedt net nadat het hoofdprogramma het aantal bytes heeft gelezen. De ISR plaatst de toegevoegde gegevens in de buffer en werkt het ontvangen aantal bij, maar het hoofdprogramma heeft het aantal al gelezen en verwerkt daarom de oorspronkelijk ontvangen gegevens. De nieuw aangekomen bytes gaan verloren.

Er zijn diverse manieren om dit gevaar te vermijden, waarvan de eenvoudigste het gebruik van een ringbuffer is. Als het niet mogelijk is om een structuur met inherente thread-veiligheid te gebruiken, worden hieronder andere manieren beschreven.

Re-entrantie

Er kan een potentieel gevaar optreden als een functie of methode wordt gedeeld tussen het hoofdprogramma en een of meer ISR’s, of tussen meerdere ISR’s. De kwestie hierbij is dat de functie zelf kan worden onderbroken en een verdere instantie van die functie kan draaien. Als dit moet kunnen gebeuren, moet de functie zodanig worden ontworpen dat deze re-entrant is. Hoe dit wordt gedaan is een gevorderd onderwerp dat buiten het bereik van deze tutorial valt.

Kritieke secties

Een voorbeeld van een kritieke codesectie is er een die meer dan één variabele benadert die door een ISR kan worden beïnvloed. Als de interrupt toevallig optreedt tussen toegangen tot de afzonderlijke variabelen, zullen hun waarden inconsistent zijn. Dit is een geval van een gevaar dat bekend staat als een race-conditie: de ISR en de hoofdprogrammalus racen om de variabelen te wijzigen. Om inconsistentie te voorkomen, moet een middel worden ingezet om te garanderen dat de ISR de waarden niet wijzigt gedurende de kritieke sectie. Eén manier om dit te bereiken is door machine.disable_irq() uit te voeren vóór het begin van de sectie, en machine.enable_irq() aan het einde. Hier is een voorbeeld van deze aanpak:

import machine
import micropython
import array
import random
import time

micropython.alloc_emergency_exception_buf(100)


class BoundsException(Exception):
    pass


ARRAYSIZE = const(20)
index = 0
data = array.array('i', [0] * ARRAYSIZE)


def callback1(t):
    global data, index
    for x in range(5):
        data[index] = random.getrandbits(30)  # simulate input
        index += 1
        if index >= ARRAYSIZE:
            raise BoundsException('Array bounds exceeded')


tim = machine.Timer(-1, freq=100, callback=callback1, hard=True)

for loop in range(1000):
    if index > 0:
        irq_state = machine.disable_irq()  # Start of critical section
        for x in range(index):
            print(data[x])
        index = 0
        machine.enable_irq(irq_state)  # End of critical section
        print('loop {}'.format(loop))
    time.sleep_ms(1)

tim.deinit()

Een kritieke sectie kan bestaan uit één enkele regel code en één enkele variabele. Beschouw het volgende codefragment.

count = 0


def cb(): # An interrupt callback
    count += 1


def main():
    # Code to set up the interrupt callback omitted
    while True:
        count += 1

Dit voorbeeld illustreert een subtiele bron van bugs. De regel count += 1 in de hoofdlus draagt een specifiek race-conditie-gevaar dat bekend staat als een read-modify-write. Dit is een klassieke oorzaak van bugs in realtime systemen. In de hoofdlus leest MicroPython de waarde van count, telt er 1 bij op en schrijft deze terug. In zeldzame gevallen treedt de interrupt op na het lezen en vóór het schrijven. De interrupt wijzigt count, maar de wijziging wordt overschreven door de hoofdlus wanneer de ISR terugkeert. In een echt systeem zou dit kunnen leiden tot zeldzame, onvoorspelbare storingen.

Zoals hierboven vermeld, moet voorzichtigheid worden betracht als een instantie van een ingebouwd Python-type wordt gewijzigd in de hoofdcode en die instantie wordt benaderd in een ISR. De code die de wijziging uitvoert, moet worden beschouwd als een kritieke sectie om te garanderen dat de instantie zich in een geldige toestand bevindt wanneer de ISR draait.

Bijzondere voorzichtigheid moet worden betracht als een dataset wordt gedeeld tussen verschillende ISR’s. Het gevaar hierbij is dat de interrupt met hogere prioriteit kan optreden wanneer die met lagere prioriteit de gedeelde gegevens gedeeltelijk heeft bijgewerkt. Het omgaan met deze situatie is een gevorderd onderwerp dat buiten het bereik van deze inleiding valt, behalve om op te merken dat de hieronder beschreven mutex-objecten soms kunnen worden gebruikt.

Het uitschakelen van interrupts gedurende een kritieke sectie is de gebruikelijke en eenvoudigste manier van werken, maar het schakelt alle interrupts uit in plaats van slechts die met de potentie om problemen te veroorzaken. Het is over het algemeen ongewenst om een interrupt lang uit te schakelen. In het geval van timer-interrupts introduceert het variabiliteit in het moment waarop een callback optreedt. In het geval van apparaat-interrupts kan het ertoe leiden dat het apparaat te laat wordt afgehandeld met mogelijk gegevensverlies of overrun-fouten in de apparaathardware. Net als ISR’s moet een kritieke sectie in de hoofdcode een korte, voorspelbare duur hebben.

Een aanpak voor het omgaan met kritieke secties die de tijd waarin interrupts uitgeschakeld zijn radicaal verkort, is het gebruik van een object dat een mutex wordt genoemd (naam afgeleid van het begrip wederzijdse uitsluiting, oftewel mutual exclusion). Het hoofdprogramma vergrendelt de mutex voordat het de kritieke sectie uitvoert en ontgrendelt deze aan het einde. De ISR test of de mutex vergrendeld is. Als dat zo is, vermijdt het de kritieke sectie en keert het terug. De ontwerpuitdaging is het definiëren van wat de ISR moet doen in het geval dat toegang tot de kritieke variabelen wordt geweigerd. Een eenvoudig voorbeeld van een mutex is hier te vinden. Merk op dat de mutex-code interrupts wel uitschakelt, maar slechts voor de duur van acht machine-instructies: het voordeel van deze aanpak is dat andere interrupts vrijwel onaangetast blijven.

Interrupts en de REPL

Interrupt-handlers, zoals die geassocieerd met timers, kunnen blijven draaien nadat een programma is beëindigd. Dit kan onverwachte resultaten opleveren waar je had kunnen verwachten dat het object dat de callback opwekt buiten bereik zou zijn gegaan. Bijvoorbeeld op een OpenMV Cam:

def bar():
    foo = machine.Timer(-1, freq=4, callback=lambda t: print('.', end=''), hard=True)

bar()

Dit blijft draaien totdat de timer expliciet wordt uitgeschakeld of het board wordt gereset met Ctrl-D.