Yeehaw!
Notice how all the screensaver examples embed their graphics data as tables within the code. Any change in graphics requires new tables. What if we could load graphics…or any other data our program might need…from files instead?
Installing CircuitPython initializes a board’s flash storage like a small USB drive. We then have Arduino libraries that can read and write files in this space.
Combining this with PicoDVI is possible, but is experimental and failure-prone. Either one of these tasks — DVI video or USB storage — is pretty demanding, and combining the two is asking a lot. In no case will it damage the board, but at worst there’s a small chance of wiping out the flash storage and having to re-load everything. Understand this is wild west stuff. We’ll walk through some best practices to make this less perilous…
First, you’ll need up-to-date versions of several packages:
Tools→Board→Boards Manager…
- The RP2040 Arduino “core” must be version 3.3.0 or later. Setup and installation is explained in the Feather RP2040 DVI guide.
Sketch→Include Library→Library Manager…
- PicoDVI library 1.1.0 or later.
- Adafruit_SPIFlash 4.2.0 or later.
- Adafruit_CPFS 1.1.0 or later.
Then head to the CircuitPython downloads page and grab the current version for your board. Keep this file around. As mentioned above, there’s a small chance of things going awry and needing to re-initialize a board’s flash storage.
Prepare Hardware
Install CircuitPython as you world normally. On most RP2040 boards, this involves holding down the BOOT button while connecting USB or tapping the reset button. A drive called RPI-RP2 will appear on your computer. Drag the CircuitPython .UF2 file to this drive and let it work. After several seconds, RPI-RP2 is replaced with a CIRCUITPY drive. Success! Now there’s a viable filesystem to hold files.
Drag files for your project to the CIRCUITPY drive and organize to your liking. It’s prudent to keep a clean copy of everything organized on your computer, should a reinstall be necessary.
The file_access example doesn’t require any additional files. All this example does is print a directory listing. But even a fresh CircuitPython install will have a few items present.
Compile and Upload Sketch
Open the file_access sketch in the Arduino IDE. Select your board type from the “Tools” menu rollover…
Tools→Board→Raspberry Pi RP2040 Boards→(board name)
Then…important detail or the sketch won’t compile…select the TinyUSB stack, which helps make the CIRCUITPY drive accessible to Arduino code and the host computer…
Tools→USB Stack→Adafruit TinyUSB
Then you can compile and upload to the board as you would any other sketch.
In Action
To keep the code brief and instructive, all this example does is print a listing of the CIRCUITPY root directory to a connected DVI display. But…selecting or previewing different files from the attached host computer, you’ll see it prints the listing again. Selecting a file updates the last “touch” time for that file, which constitutes a change. You can also try dragging files to or removing files from the CIRCUITPY drive, and should get an updated listing with each change.
This example uses a text mode, but the same principles can be applied to graphical programs as well. Most won’t even need the change-detect behavior…whatever data is loaded on start-up, that’s what you get in that case.
Code Explainer
The PicoDVI parts are already explained in other examples and won’t be covered here. Only the file-specific additions are mentioned.
First up, an extra library header and global object are declared:
#include <Adafruit_CPFS.h> // For accessing the CIRCUITPY drive FatVolume *fs = NULL; // CIRCUITPY flash filesystem, as a FAT pointer
Adafruit_CPFS (CircuitPython File System) is an Arduino library that helps make the CIRCUITPY flash filesystem available both to our Arduino sketch and presents it to a host computer over USB. fs
in this case is a global pointer to that filesystem object; initially NULL as we’ve not set it up yet.
A little later, in the setup()
function, fs
is then initialized by calling Adafruit_CPFS::
begin
like so:
fs = Adafruit_CPFS::begin(true, -1, NULL, false);
This is an unusual syntax in that all Adafruit_CPFS functions are “static” — they do not require declaring a special object type (though one can if desired), the flipside being that all function calls to that library begin with Adafruit_CPFS::
, with the two colons.
More of interest here are all the arguments passed to the begin()
function:
- The initial
true
argument tells the library to make the filesystem available both to our sketch and to a host computer over USB. - The
-1
andNULL
arguments have no meaning here. The Adafruit_CPFS library works on a variety of different hardware, and these are specific to other devices. Necessary but ignored here. - The trailing
false
argument is critical when combining Adafruit_CPFS and PicoDVI on an RP2040 microcontroller. This keeps the second core running at all times (where the DVI-generating code resides), where CPFS would normally want to pause it during writes.
If this were a non-PicoDVI project, all those arguments would be unnecessary, and begin()
would just be called like so:
fs = Adafruit_CPFS::begin();
A few lines down, after the DVI display has been initialized, the value of the fs
variable is checked to see whether the flash filesystem is present and working:
if (fs == NULL) { // If CIRCUITPY filesystem is missing or malformed... // Show error message & fast blink LED to indicate problem. Full stop. display.println("Can't access board's CIRCUITPY drive."); display.println("Has CircuitPython been previously installed?"); for (;;) digitalWrite(LED_BUILTIN, (millis() / 250) & 1); } // else valid CIRCUITPY drive, proceed...
And, after that point, fs
can be treated like a normal filesystem object, say if you’ve used the Arduino SD library before. Only difference is, because it’s a pointer to an object, one uses C’s indirection syntax ->
rather than .
:
fs->open("settings.json", FILE_READ);
The prospect of sometimes re-loading a board is frustrating but also interesting how it came about. The RP2040 chip has no internal flash memory; code is read from external flash, which also holds the CIRCUITPY flash filesystem. It’s not possible to simultaneously write or erase one part of flash while executing code from another…normally, all activity has to stop for this, which PicoDVI (generating time-critical signals) abhors. Getting all these to even sort of play nice together took some work.
Here are some steps one can take to avoid The Crash:
- Consider having your Arduino sketch treat the CIRCUITPY drive as read-only; steer clear of writing or creating new files, and open files in
FILE_READ
mode only. There’s nothing preventing writing files, and you’re welcome to try and see how it goes, but you take your chances. This is why the CP/M emulator project uses a microSD card rather than the internal flash; there’s no contention for resources that way.
- For a crucial situation or live demo, don’t power the board off a computer’s USB port, and instead use a passive source like a USB power supply or a LiPoly battery. If there’s nothing trying to mount the drive and touch files, operation should be reliable.
- Consider adding a switch or button to your circuit that can be polled on startup, and only present the filesystem over USB in one state or the other using the first argument to
Adafruit_CPFS::begin
()
(see code snippet above). An initialtrue
value makes the drive computer-writable, whilefalse
makes it so only the Arduino code sees the drive.
- Perhaps take the switch idea one step further and don’t even start the PicoDVI display when the drive is mounted. With the switch one way, the computer sees the CIRCUITPY drive and you can load up new files, but there’s no display. Switched the other way, video works but the computer won’t see the drive. This should be fairly robust.
Text editor powered by tinymce.