บทความนี้จะพูดถึงการบันทึกค่าต่าง ๆ ลงบน ESP32 โดยใช้ LITTLEFS เพื่อให้เรานำค่าเหล่านั้นมาใช้ในการทำงานบางอย่างเช่น บันทึกค่า WiFi ไว้ บันทึกค่าsettingต่าง ๆ ที่เราไม่ต้องการให้มันหายไปหลังจากปิดไฟ โดยค่าต่าง ๆ จะถูกเก็บลงบนหน่วยความจำแฟรชที่อยู่บน ESP32
LittleFS เป็นระบบไฟล์ที่ถูกพัฒนาขึ้นมาตามหลัง SPIFFS ซึ่งตัว LITTLEFS ช่วยอำนวยความสะดวกในเรื่องการจัดการ directory มากกว่า SPIFFS ซึ่งเป็นระบบเก่า (แลกมาด้วยการใช้พื้นที่จัดเก็บไฟล์เยอะกว่า) แต่จากประกาศในเว็ปผู้พัฒนา SPIFFS อาจถูกแทนที่ด้วย LittleFS ในไม่ช้า (Deprecation) และเราใช้สองระบบนี้ร่วมกันไม่ได้นะครับ
ใน LittleFSเราสามารถกำหนดชื่อของไฟล์ได้ถึง 31 ตัวอักษร และยังสามารถสร้างไฟล์ที่อยู่ในโฟลเดอร์ที่ยังไม่ได้สร้าง เช่น /doc/folder/xxx.jpg โดยที่ระบบยังไม่มีโฟลเดอร์ /doc/folder ระบบจะสร้างโฟลเดอร์นั้นขึ้นมาโดยอัตโนมัติ เช่นเดียวกันกับตอนลบ หากลบไฟล์จนหมดในโฟลเดอร์ใด ๆ โฟลเดอร์นั้น ๆ จะถูกลบโดยอัตโนมัติ ถือว่าสะดวกเลย
สำหรับ esp8266 ก็สามารถใช้ LittleFS ได้ เพียงแต่ใน esp8266 รุ่นเก่า ๆ พื้นที่ flash memory น้อย ทางผู้พัฒนาจึงแนะนำให้ใช้ SPIFFSไปก่อน ส่วนเวอร์ชั่นใหม่ ๆ ที่มีความจุ memory มากขึ้น การเลือกใช้ LittleFS เป็นตัวเลือกคุ้มค่ากว่าแน่นอน
โดยในบทความนี้จะสาธิตประโยชน์ของ LittleFS ในแง่ของการเก็บไฟล์ไว้เรียกใช้งาน และการสร้าง config ที่เก็บค่าต่าง ๆ ที่จำเป็นกับซอร์ฟแวร์เราไว้ แม้ไฟดับก็ไม่หายไปเช่นเดียวกับการเก็บลง EEPROM (แต่ง่ายกว่า) กันครับ
เริ่มต้นกันเลย
ให้เราดาวน์โหลดไลบรารี่ตามลิ้งด้านล่าง
https://github.com/lorol/LITTLEFS
หรือใช้วิธีติดตั้งผ่าน Arduino IDE จาก Tools->Manage Libraries แล้วค้นหา LittleFS
จากนั้นให้เราเช็คว่ามี option ชื่อว่า ESP32 Sketch Data Upload ใน Tools บน Arduino IDE หรือไม่ หากเป็นด้านล่าง
จะเห็นว่ายังไม่มี Option ESP32 Sketch Data Upload
ให้เราทำการติดตั้งปลั๊กอินก่อน โดยโหลดจาก
https://github.com/lorol/arduino-esp32fs-plugin
จากนั้นให้แตกไฟล์จะได้โฟลเดอร์ชื่อว่า arduino-esp32fs-plugin-master ให้เข้าไปแล้วเปิด Terminal เพืิ่อเรียกใช้ make_mac.sh กรณีเป็น mac หรือเรียกใช้ make_win.bat กรณีเป็น window
สำหรับกรณีเป็น MAC สามารถรันผ่านด้านล่างได้เลย
จากนั้นพิมพ์
sudo chmod +x make_mac.sh
เพื่อกำหนดให้เป็นไฟล์ที่สามารถรันได้
แล้วรันเพื่อติดตั้ง plugin
./make_mac.sh
จะได้หน้าตาประมาณนี้
เมื่อติดตั้ง Plugin เรียบร้อย ให้ลองคัดลอกโค้ดด้านล่างไปลองรันดูครับ
จะเห็นว่าในโค้ดนี้เราสามารถเซฟไฟล์ให้อยู่ใน flash โดยข้อมูลที่เซฟเป็น structure ของค่า val1 และ val2 ตามด้านล่าง
#pragma pack(push, 1)
typedef struct {
uint16_t ck;
} checksum_t ;
typedef struct{
float val2 = 210;
float val1 = 245;
checksum_t checksum;
}mdmconfig_s;
#pragma pack(pop)
mdmconfig_s _mdmconfig;
โดย #pragma pack(push,1) .... #pragma pack(pop) ใช้เพื่อกำหนดให้ preprocessor ทราบว่า structureตรงนี้เราจะไม่เติม padding ลงไปมั่วตั้ว คือขนาดข้อมูลแค่ไหน ก็ใช้แค่นั้นเลย ไม่เช่นนั้นแล้วการทำ checksum อาจผิดพลาดได้ (ให้ลองด้วยตัวเองครับ ลองเอาออกแล้วตรวจสอบค่า checksum ดู)
ซึ่งข้อมูลด้านบนจะถูกเก็บลงไฟล์ผ่านฟังก์ชัน saveConfigData() ซึ่งเริ่มด้วยการเปิดไฟล์ที่ต้องการเขียนลงไปด้วยคำสั่ง open เมื่อระบบเปิดไฟล์เก่าหรือกรณียังไม่มีไฟล์ก็สร้างให้อัตโนมัติ ตัวแปร file ก็จะมีค่าเป็นจริง
void saveConfigData()
{
File file = FileFS.open(FILENAME, "w");
Serial.println(F("========================\nSaveCfgFile Modulemore.com"));
if (file)
{
mdmChecksum( (uint8_t*) &_mdmconfig, sizeof(_mdmconfig) - sizeof(_mdmconfig.checksum), &_mdmconfig.checksum );
// Serial.printf("Checksum = %d\n", _mdmconfig.checksum.ck);
file.write((uint8_t*) &_mdmconfig, sizeof(_mdmconfig));
file.close();
Serial.println(F("OK"));
displayConfig(_mdmconfig);
}
else
{
Serial.println(F("failed"));
}
}
แต่ก่อนที่เราจะเริ่มเขียนไฟล์ เราจะเพิ่มตัวแปรอีกตัวขึ้นมาเพื่อไว้ตรวจสอบตอนอ่านว่าไฟล์นั้นได้อ่านอย่างถูกต้องหรือไม่ หรือได้ถูกเขียนทับ หรือ file corrupt ไปแล้ว จะได้ไม่อ่านต่อและแจ้งให้ผู้ใช้ทราบ โดยจะใช้วิธี checksum
Checksum เป็นวิธีตรวจสอบค่าอย่างง่าย โดยการรวมค่าของตัวแปรทั้งหมดใน structure (ยกเว้นตัว checksumเอง) โดยผลลัพธ์ของการรวมกันจะถูกเก็บไว้ในตัวแปรขนาด 2 byte ชื่อว่า ck ซึ่งอยู่ใน structure checksum_t
โดยเนื้อในของฟังก์ชัน checksum กำหนดไว้ดังนี้
void mdmChecksum(const uint8_t *buffer, const uint16_t length, checksum_t *checksum)
{
for (uint16_t i = 0; i < length; i++) {
checksum->ck = checksum->ck + buffer[i];
}
}
ซึ่งไม่มีอะไรมาก ข้อมูลมีแค่ไหน ก็บวกมันไปเรื่อย ๆ ทีละ byte เมื่อได้ค่า checksum แล้ว ก็ผนวกเอาค่านี้ลงไปเซฟกับเค้าด้วย
มาถึงส่วนฟังก์ชันการอ่าน loadConfigData ก็คล้าย ๆ กับการเขียน แต่เราจะสร้างตัวแปร meta_temp ซึ่งใช้ struct ประเภทเดียวกันขึ้นมารับค่าที่อ่านได้จากฟังก์ชัน readBytes ก่อน แล้วจึงเอาค่าที่อ่านได้มาคำนวณ checksum ใหม่ด้วยฟังก์ชัน mdmChecksum อีกทีให้มั่นใจว่าค่าที่อ่านมาได้นั้นมี checksum ที่เท่ากับค่าที่ระบุอยู่ในตัวแปรที่อ่านได้
ตรงนี้อาจงง ๆ นิดนึง แต่ให้นึกว่าเราเอาเงินใส่โอ่งนับไว้เรียบร้อยแปะป้ายไว้ว่า 100บาท ฝังดินผ่านไป20ปี ขุดขึ้นมาเห็นป้ายทราบว่า 100 บาท เราก็ต้องนับจำนวนมันอีกทีว่าแอบมีคนจิ๊กไปหรือปล่าว อะไรทำนองนี้
bool loadConfigData()
{
File file = FileFS.open(FILENAME, "r");
Serial.println(F("========================\nLoadCfgFile Modulemore.com"));
memset((void *) &_mdmconfig, 0, sizeof(_mdmconfig));
if (file)
{
mdmconfig_s meta_temp;
file.readBytes((char *) &meta_temp, sizeof(meta_temp));
displayConfig(meta_temp);
file.close();
Serial.println(F("OK"));
checksum_t chksum={0};
mdmChecksum( (uint8_t*) &meta_temp, sizeof(meta_temp) - sizeof(meta_temp.checksum), &chksum );
// Serial.printf("Recaled checksum = %d %d\n", chksum.ck, sizeof(meta_temp) - sizeof(meta_temp.checksum));
if ( meta_temp.checksum.ck != chksum.ck)
{
Serial.println(F("_mdmconfig checksum error."));
return false;
}
memcpy(&_mdmconfig, &meta_temp, sizeof(_mdmconfig));
displayConfig(_mdmconfig);
return true;
}
else
{
Serial.println(F("failed"));
return false;
}
}
เมื่อการ checksum test เป็นไปได้สวย คือค่าเท่ากัน เราก็ดำเนินการคัดลอกข้อมูล structure จากตัวที่สร้างขึ้นมาเพื่อรับข้อมูลใน flash ที่ชื่อว่า mdm_temp ไปยังตัวที่เราจะเอาไปใช้จริง ๆ ในระบบ ซึ่งคือ _mdmconfig นั่นเอง
มาถึงตรงนี้คิดว่าคงพอเห็นภาพบ้างแล้วว่า การเอาค่า config ไปใส่ไว้ในตัว flash memory ของ esp32 ไม่ได้ซับซ้อน(รึป่าว) ด้วยการจัดให้อยู่ในรูปแบบ structure ทำให้เพิ่มตัวแปรต่าง ๆ ได้ตามใจชอบ แต่อย่างไรก็ตามการเขียนลงบน storage ใด ๆ ล้วนมีอายุขัย flash memory บน esp32 เขียนได้ราว ๆ 10,000 - 100,000 ครั้ง ต่อพื้นที่ 1 หน่วย ดังนั้นหาก application ไหนมีการใช้งานการตั้งค่าบ่อย ๆ ควรทำระบบเช็คดังเช่นตัวอย่างนี้ไว้ด้วย โดยอาจ implement เพิ่มเติมว่า หากอ่านแล้วค่า checksum ไม่ตรงกัน ก็ให้ลองย้ายโฟลเดอร์ หรือจัดสรรค์ตำแหน่งเก็บไฟล์ใหม่ ก็อาจช่วยยืดอายุอุปกรณ์ให้ใช้งานได้อย่าง practical มากขึ้นครับ
ธนบดี บุหลันศรีชาติ
ผู้เขียน
Reference
https://www.esp32.com/viewtopic.php?t=709
https://github.com/RalphBacon/203-SPIFFS-vs-LITTLEFS
https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#uploading-files-to-file-system