Initial files

This commit is contained in:
Stephen Barriball 2025-09-12 13:37:52 +01:00
parent 4432d3252f
commit 10ad74f50c
10 changed files with 795 additions and 21 deletions

Binary file not shown.

View File

@ -0,0 +1,192 @@
#ifndef ARDUINO_USB_MODE
#error This ESP32 SoC has no Native USB interface
#elif ARDUINO_USB_MODE == 1
#warning This sketch should be used when USB is in OTG mode
void setup() {}
void loop() {}
#else
#include "USB.h"
#include "USBMSC.h"
USBMSC MSC;
#define FAT_U8(v) ((v) & 0xFF)
#define FAT_U16(v) FAT_U8(v), FAT_U8((v) >> 8)
#define FAT_U32(v) FAT_U8(v), FAT_U8((v) >> 8), FAT_U8((v) >> 16), FAT_U8((v) >> 24)
#define FAT_MS2B(s, ms) FAT_U8(((((s) & 0x1) * 1000) + (ms)) / 10)
#define FAT_HMS2B(h, m, s) FAT_U8(((s) >> 1) | (((m) & 0x7) << 5)), FAT_U8((((m) >> 3) & 0x7) | ((h) << 3))
#define FAT_YMD2B(y, m, d) FAT_U8(((d) & 0x1F) | (((m) & 0x7) << 5)), FAT_U8((((m) >> 3) & 0x1) | ((((y) - 1980) & 0x7F) << 1))
#define FAT_TBL2B(l, h) FAT_U8(l), FAT_U8(((l >> 8) & 0xF) | ((h << 4) & 0xF0)), FAT_U8(h >> 4)
const char README_CONTENTS[] = "This is tinyusb's MassStorage Class demo.\r\n\r\nIf you find any bugs or get any questions, feel free to file an\r\nissue at github.com/hathach/tinyusb";
static const uint32_t DISK_SECTOR_COUNT = 500 * 8; // 8KB is the smallest size that windows allow to mount
static const uint16_t DISK_SECTOR_SIZE = 512; // Should be 512
static const uint16_t DISC_SECTORS_PER_TABLE = 1; //each table sector can fit 170KB (340 sectors)
static uint8_t* msc_disk[DISK_SECTOR_COUNT];
//static uint8_t msc_disk[DISK_SECTOR_COUNT][DISK_SECTOR_SIZE] = {
const uint8_t sector0[512] =
//------------- Block0: Boot Sector -------------//
{ // Header (62 bytes)
0xEB, 0x3C, 0x90, //jump_instruction
'M', 'S', 'D', 'O', 'S', '5', '.', '0', //oem_name
FAT_U16(DISK_SECTOR_SIZE), //bytes_per_sector
FAT_U8(1), //sectors_per_cluster
FAT_U16(1), //reserved_sectors_count
FAT_U8(1), //file_alloc_tables_num
FAT_U16(16), //max_root_dir_entries
FAT_U16(DISK_SECTOR_COUNT), //fat12_sector_num
0xF8, //media_descriptor
FAT_U16(DISC_SECTORS_PER_TABLE), //sectors_per_alloc_table;//FAT12 and FAT16
FAT_U16(1), //sectors_per_track;//A value of 0 may indicate LBA-only access
FAT_U16(1), //num_heads
FAT_U32(0), //hidden_sectors_count
FAT_U32(0), //total_sectors_32
0x00, //physical_drive_number;0x00 for (first) removable media, 0x80 for (first) fixed disk
0x00, //reserved
0x29, //extended_boot_signature;//should be 0x29
FAT_U32(0x1234), //serial_number: 0x1234 => 1234
'N', 'e', 't', 'F', 'l', 'o', 'p', 'p', 'y', ' ', ' ', //volume_label padded with spaces (0x20)
'F', 'A', 'T', '1', '2', ' ', ' ', ' ', //file_system_type padded with spaces (0x20)
// Zero up to 2 last bytes of FAT magic code (448 bytes)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//boot signature (2 bytes)
0x55, 0xAA
};
const uint8_t sector1[] = //------------- Block1: FAT12 Table -------------//
{
FAT_TBL2B(0xFF8, 0xFFF), FAT_TBL2B(0xFFF, 0x000) // first 2 entries must be 0xFF8 0xFFF, third entry is cluster end of readme file
};
const uint8_t sector2[512] = //------------- Block2: Root Directory -------------//
{
// first entry is volume label
'N', 'e', 't', 'F', 'l', 'o', 'p', 'p', 'y', ' ', ' ',
0x08, //FILE_ATTR_VOLUME_LABEL
0x00, FAT_MS2B(0, 0), FAT_HMS2B(0, 0, 0), FAT_YMD2B(0, 0, 0), FAT_YMD2B(0, 0, 0), FAT_U16(0), FAT_HMS2B(13, 42, 30), //last_modified_hms
FAT_YMD2B(2025, 8, 26), //last_modified_ymd
FAT_U16(0), FAT_U32(0),
// second entry is readme file
'F', 'i', 'l', 'e', ' ', '1', ' ', ' ', //file_name[8]; padded with spaces (0x20)
'T', 'X', 'T', //file_extension[3]; padded with spaces (0x20)
0x20, //file attributes: FILE_ATTR_ARCHIVE
0x00, //ignore
FAT_MS2B(1, 980), //creation_time_10_ms (max 199x10 = 1s 990ms)
FAT_HMS2B(13, 42, 36), //create_time_hms [5:6:5] => h:m:(s/2)
FAT_YMD2B(2018, 11, 5), //create_time_ymd [7:4:5] => (y+1980):m:d
FAT_YMD2B(2020, 11, 5), //last_access_ymd
FAT_U16(0), //extended_attributes
FAT_HMS2B(13, 44, 16), //last_modified_hms
FAT_YMD2B(2019, 11, 5), //last_modified_ymd
FAT_U16(2), //start of file in cluster
FAT_U32(sizeof(README_CONTENTS) - 1) //file size
};
//};
static int32_t onWrite(uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
Serial.printf("MSC WRITE: lba: %lu, offset: %lu, bufsize: %lu\n", lba, offset, bufsize);
memcpy(msc_disk[lba] + offset, buffer, bufsize);
return bufsize;
}
static int32_t onRead(uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) {
Serial.printf("MSC READ: lba: %lu, offset: %lu, bufsize: %lu\n", lba, offset, bufsize);
memcpy(buffer, msc_disk[lba] + offset, bufsize);
return bufsize;
}
static bool onStartStop(uint8_t power_condition, bool start, bool load_eject)
{
Serial.printf("MSC START/STOP: power: %u, start: %u, eject: %u\n", power_condition, start, load_eject);
return true;
}
static void usbEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_base == ARDUINO_USB_EVENTS)
{
arduino_usb_event_data_t *data = (arduino_usb_event_data_t *)event_data;
switch (event_id) {
case ARDUINO_USB_STARTED_EVENT: Serial.println("USB PLUGGED"); break;
case ARDUINO_USB_STOPPED_EVENT: Serial.println("USB UNPLUGGED"); break;
case ARDUINO_USB_SUSPEND_EVENT: Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en); break;
case ARDUINO_USB_RESUME_EVENT: Serial.println("USB RESUMED"); break;
default: break;
}
}
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
// Allocate each disk sector in PSRAM
for (uint x=0; x < DISK_SECTOR_COUNT; x++)
{
msc_disk[x] = (uint8_t*)ps_malloc(DISK_SECTOR_SIZE * sizeof(uint8_t));
if (!msc_disk[x])
{
Serial.println("Unable to allocate memory");
return;
}
}
memcpy(msc_disk[0], sector0, 512);
memcpy(msc_disk[1], sector1, 6);
memcpy(msc_disk[2], sector2, 512);
memcpy(msc_disk[3], README_CONTENTS, 512);
USB.onEvent(usbEventCallback);
MSC.vendorID("ACIT"); //max 8 chars
MSC.productID("NetFloppy"); //max 16 chars
MSC.productRevision("1.0"); //max 4 chars
MSC.onStartStop(onStartStop);
MSC.onRead(onRead);
MSC.onWrite(onWrite);
MSC.mediaPresent(true);
MSC.isWritable(true); // true if writable, false if read-only
MSC.begin(DISK_SECTOR_COUNT, DISK_SECTOR_SIZE);
USB.begin();
Serial.print("Total PSRAM:");
Serial.println(ESP.getPsramSize());
Serial.print("Free PSRAM: ");
Serial.println(ESP.getFreePsram());
}
void loop()
{
//
}
#endif /* ARDUINO_USB_MODE */

View File

@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_USB_OTG_SUPPORTED=y"
]
}

View File

@ -28,7 +28,6 @@
#include <FS.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "esp_tinyusb.h"
#include "tusb.h"
// ---------------- GPIO Configuration ----------------
@ -94,23 +93,29 @@ static const char* wifi_state() {
}
// ---------------- USB MSC (TinyUSB) ----------------
extern "C" {
int32_t tud_msc_read10_cb (uint8_t, uint32_t lba, uint32_t off, void* buf, uint32_t len){
if(!mscFile) return -1; uint64_t a=(uint64_t)lba*BYTES_PER_SECTOR + off;
if(!mscFile.seek(a)) return -1; return (int32_t)mscFile.read((uint8_t*)buf, len);
}
int32_t tud_msc_write10_cb(uint8_t, uint32_t lba, uint32_t off, uint8_t const* buf, uint32_t len){
if(!mscFile) return -1; uint64_t a=(uint64_t)lba*BYTES_PER_SECTOR + off;
if(!mscFile.seek(a)) return -1; return (int32_t)mscFile.write((const uint8_t*)buf, len);
}
bool tud_msc_is_writable_cb(uint8_t){ return true; }
bool tud_msc_test_unit_ready_cb(uint8_t){ return mscReady && mscFile; }
void tud_msc_capacity_cb(uint8_t, uint32_t* bc, uint16_t* bs){ *bc=mscBlocks; *bs=BYTES_PER_SECTOR; }
bool tud_msc_start_stop_cb(uint8_t, uint8_t, bool start, bool load_eject){
mscReady = (start && !load_eject && mscFile); return true;
}
//extern "C" {
//int32_t tud_msc_read10_cb (uint8_t, uint32_t lba, uint32_t off, void* buf, uint32_t len){
// if(!mscFile) return -1; uint64_t a=(uint64_t)lba*BYTES_PER_SECTOR + off;
// if(!mscFile.seek(a)) return -1; return (int32_t)mscFile.read((uint8_t*)buf, len);
// }
//int32_t tud_msc_write10_cb(uint8_t, uint32_t lba, uint32_t off, uint8_t const* buf, uint32_t len){
// if(!mscFile) return -1; uint64_t a=(uint64_t)lba*BYTES_PER_SECTOR + off;
// if(!mscFile.seek(a)) return -1; return (int32_t)mscFile.write((const uint8_t*)buf, len);
// }
//bool tud_msc_is_writable_cb(uint8_t){ return true; }
//bool tud_msc_test_unit_ready_cb(uint8_t){ return mscReady && mscFile; }
//void tud_msc_capacity_cb(uint8_t, uint32_t* bc, uint16_t* bs){ *bc=mscBlocks; *bs=BYTES_PER_SECTOR; }
//bool tud_msc_start_stop_cb(uint8_t, uint8_t, bool start, bool load_eject)
//{
// mscReady = (start && !load_eject && mscFile); return true;
//}
//}
static void usb_init()
{
tinyusb_config_t cfg={};
tusb_init(&cfg);
mscReady=false;
}
static void usb_init(){ tinyusb_config_t cfg={}; tusb_init(&cfg); mscReady=false; }
static bool vol_open(){
if(mscFile) mscFile.close();
@ -234,9 +239,9 @@ static bool https_post_volume(const String& id){
url += "/api/volume?id="; url += id;
WiFiClientSecure client;
client.setCACert(SERVER_CA_PEM);
client.setCertificate(CLIENT_CRT_PEM);
client.setPrivateKey(CLIENT_KEY_PEM);
//client.setCACert(SERVER_CA_PEM);
//client.setCertificate(CLIENT_CRT_PEM);
//client.setPrivateKey(CLIENT_KEY_PEM);
HTTPClient https; if(!https.begin(client, url)){ lastNote="begin fail"; return false; }
https.addHeader("Content-Type","application/octet-stream");
@ -322,7 +327,7 @@ static void h_remount(){
static void enter_loaded(){
// Read tag
uint64_t tag=0;
if(!rfid_read_tag(tag, 3000)){ lastNote="no tag"; return; }
//if(!rfid_read_tag(tag, 3000)){ lastNote="no tag"; return; }
String id = hex64(tag);
if(!https_fetch_volume(id)) return;
if(!vol_open()) return;

View File

@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_USB_OTG_SUPPORTED=y"
]
}

View File

@ -0,0 +1,192 @@
#ifndef ARDUINO_USB_MODE
#error This ESP32 SoC has no Native USB interface
#elif ARDUINO_USB_MODE == 1
#warning This sketch should be used when USB is in OTG mode
void setup() {}
void loop() {}
#else
#include "USB.h"
#include "USBMSC.h"
USBMSC MSC;
#define FAT_U8(v) ((v) & 0xFF)
#define FAT_U16(v) FAT_U8(v), FAT_U8((v) >> 8)
#define FAT_U32(v) FAT_U8(v), FAT_U8((v) >> 8), FAT_U8((v) >> 16), FAT_U8((v) >> 24)
#define FAT_MS2B(s, ms) FAT_U8(((((s) & 0x1) * 1000) + (ms)) / 10)
#define FAT_HMS2B(h, m, s) FAT_U8(((s) >> 1) | (((m) & 0x7) << 5)), FAT_U8((((m) >> 3) & 0x7) | ((h) << 3))
#define FAT_YMD2B(y, m, d) FAT_U8(((d) & 0x1F) | (((m) & 0x7) << 5)), FAT_U8((((m) >> 3) & 0x1) | ((((y) - 1980) & 0x7F) << 1))
#define FAT_TBL2B(l, h) FAT_U8(l), FAT_U8(((l >> 8) & 0xF) | ((h << 4) & 0xF0)), FAT_U8(h >> 4)
const char README_CONTENTS[] = "This is tinyusb's MassStorage Class demo.\r\n\r\nIf you find any bugs or get any questions, feel free to file an\r\nissue at github.com/hathach/tinyusb";
static const uint32_t DISK_SECTOR_COUNT = 500 * 8; // 8KB is the smallest size that windows allow to mount
static const uint16_t DISK_SECTOR_SIZE = 512; // Should be 512
static const uint16_t DISC_SECTORS_PER_TABLE = 1; //each table sector can fit 170KB (340 sectors)
static uint8_t* msc_disk[DISK_SECTOR_COUNT];
//static uint8_t msc_disk[DISK_SECTOR_COUNT][DISK_SECTOR_SIZE] = {
const uint8_t sector0[512] =
//------------- Block0: Boot Sector -------------//
{ // Header (62 bytes)
0xEB, 0x3C, 0x90, //jump_instruction
'M', 'S', 'D', 'O', 'S', '5', '.', '0', //oem_name
FAT_U16(DISK_SECTOR_SIZE), //bytes_per_sector
FAT_U8(1), //sectors_per_cluster
FAT_U16(1), //reserved_sectors_count
FAT_U8(1), //file_alloc_tables_num
FAT_U16(16), //max_root_dir_entries
FAT_U16(DISK_SECTOR_COUNT), //fat12_sector_num
0xF8, //media_descriptor
FAT_U16(DISC_SECTORS_PER_TABLE), //sectors_per_alloc_table;//FAT12 and FAT16
FAT_U16(1), //sectors_per_track;//A value of 0 may indicate LBA-only access
FAT_U16(1), //num_heads
FAT_U32(0), //hidden_sectors_count
FAT_U32(0), //total_sectors_32
0x00, //physical_drive_number;0x00 for (first) removable media, 0x80 for (first) fixed disk
0x00, //reserved
0x29, //extended_boot_signature;//should be 0x29
FAT_U32(0x1234), //serial_number: 0x1234 => 1234
'N', 'e', 't', 'F', 'l', 'o', 'p', 'p', 'y', ' ', ' ', //volume_label padded with spaces (0x20)
'F', 'A', 'T', '1', '2', ' ', ' ', ' ', //file_system_type padded with spaces (0x20)
// Zero up to 2 last bytes of FAT magic code (448 bytes)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//boot signature (2 bytes)
0x55, 0xAA
};
const uint8_t sector1[] = //------------- Block1: FAT12 Table -------------//
{
FAT_TBL2B(0xFF8, 0xFFF), FAT_TBL2B(0xFFF, 0x000) // first 2 entries must be 0xFF8 0xFFF, third entry is cluster end of readme file
};
const uint8_t sector2[512] = //------------- Block2: Root Directory -------------//
{
// first entry is volume label
'N', 'e', 't', 'F', 'l', 'o', 'p', 'p', 'y', ' ', ' ',
0x08, //FILE_ATTR_VOLUME_LABEL
0x00, FAT_MS2B(0, 0), FAT_HMS2B(0, 0, 0), FAT_YMD2B(0, 0, 0), FAT_YMD2B(0, 0, 0), FAT_U16(0), FAT_HMS2B(13, 42, 30), //last_modified_hms
FAT_YMD2B(2025, 8, 26), //last_modified_ymd
FAT_U16(0), FAT_U32(0),
// second entry is readme file
'F', 'i', 'l', 'e', ' ', '1', ' ', ' ', //file_name[8]; padded with spaces (0x20)
'T', 'X', 'T', //file_extension[3]; padded with spaces (0x20)
0x20, //file attributes: FILE_ATTR_ARCHIVE
0x00, //ignore
FAT_MS2B(1, 980), //creation_time_10_ms (max 199x10 = 1s 990ms)
FAT_HMS2B(13, 42, 36), //create_time_hms [5:6:5] => h:m:(s/2)
FAT_YMD2B(2018, 11, 5), //create_time_ymd [7:4:5] => (y+1980):m:d
FAT_YMD2B(2020, 11, 5), //last_access_ymd
FAT_U16(0), //extended_attributes
FAT_HMS2B(13, 44, 16), //last_modified_hms
FAT_YMD2B(2019, 11, 5), //last_modified_ymd
FAT_U16(2), //start of file in cluster
FAT_U32(sizeof(README_CONTENTS) - 1) //file size
};
//};
static int32_t onWrite(uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
Serial.printf("MSC WRITE: lba: %lu, offset: %lu, bufsize: %lu\n", lba, offset, bufsize);
memcpy(msc_disk[lba] + offset, buffer, bufsize);
return bufsize;
}
static int32_t onRead(uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) {
Serial.printf("MSC READ: lba: %lu, offset: %lu, bufsize: %lu\n", lba, offset, bufsize);
memcpy(buffer, msc_disk[lba] + offset, bufsize);
return bufsize;
}
static bool onStartStop(uint8_t power_condition, bool start, bool load_eject)
{
Serial.printf("MSC START/STOP: power: %u, start: %u, eject: %u\n", power_condition, start, load_eject);
return true;
}
static void usbEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_base == ARDUINO_USB_EVENTS)
{
arduino_usb_event_data_t *data = (arduino_usb_event_data_t *)event_data;
switch (event_id) {
case ARDUINO_USB_STARTED_EVENT: Serial.println("USB PLUGGED"); break;
case ARDUINO_USB_STOPPED_EVENT: Serial.println("USB UNPLUGGED"); break;
case ARDUINO_USB_SUSPEND_EVENT: Serial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", data->suspend.remote_wakeup_en); break;
case ARDUINO_USB_RESUME_EVENT: Serial.println("USB RESUMED"); break;
default: break;
}
}
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
// Allocate each disk sector in PSRAM
for (uint x=0; x < DISK_SECTOR_COUNT; x++)
{
msc_disk[x] = (uint8_t*)ps_malloc(DISK_SECTOR_SIZE * sizeof(uint8_t));
if (!msc_disk[x])
{
Serial.println("Unable to allocate memory");
return;
}
}
memcpy(msc_disk[0], sector0, 512);
memcpy(msc_disk[1], sector1, 6);
memcpy(msc_disk[2], sector2, 512);
memcpy(msc_disk[3], README_CONTENTS, 512);
USB.onEvent(usbEventCallback);
MSC.vendorID("ACIT"); //max 8 chars
MSC.productID("NetFloppy"); //max 16 chars
MSC.productRevision("1.0"); //max 4 chars
MSC.onStartStop(onStartStop);
MSC.onRead(onRead);
MSC.onWrite(onWrite);
MSC.mediaPresent(true);
MSC.isWritable(true); // true if writable, false if read-only
MSC.begin(DISK_SECTOR_COUNT, DISK_SECTOR_SIZE);
USB.begin();
Serial.print("Total PSRAM:");
Serial.println(ESP.getPsramSize());
Serial.print("Free PSRAM: ");
Serial.println(ESP.getFreePsram());
}
void loop()
{
//
}
#endif /* ARDUINO_USB_MODE */

View File

@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@ -0,0 +1,5 @@
{
"requires": [
"CONFIG_SOC_USB_OTG_SUPPORTED=y"
]
}

View File

@ -0,0 +1,362 @@
// ESP32-S2/S3 native USB MSC RAM disk for Arduino-ESP32 3.3.0
// Uses USB.h / USBMSC.h (no Adafruit_TinyUSB)
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include "USB.h"
#include "USBMSC.h"
#include <WiFi.h>
#include <Preferences.h>
#define LED_ON 2
#define LED_TX 43
#define LED_RX 44
#define BUTTON 0
#define WS2812 48
// ---- Disk parameters ----
static const uint32_t DISK_SECTOR_SIZE = 512; // 512 bytes/sector
static const uint32_t DISK_SECTOR_COUNT = 1000 * 8; // ~2 MB (4000 * 512)
static const uint32_t DISK_BYTE_SIZE = DISK_SECTOR_COUNT * DISK_SECTOR_SIZE;
static uint8_t* msc_disk = nullptr;
static volatile bool media_ready = false;
USBMSC msc;
Adafruit_NeoPixel pixels(1, WS2812, NEO_GRB + NEO_KHZ800);
// Utility: bounds check for (lba, offset, len)
static inline bool in_range(uint32_t lba, uint32_t offset, uint32_t len)
{
uint64_t start = (uint64_t)lba * DISK_SECTOR_SIZE + offset;
uint64_t end = start + len;
return end <= (uint64_t)DISK_BYTE_SIZE;
}
// ---------- Settings ----------
static const uint32_t SERIAL_BAUD = 115200;
static const uint32_t CONNECT_TIMEOUT_MS = 20000; // 20s
Preferences prefs;
String ssid = "";
String pass = "";
bool autoConnect = false;
void loadPrefs()
{
prefs.begin("wifi", true); // read-only
ssid = prefs.getString("ssid", "Barriball - Automation");
pass = prefs.getString("pass", "password123abc");
autoConnect = prefs.getBool("auto", true);
prefs.end();
}
void savePrefs()
{
prefs.begin("wifi", false);
prefs.putString("ssid", ssid);
prefs.putString("pass", pass);
prefs.putBool("auto", autoConnect);
prefs.end();
}
void forgetPrefs()
{
prefs.begin("wifi", false);
prefs.remove("ssid");
prefs.remove("pass");
prefs.remove("auto");
prefs.end();
ssid = "";
pass = "";
autoConnect = false;
}
void showStatus()
{
wl_status_t st = WiFi.status();
Serial.println("\n=== WiFi Status ===");
Serial.printf("Mode: %s\n", WiFi.getMode() == WIFI_MODE_STA ? "STA" : "Other");
Serial.printf("Saved SSID: %s\n", ssid.c_str());
Serial.printf("Auto-connect: %s\n", autoConnect ? "ON" : "OFF");
Serial.printf("Runtime status: %d\n", (int)st);
if (st == WL_CONNECTED)
{
Serial.printf("Connected SSID: %s\n", WiFi.SSID().c_str());
Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
}
Serial.println("===================\n");
}
void changeMediaStatus(bool mediaPresent)
{
if (mediaPresent != media_ready)
{
media_ready = mediaPresent;
msc.mediaPresent(mediaPresent);
if (mediaPresent)
{
Serial.printf("Mount disk\n");
// Set the size of the disk
// initialiseDisk();
showGreenLed();
}
else
{
Serial.printf("Unmount Disk\n");
// msc.end();
showOrangeLed();
}
}
}
void showGreenLed()
{
pixels.setPixelColor(0, pixels.Color(0, 150, 0));
pixels.show();
}
void showOrangeLed()
{
pixels.setPixelColor(0, pixels.Color(150, 150, 0));
pixels.show();
}
void showRedLed()
{
pixels.setPixelColor(0, pixels.Color(150, 0, 0));
pixels.show();
}
void showBlueLed()
{
pixels.setPixelColor(0, pixels.Color(0, 0, 150));
pixels.show();
}
void usbEvent(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
}
bool onStartStop(uint8_t power_condition, bool start, bool load_eject)
{
(void)power_condition;
// start: true = mount/open; false = stop
// load_eject: true when host is (eject) requesting unload
Serial.printf("StartStop: pwr=%u start=%u load_eject=%u\n", power_condition, start, load_eject);
if (load_eject && !start)
{
Serial.printf("Eject");
// Zero out storage area
//memset(msc_disk, 0x00, DISK_BYTE_SIZE);
// Host requested eject
changeMediaStatus(false);
return true;
}
else if (load_eject && start)
{
Serial.printf("Insert");
// Re-insert
changeMediaStatus(true);
return true;
}
else if (!load_eject && !start)
{
Serial.printf("Stop");
//showRedLed();
// Stop (could flush if backing store were non-volatile)
return true;
}
else
{
Serial.printf("Start");
// Start
changeMediaStatus(true);
return true;
}
}
void initialiseDisk()
{
// Zero out storage area
memset(msc_disk, 0x00, DISK_BYTE_SIZE);
// Bring up native USB (CDC + MSC share the device)
//USB.onEvent(usbEvent)
USB.begin();
// int32_t (*msc_read_cb)(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
msc.onRead([](uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) -> int32_t
{
if (!media_ready)
{
//Serial.println("READ: media not ready");
return 0;
}
if (!in_range(lba, offset, bufsize))
{
Serial.printf("READ OOB: lba=%lu off=%lu len=%lu\n", lba, offset, bufsize);
return -1;
}
memcpy(buffer, msc_disk + (lba * DISK_SECTOR_SIZE) + offset, bufsize);
//Serial.printf("READ lba=%lu off=%lu len=%lu\n", lba, offset, bufsize);
return (int32_t)bufsize;
});
// int32_t (*msc_write_cb)(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
msc.onWrite([](uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) -> int32_t
{
if (!media_ready)
{
//Serial.println("WRITE: media not ready");
return -1;
}
if (!in_range(lba, offset, bufsize))
{
Serial.printf("WRITE OOB: lba=%lu off=%lu len=%lu\n", lba, offset, bufsize);
return -1;
}
memcpy(msc_disk + (lba * DISK_SECTOR_SIZE) + offset, buffer, bufsize);
//Serial.printf("WRITE lba=%lu off=%lu len=%lu\n", lba, offset, bufsize);
return (int32_t)bufsize;
});
msc.onStartStop(onStartStop);
// Set disks properties
msc.vendorID("ACIT");
msc.productID("NetFloppy");
msc.productRevision("1.0");
msc.isWritable(true);
msc.mediaPresent(true);
// Set the size of the disk
if (!msc.begin(DISK_SECTOR_COUNT, DISK_SECTOR_SIZE))
{
Serial.println("Error: Could not initialise USB disk");
}
changeMediaStatus(true);
// No setUnitReady() / onReady() in this API; readiness handled inside callbacks.
}
void setup()
{
Serial.begin(SERIAL_BAUD);
while (!Serial)
{
delay(10);
}
delay(200);
Serial.flush();
pinMode(LED_ON, OUTPUT);
digitalWrite(LED_ON, LOW);
pixels.begin();
showBlueLed();
Serial.printf("\n\n+=====================================+\n");
Serial.printf("| NetFloppy |\n");
Serial.printf("| Version 1.0 |\n");
Serial.printf("| Copyright Aqua Cube IT Limited 2025 |\n");
Serial.printf("+=====================================+\n\n");
uint32_t chipId = 0;
for (int i = 0; i < 17; i = i + 8)
{
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
Serial.printf("ESP32 Chip model = %s Rev %d\n", ESP.getChipModel(), ESP.getChipRevision());
Serial.printf("This chip has %d cores\n", ESP.getChipCores());
Serial.print("Chip ID: ");
Serial.println(chipId);
Serial.printf("Initialising...\n");
Serial.printf("Allocating memory...\n");
// Allocate ramdisk in PSRAM
msc_disk = (uint8_t*) ps_malloc(DISK_BYTE_SIZE);
if (!msc_disk)
{
Serial.println("PSRAM alloc FAILED\n");
}
Serial.printf("Allocated %u bytes of %u - %u Free\n", (unsigned)DISK_BYTE_SIZE, ESP.getPsramSize(), ESP.getFreePsram());
loadPrefs();
if (ssid == "")
{
ssid = "Barriball - Automation";
pass = "password123abc";
autoConnect = true;
Serial.printf("SSID: '%s', Pass: '%s', Auto: %u\n", ssid.c_str(), pass.c_str(), autoConnect);
savePrefs();
}
if (autoConnect && ssid.length())
{
Serial.printf("Connecting to WIFI '%s'...\n", ssid.c_str());
//connectWiFi(ssid, pass);
WiFi.begin(ssid.c_str(), pass.c_str());
showStatus();
}
initialiseDisk();
}
void loop()
{
// Simple heartbeat
//digitalWrite(LED_ON, !digitalRead(LED_ON));
delay(10);
}

0
Frontend/test.ino Normal file
View File