Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae75ada25 |
9 changed files with 12 additions and 449 deletions
1
.nojekyll
Normal file
1
.nojekyll
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
// 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
107
Adafruit_CPFS.h
|
|
@ -1,107 +0,0 @@
|
|||
// 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
|
||||
21
LICENSE
21
LICENSE
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Adafruit Industries
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
33
README.md
33
README.md
|
|
@ -1,33 +0,0 @@
|
|||
# Adafruit_CPFS
|
||||
|
||||
[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.
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
}
|
||||
11
index.html
Normal file
11
index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="1;url=html/index.html">
|
||||
<title>Page Redirection</title>
|
||||
</head>
|
||||
<body>
|
||||
If you are not redirected automatically, follow the <a href="html/index.html">link to the documentation</a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
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
|
||||
Loading…
Reference in a new issue