Add USB drive mode to TinyUSB, SingleFileDisk (#1034)

SingleFileDisk allows for exporting a file from the onboard LittleFS
filesystem to a PC through an emulated FAT drive when connected.  The
PC can open and copy the file, as well as delete it, but the PC has no
access to the main onboard LittleFS and no actual on-flash FAT
structures are used.

This is handy for things like data loggers.  They can run connected to
USB power for some time, and then connected to a PC to dowmload the CSV
log recorded.

It's almost 2023, allow LFN (long file names) on the emulated USB disk.

Reduce the disk buffer size to 64 bytes.  The buffer is statically
allocated so it's always present, even in non-USB disk mode, meaning
all apps will pay the RAM price for it.  64 bytes is slower to read
but works and saves ~1/2KB of heap for all apps.
This commit was merged in pull request #1034.
This commit is contained in:
Earle F. Philhower, III
2022-12-09 13:59:23 -08:00
committed by GitHub
parent 80d6e2f0ec
commit fca7fb5e0f
15 changed files with 743 additions and 12 deletions
+69 -4
View File
@@ -68,9 +68,11 @@ static int __usb_task_irq;
#define USBD_STR_SERIAL (0x03)
#define USBD_STR_CDC (0x04)
#define EPNUM_HID 0x83
#define USBD_MSC_EPOUT 0x03
#define USBD_MSC_EPIN 0x84
#define USBD_MSC_EPSIZE 64
const uint8_t *tud_descriptor_device_cb(void) {
static tusb_desc_device_t usbd_desc_device = {
@@ -89,7 +91,7 @@ const uint8_t *tud_descriptor_device_cb(void) {
.iSerialNumber = USBD_STR_SERIAL,
.bNumConfigurations = 1
};
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallJoystick) {
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallJoystick && !__USBInstallMassStorage) {
// Can use as-is, this is the default USB case
return (const uint8_t *)&usbd_desc_device;
}
@@ -103,6 +105,9 @@ const uint8_t *tud_descriptor_device_cb(void) {
if (__USBInstallJoystick) {
usbd_desc_device.idProduct |= 0x0100;
}
if (__USBInstallMassStorage) {
usbd_desc_device.idProduct ^= 0x2000;
}
// Set the device class to 0 to indicate multiple device classes
usbd_desc_device.bDeviceClass = 0;
usbd_desc_device.bDeviceSubClass = 0;
@@ -223,7 +228,7 @@ void __SetupUSBDescriptor() {
if (!usbd_desc_cfg) {
bool hasHID = __USBInstallKeyboard || __USBInstallMouse || __USBInstallJoystick;
uint8_t interface_count = (__USBInstallSerial ? 2 : 0) + (hasHID ? 1 : 0);
uint8_t interface_count = (__USBInstallSerial ? 2 : 0) + (hasHID ? 1 : 0) + (__USBInstallMassStorage ? 1 : 0);
uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
@@ -238,7 +243,12 @@ void __SetupUSBDescriptor() {
TUD_HID_DESCRIPTOR(hid_itf, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10)
};
int usbd_desc_len = TUD_CONFIG_DESC_LEN + (__USBInstallSerial ? sizeof(cdc_desc) : 0) + (hasHID ? sizeof(hid_desc) : 0);
uint8_t msd_itf = interface_count - 1;
uint8_t msd_desc[TUD_MSC_DESC_LEN] = {
TUD_MSC_DESCRIPTOR(msd_itf, 0, USBD_MSC_EPOUT, USBD_MSC_EPIN, USBD_MSC_EPSIZE)
};
int usbd_desc_len = TUD_CONFIG_DESC_LEN + (__USBInstallSerial ? sizeof(cdc_desc) : 0) + (hasHID ? sizeof(hid_desc) : 0) + (__USBInstallMassStorage ? sizeof(msd_desc) : 0);
uint8_t tud_cfg_desc[TUD_CONFIG_DESC_LEN] = {
// Config number, interface count, string index, total length, attribute, power in mA
@@ -260,6 +270,10 @@ void __SetupUSBDescriptor() {
memcpy(ptr, hid_desc, sizeof(hid_desc));
ptr += sizeof(hid_desc);
}
if (__USBInstallMassStorage) {
memcpy(ptr, msd_desc, sizeof(msd_desc));
ptr += sizeof(msd_desc);
}
}
}
}
@@ -367,4 +381,55 @@ extern "C" void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_r
(void) bufsize;
}
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) __attribute__((weak));
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
(void) lun;
(void) lba;
(void) offset;
(void) buffer;
(void) bufsize;
return -1;
}
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) __attribute__((weak));
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
(void) lun;
return false;
}
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) __attribute__((weak));
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
(void) lun;
(void) lba;
(void) offset;
(void) buffer;
(void) bufsize;
return -1;
}
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) __attribute__((weak));
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
(void) lun;
(void) scsi_cmd;
(void) buffer;
(void) bufsize;
return 0;
}
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) __attribute__((weak));
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
(void) lun;
*block_count = 0;
*block_size = 0;
}
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) __attribute__((weak));
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
(void) lun;
vendor_id[0] = 0;
product_id[0] = 0;
product_rev[0] = 0;
}
#endif
+1
View File
@@ -26,6 +26,7 @@ extern void __USBInstallSerial() __attribute__((weak));
extern void __USBInstallKeyboard() __attribute__((weak));
extern void __USBInstallJoystick() __attribute__((weak));
extern void __USBInstallMouse() __attribute__((weak));
extern void __USBInstallMassStorage() __attribute__((weak));
// Big, global USB mutex, shared with all USB devices to make sure we don't
// have multiple cores updating the TUSB state in parallel
+2
View File
@@ -39,6 +39,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
USB (Arduino and Adafruit_TinyUSB) <usb>
Multicore Processing <multicore>
Single File USB Drive <singlefile>
FreeRTOS SMP (multicore) <freertos>
WiFi (Pico-W Support) <wifi>
+82
View File
@@ -0,0 +1,82 @@
SingleFileDrive
===============
USB drive mode is supported through the ``SingleFileDrive`` class which
allows the Pico to emulate a FAT-formatted USB stick while preserving the
onboard LittleFS filesystem. A single file can be exported this way without
needing to use FAT as the onboard filesystem (FAT is not appropriate for
flash-based devices without complicated wear leveling because of the update
frequency of the FAT tables).
This emulation is very simple and only allows for the reading of the single
file, and deleting it.
Callbacks, Interrupt Safety, and File Operations
------------------------------------------------
The ``SingleFileDrive`` library allows your application to get a callback
when a PC attempts to mount or unmount the Pico as a drive. Your app can
also get a callback if the user attempts to delete the file (but your
sketch does not actually need to delete the file, it's up to you).
Note that when the USB drive is mounted by a PC it is not safe for your
main sketch to make changes to the LittleFS filesystem or the file being
exported. So, normally, your ``onPlug`` callback will set a flag letting
your application know not to touch the filesystem, with the ``onUnplug``
callback clearing this flag.
Also, because the USB port can be connected at any time, it is important
to disable interrupts using ``noInterrupts()`` before writing to a file
you will be exporting (and restoring them with ``interrupts()`` afterwards).
It is also important to ``close()`` the file after each update, or the
on-flash version the ``SingleFileDrive`` will attempt to export may not be
up to date causing issues later on.
See the included ``DataLoggerUSB`` sketch for an example of working with
these limitations.
Using SingleFileDrive
---------------------
Implementing the drive requires including the header file, starting LittleFS,
defining your callbacks, and telling the library what file to export. No
polling or other calls are required outside of your ``setup()``. (Note that
the callback routines allow for a parameter to be passed to them, but in most
cases this can be safely ignored.)
.. code:: cpp
#include <LittleFS.h>
#include <SingleFileDrive.h>
void myPlugCB(uint32_t data) {
// Tell my app not to write to flash, we're connected
}
void myUnplugCB(uint32_t data) {
// I can start writing to flash again
}
void myDeleteDB(uint32_t data) {
// Maybe LittleFS.remove("myfile.txt")? or do nothing
}
void setup() {
LittleFS.begin();
singleFileDrive.onPlug(myPlugCB);
singleFileDrive.onUnplug(myUnplugCB);
singleFileDrive.onDelete(myDeleteCB);
singleFileDrive.begin("littlefsfile.csv", "Data Recorder.csv");
// ... rest of setup ...
}
void loop() {
// Take some measurements, delay, etc.
if (okay-to-write) {
noInterrupts();
File f = LittleFS.open("littlefsfile.csv", "a");
f.printf("%d,%d,%d\n", data1, data2, data3);
f.close();
interrupts();
}
}
+2 -3
View File
@@ -72,15 +72,14 @@
//------------- CLASS -------------//
#define CFG_TUD_HID (2)
#define CFG_TUD_CDC (1)
#define CFG_TUD_MSC (0)
#define CFG_TUD_MSC (1)
#define CFG_TUD_MIDI (0)
#define CFG_TUD_VENDOR (0)
#define CFG_TUD_CDC_RX_BUFSIZE (256)
#define CFG_TUD_CDC_TX_BUFSIZE (256)
#define CFG_TUD_MIDI_RX_BUFSIZE (64)
#define CFG_TUD_MIDI_TX_BUFSIZE (64)
#define CFG_TUD_MSC_EP_BUFSIZE (64)
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE (64)
Binary file not shown.
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -5,6 +5,6 @@ maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Configures requests for OTA bootloader
paragraph=Example repository for Ethernet drivers
category=Device Control
url=https://github.com/earlephilhower.arduino-pico
url=https://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
@@ -0,0 +1,92 @@
// Simple logger with USB upload to PC
// Uses SingleFileDrive to export an onboard LittleFS file to the computer
// The PC can open/copy the file, and then the user can delete it to restart
// Released to the public domain, 2022 - Earle F. Philhower, III
#include <SingleFileDrive.h>
#include <LittleFS.h>
uint32_t cnt = 0;
bool okayToWrite = true;
// Make the CSV file and give it a simple header
void headerCSV() {
File f = LittleFS.open("data.csv", "w");
f.printf("sample,millis,temp,rand\n");
f.close();
cnt = 0;
}
// Called when the USB stick connected to a PC and the drive opened
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
void plug(uint32_t i) {
(void) i;
okayToWrite = false;
}
// Called when the USB is ejected or removed from a PC
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
void unplug(uint32_t i) {
(void) i;
okayToWrite = true;
}
// Called when the PC tries to delete the single file
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
void deleteCSV(uint32_t i) {
(void) i;
LittleFS.remove("data.csv");
headerCSV();
}
void setup() {
Serial.begin();
delay(5000);
LittleFS.begin();
// Set up the USB disk share
singleFileDrive.onDelete(deleteCSV);
singleFileDrive.onPlug(plug);
singleFileDrive.onUnplug(unplug);
singleFileDrive.begin("data.csv", "Recorded data from the Raspberry Pi Pico.csv");
// Find the last written data
File f = LittleFS.open("data.csv", "r");
if (!f || !f.size()) {
cnt = 1;
headerCSV();
} else {
if (f.size() > 2048) {
f.seek(f.size() - 1024);
}
do {
String s = f.readStringUntil('\n');
sscanf(s.c_str(), "%lu,", &cnt);
} while (f.available());
f.close();
cnt++;
}
Serial.printf("Starting acquisition at %d\n", cnt);
}
void loop() {
float temp = analogReadTemp();
uint32_t hwrand = rp2040.hwrand32();
// Make sure the USB connect doesn't happen while we're writing!
noInterrupts();
if (okayToWrite) {
Serial.printf("Sampling...%lu\n", cnt);
// Don't want the USB to connect during an update!
File f = LittleFS.open("data.csv", "a");
if (f) {
f.printf("%lu,%lu,%f,%lu\n", cnt++, millis(), temp, hwrand);
f.close();
}
}
interrupts();
delay(10000);
}
+22
View File
@@ -0,0 +1,22 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SingleFileDrive KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
singleFileDrive KEYWORD1
onDelete KEYWORD1
onPlug KEYWORD1
onUnplug KEYWORD1
#######################################
# Constants (LITERAL1)
#######################################
@@ -0,0 +1,10 @@
name=SingleFileDrive
version=1.0.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Allows using USB MSC (USB stick) to transfer an onboard flash file to a PC
paragraph=Emulates a USB stick and presents a single file for users to copy over/erase
category=Device Control
url=https://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
@@ -0,0 +1,396 @@
/*
SingleFileDrive - Emulates a USB stick for easy data transfer
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <SingleFileDrive.h>
#include <LittleFS.h>
#include <class/msc/msc.h>
SingleFileDrive singleFileDrive;
static const uint32_t _hddsize = (256 * 1024 * 1024); // 256MB
static const uint32_t _hddsects = _hddsize / 512;
// Ensure we are logged in to the USB framework
void __USBInstallMassStorage() {
/* dummy */
}
SingleFileDrive::SingleFileDrive() {
}
SingleFileDrive::~SingleFileDrive() {
end();
}
void SingleFileDrive::onDelete(void (*cb)(uint32_t), uint32_t cbData) {
_cbDelete = cb;
_cbDeleteData = cbData;
}
void SingleFileDrive::onPlug(void (*cb)(uint32_t), uint32_t cbData) {
_cbPlug = cb;
_cbPlugData = cbData;
}
void SingleFileDrive::onUnplug(void (*cb)(uint32_t), uint32_t cbData) {
_cbUnplug = cb;
_cbUnplugData = cbData;
}
bool SingleFileDrive::begin(const char *localFile, const char *dosFile) {
if (_started) {
return false;
}
_localFile = strdup(localFile);
_dosFile = strdup(dosFile);
_started = true;
return true;
}
void SingleFileDrive::end() {
_started = false;
free(_localFile);
free(_dosFile);
_localFile = nullptr;
_dosFile = nullptr;
}
void SingleFileDrive::bootSector(char buff[512]) {
// 256MB FAT16 stolen from mkfs.fat
// dd if=/dev/zero of=/tmp/fat.bin bs=1M seek=255 count=1
// mkfs.fat -F 16 -r 16 -n PICODISK -i 12345678 -s 128 -m ':(' /tmp/fat.bin
const uint8_t hdr[] = {
0xeb, 0x3c, 0x90, 0x6d, 0x6b, 0x66, 0x73, 0x2e, 0x66, 0x61, 0x74, 0x00,
0x02, 0x80, 0x80, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0xf8, 0x80, 0x00,
0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x80, 0x00, 0x29, 0x78, 0x56, 0x34, 0x12, 0x50, 0x49, 0x43, 0x4f, 0x44,
0x49, 0x53, 0x4b, 0x20, 0x20, 0x20, 0x46, 0x41, 0x54, 0x31, 0x36, 0x20,
0x20, 0x20, 0x0e, 0x1f, 0xbe, 0x5b, 0x7c, 0xac, 0x22, 0xc0, 0x74, 0x0b,
0x56, 0xb4, 0x0e, 0xbb, 0x07, 0x00, 0xcd, 0x10, 0x5e, 0xeb, 0xf0, 0x32,
0xe4, 0xcd, 0x16, 0xcd, 0x19, 0xeb, 0xfe, 0x3a, 0x28, 0x0d, 0x0a, 0x00
};
memset(buff, 0, 512);
memcpy(buff, hdr, sizeof(hdr));
buff[0x1fe] = 0x55;
buff[0x1ff] = 0xff;
}
static char _toLegalFATChar(char c) {
const char *odds = "!#$%&'()-@^_`{}~";
c = toupper(c);
if (((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || strchr(odds, c)) {
return c;
} else {
return '~';
}
}
void SingleFileDrive::directorySector(char buff[512]) {
const uint8_t lbl[] = {
0x50, 0x49, 0x43, 0x4f, 0x44, 0x49, 0x53, 0x4b, 0x20, 0x20, 0x20, 0x08, 0x00, 0x00, 0xac, 0x56,
0x82, 0x55, 0x82, 0x55, 0x00, 0x00, 0xac, 0x56, 0x82, 0x55
}; //, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
memset(buff, 0, 512);
memcpy(buff, lbl, sizeof(lbl));
buff += 32; // Skip the just-set label
// Create a legal 11-char UPPERCASE FILENAME WITH 0x20 PAD
char SFN[11];
memset(SFN, ' ', 11);
for (int i = 0; (i < 8) && _dosFile[i] && (_dosFile[i] != '.'); i++) {
SFN[i] = _toLegalFATChar(_dosFile[i]);
}
char *dot = _dosFile + strlen(_dosFile) - 1;
while ((dot >= _dosFile) && (*dot != '.')) {
dot--;
}
if (*dot == '.') {
dot++;
for (int i = 0; (i < 3) && dot[i]; i++) {
SFN[8 + i] = _toLegalFATChar(dot[i]);
}
}
uint8_t chksum = 0; // for LFN
for (int i = 0; i < 11; i++) {
chksum = (chksum >> 1) + (chksum << 7) + SFN[i];
}
// Create LFN structure
int entries = (strlen(_dosFile) + 12) / 13; // round up
for (int i = 0; i < entries; i++) {
*buff++ = (entries - i) | (i == 0 ? 0x40 : 0);
const char *partname = _dosFile + 13 * (entries - i - 1);
for (int j = 0; j < 13; j++) {
uint16_t u;
if (j > (int)strlen(partname)) {
u = 0xffff;
} else {
u = partname[j] & 0xff;
}
*buff++ = u & 0xff;
*buff++ = (u >> 8) & 0xff;
if (j == 4) {
*buff++ = 0x0f; // LFN ATTR
*buff++ = 0;
*buff++ = chksum;
} else if (j == 10) {
*buff++ = 0;
*buff++ = 0;
}
}
}
// Create SFN
memset(buff, 0, 32);
for (int i = 0; i < 11; i++) {
buff[i] = SFN[i];
}
buff[0x0b] = 0x20; // ATTR = Archive
// Ignore creation data/time, etc.
buff[0x1a] = 0x03; // Starting cluster 3
File f = LittleFS.open(_localFile, "r");
int size = f.size();
f.close();
buff[0x1c] = size & 255;
buff[0x1d] = (size >> 8) & 255;
buff[0x1e] = (size >> 16) & 255; // 16MB or smaller
}
void SingleFileDrive::fatSector(char fat[512]) {
memset(fat, 0, 512);
fat[0x00] = 0xff;
fat[0x01] = 0xf8;
fat[0x02] = 0xff;
fat[0x03] = 0xff;
int cluster = 3;
File f = LittleFS.open(_localFile, "r");
int size = f.size();
f.close();
while (size > 65536) {
fat[cluster * 2] = (cluster + 1) & 0xff;
fat[cluster * 2 + 1] = ((cluster + 1) >> 8) & 0xff;
cluster++;
size -= 65536;
}
fat[cluster * 2] = 0xff;
fat[cluster * 2 + 1] = 0xff;
}
// Invoked to determine max LUN
extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
return 1;
}
// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
(void) lun;
const char vid[] = "PicoDisk";
const char pid[] = "Mass Storage";
const char rev[] = "1.0";
memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, pid, strlen(pid));
memcpy(product_rev, rev, strlen(rev));
}
bool SingleFileDrive::testUnitReady() {
return _started;
}
// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
(void) lun;
return singleFileDrive.testUnitReady();
}
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
(void) lun;
*block_count = _hddsects;
*block_size = 512;
}
// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
(void) lun;
return singleFileDrive.read10(lba, offset, buffer, bufsize);
}
int32_t SingleFileDrive::read10(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
if (!_started || (lba >= _hddsects)) {
return -1;
}
uint32_t toread = bufsize;
char buff[512];
uint8_t *curbuff = (uint8_t *)buffer;
while (bufsize > 0) {
if (lba == 0) {
bootSector(buff);
} else if ((lba == 128) || (lba == 256)) {
fatSector(buff);
} else if (lba == 384) {
directorySector(buff);
} else if (lba >= 640) {
File f = LittleFS.open(_localFile, "r");
f.seek((lba - 640) * 512);
f.read((uint8_t*)buff, 512);
f.close();
} else {
memset(buff, 0, sizeof(buff));
}
uint32_t cplen = 512 - offset;
if (bufsize < cplen) {
cplen = bufsize;
}
memcpy(curbuff, buff + offset, cplen);
curbuff += cplen;
offset = 0;
lba++;
bufsize -= cplen;
}
return toread;
}
extern "C" bool tud_msc_is_writable_cb(uint8_t lun) {
(void) lun;
return true;
}
// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
(void) lun;
return singleFileDrive.write10(lba, offset, buffer, bufsize);
}
int32_t SingleFileDrive::write10(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
if (!_started || (lba >= _hddsects)) {
return -1;
}
uint32_t addr = lba * 512 + offset;
uint32_t hotspot = 384 * 512 + 0x20;
if ((addr > hotspot) || (addr + bufsize < hotspot)) {
// Did not try and erase the file entry, ignore
return bufsize;
}
int off = hotspot - addr;
uint8_t *ptr = (uint8_t *)buffer;
ptr += off;
if (*ptr == 0xe5) {
if (_cbDelete) {
_cbDelete(_cbDeleteData);
}
}
return bufsize;
}
extern "C" bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier);
// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
const int SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E;
const int SCSI_CMD_START_STOP_UNIT = 0x1B;
const int SCSI_SENSE_ILLEGAL_REQUEST = 0x05;
void const* response = NULL;
int32_t resplen = 0;
// most scsi handled is input
bool in_xfer = true;
scsi_start_stop_unit_t const * start_stop = (scsi_start_stop_unit_t const *) scsi_cmd;
switch (scsi_cmd[0]) {
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
// Host is about to read/write etc ... better not to disconnect disk
if (scsi_cmd[4] & 1) {
singleFileDrive.plug();
}
resplen = 0;
break;
case SCSI_CMD_START_STOP_UNIT:
// Host try to eject/safe remove/poweroff us. We could safely disconnect with disk storage, or go into lower power
if (!start_stop->start && start_stop->load_eject) {
singleFileDrive.unplug();
} else if (start_stop->start && start_stop->load_eject) {
singleFileDrive.plug();
}
resplen = 0;
break;
default:
// Set Sense = Invalid Command Operation
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
// negative means error -> tinyusb could stall and/or response with failed status
resplen = -1;
break;
}
// return resplen must not larger than bufsize
if (resplen > bufsize) {
resplen = bufsize;
}
if (response && (resplen > 0)) {
if (in_xfer) {
memcpy(buffer, response, resplen);
} else {
// SCSI output
}
}
return resplen;
}
void SingleFileDrive::plug() {
if (_started && _cbPlug) {
_cbPlug(_cbPlugData);
}
}
void SingleFileDrive::unplug() {
if (_started && _cbUnplug) {
_cbUnplug(_cbUnplugData);
}
}
// Callback invoked on start/stop
extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) {
(void) lun;
(void) power_condition;
if (start && load_eject) {
singleFileDrive.plug();
} else if (!start && load_eject) {
singleFileDrive.unplug();
}
return true;
}
@@ -0,0 +1,63 @@
/*
SingleFileDrive - Emulates a USB stick for easy data transfer
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
class SingleFileDrive {
public:
SingleFileDrive();
~SingleFileDrive();
bool begin(const char *localFile, const char *dosFile);
void end();
void onDelete(void (*cb)(uint32_t), uint32_t cbData = 0);
void onPlug(void (*cb)(uint32_t), uint32_t cbData = 0);
void onUnplug(void (*cb)(uint32_t), uint32_t cbData = 0);
// Only for internal TinyUSB callback use
bool testUnitReady();
int32_t read10(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
int32_t write10(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
void plug();;
void unplug();
private:
void bootSector(char buff[512]);
void directorySector(char buff[512]);
void fatSector(char buff[512]);
private:
bool _started = false;
char *_localFile = nullptr;
char *_dosFile = nullptr;
void (*_cbDelete)(uint32_t) = nullptr;
uint32_t _cbDeleteData = 0;
void (*_cbPlug)(uint32_t) = nullptr;
uint32_t _cbPlugData = 0;
void (*_cbUnplug)(uint32_t) = nullptr;
uint32_t _cbUnplugData = 0;
};
extern SingleFileDrive singleFileDrive;
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S \
for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleFileDrive \
./libraries/LittleFS/src ./libraries/LittleFS/examples \
./libraries/rp2040 ./libraries/SD ./libraries/ESP8266SdFat \
./libraries/Servo ./libraries/SPI ./libraries/Wire ./libraries/PDM \
+2 -3
View File
@@ -72,15 +72,14 @@
//------------- CLASS -------------//
#define CFG_TUD_HID (2)
#define CFG_TUD_CDC (1)
#define CFG_TUD_MSC (0)
#define CFG_TUD_MSC (1)
#define CFG_TUD_MIDI (0)
#define CFG_TUD_VENDOR (0)
#define CFG_TUD_CDC_RX_BUFSIZE (256)
#define CFG_TUD_CDC_TX_BUFSIZE (256)
#define CFG_TUD_MIDI_RX_BUFSIZE (64)
#define CFG_TUD_MIDI_TX_BUFSIZE (64)
#define CFG_TUD_MSC_EP_BUFSIZE (64)
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE (64)