by David "ishotjr" Groom
When Dennis Ritchie created C in the 1970s, he was targeting UNIX systems the size of several large appliances – it’s hard to believe he could have ever imagined that the language he was cobbling together could end up as a critical underpinning for the countless invisible microcontrollers that surround us every day. And he certainly wasn’t thinking about approachability for the would-be microcontroller audience. Fast-forward fifty or so years, however, and Adafruit have attempted to simplify learning to code on microcontrollers by developing the CircuitPython language. CircuitPython is a fork of MicroPython, itself a (mostly) Python 3 compatible compiler and runtime for microcontrollers, and aims to make things easy by simplifying a few key aspects of programming:
Accessibility and approachability are important to us at Alpenglow, so for our next big project (currently codenamed Minimum Viable Cat…!) we took a look at approaching it from a beginner-friendly CircuitPython perspective. But we’re mostly oldhead C folks, so some concepts have taken us a hot minute to grasp or adjust to. And one such momentary mindfuck was something we’ve long taken for granted in C-world: Interrupts.
As you do, we started by searching for “circuitpython interrupts”, expecting helpful Stack Overflow pages or maybe some random yet magical forum threads. Instead we found a bunch of lengthy discussions in GitHub issues with titles like “no interrupts?” and “Asynchronous events: what are your use cases?” – not explaining how to use interrupts, but rather: confirming their absence. The feeling of confusion about the lack of this MCU mainstay was echoed in other discussions, such as “Anyone have any guidance on working without interrupts?” on Reddit. Numerous references to interrupts in MicroPython were forthcoming, and CircuitPython is just a fork of that, right, so … ? But the CP docs confirmed:
Concurrency within Python is not well supported. Interrupts and threading are disabled. async/await keywords are available on some boards for cooperative multitasking.
note that the adafruit_motor, asyncio, and adafruit_ticks libraries need to be added to the lib folder in order to run the above cody.py
So, what’s going on here? First in twitch() we have an infinite loop of random servo movements that result in our servo moving around randomly, forever. Then in purr() we have another infinite loop that vibrates our motor at various intensities then rests, again, forever. So how does that even work – wouldn’t one infinite loop preempt the other from running? That’s where asyncio comes in! By replacing traditional time.sleep() calls with asyncio.sleep() calls, other tasks (defined with asyncio.create_task() and orchestrated via asyncio.gather()) are welcome to jump in and do their thing!
And it turns out, it is possible to use interrupts directly in some situations. CP’s countio module uses actual interrupts to count rising/falling-edge pin transitions, the result of which you could use to e.g. see if a button has been pressed. Its use is fairly limited, but it’s at least good to know our friend the interrupt hasn’t been completely forgotten. One other caveat: asyncio is only supported in CP 7.1.0 and above, and only on certain boards. Our frequent go-to SAMD21, for example, is not supported. 😔
We’re having a blast exploring CircuitPython, but continue to be surprised by how different some aspects of it are from other microcontroller development we’ve experienced in the past. What surprises have you encountered in CircuitPython? Our hope is to share our findings as we explore from a C-based perspective, and we’d love to hear what you’ve learned or what you’re struggling with so we can help guide our blog post series! :)