Circuitpython + Pi Pico: saving settings at runtime without an EEPROM

Unlike some other microcontrollers, the RP2040 doesn’t have any EEPROM onboard. On something like the ATmega328P for example, it is easy to save the value of a variable to the EEPROM so that you can read it back on the next boot; this is handy for remembering a user setting or recalling the last state your project was in between power cycles. I have used this in the past with some of my word clocks so that when it is powered up it will “remember” the last color setting.

So, how to do this with something like the Raspberry Pi Pico? The solution I am putting forth uses the flash storage where the program files and libraries are stored. I didn’t invent this idea, but didn’t find everything I was looking for very easily so decided I would write it down here. The main idea comes from Adafruit: https://learn.adafruit.com/cpu-temperature-logging-with-circuit-python/writing-to-the-filesystem

A couple points on this solution:

  1. Limited write cycles: Flash memory has a limited number of write cycles before it wears out, so be conscious of this. A good strategy is to only write when something meaningful is changed, or if you are logging data to a file consider the interval and pick something reasonable. If you write on every loop of your program you could accelerate the failure
  2. Only one device can write files at a time: Files on the device are generally only available to be written by one device at a time. Normally the files are read-only for the microcontroller – it can read them but not change them during operation, and the files are read/write for a computer if it is mounted as a USB device.
  3. Write permissions are only changed on boot: switching the read/write permissions on the filesystem only happens when the microcontroller boots up. A reset doesn’t count, so resetting via software, the REPL, or writing a new file doesn’t do the trick – the setting is made when it powers up. This means this isn’t a good solution if you wanted to have both a PC and the microcontroller writing to the same (or two separate) files at the same time.
  4. When the microcontroller has write access, you cannot upload new code: This means that in order to update the device with new code, you need a way to switch the access – like a switch or jumper. If you accidentally end up locked out, check the Adafruit link above and there’s a note on how to fix this via the REPL.

How to do it:

First, create a JSON file that will contain the settings. Here is the example I’m using:

{"color": "Red", "brightness": 0.1, "mode": 1}

Second, the filesystem permission needs to be set at boot time, the code below needs to be in the “boot.py” file. In this example I’m using GPIO 17: when the pin is low at boot, the microcontroller has write access, and when it is high the PC will have write access. You could reverse this so the default behavior matches your requirement.

import board
import digitalio
import storage

switch = digitalio.DigitalInOut(board.GP17)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP

# If the GP17 is connected to ground with a wire
# CircuitPython can write to the drive
storage.remount("/", switch.value)
print(switch.value)

Third, in “code.py”, you can treat the filesystem like you might with python on any other system. I like to use a JSON file because it gives an easy structure to work with for things like settings, but once you are able to open and write files you can do whatever you want. In this example I’m reading the file, changing one value, then overwriting it.

#If the device boots up and has access to the filesystem, you should observe that the value for
#color in the settings.json file is "blue".  You will also be unable to modify or upload files

#If it boots up and the PC has write access to the filesystem, the serial monitor will show
#an error, and the contents of the file won't be modified

import json

#open the settings file, read the JSON into an object named "data", then close it
with open("settings.json") as infile:
    data = json.load(infile)
    print(data["color"])
infile.close()

#change one of the values in "data"
data["color"] = "Blue"

#finally, open the file again and overwrite it with the current contents of "data"
with open("settings.json", "w") as outfile:
    json.dump(data, outfile)
outfile.close()

Result

When this code all runs, the result should be that the JSON file now looks like this:

{"color": "Blue", "brightness": 0.1, "mode": 1}

And at the same time, if you try to edit or upload a file to the device, your PC should give an error like this:

And that’s all there is to it. I recommend adding a button to the pin that changes the mode at boot, at least while you’re developing, or reuse a pin that you already have a switch attached to. As a side-effect, this can reduce the risk of someone accidentally deleting or modifying the files for a device you’ve released into the wild. The three files are in a zip file below. Have fun!

Leave a Reply

Your email address will not be published. Required fields are marked *