Initial commit

This commit is contained in:
Phillip Burgess 2023-04-04 17:06:24 -07:00
parent 1871741ea3
commit c997b8832a
7 changed files with 431 additions and 1 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Our handy .gitignore for automation ease
Doxyfile*
doxygen_sqlite3.db
html

210
Adafruit_CPFS.cpp Normal file
View file

@ -0,0 +1,210 @@
// SPDX-FileCopyrightText: 2023 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*!
* @file Adafruit_CPFS.cpp
*
* This is a barebones library to:
*
* - Make a CircuitPython-capable board's flash filesystem accessible to
* Arduino code.
* - Make this same drive accessible to a host computer over USB.
*
* This is an "80/20" library to cover the most common use case, with least
* code and documentation, for non-technical users: if a board supports
* CircuitPython, then Arduino code and a host computer can both access that
* drive. Flash formatting is done by installing CircuitPython once
* (pre-built for just about everything), no special steps. That's it.
* NOT for SD cards, special flash partitioning, etc. Those can always be
* implemented manually using the Adafruit_TinyUSB library, but this is
* not the code for it. Keeping it really simple.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#if defined(USE_TINYUSB) || defined(ESP32)
#include "Adafruit_CPFS.h"
#include <Adafruit_SPIFlash.h>
#include <Adafruit_TinyUSB.h>
#if defined(_SAMD21_)
#include <Adafruit_InternalFlash.h>
// These apply to M0 boards only, ignored elsewhere:
#define INTERNAL_FLASH_FS_SIZE (64 * 1024)
#define INTERNAL_FLASH_FS_START (0x00040000 - 256 - 0 - INTERNAL_FLASH_FS_SIZE)
#endif
// Library state is maintained in a few global variables (rather than in the
// Adafruit_CPFS class) as there's only one filesystem instance anyway, and
// also that the mass storage callbacks are C and require access to this info.
// For several major board types, the correct flash transport to use
// is known at compile-time:
#if defined(ARDUINO_ARCH_ESP32)
static Adafruit_FlashTransport_ESP32 _transport;
#elif defined(ARDUINO_ARCH_RP2040)
static Adafruit_FlashTransport_RP2040_CPY _transport;
#elif defined(EXTERNAL_FLASH_USE_QSPI)
static Adafruit_FlashTransport_QSPI _transport;
#elif defined(EXTERNAL_FLASH_USE_CS) && defined(EXTERNAL_FLASH_USE_SPI)
static Adafruit_FlashTransport_SPI _transport(EXTERNAL_FLASH_USE_CS,
EXTERNAL_FLASH_USE_SPI);
#else
// If not one of the above board types, nor EXTERNAL_FLASH_USE_* defined,
// it's probably a SAMD21. Some can be "Haxpress" modified to add external
// SPI flash & run a special CircuitPython build, but Arduino IDE lacks a
// distinct special board select...at compile-time, indistinguishable from
// a stock M0 board, could go either way. Thus, the transport and flash
// members are pointers, initialized at run-time depending on arguments
// passed (or not) to the begin() function.
#define HAXPRESS
static Adafruit_FlashTransport_SPI *_transport;
static void *_flash;
#endif
#if !defined HAXPRESS
static Adafruit_SPIFlash _flash(&_transport);
#endif
static Adafruit_USBD_MSC _usb_msc;
static FatVolume _fatfs;
static bool _started = 0;
static bool _changed = 0;
#if defined(HAXPRESS)
// On Haxpress-capable boards, flash type (internal vs SPI) isn't known
// at compile time, so callbacks are provided for both, and one set or
// other is installed in begin().
static int32_t msc_read_cb_internal(uint32_t lba, void *buffer,
uint32_t bufsize) {
return ((Adafruit_InternalFlash *)_flash)
->readBlocks(lba, (uint8_t *)buffer, bufsize / 512)
? bufsize
: -1;
}
static int32_t msc_write_cb_internal(uint32_t lba, uint8_t *buffer,
uint32_t bufsize) {
_changed = 1;
return ((Adafruit_InternalFlash *)_flash)
->writeBlocks(lba, buffer, bufsize / 512)
? bufsize
: -1;
}
static void msc_flush_cb_internal(void) {
((Adafruit_InternalFlash *)_flash)->syncBlocks();
_fatfs.cacheClear();
}
static int32_t msc_read_cb_spi(uint32_t lba, void *buffer, uint32_t bufsize) {
return ((Adafruit_SPIFlash *)_flash)
->readBlocks(lba, (uint8_t *)buffer, bufsize / 512)
? bufsize
: -1;
}
static int32_t msc_write_cb_spi(uint32_t lba, uint8_t *buffer,
uint32_t bufsize) {
_changed = 1;
return ((Adafruit_SPIFlash *)_flash)->writeBlocks(lba, buffer, bufsize / 512)
? bufsize
: -1;
}
static void msc_flush_cb_spi(void) {
((Adafruit_SPIFlash *)_flash)->syncBlocks();
_fatfs.cacheClear();
}
#else
// Flash type is known at compile time. Simple callbacks.
static int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) {
return _flash.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize
: -1;
}
static int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) {
_changed = 1;
return _flash.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1;
}
static void msc_flush_cb(void) {
_flash.syncBlocks();
_fatfs.cacheClear();
}
#endif // end !HAXPRESS
FatVolume *Adafruit_CPFS::begin(int cs, void *spi) {
if (_started)
return &_fatfs; // Don't re-init if already running
_started = 1;
#if defined(HAXPRESS)
if ((cs >= 0) && (spi != NULL)) { // External flash
if ((_transport = new Adafruit_FlashTransport_SPI(cs, (SPIClass *)spi))) {
if ((_flash = (void *)new Adafruit_SPIFlash(_transport))) {
((Adafruit_SPIFlash *)_flash)->begin();
_usb_msc.setID("Adafruit", "External Flash", "1.0");
_usb_msc.setReadWriteCallback(msc_read_cb_spi, msc_write_cb_spi,
msc_flush_cb_spi);
_usb_msc.setCapacity(((Adafruit_SPIFlash *)_flash)->size() / 512, 512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
if (_fatfs.begin((Adafruit_SPIFlash *)_flash))
return &_fatfs;
} // end if new flash
} // end if new transport
} else { // Internal flash
if ((_flash = (void *)new Adafruit_InternalFlash(INTERNAL_FLASH_FS_START,
INTERNAL_FLASH_FS_SIZE))) {
((Adafruit_InternalFlash *)_flash)->begin();
_usb_msc.setID("Adafruit", "Internal Flash", "1.0");
_usb_msc.setReadWriteCallback(msc_read_cb_internal, msc_write_cb_internal,
msc_flush_cb_internal);
_usb_msc.setCapacity(((Adafruit_InternalFlash *)_flash)->size() / 512,
512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
if (_fatfs.begin((Adafruit_InternalFlash *)_flash))
return &_fatfs;
} // end if new flash
}
#else
_flash.begin();
_usb_msc.setID("Adafruit", "Onboard Flash", "1.0");
_usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb);
_usb_msc.setCapacity(_flash.size() / 512, 512);
_usb_msc.setUnitReady(true);
_usb_msc.begin();
if (_fatfs.begin(&_flash))
return &_fatfs;
#endif // end HAXPRESS
_started = 0;
return NULL;
}
bool Adafruit_CPFS::changed(void) { return _changed; }
void Adafruit_CPFS::change_ack(void) { _changed = 0; }
#endif // end USE_TINYUSB || ESP32

107
Adafruit_CPFS.h Normal file
View file

@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2023 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
/*!
* @file Adafruit_CPFS.h
*
* This is a barebones library to:
*
* - Make a CircuitPython-capable board's flash filesystem accessible to
* Arduino code.
* - Make this same drive accessible to a host computer over USB.
*
* This is an "80/20" library to cover the most common use case, with least
* code and documentation, for non-technical users: if a board supports
* CircuitPython, then Arduino code and a host computer can both access that
* drive. Flash formatting is done by installing CircuitPython once
* (pre-built for just about everything), no special steps. That's it.
* NOT for SD cards, special flash partitioning, etc. Those can always be
* implemented manually using the Adafruit_TinyUSB library, but this is
* not the code for it. Keeping it really simple.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries.
*
* MIT license, all text here must be included in any redistribution.
*
*/
#pragma once
#if defined(USE_TINYUSB) || defined(ESP32)
#include <SdFat.h>
/*!
@brief Adafruit_CPFS is a minimal class to assist in using a board's
CIRCUITPY flash filesystem with Arduino code, and making it
available to a host computer over USB.
All functions here are currently static -- you do not need to
declare an object (unless you want to). Since there's only one
CIRCUITPY filesystem, library state is maintained internally
and any of these functions can be called directly,
e.g. Adafruit_CPFS::begin(). Putting the functions inside a
class simply avoids namespace issues.
*/
class Adafruit_CPFS {
public:
/*!
@brief Adafruit_CPFS constructor. No arguments. User code is not
required to declare an object (all functions are static and can
be invoked directly without an object), but it's still an option
if the resulting code reads easier for you (e.g. using
object.func() rather than class::func() syntax), all good.
*/
Adafruit_CPFS(void){};
/*!
@brief Adafruit_CPFS destructor.
*/
~Adafruit_CPFS(void){};
/*!
@brief Access a board's CIRCUITPY flash filesystem, making it
available to code and to a host computer over USB.
IMPORTANT: this function should always be called BEFORE
Serial.begin().
@param cs OPTIONAL SPI flash chip-select pin. This should ONLY be
used on "Haxpress" boards (QT Py or Trinket M0
with flash chip retrofitted). For most boards,
including unmodified QT Py or Trinket M0, do not
pass any arguments.
@param spi OPTIONAL Pointer to SPI peripheral interfaced with flash
chip. Again, only for a couple of Haxpress M0
boards.
@return FatVolume* On success, a non-NULL pointer to a FatVolume
object, where files can then be opened and accessed.
NULL on error (uninitialized CIRCUITPY drive, or
invalid cs/spi combo)..
*/
static FatVolume *begin(int cs = -1, void *spi = NULL);
/*!
@brief Checks if USB-connected host computer has made any changes
(new or altered files) to the drive. Code can use this if it
needs to auto-restart on change.
@return 1/true if host computer has written to drive, 0/false otherwise.
*/
static bool changed(void);
/*!
@brief Acknowledge and reset status of changed() polling. Change-
sensitive code can call this to distinguish subsequent
changed() calls.
*/
static void change_ack(void);
};
#else
#error "Requires TinyUSB stack. From the Arduino IDE 'Tools' menu,"
#error "select 'USB Stack -> Adafruit TinyUSB' and recompile."
#endif // end USE_TINYUSB || ESP32

View file

@ -1,2 +1,33 @@
# Adafruit_CPFS
[C]ircuit[P]ython [F]ile[S]ystem library for Arduino
[C]ircuit[P]ython [F]ile[S]ystem library for Arduino.
This is a small library that does one...well okay, two...things:
* Makes a CircuitPython-capable board's flash filesystem accessible to
Arduino code.
* Make this same drive accessible to a host computer over USB.
Nothing new here, same can be done using Adafruit_TinyUSB and SdFat, this
simply wraps the code in a library out of sight, as it's normally quite a
tangle of #ifdefs. NOT for SD cards or unusual flash partitioning. Again,
those can be implemented with the aforementioned libraries. This is an
"80/20" library to cover the most common use case, with least code and
documentation, for non-technical users: if a board supports CircuitPython,
then Arduino code and a host computer can both access that drive. Flash
formatting is done by installing CircuitPython once (pre-built for just
about everything), no special steps. That's it.
When an Arduino sketch using this library is uploaded to the board,
CircuitPython will be overwritten, but the flash drive contents remain
intact and are available to our code. CircuitPython can be reinstalled
later via bootloader, drive again remains intact. Best of both worlds!
Must enable TinyUSB before compiling:
Tools -> USB Stack -> Adafruit_TinyUSB
IMPORTANT: keep a backup of the CIRCUITPY drive contents somewhere safe.
USB + mass storage + Serial is pretty demanding, and accidents do happen.
You may need to reinstall CircuitPython to initialize a botched drive.
See examples/simple for use.

View file

View file

@ -0,0 +1,68 @@
/*
Barebones example for Adafruit_CPFS. Lists CIRCUITPY drive contents to
the Serial console. If anything on the drive changes, a new listing is
shown.
Board must have CircuitPython installed at least once to initialize
the flash filesystem. When this Arduino sketch is uploaded to board,
CircuitPython will be overwritten, but the flash drive contents remain
intact and are available to our code. CircuitPython can be reinstalled
later via bootloader, drive again remains intact. Best of both worlds!
Must enable TinyUSB before compiling:
Tools -> USB Stack -> Adafruit_TinyUSB
IMPORTANT: keep a backup of the CIRCUITPY drive contents somewhere safe.
USB + mass storage + Serial is pretty demanding, and accidents do happen.
*/
#include <Adafruit_CPFS.h>
FatVolume *fs = NULL; // CIRCUITPY flash filesystem, as a FAT pointer
void setup(void) {
// Start the CIRCUITPY flash filesystem FIRST. Very important!
fs = Adafruit_CPFS::begin();
// For "Haxpress" boards (small M0 boards retrofitted with SPI flash),
// a chip-select pin and/or SPI instance can be passed to begin():
// fs = Adafruit_CPFS::begin(SS1, &SPI1); // QT Py M0 Haxpress
// Start Serial AFTER Adafruit_CPFS, or CIRCUITPY won't show on computer.
Serial.begin(115200);
//while(!Serial);
pinMode(LED_BUILTIN, OUTPUT);
if (fs == NULL) { // If CIRCUITPY filesystem is missing or malformed...
// Show error message & blink LED to indicate problem. Full stop.
Serial.println("Can't access board's CIRCUITPY drive.");
Serial.println("Has CircuitPython been previously installed?");
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
} // else valid CIRCUITPY drive, proceed...
// Most simple programs can jump right in at this point, such as reading
// settings or graphics files. Because this particular example monitors
// for changes in the filesystem, it's good to pause here -- a LOT happens
// in begin() when the filesystem is connected to USB, and many rapid-fire
// change events occur. Allow a couple seconds to settle...
delay(2500);
Adafruit_CPFS::change_ack(); // Clear any pent-up change notifications
// Then access files and directories using any SdFat calls (open(), etc.)
// Because fs is a pointer, we use "->" indirection rather than "." access.
fs->ls("/", LS_R | LS_SIZE); // List initial drive contents
}
void loop(void) {
if (Adafruit_CPFS::changed()) { // Anything changed on CIRCUITPY drive?
Adafruit_CPFS::change_ack(); // Got it, thanks.
Serial.println("CIRCUITPY drive contents changed.");
fs->ls("/", LS_R | LS_SIZE); // List updated drive contents
}
// Note that "changes" are often inconsequential -- updating the last-
// touch times when clicking a file from the host computer, for example.
// You might see the directory listing refresh multiple times even when
// nothing of much substance has occurred on the drive. This is normal.
// Most projects need not even concern themselves with change detection.
}

10
library.properties Normal file
View file

@ -0,0 +1,10 @@
name=Adafruit CPFS
version=1.0.0
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=Arduino library for accessing a board's CircuitPython flash filesystem and presenting it over USB.
paragraph=Arduino library for accessing a board's CircuitPython flash filesystem and presenting it over USB.
category=Data Storage
url=https://github.com/adafruit/Adafruit_CPFS
architectures=samd, rp2040, esp32
depends=SdFat - Adafruit Fork, Adafruit SPIFlash, Adafruit TinyUSB Library, Adafruit InternalFlash, FlashStorage