กลับไปหน้ารวมไฟล์
a-better-sd-library-with-rt-thread-dc3cb2.md

พื้นหลัง

ไลบรารี SD ที่มากับ Arduino IDE นั้นใช้ง่ายดี แต่ก็ขาดฟีเจอร์สำคัญๆ ไปหลายอย่าง เช่น exFAT, ชื่อไฟล์ยาวๆ (LFN) และการรองรับตัวอักษรภาษาอื่นๆ บทความนี้เลยจะมาแนะนำไดรเวอร์การ์ด SD ตัวใหม่ (ที่ยึดจาก RT-Thread) เพื่อแก้ปัญหาพวกนี้กัน

RT-Thread

RT-Thread เป็นระบบปฏิบัติการแบบเรียลไทม์ (RTOS) ที่ฟรีและโอเพ่นซอร์ส (สัญญาอนุญาต Apache 2.0) และมีให้ใช้ในรูปแบบของไลบรารี Arduino ด้วย ถ้าน้องอยากเข้าใจพื้นฐานของ RT-Thread แนะนำให้ไปอ่านบทความเรื่องการทำมัลติทาสกิ้งบน Arduino ก่อนก็ได้ (แต่เดี๋ยวพี่จะสอนเองแหละ อย่าเพิ่งหนี!)

มาเริ่มกันด้วยการติดตั้งไลบรารีผ่าน Library Manager ของ Arduino IDE กันเลยดีกว่า (บทความนี้ใช้ RT-Thread library เวอร์ชัน 0.4.4 นะจ๊ะ)

ไดรเวอร์การ์ด SD (สรุปสั้นๆ)

(ถ้าน้องไม่อยากรู้รายละเอียดด้านล่าง อยากข้ามไปใช้เลย ก็กระโดดข้ามส่วนนี้ไปได้เลยนะ วัยรุ่น!)

การรองรับการ์ด SD ในไลบรารี RT-Thread นั้นอยู่ในรูปแบบของ DFS (Device File System) ซึ่งเป็นส่วนหนึ่งของสถาปัตยกรรม RT-Thread ระบบไฟล์ FAT เป็นหนึ่งในระบบไฟล์ที่ RT-Thread รองรับ (ในเวอร์ชัน 0.4.4 ของไลบรารี RT-Thread, FAT เป็นระบบไฟล์เดียวที่รองรับอยู่ตอนนี้)

DFS ของ FAT ตัวนี้พัฒนาต่อยอดมาจากผลงานชั้นยอดของโปรเจกต์ FatFs ของคุณ ChaN

DFS มาตรฐานของ RT-Thread จะมีอินเทอร์เฟซสำหรับระบบไฟล์และไฟล์ประมาณนี้:

/* File system operations */
struct dfs_filesystem_ops
{
   const char *name;
   uint32_t flags;      /* flags for file system operations */
   /* operations for file */
   const struct dfs_file_ops *fops;
   /* mount and unmount file system */
   int (*mount)    (struct dfs_filesystem *fs, unsigned long rwflag, const void *data);
   int (*unmount)  (struct dfs_filesystem *fs);
   /* make a file system */
   int (*mkfs)     (rt_device_t devid);
   int (*statfs)   (struct dfs_filesystem *fs, struct statfs *buf);
   int (*unlink)   (struct dfs_filesystem *fs, const char *pathname);
   int (*stat)     (struct dfs_filesystem *fs, const char *filename, struct stat *buf);
   int (*rename)   (struct dfs_filesystem *fs, const char *oldpath, const char *newpath);
};
/* File operations */
struct dfs_file_ops
{
   int (*open)     (struct dfs_fd *fd);
   int (*close)    (struct dfs_fd *fd);
   int (*ioctl)    (struct dfs_fd *fd, int cmd, void *args);
   int (*read)     (struct dfs_fd *fd, void *buf, size_t count);
   int (*write)    (struct dfs_fd *fd, const void *buf, size_t count);
   int (*flush)    (struct dfs_fd *fd);
   int (*lseek)    (struct dfs_fd *fd, off_t offset);
   int (*getdents) (struct dfs_fd *fd, struct dirent *dirp, uint32_t count);
   int (*poll)     (struct dfs_fd *fd, struct rt_pollreq *req);
}

DFS เฉพาะทางอาจจะ implement ฟังก์ชันทั้งหมดหรือแค่บางส่วนก็ได้ เวลาที่เราทำการ mount DFS เช่น dfs_mount("SD", "/", "elm", 0, 0), DFS เฉพาะทางนั้นจะถูกผูกเข้ากับอุปกรณ์หนึ่ง ในกรณีนี้คือ DFS "elm" (FatFs) ถูกผูกกับอุปกรณ์ชื่อ "SD"

ส่วนอุปกรณ์มาตรฐานของ RT-Thread จะมีอินเทอร์เฟซประมาณนี้:

/* operations set for device object */
struct rt_device_ops
{
   /* common device interface */
   rt_err_t  (*init)   (rt_device_t dev);
   rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
   rt_err_t  (*close)  (rt_device_t dev);
   rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
   rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
   rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);
};

อุปกรณ์เฉพาะทางอาจจะ implement ฟังก์ชันทั้งหมด หรือไม่ implement เลย (เซ็ต pointer ฟังก์ชันเป็น NULL) ก็ได้

ในไลบรารีของเรา อุปกรณ์ที่ชื่อ "SD" นี่แหละที่ implement ฟังก์ชันเกี่ยวกับการเข้าถึงการ์ด SD ซึ่งมันจะไปเรียกใช้อุปกรณ์ระดับล่างที่ชื่อ "SPI1" ในกรณีของบอร์ด MKRZERO และอุปกรณ์ SPI นี้ก็จะไปเรียกใช้ไลบรารี SPI ของ Arduino อีกทีหนึ่ง เรียกว่าซ้อนกันเป็นชั้นๆ เลยล่ะ อย่าเพิ่งมึนนะน้อง!

ตัวอย่าง Data Logger

ไลบรารี SD ของ Arduino มีตัวอย่างชื่อ "Datalogger" ให้เล่นกันอยู่ ส่วนในไลบรารี RT-Thread ก็มีเหมือนกัน ตามโค้ดที่แปะไว้ด้านล่างเลย

ข้อแตกต่างคือ ในโค้ดตัวอย่างด้านล่างนี้ เราจะให้มันสุ่มตัวอย่างทุกๆ 1 วินาที และทำแค่ 10 รอบเท่านั้น

น่าจะสังเกตเห็นคอมเมนต์เหนือฟังก์ชัน open() กันแล้ว ถ้าอยากเปิดไฟล์ที่มีอยู่แล้วและลบข้อมูลเก่าทิ้งหมด ก็แค่เปลี่ยน flag จาก O_APPEND เป็น O_TRUNC ง่ายๆ เอง

ลองรันตัวอย่างด้านล่างกับบอร์ด MKRZERO ดู จะเห็นผลลัพธ์ประมาณนี้ใน Serial Monitor:

\\ | /
- RT -     Thread Operating System
/ | \\     4.0.1 build Apr 17 2019
2006 - 2019 Copyright by rt-thread team
+ Mount SD to "/"
416,347,312
finsh />436,369,335
442,375,340
449,376,338
449,375,346
429,374,341
447,369,342
449,363,338
426,363,334
419,353,327

Multi-Threaded Storage Kernel

โปรเจคนี้มาแนะนำทางเลือกใหม่ที่แรงกว่าไลบรารี SD แบบเดิมๆ ด้วยการพึ่งพา RT-Thread IoT OS เพื่อให้การทำงานกับไฟล์บน Arduino เร็วขึ้นและเสถียรขึ้น

  • RT-Thread OS Real-Time Scheduling: ด้วยการรันระบบปฏิบัติการเรียลไทม์บน Arduino เฟิร์มแวร์ของเราสามารถเขียนข้อมูลลงการ์ด SD ใน "พื้นหลัง" ได้ ขณะที่แอปพลิเคชันหลักยังคงอ่านค่าจากเซนเซอร์ต่อไปได้ปกติ
  • Fine-Grained Sector Access: ไม่เหมือนไลบรารีมาตรฐาน การใช้งานนี้ใช้ไดรเวอร์เฉพาะของ RT-Thread เพื่อเพิ่มประสิทธิภาพการถ่ายโอนข้อมูลแบบบล็อกความเร็วสูง ลดความหน่วง (latency) ในการทำ Data-logging ลงได้อย่างมาก

ประสิทธิภาพ

  • Cloud editor verified: OS kernel และ logic การจัดการเธรดได้รับการปรับปรุงใน Arduino Web Editor เพื่อให้ได้โซลูชันการจัดเก็บข้อมูลที่แข็งแกร่งและป้องกันการแครช สำหรับโหนด IoT ที่ซับซ้อน

การจัดการด้วย Shell

ข้อได้เปรียบจริงๆ ของการใช้ไลบรารี RT-Thread คือมันทำให้เราจัดการไฟล์ด้วยคำสั่ง shell (FinSH) ได้

ใน Serial Monitor หรือเครื่องมือเทอร์มินัลแบบอนุกรมอื่นๆ พิมพ์คำสั่ง ls() จะแสดงรายการไฟล์ในไดเรกทอรีปัจจุบัน (ในที่นี้คือ "/") ประมาณนี้

ls()
Directory /:
DATALOG.TXT         240
HI_UTF8.TXT         35
A_REAL~1.TXT        22
       0, 0x00000000 

ตัวเลขหลังชื่อไฟล์คือขนาดไฟล์ในหน่วยไบต์ ในภาพตัวอย่างข้างบน ขนาดไฟล์ "datalog.txt" คือ 240 ไบต์ เพราะรันตัวอย่างไปสองรอบแล้ว

พิมพ์คำสั่ง cat("datalog.txt") จะแสดงเนื้อหาของไฟล์ "datalog.txt" เพื่อยืนยันว่ามีบันทึกอยู่ 20 แถวจริงๆ

finsh />cat("datalog.txt")
464,358,333
464,368,336
480,381,354
447,364,346
443,363,340
441,365,343
463,371,345
467,374,313
447,364,345
465,369,346
416,347,312
436,369,335
442,375,340
449,376,338
449,375,346
429,374,341
447,369,342
449,363,338
426,363,334
419,353,327
       0, 0x00000000

นอกจากนี้ยังมีคำสั่ง copy() และ rm() ด้วยนะ

finsh />copy("datalog.txt", "copy.txt")
       0, 0x00000000
finsh />ls()
Directory /:
COPY.TXT            240
DATALOG.TXT         240
HI_UTF8.TXT         35
A_REAL~1.TXT        22
       0, 0x00000000
finsh />rm("copy.txt")
       0, 0x00000000
finsh />ls()
Directory /:
DATALOG.TXT         240
HI_UTF8.TXT         35
A_REAL~1.TXT        22
       0, 0x00000000

ถ้าอยากดูว่ามีคำสั่งอะไรให้ใช้บ้าง พิมพ์ list() เข้าไปเลย

finsh />list()
--Function List:
hello            -- say hello world
version          -- show RT-Thread version information
list             -- list available commands
list_mem         -- list memory usage information
list_thread      -- list thread
list_sem         -- list semaphore in system
list_mutex       -- list mutex in system
list_event       -- list event in system
list_mb          -- list mail box in system
list_mq          -- list message queue in system
list_memp        -- list memory pool in system
list_timer       -- list timer in system
list_dev         -- list device in system
mkfs             -- make a file system
df               -- get disk free
mkdir            -- create a directory
cd               -- change current working directory
ls               -- list directory contents
rm               -- remove files or directories

cat -- พิมพ์เนื้อหาไฟล์ copy -- คัดลอกไฟล์หรือโฟลเดอร์ list_sd -- แสดงข้อมูล SD Card --รายการตัวแปร: dummy -- ตัวแปรปลอมสำหรับ finsh 0, 0x00000000


### การรองรับ ExFAT, ชื่อไฟล์ยาว (LFN) และตัวอักษรภาษาอื่น
ฟีเจอร์ ExFAT, ชื่อไฟล์ยาว (LFN) และการรองรับตัวอักษรที่ไม่ใช่ภาษาอังกฤษจะถูกปิดไว้เป็นค่าเริ่มต้น (เพื่อให้ตัวอย่างโปรแกรมมีขนาดเล็กลง) เปิดการตั้งค่าต่อไปนี้ในไฟล์ **"rtconfig.h"** (อยู่ในไดเรกทอรีไลบรารี RT-Thread) เพื่อเปิดใช้งานฟีเจอร์พวกนี้

#define RT_DFS_ELM_USE_EXFAT #define RT_DFS_ELM_USE_LFN (2) #define RT_DFS_ELM_MAX_LFN (255) #define RT_DFS_ELM_CODE_PAGE 936

**RT_DFS_ELM_MAX_LFN** หมายถึงความยาวสูงสุดของชื่อไฟล์ สามารถตั้งค่าได้ตั้งแต่ 12 ถึง 255

**RT_DFS_ELM_CODE_PAGE** ตั้งค่าเป็น 437 (สำหรับสหรัฐอเมริกา) เป็นค่าเริ่มต้น เปลี่ยนเป็น 936 จะเปิดใช้งานการรองรับภาษาจีนตัวย่อ ดังตัวอย่างด้านล่าง

finsh />ls() Directory /: DATALOG.TXT 240 hi_utf8.txt 35 a_really_long_file_name.txt22 0, 0x00000000 finsh />cat("hi_utf8.txt") Hello, world! 世界,你好!\t0, 0x00000000


### ขั้นตอนต่อไป
- ไปศึกษาการทำ Multitasking บน Arduino ต่อได้เลยวัยรุ่น
- RT-Thread Primer (เร็วๆ นี้)

ข้อมูล Frontmatter ดั้งเดิม

apps:
  - "1x Arduino Web Editor"
  - "1x RT-Thread IoT OS"
  - "1x Arduino IDE"
author: "onelife"
category: "Lab Stuff"
components:
  - "1x Arduino MKR Zero"
description: "Do you need to have exFAT support? Use long filename? Open multiple files? Or read files with non-English characters? Then check this out!"
difficulty: "Easy"
documentationLinks: []
downloadableFiles:
  - "https://create.arduino.cc/editor/onelife/ad989199-8682-4888-b2ec-8ecfeef0c3e4/preview"
encryptedPayload: "U2FsdGVkX1+E4YMBC1CIwJ0Uxteo/gEP7qb849UPwDGrx2Q+vTHE72cH9yqA6nBeOPiGBMjEnj81J89XjqlFaNp9ZOcsVzBLF/268YnrKuQQUeytatopOCnOuUimT40LdcpUtSd7V8yNmEbJPSRGwEWLNluenP/PbpxCk8ZONzg="
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/a-better-sd-library-with-rt-thread-dc3cb2_cover.jpg"
lang: "en"
likes: 3
passwordHash: "f6b76ebbab58a0336870e9f87443758df59bb5b7d7252d88142fc8ee15566f56"
price: 200
seoDescription: "Enhance your SD Library with RT-Thread. Supports exFAT, long filename, multiple files, and non-English characters for Arduino projects."
tags:
  - "rt-thread"
  - "exfat"
  - "rtos"
  - "data collection"
title: "อัปเกรด Library SD ให้เทพด้วย RT-Thread!"
tools: []
videoLinks: []
views: 10175