CircuitPython-compatible microcontrollers show up as a CIRCUITPY drive when plugged into your computer, allowing you to edit code directly on the board. Perhaps you've wondered whether or not you can write data from CircuitPython directly to the board to act as a data logger. The answer is yes!

The storage module in CircuitPython enables you to write code that allows CircuitPython to write data to the CIRCUITPY drive. This process requires you to include a boot.py file on your CIRCUITPY drive, along side your code.py file.

The boot.py file is special - the code within it is executed when CircuitPython starts up, either from a hard reset or powering up the board. It is not run on soft reset, for example, if you reload the board from the serial console or the REPL. This is in contrast to the code within code.py, which is executed after CircuitPython is already running.

The CIRCUITPY drive is typically writable by your computer; this is what allows you to edit your code directly on the board. The reason you need a boot.py file is that you have to set the filesystem to be read-only by your computer to allow it to be writable by CircuitPython. This is because CircuitPython cannot write to the filesystem at the same time as your computer. Doing so can lead to filesystem corruption and loss of all content on the drive, so CircuitPython is designed to only allow one at at time.

You can only have EITHER your computer edit files on the CIRCUITPY drive, OR have CircuitPython edit files. You cannot have both writing to the CIRCUITPY drive at the same time. CircuitPython doesn't allow it!

The boot.py File

The filesystem will NOT automatically be set to read-only on creation of this file! You'll still be able to edit files on CIRCUITPY after saving this boot.py.
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: Unlicense
"""
CircuitPython Essentials Storage CP Filesystem boot.py file
"""
import board
import digitalio
import storage

button = digitalio.DigitalInOut(board.BUTTON)
button.switch_to_input(pull=digitalio.Pull.UP)

# If the OBJECT_NAME is connected to ground, the filesystem is writable by CircuitPython
storage.remount("/", readonly=button.value)

The storage.remount() command has a readonly keyword argument. This argument refers to the read/write state of CircuitPython. It does NOT refer to the read/write state of your computer.

When the button is pressed, it returns False. The readonly argument in boot.py is set to the value of the button. When the value=True, the CIRCUITPY drive is read-only to CircuitPython (and writable by your computer). When the value=False, the CIRCUITPY drive is writable by CircuitPython (and read-only by your computer).

The code.py File

Save the following as code.py on your CIRCUITPY drive.

In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory Adafruit_KB2040/Storage/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.

Your CIRCUITPY drive should now look similar to the following image:

CIRCUITPY
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: Unlicense
"""
CircuitPython Essentials Storage CP Filesystem code.py file
"""
import time
import board
import microcontroller
import neopixel

pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)

try:
    with open("/temperature.txt", "a") as temp_log:
        while True:
            # The microcontroller temperature in Celsius. Include the
            # math to do the C to F conversion here, if desired.
            temperature = microcontroller.cpu.temperature

            # Write the temperature to the temperature.txt file every 10 seconds.
            temp_log.write('{0:.2f}\n'.format(temperature))
            temp_log.flush()

            # Blink the NeoPixel on every write...
            pixel.fill((255, 0, 0))
            time.sleep(1)  # ...for one second.
            pixel.fill((0, 0, 0))  # Then turn it off...
            time.sleep(9)  # ...for the other 9 seconds.

except OSError as e:  # When the filesystem is NOT writable by CircuitPython...
    delay = 0.5  # ...blink the NeoPixel every half second.
    if e.args[0] == 28:  # If the file system is full...
        delay = 0.15  # ...blink the NeoPixel every 0.15 seconds!
    while True:
        pixel.fill((255, 0, 0))
        time.sleep(delay)
        pixel.fill((0, 0, 0))
        time.sleep(delay)

First you import the necessary modules to make them available to your code, and you set up the LED.

Next you have a try/except block, which is used to handle the three potential states of the board: read/write, read-only, or filesystem full. The code in the try block will run if the filesystem is writable by CircuitPython. The code in the except block will run if the filesystem is read-only to CircuitPython OR if the filesystem is full.

Under the try, you open a temperature.txt log file. If it is the first time, it will create the file. For all subsequent times, it opens the file and appends data. Inside the loop, you get the microcontroller temperature value and assign it to a temperature variable. Then, you write the temperature value to the log file, followed by clearing the buffer for the next time through the loop. The temperature data is limited to two decimal points to save space for more data. Finally, you turn the LED on for one second, and then turn it off for the next nine seconds. Essentially, you blink the LED for one second every time the temperature is logged to the file which happens every ten seconds.

Next you except an OSError. An OSError number 30 is raised when trying to create, open or write to a file on a filesystem that is read-only to CircuitPython. If any OSError other than 28 is raised (e.g. 30), the delay is set to 0.5 seconds. If the filesystem fills up, CircuitPython raises OSError number 28. If OSError number 28 is raised, the delay is set to 0.15 seconds. Inside the loop, the LED is turned on for the duration of the delay, and turned off for the duration of the delay, effectively blinking the LED at the speed of the delay.

Logging the Temperature

At the moment, the LED on your board should be blinking once every half second. This indicates that the board is currently read-only to CircuitPython, and writable to your computer, allowing you to update the files on your CIRCUITPY drive as needed.

The way the code in boot.py works is, it checks to see if the button is pressed when the board is powered on and boot.py is run. To begin logging the temperature, you must press the button.

The boot button, labeled BOOT on the silk, is in the lower right corner of the board.

While holding down the button, you need to either hard reset the board by pressing the reset button, or by unplugging the USB cable and plugging it back in. This will run the code within boot.py and set your board to writable by CircuitPython, and therefore, read-only by the computer.

The red blinking will slow down to one second long, every 10 seconds. This indicates that the board is currently logging the temperature, once every 10 seconds.

As long as the button is pressed, you can plug the board in anywhere you have USB power, and log the temperature in that location! The temperature is not the ambient temperature; it is the temperature inside the microcontroller, which will typically be higher than ambient temperature. However, running only this code, once the microcontroller temperature stabilises, it should at least be consistent, and therefore usable for tracking changes in ambient temperature.

If the LED starts blinking really quickly, it means the filesystem is full! You'll need to get your temperature data and delete the temperature log file to begin again.

That's all there is to logging the temperature using CircuitPython!

Recovering a Read-Only Filesystem

In the event that you make your CIRCUITPY drive read-only to your computer, and for some reason, it doesn't easily switch back to writable, there are a couple of things you can do to recover the filesystem.

Even when the CIRCUITPY drive is read-only to your computer, you can still access the serial console and REPL. If you connect to the serial console and enter the REPL, you can run either of the following two sets of commands at the >>> prompt. You do not need to run both.

First, you can rename your boot.py file to something other than boot.py.

import os
os.rename("boot.py", "something_else.py")

Alternatively, you can remove the boot.py file altogether.

import os
os.remove("boot.py")

Then, restart the board by either hitting the reset button or unplugging USB and plugging it back in. CIRCUITPY should show up on your computer as usual, but now it should be writable by your computer.

This guide was first published on Dec 03, 2021. It was last updated on Mar 28, 2024.

This page (Storage) was last updated on Mar 28, 2024.

Text editor powered by tinymce.