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 file on your CIRCUITPY drive, along side your file.

The 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, 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 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!

Wiring for MCP9808

You're going to be logging the temperature. For this task, you will need to wire up a temperature sensor, like the MCP9808. Connect it to your microcontroller as shown below.

Connect the STEMMA QT cable from the STEMMA QT port on your board to the STEMMA QT port on the MCP9808.

The 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
# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: MIT
CircuitPython Essentials Storage CP Filesystem file
import time
import board
import digitalio
import storage
import neopixel

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

button = digitalio.DigitalInOut(board.BUTTON)

# Turn the NeoPixel white for one second to indicate when to press the boot button.
pixel.fill((255, 255, 255))

# If the button 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 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 File

Save the following as on your CIRCUITPY drive.

# SPDX-FileCopyrightText: 2022 Kattni Rembor for Adafruit Industries
# SPDX-License-Identifier: MIT
CircuitPython Essentials Storage CP Filesystem file

For use with boards with a built-in red LED.

Logs temperature using MCP9808 temperature sensor.
import time
import board
import digitalio
import adafruit_mcp9808

led = digitalio.DigitalInOut(board.LED)

# For connecting MCP9808 via STEMMA QT
mcp9808 = adafruit_mcp9808.MCP9808(board.STEMMA_I2C())

# For connecting MCP9808 via pins and breadboard
# mcp9808 = adafruit_mcp9808.MCP9808(board.I2C())

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

            # Write the temperature to the temperature.txt file every 10 seconds.

            # Blink the LED on every write...
            led.value = True
            time.sleep(1)  # ...for one second.
            led.value = False  # 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 LED every half second.
    if e.args[0] == 28:  # If the file system is full...
        delay = 0.15  # ...blink the LED every 0.15 seconds!
    while True:
        led.value = not led.value

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 works is, it checks to see if the button is pressed when the board is powered on and is run. To begin logging the temperature, you must press the button.

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 and set your board to writable by CircuitPython, and therefore, read-only by the computer.

For the ESP32-S3 Reverse TFT Feather, the button-press timing is a little different. Press it when the NeoPixel LED turns white!

For the ESP32-S3 Reverse TFT Feather, it's difficult to get the timing right for when to press the boot button. So, the file includes turning the NeoPixel on bright white for one second. Press the boot button when the NeoPixel is white!

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!

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 file to something other than

import os
os.rename("", "")

Alternatively, you can remove the file altogether.

import os

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 Mar 03, 2023. It was last updated on Jul 22, 2024.

This page (Storage) was last updated on Jul 21, 2024.

Text editor powered by tinymce.