Here’s a very useful technique if you’re working on a CircuitPython program that you need to store data on the host microcontroller’s Flash — and to continue to be able to mount and access the device from your computer. I’ve used it with a Raspberry Pi RP2040-based board, but it should work with other CircuitPython devices too.
CircuitPython has a great advantage that is also a great disadvantage. It is designed to make the host microcontroller’s Flash accessible to a connected computer as a USB flash drive. This is very handy because it allows you to copy your code across without any extra tools. The downside, of course, is that code can’t write data to disk at the same time. If it could, the two ‘views’ of the Flash — the microcontroller’s and the computer’s — could get out of sync, which is bad.
Credit to CircuitPython’s developers: they understood this might be an issue for some programmers, so provided a solution. You can choose whether you want the computer have access to the device (‘disk mode’) or to allow app-to-Flash writes (‘Flash mode’). Setting up for either mode is easy, but it requires finessing if you’re to support both modes as and when you need them: say when you’re running code that needs access to the Flash, but may latter need to be updated.
CircuitPython’s API to control access to the Flash is this:
storage.remount("/", (True or False))
If you pass True
as the second argument, CircuitPython activates disk mode; pass False
instead to switch to Flash mode.
This code should be used in your boot.py
file so it can switch on Flash mode before your application code, in code.py
, starts to run.
In Flash mode, the CircuitPython device will still mount as a drive if connected to your computer, but as a read-only volume. However, your app code can write data — application preferences, for example — to storage.
To flip between the two modes at will, you need some sort of switch. You could connect a button to GPIO or use an on-board button if one is available. My project doesn’t have the latter and I’m keen to avoid the former to reduce clutter. But I do have an I²C segment LED attached via a Stemma QT cable, so I chose to use that as a a proxy switch: if the LED is attached, assume the application is in use so run in Flash mode. Otherwise, the code is to be updated, so run in disk mode.
Again, all this takes place in boot.py
so the system is fully prepared when code.py
runs. Here’s the detection code:
''' RP2040 Segment Clock - boot.py ''' import board import busio import storage # Assume we'll use disk mode display_present = False # Instantiate I2C and check whether a display is # connected. If it is, set the Trinkey Flash to be accessible # to code; otherwise make it accessible as a USB drive. i2c = busio.I2C(board.SCL, board.SDA) while not i2c.try_lock(): pass devices = i2c.scan() if len(devices) > 0: for device in devices: if int(device) in (0x3C, 0x70): display_present = True break # For the second parameter of `storage.remount()`: # Pass True to make the `CIRCUITPY` drive writable by your computer. # Pass False to make the `CIRCUITPY` drive writable by CircuitPython. # This is the opposite of `display_present`, ie. when the display is # not connected, you can update the code storage.remount("/", (False if display_present is True else True)) print("CIRCUITPY","LOCKED" if display_present is True else "UNLOCKED")
Checking for an I²C device this way is quite handy, and I also use it in the application code so I’m not running unnecessary tasks when the setup is in disk mode.