/* ============================================================ * Infinite Flash Drive — LEVEL 1 : Basic USB Mass Storage * บอร์ด: ESP32-S3 (ต้องเป็น S3 เพราะใช้ Native USB) * ============================================================ * * สิ่งที่คลิปโชว์: * เสียบสาย USB (พอร์ต USB-OTG ของ S3 ตัวที่อยู่ติดกับ UART) * -> คอมขึ้นไดรฟ์ใหม่ ~960KB ที่ format เป็น FAT เรียบร้อย * -> ก๊อปไฟล์เข้า-ออกได้เหมือนแฟลชไดรฟ์จริง (เก็บใน flash ของบอร์ด) * * ---------- Arduino IDE / arduino-cli settings ---------- * Board : "ESP32S3 Dev Module" * USB CDC On Boot : Enabled (ไว้ดู Serial log) * USB Mode : "USB-OTG (TinyUSB)" << สำคัญมาก * Partition Scheme : "Default 4MB with ffat" << ต้องมี partition ffat * ต้องใช้ ESP32 Arduino core v3.x ขึ้นไป * * วิธีคิดของโค้ด: * ใช้ flash partition ชื่อ "ffat" เป็นตัวเก็บข้อมูลจริง * mount เป็น FAT (ผ่าน wear-leveling) ครั้งแรกเพื่อ format ให้สะอาด * แล้วต่อ USB MSC เข้ากับ layer wear-leveling ตัวเดียวกัน * -> คอมอ่าน/เขียนไปที่ flash โดยตรง เห็นเป็น FAT ที่ valid * ============================================================ */ #include "USB.h" #include "USBMSC.h" #include "esp_partition.h" #include "wear_levelling.h" #include "esp_vfs_fat.h" // ---- ค่าคงที่ ---- static const char *PARTITION_LABEL = "ffat"; // ต้องตรงกับชื่อใน partition scheme static const char *MOUNT_POINT = "/disk"; USBMSC msc; // ตัวจัดการ USB Mass Storage wl_handle_t s_wl = WL_INVALID_HANDLE; // handle ของ wear-leveling layer uint32_t s_sectorSize = 0; // ขนาด sector (ปกติ 4096) uint32_t s_sectorCount = 0; // จำนวน sector ทั้งหมด // -------- callback: คอมขอ "อ่าน" ข้อมูลจากไดรฟ์ -------- static int32_t onRead(uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { // แปลง (lba, offset) เป็น address จริงบน flash แล้วอ่านผ่าน wear-leveling esp_err_t err = wl_read(s_wl, (size_t)lba * s_sectorSize + offset, buffer, bufsize); return (err == ESP_OK) ? (int32_t)bufsize : -1; } // -------- callback: คอมขอ "เขียน" ข้อมูลลงไดรฟ์ -------- static int32_t onWrite(uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { size_t addr = (size_t)lba * s_sectorSize + offset; // flash ต้อง erase ก่อนเขียนเสมอ (MSC เขียนทีละ block ที่ตรงขอบ sector อยู่แล้ว) if (wl_erase_range(s_wl, addr, bufsize) != ESP_OK) return -1; if (wl_write(s_wl, addr, buffer, bufsize) != ESP_OK) return -1; return (int32_t)bufsize; } // -------- callback: คอมสั่ง start/stop (เช่นตอน eject) -------- static bool onStartStop(uint8_t power_condition, bool start, bool load_eject) { Serial.printf("[MSC] StartStop start=%d eject=%d\n", start, load_eject); return true; } void setup() { Serial.begin(115200); delay(300); Serial.println("\n=== Infinite Flash Drive — LEVEL 1 ==="); // 1) mount FAT ครั้งแรก: ถ้ายังไม่ได้ format จะ format ให้อัตโนมัติ // ทำเพื่อให้แน่ใจว่า flash มี FAT filesystem ที่ valid (คอมจะได้ไม่ขอ format) esp_vfs_fat_mount_config_t mountCfg = { .format_if_mount_failed = true, .max_files = 4, .allocation_unit_size = CONFIG_WL_SECTOR_SIZE, }; esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl( MOUNT_POINT, PARTITION_LABEL, &mountCfg, &s_wl); if (err != ESP_OK) { Serial.printf("!! mount/format ล้มเหลว: %s\n", esp_err_to_name(err)); Serial.println(" ตรวจว่าเลือก Partition Scheme ที่มี ffat แล้วหรือยัง"); return; } // ขนาด sector และจำนวน sector (เอาไว้บอก USB MSC) s_sectorSize = wl_sector_size(s_wl); s_sectorCount = (uint32_t)(wl_size(s_wl) / s_sectorSize); Serial.printf("[FAT] sector=%u bytes, count=%u (≈ %u KB)\n", s_sectorSize, s_sectorCount, (s_sectorSize * s_sectorCount) / 1024); // 2) ต่อ USB Mass Storage เข้ากับ flash เดียวกัน msc.vendorID("QLAB"); // ชื่อโชว์ตอนเสียบ (<=8 ตัว) msc.productID("InfiniteFD"); // (<=16 ตัว) msc.productRevision("1.0"); msc.onRead(onRead); msc.onWrite(onWrite); msc.onStartStop(onStartStop); msc.mediaPresent(true); msc.begin(s_sectorCount, s_sectorSize); // บอกความจุไดรฟ์ให้คอมรู้ USB.begin(); // เริ่มทำงานเป็นอุปกรณ์ USB Serial.println("[USB] พร้อมแล้ว — เสียบเข้าคอมได้เลย ไดรฟ์จะขึ้นมาเอง"); } void loop() { // Level 1 ไม่มีอะไรทำในลูป — งานทั้งหมดอยู่ใน callback ของ USB delay(1000); }