มาออกแบบ Protocol ไว้สื่อสารกันเถอะ



        วันนี้ดันมีอารมณ์อยากเขียนบทความนี้ขึ้นมา  จริง ๆ เป็นเรื่องที่ค่อนข้างเจอบ่อยในการสื่อสารข้อมูลของเจ้าตัว Arduino ก็คือ Serial เจ้าของบล็อกเคยเขียนบทความที่คล้าย ๆ กับบทความนี้ไว้ในบทความ อยากส่ง int ผ่าน Serial ไว ๆ ทำไงดี :D แต่ในบทความนี้จะเป็นการออกแบบ Packet ซึ่งปรกติแล้วจะนิยมออกแบบกับการสื่อสารแบบ Bus (ถ้ามีพื้นฐานพวก bit operator พวกนี้มาบ้างน่าจะทำให้อ่านเข้าใจยิ่งขึ้นมั้งนะ)


        


        การสื่อสารแบบ Bus จะมีอุปกรณ์หลายตัวเชื่อมต่อไปยังสายข้อมูลเดียวกัน เมื่อมีข้อมูลวิ่งอยู่บน Bus อุปกรณ์ทุกตัวจะได้ข้อมูลเหมือนกัน แต่เราจะจัดการอย่างไรเมื่อเราต้องการสื่อสารกับอุปกรณ์แค่ตัวเดียวหรือตามที่เรากำหนด จึงจำเป็นต้องออกแบบ Packet ในการส่งข้อมูลนั่นเอง




         สิ่งที่เราคาดหวังในการสื่อสารข้อมูล เราอยากจะส่งข้อมูลอะไรซักอย่างให้กับ Module ที่เราต้องการจะคุยด้วย แต่ติดตรงที่ข้อมูลทุกตัวดันได้รับข้อมูลเหมือนกันนี่สิ คิดว่าเราจะแก้ไขปัญหานี้ยังไงดี.. เราก็กำหนด Address ให้กับ Module ทุกตัวใน Bus ของเราไปเลยสิและเมื่อ Module มี Address แล้ว ทีนี้การส่งข้อมูลของเราก็ต้องส่ง Address ของ Module ที่เราต้องการจะคุยไปด้วยพร้อมกับข้อมูลที่เราต้องการส่งไปให้ Module นั้น



เพิ่มคำอธิบายภาพ

         ในส่วนของการออกแบบ Packet เจ้าของบล็อกจะอ้างอิงจาก Protocol ของ Dynamixel rx28 ซึ่งตัว Dynamixel rx28 คือ Servo motor ที่ค่อนข้างพิเศษสามารถสั่งงานผ่านทาง Serial และสื่อสารกันในรูปแบบ Bus ซึ่งก็คือสามารถต่อ Dynamixel ได้หลายตัวแต่ใช้สายในการต่อแค่ 4 สาย ก็คือ Tx Rx Vcc และ Gnd และ Dynamixel ทุกตัวจะจั้มสายมาที่เดียวกัน เพราะมันคือการส่งข้อมูลกันในรูปแบบ Bus



          
          ต่อมาคือหน้าตา Packet ของ rx28 เจ้าของบล็อกคิดว่าการกำหนด Packet แบบนี้ค่อนข้าง Univasal เพราะมีคนใช้โปรโตคอลแบบนี้ค่อนข้างแพร่หลาย

อ้างอิงจาก http://www.hizook.com/files/users/3/RX-28_Robotis_Dynamixel_Servo_UserGuide.pdf

           การกำหนด protocol แบบนี้ข้อมูลที่ Module รับได้จะมีความแม่นยำสูง และโอกาที่ Module รับค่าได้แล้ว error จะน้อยมาก เพราะใน protocol จะมีการส่ง Check sum ตามไปด้วย เพื่อให้ฝั่ง module ที่รับค่าเช็คว่าข้อมูลถูกต้องหรือไม่

หลังจากนี้เจ้าของบล็อกขอเรียก Module ที่คอยรับข้อมูลว่า slave นะครับ
 
            มาดูกันว่า Packet นี้มีองค์ประกอบอะไรบ้าง


  • 0xFF 2 ตัวข้างหน้าคือการส่งไปบอก slave ว่าจะมีข้อมูลส่งมาแล้ว ถ้าหากว่าไม่มี 0xFF 2 ตัวส่งมาก่อน ถึงแม้ว่าจะมีข้อมูลอื่นคอยส่งมาอยู่เรื่อย ๆ ก็ตาม slave จะไม่อ่านข้อมูลที่เหลือ เพราะถือว่ายังไม่ถึงเวลาเริ่มรับข้อมูล
  • ID ก็คือ Address ของ slave ที่เราต้องส่งข้อมูลไปหา
  • LENGTH ก็คือจำนวนของข้อมูลที่ต้องการส่งไปให้ Slave ถ้าดูจากตัวอย่างข้อมูล Packet จะนับตั้งแต่ INSTRUCTION,PARAMETER1,PARAMETER2,...PARAMETER..N และนับ CHECK SUM ด้วย 
  • INSTRUCTION มักจะใช้เป็นการบอก Slave ว่าเราอยากจะทำอะไรกับมัน เช่นส่ง 1 คือการ Write ข้อมูลใน Slave ส่ง 2 คือการ Read ข้อมูลจาก Slave และ 3 คือการเช็คสถานะของ Slave
  • PARAMETER คือส่งของคำสั่งที่เราต้องการให้ Slave มันทำงาน ถ้าเป็น PARAMETER ตัวแรกของ RX 28 มันจะเป็นการเขียน Address ของคำสั่ง ว่าเราอยากจะสั่งอะไร ก็เลือก Address นั้น หลังจากนั้น PARAMETER ที่ 2 จนถึง PARAMETER ที่ N จะเป็นการเขียนค่าลงไปตามคำสั่งที่เรา Set ไว้ใน PARAMETER 1 
  • CHECK SUM ในส่วนนี้จะเป็นการบวกค่าข้อมูลตั้งแต่ ID จนถึง PARAMETER ตัวที่ N จากนั้นเอาค่าผลบวกที่ได้มา Not แล้วหลังจากนั้นก็ไป & กับ 0xFF เพื่อให้ CHECK SUM มีขนาดเหลือแค่ 1 byte



 อ้างอิงจาก http://www.hizook.com/files/users/3/RX-28_Robotis_Dynamixel_Servo_UserGuide.pdf
   
             โอเคตอนนี้เรารู้แล้วเนาะว่ารูปร่างหน้าตา Packet มันเป็นยังไง ต่อมาเราลองมาสร้าง Packet กันเองเลยดีกว่า ใช้ Arduino นี่แหละ

             เจ้าของบล็อกลองสร้าง function ที่ทำหน้าที่ส่ง Packet ไปยัง Slave และถ้าสังเกตุดูการออกแบบ Packet จะคล้าย ๆ กับ rx 28 เลยทีเดียว ใน parameter แรงของ function เจ้าของบล็อกให้เป็น Data ที่ต้องการส่งไปให้ตัว Slave ส่วน parameter ตัวที่ 2 ก็คือ Address ของ slave ที่เราต้องการส่งไปให้ ในส่วนสุดท้าย parameter ตัวที่ 3 คือ instruction มันคือการบอกว่าจะให้ Slave ทำอะไรนั่นเอง





             เมื่อวลาส่งข้อมูลออกไปนั้น เราจะเริ่มส่ง Header ออกไปก่อนเสมอเพื่อให้ Slave รู้ว่าเมื่อมีการส่ง Header มา จะมีข้อมูลอื่นตามมาด้วย


             เมื่อ Slave ได้รับข้อมูลมา Slave จะเอาข้อมูลที่ได้มาไป check ว่าส่งมาถูกต้องหรือไม่ จากที่เคยบอกไปเราจะให้ Slave เริ่มเช็คที่ Header ก่อนเลยว่าข้อมูลที่อ่านมาได้นั้นใช้ข้อมูลที่เราต้องการจะเอามาประมวลผลหรือไม่ เมื่อมี Header ส่งมาถูกต้องลำดับต่อมาจริง ๆ จะ check ที่ check sum ก่อนก็ได้ แต่ในตัวอย่างเจ้าของบล็อกจะให้ check ที่ id ก่อนว่าส่งมาถูก ID หรือเปล่า เมื่อ ID ถูกต้อง ก็ไป check sum ดูว่า ข้อมูลที่ส่งมามีข้อมูลอะไรผิดเพียนไปหรือไม่ จะอ้างอิงจาก check sum นี่แหละ และสุดท้ายเมื่อผ่าน checksum มาได้ เราก็จะมั่นใจแล้วว่าข้อมูลที่เรารับมาได้นั้นเป็นข้อมูลที่สมบูรณ์ที่สุดจึงจะเริ่ม check instruction ต่อเลย check ตรงนี้เพื่อดูว่า Master ที่ส่งมานั้นต้องการจะทำอะไรกับ Slave ก็ให้จัดการข้อมูลกันในนี้เลย

มาดูตัวอย่าง code ฝั่ง slave กันบ้าง 

            จะเห็นว่าใน code ฝั่ง slave จะมี ID เป็นของตัวเอง ซึ่งเราก็กำหนดขึ้นมาเองเลยนั่นแหละ ถ้ายังอ่าน code แล้วยัง งง ๆ ในส่วนของ Depacket อยู่ ลองไปอ่านบทความนี้ดูก่อน อยากส่ง int ผ่าน Serial ไว ๆ ทำไงดี :D ในบทความนี้ยังไม่มีตัวอย่างผลลัพ์ที่ได้จากการส่งข้อมูลในลักษณะนี้ เดี๋ยวว่าง ๆ จะอัพ Video ลงให้นะครับ

             ถ้าสังเกตุดูข้อมูลที่ส่งจะมีขนาดไม่เยอะมากและค่อนข้างคงที่ แล้วแต่ Packet design ยังไง ตามตัวอย่างนี้ 8 byte เจ้าของบล็อกเคยเห็นตามกลุ่มเฟส เขาใช้วิธีส่งแบบ "fa123b" ถ้ามานับดูแต่ละตัวอักษรก็มี 6 ตัว หรือประมาณ 6 byte ตัวแรกบอกประมาณว่า "f" คือจะมีข้อมูลจะส่งมาแล้วนะ "a" คือ address ที่ต้องการส่งให้ ส่วน "123" เป็นค่าอะไรซักอย่างที่ต้องการส่งให้ Slave และ "b" คือการบอกว่าจบคำสั่ง

             เอาใหม่ ถ้า Slave มีตัวแปรที่สามารถ update ได้จาก Master อยู่ 3 ตัว และถ้าอยากจะ update แต่ละตัวได้ ตอนส่งไปก็คงต้องเพิ่มตัวอักษรอะไรอีกซักอย่าง "fad123b" เริ่มออกแบบยากแล้วใช่มั้ยละ แล้วถ้าค่าที่ต้องการอัพเดทประมาณ 20000 มันก็จะเป็น "fad20000b" ซึ่งนับทั้งหมดจะได้ตัวอักษร 9 ตัวหรือ 9 byte ซึ่งที่ส่งไปนี้เราก็ไม่มั่นใจว่าข้อมูลจะถูกหมดทุกครั้ง แล้วเราจะเช็คยังไง?
             จริง ๆ ก็ไม่ใช่วิธีที่เลวร้ายหรอกนะครับ ข้อมูลก็ถูกส่งได้เหมือนกัน แต่อยากให้ดูบริบทของงานมากว่า ว่าเราทำงานแบบไหน ถ้าต้องการความถูกต้องของข้อมูล ก็ใช้วิธีที่เจ้าของบล็อกแนะนำก็ได้ครับ แต่ถ้าเป็นการส่งแบบธรรมดา ไม่ซีเรียสว่าข้อมูลจะผิดพลาด ก็ใช้เป็นแบบส่ง String ไปก็ได้ อยู่ที่ลักษณะงานที่จะทำมากกว่าครับ :)
            

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้

ว่าด้วยเรื่องหน่วยความจำ สิ่งที่หลายคนมองข้าม : รู้จักกับ Memory

คณิตศาสตร์กับโปรแกรมมิ่งมันเป็นยังไงนะ ตอนที่ 1 เกริ่นพีทาโกรัส

Pointer กับตัวแปร Array นะจ๊ะ.. [Back to basic แต่ไม่ basic]