Some differences between circuitpython and micropython (persuasion strategy)

Those who have seen the introduction of circuitpython generally covet the new modules of circuitpython. For example, py can directly drive the camera, support MP3 soft solution, numpy library, matrix LED driver, etc. But the ideal is very full, the reality is very skinny, and the skinny feeling is that what you think is there is actually not (or in another way).

Although circuitpython is said to be based on micropython, it is actually very different from micropython. Don’t naively think that the code that micropython can run can run with circuitpython. Here is a record of the painful points and the corresponding countermeasures:

1) The basic modules are different, there is no machine, and the microcontroller is used instead, as are other modules, which makes the code of micropython almost impossible to run in circuitpython. What is this called? Based on micropython, it has been completely revamped. . .

2) The PIN interface is different, and the PIN cannot be dynamically specified according to the parameters. I wrote a piece of code to match the int value with the PIN according to the name:

import sys
import gc

CIRCUIT: bool = (sys.implementation.name == 'circuitpython') # Whether circuitpython

if CIRCUIT:
    # IO mapping of circuitpython
    import microcontroller
    iomapper: dict[int, microcontroller. Pin] = {}

    for pin_str in dir(microcontroller.pin):
        pin = getattr(microcontroller. pin, pin_str)
        if isinstance(pin, microcontroller. Pin):
            iomapper[int(pin_str[4:])] = pin

Then iomapper[10] gets IO10
3) IO reading and writing, SPI, and I2C are used differently:

Use two other modules busio and digitalio
busio provides SPI, and digitalio provides IO level reading and setting.
IO writing is similar to the following: Note that mpy uses 0 and 1 to indicate high and low levels, while cpy uses True and False

 def digital_write(self, pin: Pin | digitalio. DigitalInOut, value):
        if CIRCUIT:
            pin.value = (value > 0)
        else:
            pin. value(value)

4) The keyboard module is different. Although a simple package is made, the interrupted interface is completely shielded and packaged. After repackaging, it can barely be used. The code after the previous keyboard module integrates circuitpython:

from lib.epui import *
import lib.time as time

if CIRCUIT:
    from microcontroller import Pin
else:
    from machine import Pin

"""
micropython switch check module
By @Jim 2023-2025
"""

class BaseSwitchButton:
    """
    button interface
    Key detection module that supports anti-shake and continuous pressing
    """
    def check_continuous(self):
        """
        Check if the button is pressed continuously
        :return:
        """
        pass

    def pull_state(self) -> tuple(bool, bool):
        """
        pull status
        :return: (whether it was pressed during the period, whether it was pressed continuously)
        """
        return (None, None)

if CIRCUIT:
    import lib.adafruit_ticks as adafruit_ticks
    import keypad

    class SwitchButton(BaseSwitchButton):
        """Switch Button Class
        Circuitpython, a key detection module that supports anti-shake and continuous pressing
        """

        continuous_period = 1000 # How many seconds to press continuously to enter continuous trigger

        pins: list[Pin] = []
        buttons = []

        def __init__(self, pin_num: int):
            self.__pressed = False
            self.__continuous = False
            self.__press_tick = 0
            self.__stat_on = False # Whether to press the status

            self.pin = iomapper[pin_num]
            if self.pin is None:
                raise ValueError(f'pin number {pin_num} not available')
            self.key_number = len(SwitchButton.pins)
            SwitchButton.pins.append(self.pin)
            SwitchButton. buttons. append(self)

        @classmethod
        def init_keypad(cls):
            SwitchButton.keys = keypad.Keys(tuple(SwitchButton.pins), value_when_pressed=False, pull=True)

        @classmethod
        def sync_button(cls):
            while True: # Pull all states
                event = SwitchButton.keys.events.get()
                if event is None:
                    break
                button = SwitchButton.buttons[event.key_number]
                if event.pressed:
                    button.__pressed = True # record has been pressed
                    button.__press_tick = event.timestamp
                    button.__stat_on = True
                if event.released:
                    button.__stat_on = False

        def check_continuous(self):
            if self.__stat_on: # pressed
                if adafruit_ticks.ticks_diff(adafruit_ticks.ticks_ms(), self.__press_tick) > SwitchButton.continuous_period:
                    self.__continuous = True
                    self.__pressed = True

        def pull_state(self) -> tuple(bool, bool):
            result = (self.__pressed, self.__continuous)
            self.__pressed = False
            self.__continuous = False
            return result
else: #micropython
    class SwitchButton(BaseSwitchButton):
        """Switch Button Class
        Micropython, a button detection module that supports anti-shake and continuous pressing
        """

        continuous_period = 1000 # How many seconds to press continuously to enter continuous trigger
        throttle_ms = 400 # cheating will trigger multiple times, do some throttling

        def __init__(self, pin_num: int):
            """
            :param pin_num:
            """
            self.__pin = Pin(pin_num, Pin.IN, Pin.PULL_UP)
            self.__pin.irq(handler=self.__switch_change, trigger=Pin.IRQ_FALLING) # Falling edge trigger

            self.__pressed = False
            self.__continuous = False
            self.__press_tick = 0

        def __switch_change(self, _):
            if time.ticks_diff(time.ticks_ms(), self.__press_tick) < SwitchButton.throttle_ms: # throttle
                return
            self.__pressed = True
            self.__press_tick = time.ticks_ms()
            self.__continuous = False

        def check_continuous(self):
            if self.__pin.value() == 0: # press
                if time.ticks_diff(time.ticks_ms(), self.__press_tick) > SwitchButton.continuous_period:
                    self.__continuous = True
                    self.__pressed = True

        def pull_state(self) -> tuple(bool, bool):
            result = (self.__pressed, self.__continuous)
            self.__pressed = False
            self.__continuous = False
            return result

        def sleep(self): # sleep irq
            self.__pin.irq(trigger=0)

        def awake(self):
            self.__pin.irq(handler=self.__switch_change, trigger=Pin.IRQ_FALLING) # Falling edge trigger

if __name__ == '__main__':
    my_switch = SwitchButton(12 if CIRCUIT else 2)
    if CIRCUIT:
        SwitchButton.init_keypad()
    else:
        from machine import disable_irq, enable_irq

    print('start')
    while True:
        if CIRCUIT:
            SwitchButton. sync_button()
        my_switch. check_continuous()
        pressed = continuous = False
        if not CIRCUIT:
            irq_state = disable_irq()
        [pressed, continuous] = my_switch. pull_state()
        if CIRCUIT:
            time. sleep(0.1)
        else:
            enable_irq(irq_state)
            time. sleep_ms(100)

        if pressed:
            print(f'OK is {"continuously pressed" if continuous else "pressed"}')

4) There is no coroutine module. This is not packaged in the firmware, but the py code is provided separately. After reading it, it seems to be barely usable (by the way, Micropython’s coroutine is also a bunch of py packaged):

https://github.com/adafruit/Adafruit_CircuitPython_asyncio/tree/main

5) If drawing generally starts from framebuffer, but there is no framebuffer, only a highly encapsulated framebufferio. This is not packaged in the firmware, there is also a separate py code

GitHub – adafruit/Adafruit_CircuitPython_framebuf: CircuitPython framebuf module, based on the Python framebuf module

But… the above module is re-implemented in python, and blit is not implemented, and the block copy that can improve performance the most is not there. . . OMG, the solution is to transplant. I have transplanted the version of framebuffer1.20 to circuitpython (firmware has been provided). Framebuffer1.20 has many more good things than 1.19. For example, drawing ellipses (including circles and rounded rectangles becomes Simple), polygon filling, this solves the problem of drawing

Well, it doesn’t matter if you don’t have it. At worst, let’s move from micropython. Please see the related article “Compiling circuitpython with smartconfig and framebuffer”

6) zlib only has decompress, no DecompIO
CircuitPython does drop some good things, decompress puts the decompressed data into memory. And DecompIO can achieve streaming decompression. For example, if you want to process a 10M text for streaming retrieval, then DecompIO can process the loss, but decompress is unable to do it. For example, you can put a binary tree index list into the compressed file. . . etc.

In the end, it can only be transplanted. I have successfully completed the transplantation of the corresponding function from micropython to circuitpython.

7) The root partition is mounted read-only by default, which means that all write operations will fail. Solution: Add in code.py:

import storage

storage.remount("/", False) #Crap, the default mount is readonly

Then unplug and restart

8) The time module is different. There is an extra useless -1 in the tuple. I wrote a time module myself to smooth the difference between the two systems:

"""
Compatible with both micropython and circuitpython time modules
This smooths out platform differences
@Jim 2023-07
"""

from lib.epui import *

if CIRCUIT:
    from lib.adafruit_ticks import *
    from time import *

    def sleep_ms(ms: int):
        sleep(ms/1000)

    def sleep_us(us: int):
        sleep(us/1000000)

    wrap_mktime = mktime
    wrap_localtime = localtime
    def mktime(t: struct_time | tuple) -> int:
        if isinstance(t, tuple) and len(t) == 8:
            l = list(t)
            l.append(-1)
            return wrap_mktime(tuple(l))
        else:
            return wrap_mktime(t)

    def localtime(secs: int = None) -> tuple:
        s_t = wrap_localtime(secs)
        s_t = list(s_t)
        del s_t[-1]
        return tuple(s_t)
else:
    from utime import *

The following is the transplanted version of the module compiled by me:
CircuitPython module overview

Wi-Fi: off | REPL | 8.2.0-dirty\

Adafruit CircuitPython 8.2.0-dirty on 2023-07-22; S2Mini with ESP32S2-S2FN4R2

>>> help('modules')
__future__ countio msgpack sys
__main__ digitalio neopixel terminalio
_asyncio displayio neopixel_write time
_pixelmap dualbank nvm touchio
adafruit_bus_device errno onewireio traceback
adafruit_bus_device.i2c_device espidf os ulab
adafruit_bus_device.spi_device espnow paralleldisplay ulab.numpy
adafruit_pixelbuf espulp ps2io ulab.numpy.fft
aesio fontio pulseio ulab.numpy.linalg
alarm framebuf pwmio ulab.scipy
analogbufio framebufferio rainbowio ulab.scipy.linalg
analogio frequencyio random ulab.scipy.optimize
array gc re ulab.scipy.signal
atexit getpass rgbmatrix ulab.scipy.special
audiobusio hashlib rotaryio ulab.utils
audiocore i2cperipheral rtc usb_cdc
audiomixer i2ctarget sdcardio usb_hid
audiomp3 io select usb_midi
binascii ipaddress sharpdisplay useselect
bitbangio json smartconfig vectorio
bitmaptools keypad socketpool watchdog
board math ssl wifi
builtins mdns storage zlib
busio memorymap struct
canio microcontroller supervisor
collections micropython synthio
Plus any modules on the filesystem

Download https://download.csdn.net/download/applebomb/88074245