โปรเจกต์ESP32 & IoT

The Infinite Flash Drive — แฟลชไดรฟ์ความจุไม่จำกัดจาก ESP32-S3

ทำแฟลชไดรฟ์จากบอร์ด ESP32-S3 ใน 3 ระดับ: ไดรฟ์ USB ธรรมดา → ลากไฟล์ใส่แล้วขึ้นคลาวด์เอง → ไดรฟ์ 512MB ที่ไม่มีไฟล์อยู่ในบอร์ดเลย แต่สตรีมทุกไบต์มาจาก server

ESP32-S3ปานกลาง บอร์ดราว 200 บาท อ่าน 5 นาที อัปเดต 28 ก.ย. 2569
ESP32-S3
ในหน้านี้
  1. อุปกรณ์ที่ต้องใช้
  2. ตั้งค่า Arduino IDE ก่อนเริ่ม
  3. ระดับ 1 — แฟลชไดรฟ์ธรรมดา
  4. ระดับ 2 — ลากไฟล์ใส่ แล้วขึ้นคลาวด์เอง
  5. ระดับ 3 — ไดรฟ์ 512MB ที่ไม่มีไฟล์อยู่จริง
  6. รัน server (ใช้ในระดับ 2 และ 3)
  7. ทดสอบ server โดยไม่ต้องมีบอร์ด
  8. ข้อจำกัดที่ควรรู้
  9. โค้ดและไฟล์

บอร์ด ESP32-S3 ตัวเล็กๆ ตัวเดียว เสียบเข้าคอมแล้วกลายเป็นแฟลชไดรฟ์ได้จริง และถ้าเขียนโค้ดให้ฉลาดขึ้นอีกนิด มันจะแอบอัปโหลดไฟล์ขึ้นคลาวด์เอง จนถึงขั้นสุดท้ายที่คอมเห็นไดรฟ์ 512MB เต็มไปด้วยไฟล์ ทั้งที่ในบอร์ดไม่มีข้อมูลไฟล์พวกนั้นอยู่เลยสักไบต์

โปรเจกต์นี้แบ่งเป็น 3 ระดับ ทำทีละขั้นได้ แต่ละระดับใช้บอร์ดตัวเดิม เปลี่ยนแค่โค้ด

ระดับ คอมเห็นอะไร ความลับอยู่ตรงไหน
1 · Basic USB ไดรฟ์ ~960KB ก๊อปไฟล์เข้าออกได้ ใช้ flash ของบอร์ดเป็นที่เก็บจริง
2 · Cloud-Sync ไดรฟ์เหมือนเดิม แต่ไฟล์เด้งขึ้น server เอง บอร์ดรอให้ก๊อปเสร็จ แล้ว POST ไฟล์ขึ้นเน็ต
3 · Infinite ไดรฟ์ ~512MB ไฟล์เต็ม บอร์ดปั้นตารางไฟล์ปลอม แล้วสตรีมของจริงจาก server

อุปกรณ์ที่ต้องใช้

0/4 ชิ้น
  • ×1

    ต้องเป็นรุ่น S3 เท่านั้น ESP32 รุ่นธรรมดาและ C3 ทำ USB Mass Storage แบบนี้ไม่ได้ แนะนำบอร์ดที่มีพอร์ต USB-C 2 ช่อง จะดู Serial log ไปพร้อมกับใช้ไดรฟ์ได้

  • ×1

    สายที่ชาร์จได้อย่างเดียวจะไม่ขึ้นไดรฟ์ ถ้าเสียบแล้วเงียบให้ลองเปลี่ยนสายก่อน

  • ×1

    ใช้รัน server ในระดับ 2 และ 3 (ระดับ 1 ไม่ต้องใช้)

  • ×1 วง

    บอร์ดกับคอมที่รัน server ต้องต่อ WiFi วงเดียวกัน

ตั้งค่า Arduino IDE ก่อนเริ่ม

ต้องใช้ ESP32 Arduino core v3.x ขึ้นไป (Boards Manager → esp32 by Espressif) แล้วตั้งค่าบอร์ดตามนี้

หัวข้อ ค่าที่ต้องเลือก
Board ESP32S3 Dev Module
USB CDC On Boot Enabled (ไว้ดู Serial log)
USB Mode USB-OTG (TinyUSB)
Partition Scheme Default 4MB with ffat (ระดับ 1–2)
Upload Mode UART0 / Hardware CDC

ระดับ 1 — แฟลชไดรฟ์ธรรมดา

เปิด level1_basic_usb.ino คอมไพล์และอัปโหลดได้เลย ไม่ต้องแก้อะไร เสียบเข้าคอมแล้วจะขึ้นไดรฟ์ใหม่ราว 960KB ที่ format เป็น FAT เรียบร้อย

หลักการคือเอา flash partition ชื่อ ffat ของบอร์ดมาเป็นที่เก็บข้อมูล แล้วต่อเข้ากับ USB Mass Storage ผ่านชั้น wear-leveling เวลาคอมขออ่านหรือเขียน บอร์ดแค่แปลงตำแหน่ง sector เป็นตำแหน่งบน flash

level1_basic_usb.ino · บรรทัด 40–54
// -------- 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;}

ระดับ 2 — ลากไฟล์ใส่ แล้วขึ้นคลาวด์เอง

หน้าตาไดรฟ์เหมือนระดับ 1 ทุกอย่าง แต่คราวนี้บอร์ดต่อ WiFi อยู่เบื้องหลัง พอเราลากไฟล์ใส่ บอร์ดจะรอให้คอมหยุดเขียนสักพัก (ถือว่าก๊อปเสร็จ) แล้วอัปโหลดไฟล์ใหม่ขึ้น server ให้เอง ไม่ต้องกดอะไร

ก่อนคอมไพล์ แก้ 4 บรรทัดบนสุดให้ตรงกับเครื่องของเรา

level2_cloud_sync.ino · บรรทัด 22–25
#define WIFI_SSID   "ใส่ชื่อ_WiFi_ที่นี่"#define WIFI_PASS   "ใส่รหัส_WiFi_ที่นี่"#define SERVER_HOST "192.168.1.100"   // IP เครื่องที่รัน node server.js#define SERVER_PORT 3000

SERVER_HOST คือ IP ของคอมที่รัน server (ดูวิธีหา IP ในหัวข้อ รัน server) ส่วนจังหวะ "ก๊อปเสร็จแล้ว" ดูจากเวลาที่คอมเขียนครั้งล่าสุด ถ้าเงียบเกิน 1.5 วินาทีก็เริ่ม sync

level2_cloud_sync.ino · บรรทัด 159–169
  // ถ้ามีการเขียน และ PC หยุดเขียนเกิน 1.5 วิ = ก๊อปเสร็จ -> sync ขึ้น cloud  if (s_dirty && (millis() - s_lastWriteMs > 1500)) {    s_dirty = false;    if (WiFi.status() == WL_CONNECTED) {      Serial.println("[sync] PC หยุดเขียนแล้ว เริ่ม sync ขึ้น cloud...");      syncNewFiles();    } else {      Serial.println("[sync] ยังไม่มี WiFi — ข้ามรอบนี้ไปก่อน");      s_dirty = true;   // ลองใหม่รอบหน้า    }  }

ระดับ 3 — ไดรฟ์ 512MB ที่ไม่มีไฟล์อยู่จริง

ระดับนี้คือไม้ตาย บอร์ดดึงรายชื่อไฟล์จาก server ตอนเปิดเครื่อง แล้วปั้น "ภาพ" ของไดรฟ์ FAT16 ขนาดราว 512MB ขึ้นมาสดๆ คอมเลยเห็นไฟล์เต็มไดรฟ์ ทั้งที่บอร์ดเก็บแค่รายชื่อ

flowchart LR
  PC["คอม<br/>เห็นไดรฟ์ 512MB"] -- "USB: ขออ่าน sector" --> ESP["ESP32-S3<br/>ปั้น FAT ปลอม"]
  ESP -- "WiFi: HTTP Range" --> SRV["Server<br/>ไฟล์จริงอยู่ที่นี่"]
  SRV -- "ส่งเฉพาะไบต์ที่ขอ" --> ESP
  ESP -- "ข้อมูล sector" --> PC

มันหลอกคอมได้ด้วย 2 อย่าง

  1. ตารางไฟล์ปลอม — sector แรกๆ ของไดรฟ์ (boot sector, ตาราง FAT, รายการไฟล์) ถูกคำนวณจากรายชื่อไฟล์บน server ทุกครั้งที่คอมขออ่าน ไม่ได้เก็บไว้ที่ไหนเลย
  2. สตรีมตอนเปิดไฟล์ — พอคอมขออ่าน sector ในโซนข้อมูล บอร์ดแปลงกลับเป็น "ไฟล์ไหน ไบต์ที่เท่าไหร่" แล้วยิงคำขอ HTTP Range ไปดึงเฉพาะช่วงนั้นจาก server

ขนาดของไดรฟ์ปลอมกำหนดจากค่าพวกนี้ (sector ละ 512 ไบต์, cluster ละ 32KB, 16384 cluster)

level3_infinite.ino · บรรทัด 41–51
static const uint32_t BPS         = 512;      // bytes ต่อ sectorstatic const uint32_t SPC         = 64;       // sector ต่อ cluster -> cluster = 32KBstatic const uint32_t CLUSTER     = BPS * SPC;// 32768static const uint32_t RSVD        = 1;        // reserved sector (boot)static const uint32_t NUM_FATS    = 1;static const uint32_t FAT_SECS    = 65;       // ขนาด FAT (รองรับ ~16384 cluster)static const uint32_t ROOT_ENTS   = 512;      // จำนวน entry ใน root dirstatic const uint32_t ROOT_SECS   = (ROOT_ENTS * 32) / BPS;      // = 32static const uint32_t DATA_START  = RSVD + NUM_FATS * FAT_SECS + ROOT_SECS; // = 98static const uint32_t DATA_CLUS   = 16384;    // จำนวน cluster ในโซนข้อมูล -> ~512MBstatic const uint32_t TOTAL_SECS  = DATA_START + DATA_CLUS * SPC;

และนี่คือหัวใจของการสตรีม ขอไบต์ช่วงเดียวจากไฟล์บน server

level3_infinite.ino · บรรทัด 188–195
static bool fetchRange(const char *realname, uint32_t start, uint32_t len, uint8_t *out) {  memset(out, 0, len);  WiFiClient c;  if (!c.connect(SERVER_HOST, SERVER_PORT)) return false;  c.printf("GET /file/%s HTTP/1.1\r\n", realname);  c.printf("Host: %s\r\n", SERVER_HOST);  c.printf("Range: bytes=%u-%u\r\n", start, start + len - 1);  c.print("Connection: close\r\n\r\n");

รัน server (ใช้ในระดับ 2 และ 3)

server เขียนด้วย Node.js ล้วน ไม่ต้อง npm install อะไรเลย

Shell
cd server
node server.js

หา IP ของเครื่องที่รัน server แล้วเอาไปใส่ใน SERVER_HOST

  • Windows: พิมพ์ ipconfig แล้วดูบรรทัด IPv4 Address
  • Mac / Linux: พิมพ์ ifconfig หรือ ip addr แล้วดู inet ของ WiFi

อยากให้ระดับ 3 มีของโชว์ ให้วางไฟล์ใหญ่ๆ (วิดีโอ, zip) ไว้ในโฟลเดอร์ server/storage/ ก่อนรัน server ตัว server สร้างไฟล์ตัวอย่าง HELLO.TXT และ BIGFILE.TXT ขนาด 5MB ไว้ให้แล้ว

ทดสอบ server โดยไม่ต้องมีบอร์ด

Shell
# ดูรายชื่อไฟล์ (ระดับ 3 ใช้ endpoint นี้)
curl http://localhost:3000/files

# อัปโหลดไฟล์แบบเดียวกับระดับ 2
curl -X POST "http://localhost:3000/upload?name=test.txt" --data-binary "hello"

# อ่านแค่ 5 ไบต์แรก ด้วย HTTP Range แบบเดียวกับระดับ 3
curl -r 0-4 http://localhost:3000/file/test.txt

ข้อจำกัดที่ควรรู้

  • ชื่อไฟล์ในระดับ 3 เป็นแบบ 8.3 (เช่น HELLO.TXT) ชื่อยาวหรือภาษาไทยจะถูกตัดเหลือตัวพิมพ์ใหญ่ 8 ตัว ตั้งชื่อไฟล์ใน storage/ ให้สั้นจะสวยกว่า
  • ความเร็วตอนเปิดไฟล์ขึ้นกับ WiFi ไฟล์ใหญ่มากคอมจะอ่านทีละก้อน แถบโหลดจะวิ่งช้าๆ ซึ่งเป็นหลักฐานว่ามันสตรีมจริง
  • ระดับ 1–2 เก็บไฟล์ใน flash จริง ข้อมูลอยู่ข้ามการรีบูต ระดับ 3 ไม่เก็บอะไรเลย
  • บาง OS พยายามเขียนไฟล์ระบบ (เช่น System Volume Information) ลงไดรฟ์ ระดับ 2 กรองทิ้งให้แล้ว ระดับ 3 รับแล้วทิ้งอยู่แล้ว

โค้ดและไฟล์

โหลดทั้งหมด (.zip)
level1_basic_usb.ino

ESP32-S3 ทำตัวเป็นแฟลชไดรฟ์ราว 960KB เก็บไฟล์ใน flash ของบอร์ด คอมไพล์ได้เลยไม่ต้องแก้

Arduino · 105 บรรทัด · 5.9 KBดาวน์โหลด
level2_cloud_sync.ino

ลากไฟล์ใส่ไดรฟ์แล้วบอร์ดอัปโหลดขึ้น server ให้เอง แก้ 4 บรรทัดบนสุด (WiFi และ IP ของ server) ก่อนคอมไพล์

Arduino · 171 บรรทัด · 7.7 KBดาวน์โหลด
level3_infinite.ino

ไดรฟ์ FAT16 ปลอมขนาดราว 512MB ที่สตรีมไฟล์จาก server ด้วย HTTP Range แก้ 4 บรรทัดบนสุดก่อนคอมไพล์

Arduino · 387 บรรทัด · 17 KBดาวน์โหลด
server.js

server ของระดับ 2 และ 3 เขียนด้วย Node.js ล้วน ไม่ต้อง npm install รันด้วยคำสั่ง node server.js

JavaScript · 149 บรรทัด · 6.1 KBดาวน์โหลด