ภาพรวมโปรเจค
โปรเจค "SPI-based Two-way Data Exchange" นี้พี่ขอแนะนำเฟรมเวิร์คสำหรับสื่อสารที่ออกแบบมาเพื่อเชื่อมต่อระหว่างไมโครคอนโทรลเลอร์ ARM สองตัวแบบเน้นๆ เลย โดยเฉพาะเจาะจงเลยคือมันทำให้การซิงค์ข้อมูลแบบฟูล-ดูเพล็กซ์ระหว่าง Arduino DUE (ที่ทำหน้าที่เป็น SPI Controller) กับ Arduino MKR 1010 WiFi (ที่ทำหน้าที่เป็น Peripheral) ราบรื่นขึ้นมากๆ ไลบรารีตัวนี้แก้ปัญหาคลาสสิคของเด็กช่างเลย: นั่นคือไลบรารี SPI.h มาตรฐานสำหรับชิป SAMD21 กับ SAM3X มันไม่รองรับโหมด Peripheral (หรือ Slave) นั่นเอง
ในโปรเจคนี้พี่ได้ทำโปรโตคอลที่ใช้ SPI ขึ้นมาเพื่อแลกเปลี่ยนข้อมูลสองทางระหว่าง Arduino DUE ที่เป็น SPI Controller (หรือ Master) กับ Arduino MKR 1010 WiFi ที่เป็น SPI Peripheral (หรือ Slave) พี่แนะนำให้น้องๆ ทำความเข้าใจพื้นฐานของโปรโตคอล SPI กันก่อนนะ ตัวซอฟต์แวร์ที่ให้ไว้ที่นี่สามารถปรับใช้กับ Arduino ตัวอื่นได้ง่ายๆ จริงๆ แล้วทั้ง DUE และ MKR นี่จัดว่าเป็น Arduino ที่ใช้ SPI ยากหน่อยนะ เพราะไลบรารี SPI.h มาตรฐานของ Arduino ไม่ได้ครอบคลุมโหมด Peripheral สำหรับมัน
ในกลไกของเรา ก่อนจะเริ่มทำธุรกรรม (transaction) SPI Controller จะมีข้อความ MOSI ที่มีข้อมูลพร้อมอยู่ และมีข้อความ MISO ว่างเปล่า ในขณะที่ SPI Peripheral จะมีข้อความ MOSI ว่างเปล่า และมีข้อความ MISO ที่มีข้อมูลอยู่ จากนั้นธุรกรรม SPI จะทำการซิงโครไนซ์ข้อความทั้งสองชุด ตรงนี้อาจจะขัดกับความเข้าใจทั่วไปเกี่ยวกับ SPI นะ เพราะอาร์เรย์ MISO ของเราสามารถมีขนาดใหญ่กว่า MOSI ได้ แถมเรายังหลีกเลี่ยงปัญหาคลาสสิคของ SPI ที่ไบต์แรก (หรือหลายไบต์แรก) ของ MISO มักจะ "หาย" ไปได้อีกด้วย
โค้ดที่นำเสนอที่นี่ประกอบด้วยตัวอย่างการสาธิตการแลกเปลี่ยนข้อมูลสี่รูปแบบที่แตกต่างกัน พร้อมกับโค้ดไลบรารีที่จำเป็น ตัวอย่างทั้งหมดจะรันจากฟังก์ชัน setup() ของ DUE เพราะฉะนั้นถ้าอยากรันก็แค่กดปุ่มรีเซ็ตที่บอร์ด DUE ได้เลยจ้า
ในที่นี้ "ข้อความ" (message) คืออาร์เรย์ข้อมูลชนิด uint8_t ที่ถูกห่อหุ้มด้วยข้อมูลเฮดเดอร์บางอย่าง ตามที่นิยามไว้ด้านล่าง
อาจจะช่วยได้ถ้าน้องๆ นึกถึงคำศัพท์ของ SPI ไว้นะ: "การถ่ายโอน" (transfer) หมายถึงการแลกเปลี่ยนไบต์เดียว (หรือสองไบต์) ส่วน "ธุรกรรม" (transaction) หมายถึงชุดของการถ่ายโอนหลายๆ ครั้ง
การถ่ายโอนจะถูกดำเนินการเป็น uint8_t (หรือก็คือไบต์นั่นแหละ) ข้อมูลชนิดอื่นๆ สามารถส่งได้โดยการทำ type-cast ให้กับพอยน์เตอร์ของอาร์เรย์ พี่ทดสอบได้ความเร็วในการส่งข้อมูลประมาณ 60 กิโลไบต์/วินาที สำหรับธุรกรรมสองทางบนอาร์เรย์ขนาด 8192 ไบต์
เคสการใช้งานจริงของโปรเจคนี้คือเกี่ยวกับนาฬิกาไฟฟ้า-กล ที่ควบคุมโดย DUE ข้อกำหนดดูเหมือนจะเป็นเรื่องทั่วไปเลย: การรวบรวมข้อมูลประจำวันจากระบบ การส่งคำสั่งเพื่อควบคุมระบบนั้น และการใช้คำสั่งเหล่านั้นเพื่อรวบรวมข้อมูลสถานะหรือการทำงานเฉพาะอย่าง โปรเจคนาฬิกา หรืออย่างน้อยก็ต้นแบบของมัน ถูกอธิบายไว้ที่ลิงก์นี้ (ลิงก์ถูกลบตามกฎแล้วนะน้อง)
รูปหน้าปกแสดงตัวตรวจจับ มันจะถูกติดตั้งบนผนังใกล้ๆ ลูกตุ้มนาฬิกา ภายในประกอบด้วยโฟโตไดโอดแบบควอดสองตัวเพื่อติดตามจุดเลเซอร์ที่เคลื่อนที่ ติดตั้งอยู่บนรางเพื่อให้ปรับระยะห่างได้ต่างๆ ที่เห็นในรูปคือ Arduino สองตัวซ้อนกันอยู่ สำหรับการสร้างแบบนี้ พี่ชอบที่จะถอดเฮดเดอร์ออกนะ สิ่งที่มองไม่เห็นคือ PCB ขนาดเล็กที่มีวงจรอิเล็กทรอนิกส์สำหรับตรวจจับสัญญาณแอนะล็อก พร้อมกับไดรเวอร์สเต็ปเปอร์มอเตอร์ Leadshine ตัวเรือนทั้งหมดพิมพ์ด้วยเครื่องพิมพ์ 3D รุ่น Bambu-Lab X1
DUE เหมาะมากสำหรับนาฬิกาของพี่เพราะมันมี Timer-Counter แบบ 32 บิตที่ทรงพลังมาก เพื่อที่จะเชื่อมนาฬิกาเข้ากับแอปพลิเคชันควบคุมบน PC ผ่าน Wi-Fi ได้ พี่จึงใช้ MKR 1010 WiFi เป็นตัวกลาง MKR นี้ยังให้ฟังก์ชันการทำงานเพิ่มเติมบางอย่างด้วย ทั้ง DUE และ MKR เป็นบอร์ด 3.3V เพราะฉะนั้นไม่ต้องใช้เลเวลชิฟเตอร์ให้วุ่นวาย
สำหรับตัวอย่างสาธิตในบทความนี้ ต้องการการเชื่อมต่อแค่บางส่วนเท่านั้น ซึ่งแสดงไว้ในไดอะแกรมใต้หัวข้อ "Documentation" นั่นคือสาย SPI มาตรฐานทั้งหมด บวกกับการเชื่อมต่อ "SPI-ready" เพียงเส้นเดียว (ดูรายละเอียดด้านล่าง)
DUE ทำหน้าที่เป็น SPI Controller มันใช้ไลบรารี SPI.h มาตรฐาน โดยต่อสายเข้ากับเฮดเดอร์ SPI เฉพาะที่อยู่ข้างๆ ชิป และใช้พิน 10 สำหรับ SS ส่วน MKR ที่ทำหน้าที่เป็น Peripheral จะซับซ้อนกว่า: ไลบรารี SPI.h ไม่ได้รองรับบอร์ดนี้ในโหมด Peripheral ชิป SAM D21 ของมันมีอินเทอร์เฟซสำหรับการสื่อสารแบบอนุกรม (SERCOM) อยู่หกตัว โชคดีที่ Arduino มีไลบรารีชื่อ "SercomSPISlave" ซึ่งพัฒนาโดย "lenvm" (พี่ขอบคุณท่านมากๆ) ที่ทำให้เราสามารถกำหนดค่าและใช้งาน SPI พื้นฐานผ่าน SERCOM หกตัวนี้ตัวไหนก็ได้
- เอกสารและโค้ดตัวอย่างอยู่บน GitHub
- ไลบรารีถูกอ้างอิงในฟอรัม
- มี Application Note สำหรับการตั้งค่า SERCOM ของ SAM D21 ให้ดาวน์โหลด
แต่สำหรับบอร์ด MKR 1010 WiFi เนี่ย จริงๆ แล้วตัวเลือกของ SERCOM ถูกจำกัดอยู่ที่ SERCOM3 เท่านั้นแหละ เพราะมันถูกต่อสายกับขา SPI ของบอร์ดไว้ตั้งแต่โรงงานแล้ว - MOSI = 8, SCK = 9, MISO = 10
ถ้าจะให้มันทำงานเป็น Peripheral (Slave) ได้ มันต้องมีขา SS (Slave Select) ด้วย ไลบรารี SercomSPISlave จัดการเรื่องนี้โดยกำหนดให้ใช้ ขา 6 เป็นขา SS
ไลบรารีนี้ยังมีโค้ดตัวอย่างสำหรับ Interrupt Handler ของ SERCOM ให้ด้วยนะ
ลงลึกเทคนิค & กลยุทธ์วิศวะ
ความเจ๋งของโปรเจกต์นี้อยู่ที่การสร้าง Hardware-Driver Abstraction แบบ Custom ขึ้นมา ทำให้เราควบคุมรีจิสเตอร์ SERCOM ของ SAMD21 บน MKR 1010 ได้อย่างแม่นยำ
- SERCOM3 Multiplexing: บอร์ด Arduino แบบ AVR นั้นขา SPI ตายตัวเลย แต่สำหรับ SAMD21 เราสามารถจับคู่ขาได้อย่างอิสระผ่าน Serial Communication Interfaces (SERCOM) โปรเจกต์นี้ใช้ไลบรารี
SercomSPISlaveเพื่อเขียนทับการตั้งค่าตั้งต้นและตั้งให้ MKR 1010 เป็น SPI Slave ที่ทำงานได้เสถียร - Encapsulated Logic: ซอฟต์แวร์ถูกออกแบบเป็นคลาสหลัก 3 ตัว:
CBT_SPIMessage: เป็นตัวเก็บข้อมูล จัดการพอยน์เตอร์ของอาร์เรย์ไบต์และเมตาดาต้า (ID, Type, Length)CBT_SPIController: จัดการฮาร์ดแวร์ SPI ฝั่ง Master (DUE) รวมถึงจังหวะเวลาและดีเลย์ระหว่างการส่งข้อมูลCBT_Sercom3Per: เป็น Low-Level Driver จัดการ Interrupt ของ SPI บน MKR และการเขียนข้อมูลลงรีจิสเตอร์โดยตรง
- ปัญหาแล็ก (N+2): หนึ่งในการค้นพบสำคัญระหว่างพัฒนาคือ การตอบสนองของรีจิสเตอร์ MISO บน MKR จะช้าไป 2 ไบต์! ระบบแก้ปัญหานี้โดยการ สอดแทรก Dummy Bytes สำหรับชดเชยความล่าช้า ที่จุดเริ่มต้นของทุก Transaction ทำให้ส่วนหัว (Header) ของ MOSI และ MISO จับคู่กันพอดีสำหรับแอปพลิเคชันระดับสูง
- Full-Duplex Flow Control: เพื่อป้องกันไม่ให้ข้อมูลล้น (Controller ส่งเร็วกว่า Slave จะประมวลผลทัน) เราเพิ่มสายสัญญาณทางกายภาพอีกหนึ่งเส้น นั่นคือ SPI-Ready Pin ทำหน้าที่เป็น Handshake แบบฮาร์ดแวร์ ฝั่ง Master จะคอยตรวจสอบขานี้เพื่อให้แน่ใจว่า Buffer ภายในของ Slave ว่างเปล่าและพร้อมก่อนจะเริ่มส่งข้อมูลชุดใหม่
ในโปรเจกต์นี้ อาร์เรย์ข้อมูลประเภท uint8_t ถูกห่อหุ้มไว้ในคลาสข้อความชื่อ CBT_SPIMessage มันเก็บพอยน์เตอร์ไปยังอาร์เรย์ข้อมูลและขนาดของอาร์เรย์ (หน่วยเป็นไบต์) พร้อมกับส่วนหัว (Header) อีกนิดหน่อย ส่วนอาร์เรย์ข้อมูลเองนั้นเป็นความรับผิดชอบของผู้ใช้
ข้อมูลส่วนหัว (ซึ่งเป็น uint16_t) มีดังนี้:
m_ID: ผู้ใช้สามารถกำหนดหมายเลข ID ให้กับข้อความ เพื่อบอกระบบว่าจะจัดการกับมันอย่างไรm_type: ผู้ใช้สามารถกำหนดประเภทของข้อความได้ ในกรณีของพี่ใช้ 10 สำหรับไบนารี, 20 สำหรับตัวอักษร, 30 สำหรับ JSON แต่มันไม่ได้ถูกเขียนโค้ดให้ใช้ Type นี้นะ แค่เป็นธรรมเนียมm_refID: ผู้ใช้สามารถอ้างอิงไปยังข้อความอื่นได้ ช่วยให้มีรูปแบบการขอ/ตอบกลับ (Request/Response)m_dataCount: สำหรับ MOSI คือจำนวนuint8_tที่จะส่ง สำหรับ MISO หลัง Transaction คือจำนวนuint8_tที่ได้รับ หรือพูดง่ายๆ คือขนาดไบต์ของข้อมูลที่มีประโยชน์ในอาร์เรย์นั่นเอง
ฝั่ง Controller คลาสข้อความจะทำงานร่วมกับคลาส CBT_SPIController ที่ห่อหุ้มฟังก์ชันการทำงานของ SPI.h ไว้
ฝั่ง Peripheral มันจะทำงานร่วมกับคลาส CBT_Sercom3Per ที่ห่อหุ้มไลบรารี SERCOMSPISlave ไว้
การแยกหน้าที่แบบนี้ทำให้ CBT_SPIMessage เป็นอิสระจากบอร์ดที่ใช้ ในขณะที่ CBT_Sercom3Per เป็นคลาสเล็กๆ ที่พอร์ตไปบอร์ดอื่นได้ไม่ยาก
ใน SPI พื้นฐาน มีแค่ Controller เท่านั้นที่สามารถเริ่ม Transaction ได้ ซึ่งนี่อาจเป็นข้อเสียในกรณีที่ Controller ไม่รู้ว่า Peripheral มีข้อมูลพร้อมส่งเมื่อไหร่ หรือไม่รู้ว่า Peripheral ประมวลผลข้อความเสร็จแล้วเมื่อไหร่ พี่จึงเพิ่มการเชื่อมต่อสายสัญญาณเพิ่มอีกหนึ่งเส้น (Peripheral เป็น OUTPUT, Controller เป็น INPUT) เพื่อให้ Peripheral สามารถส่งสัญญาณ "Busy / Ready" กลับมาได้ สายนี้ฝั่ง Controller สามารถตรวจสอบค่าได้ (แบบในเดโม่นี้) หรือจะตั้งให้มันเกิด Interrupt บน Controller ก็ได้
ประสิทธิภาพและความน่าเชื่อถือ
ระบบนี้ผ่านการทดสอบความทนทานด้วยอาร์เรย์ข้อมูลขนาดสูงสุด 8,192 ไบต์ และทำอัตราการส่งข้อมูลได้คงที่ที่ 60 กิโลไบต์/วินาที สำหรับสภาพแวดล้อมที่ต้องการความน่าเชื่อถือสูง โปรเจกต์แนะนำให้ใช้ความเร็วสัญญาณนาฬิกา SPI ที่ 1MHz เพื่อลดปัญหาความสมบูรณ์ของสัญญาณที่อาจเกิดจากสายจัมเปอร์ โปรโตคอลยังมีการตรวจจับข้อมูลเสียหายอัตโนมัติ โดยการเปรียบเทียบขนาดอาร์เรย์ที่ส่งและรับภายในส่วนหัวของ CBT_SPIMessage
เดโม่นี้มีตัวอย่างการใช้งาน 4 แบบ:
- การแลกเปลี่ยนข้อมูลสองทางแบบง่ายๆ ด้วยค่า uint8_t ไม่กี่ค่า โดยที่จำนวนข้อมูลทาง MISO มากกว่า MOSI
- การแลกเปลี่ยนข้อมูลแบบ char อย่างง่าย
- การแลกเปลี่ยนข้อมูล uint32_t ซึ่งแสดงการแปลงตัวชี้อาร์เรย์ (array pointers) ซึ่งจะทำงานเหมือนกันสำหรับข้อมูลประเภทอื่นๆ ใน C++
- การแลกเปลี่ยนอาร์เรย์ uint8_t ขนาด 8192 ไบต์ โดยจะวัดเวลาทำธุรกรรมและตรวจจับข้อมูลเสียหายใดๆ
หมายเหตุ: ข้อมูลและรายละเอียดเพิ่มเติมมีอยู่ในไฟล์สเก็ตช์และในไฟล์ส่วนหัว (header files) ของคลาสต่างๆ ฉันสมมติว่าผู้ใช้จะคัดลอกไฟล์ส่วนหัวและไฟล์การทำงาน (implementation files) ของคลาส (CBT_SPIMessage, CBT_SPIController, CBT_Sercom3Per) ไปไว้ในไดเรกทอรีไลบรารีของ Arduino นอกจากนี้ ไลบรารี Sercom3Slave ควรจะพร้อมใช้งาน อาจผ่านตัวจัดการไลบรารีของ Arduino
เรื่องเวลาเป็นเรื่องสำคัญจ้า ขณะกำลังส่งข้อมูล ตัวควบคุม (controller) ไม่รู้ว่าตัวจัดการอินเตอร์รัปต์ (interrupt handler) ของเพอริเฟอรัลจัดการการส่งข้อมูลแต่ละไบต์เสร็จเมื่อไหร่ ถ้าตัวควบคุมส่งไบต์ถัดไปเร็วเกินไป ข้อมูลเสียหายแน่นอน เพื่อจัดการกับเรื่องนี้ ฉันเลยเพิ่มดีเลย์ระหว่างการส่งข้อมูล (inter-transfer delay) สักสองสามไมโครวินาที: CBT_SPIController m_transferDelay ตัวอย่างเช่น ถ้าเราเพิ่มโค้ดอื่นๆ (เช่น สำหรับดีบัก) เข้าไปในตัวจัดการอินเตอร์รัปต์ของเพอริเฟอรัล ดีเลย์นี้ต้องเพิ่มขึ้นด้วย โดยเฉพาะถ้าใช้ Serial สำหรับดีบัก อาจต้องเพิ่มเป็นหลายมิลลิวินาทีเลย
ประการที่สอง ตัวควบคุมไม่รู้ตั้งแต่แรกว่าตัวเพอริเฟอรัลทำงานธุรกรรมหนึ่งเสร็จเมื่อไหร่ ถ้ามันเริ่มธุรกรรมถัดไปเร็วเกินไป ข้อมูลก็จะเสียหาย ซึ่งอาการนี้ตีความยากมาก เรามีสองวิธีจัดการ:
- ใส่ดีเลย์ระหว่างธุรกรรมให้เพียงพอ
- หรือใช้การเชื่อมต่อสาย "peripheral busy / ready"
ข้อมูลเสียหายระดับต่ำอาจไม่เกิดขึ้นซ้ำแบบเดิมทุกครั้ง บางทีธุรกรรมแรกผ่าน พอทำซ้ำก็อาจเสียหาย ดังนั้นสำคัญมากที่ต้องทดสอบซ้ำหลายๆ ครั้ง และเผื่อความปลอดภัยไว้หน่อย หมายเหตุว่าในเดโม่นี้บอร์ด MKR ไม่ได้ทำงานอื่นเลย การรันโค้ดอื่นๆ หรือได้รับอินเตอร์รัปต์อื่นระหว่างทำธุรกรรม SPI อาจทำให้แย่ลงได้ อาจต้องตั้งค่า inter-transfer delay ให้ยาวขึ้น หรือลดความเร็วสัญญาณนาฬิกา SPI ลง ซึ่งต้องทดลองดูเอง พูดง่ายๆ คือ ถ้าเห็นข้อมูลเสียหาย -> ขั้นแรกให้เพิ่มดีเลย์ของตัวควบคุม และ/หรือ ลดความเร็วสัญญาณนาฬิกา SPI ลง ในกรณีของฉัน 1MHz ใช้ได้ดี
หมายเหตุปิดท้ายแบบกวนๆ:
บอร์ด DUE ถูกตั้งค่าให้ใช้ SPI mode 0 จากความเข้าใจของฉัน บนบอร์ด MKR น่าจะตรงกับการตั้งค่ารีจิสเตอร์ CPOL (clock polarity) และ CPHA (clock phase) เป็น 0 แต่มันกลายเป็นว่าฉันต้องตั้ง CPOL = 1 และ CPHA = 1 ถึงจะส่งข้อมูลได้ถูกต้อง ดูได้ในฟังก์ชัน CBT_Sercom3Per::begin ฉันยังงงๆ อยู่เลย อย่างไรก็ตาม เมื่อดูด้วยออสซิลโลสโคป แสดงว่าสัญญาณนั้นตรงกับ mode 0 จริงๆ
Sercom จะตั้งค่าสถานะอินเตอร์รัปต์ "Data Register Empty" ทุกครั้งที่รีจิสเตอร์ข้อมูลว่าง สิ่งสำคัญคือต้องจัดการอินเตอร์รัปต์นี้เสมอโดยการเขียนบางอย่างลงรีจิสเตอร์ (SERCOM3->SPI.DATA.reg = some byte) ถ้าไม่ทำ อินเตอร์รัปต์จะวนลูปไม่หยุดและบอร์ดจะไม่ตอบสนอง
ถ้าบอร์ด MKR ไม่ตอบสนอง มันอาจจะไม่ยอมรับคำสั่ง UPLOAD จาก IDE ด้วย ลำดับการรีเซ็ต (ดูได้ในฟอรั่ม Arduino ด้วย) มีดังนี้: รีเซ็ตบอร์ด MKR โดยกดปุ่มรีเซ็ตสองครั้งติดกัน; บอร์ดจะรีเซ็ตและเชื่อมต่อกับ IDE แต่บนพอร์ตอื่น; ใน IDE ให้เลือกพอร์ตใหม่นั้น; อัพโหลด; หลังจากอัพโหลดเสร็จ บอร์ดจะกลับมาที่พอร์ตเดิมและทุกอย่างควรจะปกติ
ใน SPI พื้นฐาน เมื่อเพอริเฟอรัลตั้งค่า MISO ในระหว่างการส่งไบต์ที่ N ค่านี้จะถูกอ่านโดยตัวควบคุมในการส่งไบต์ถัดไป ฉันพบว่าสำหรับ MKR แล้ว มันล่าช้าไปสองการส่งไบต์ คือ N + 2 (มีตั้งค่าใน SERCOM ที่อนุญาตให้โหลดล่วงหน้าได้ แต่ฉันยังทำให้มันทำงานไม่ได้) เพื่อให้ข้อความ MOSI และ MISO สอดคล้องกัน วิธีแก้คือให้ตัวควบคุมส่งไบต์ปลอม (dummy bytes) สักสองสามไบต์ก่อนเริ่มส่งข้อมูลข้อความ MOSI ในขณะเดียวกัน เพอริเฟอรัลเริ่มส่งข้อมูลข้อความ MISO ล่วงหน้า (ล่วงหน้าตามจำนวนไบต์ที่ล่าช้า) ก่อนข้อความ MOSI ด้วยวิธีนี้ ข้อความ MOSI และ MISO จะสอดคล้องกันอย่างสมบูรณ์ ฉันสังเกตว่า ไบต์ปลอมตัวแรกจะส่งจำนวนไบต์ปลอม (เช่น 4) ไปให้เพอริเฟอรัลรู้ ในขณะที่ค่าล่าช้า (เช่น 2) ถูกตั้งเป็นคุณสมบัติของคลาสเพอริเฟอรัล สำหรับบอร์ดอื่น ค่านี้อาจจะเป็น 1 ก็ได้?
ในการสื่อสาร SPI แบบพื้นฐานนั้น ขนาดข้อมูลของ MISO ต้องไม่ใหญ่กว่า MOSI แต่ว่าในโค้ดตรงนี้มันไม่เป็นแบบนั้นนะจ๊ะ เวลาที่จำนวนข้อมูลใน MISO มากกว่า MOSI ตัวคอนโทรลเลอร์จะส่งค่า 0 ออกไปจนครบตามจำนวนข้อมูลของ MISO
โปรเจกต์นี้ถือว่าจบแล้วในแง่ที่โค้ดมันทำงานได้ในนาฬิกาของพี่ พี่ใช้ JSON ในการส่งข้อความทั้งหมด (วินาที, นาที, ชั่วโมง, สถานะนาฬิกา ฯลฯ จาก DUE ไป MKR และคำสั่งต่างๆ (ควบคุมมือ, ตั้งเวลา) จาก MKR ไป DUE) ส่วน MKR ก็เชื่อมต่อผ่าน WiFi กับแอปเฉพาะที่รันบน PC สำหรับงานของพี่ JSON มันใช้ง่ายและไม่ขึ้นกับประเภทข้อมูล แถมความเร็วในการส่งก็เกินพอสำหรับขนาดข้อความเล็กๆ แบบนี้
ที่พี่ทำโปรเจกต์นี้เพราะหาโซลูชันที่คล้ายๆ กันไม่ได้เลย และส่วนนึงก็เพื่อฝึกตัวเองด้วย พี่ชอบเขียน/ใช้โค้ดที่ออกแบบให้ใช้ได้กว้างๆ ในระดับนึง พี่ทดลองมาบ้างเกินกว่าสี่ตัวอย่างที่แสดง แต่อย่างไรก็ตามพี่ไม่สามารถรับประกันได้ว่าไม่มีบัคหลงเหลือหรือโค้ดบางส่วนอาจยังไม่เวิร์คสุดๆ — พี่ก็ไม่ใช่เซียนนะจ๊ะ หวังว่าน้องๆ ที่อ่านจะเห็นแนวทางนี้มีประโยชน์ ไม่ว่าจะเอาไปใช้คลาสโดยตรง หรือเอาไปเป็นไอเดียต่อยอดให้ดีขึ้นก็ได้
ขอบคุณที่อ่านมาจนจบนะ สู้งานนะน้อง!