ผลต่างระหว่างรุ่นของ "การติดต่อกับบอร์ด MCU ผ่าน USB ด้วย Arduino"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 4 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 1: แถว 1:
: ''วิกินี้เป็นส่วนหนึ่งของรายวิชา [[01204223]]''
+
#REDIRECT [[การติดต่อกับบอร์ดไมโครคอนโทรลเลอร์ผ่านพอร์ท USB ด้วย Arduino]]
 
 
ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมเฟิร์มแวร์เท่านั้น วิกินี้อธิบายถึงขั้นตอนและตัวอย่างการพัฒนาเฟิร์มแวร์ภายใต้สภาพแวดล้อมของ Arduino เพื่อให้บอร์ดไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ สำหรับสื่อสารกับแอพลิเคชันที่ทำงานบนเครื่องคอมพิวเตอร์ได้
 
 
 
==ไลบรารีและเครื่องมือที่จำเป็น==
 
ให้แน่ใจว่าได้ติดตั้งไลบรารีและเครื่องมือที่จำเป็นตามที่ได้อธิบายไว้ในวิกิด้านล่าง ก่อนเริ่มทำตามขั้นตอนในวิกินี้
 
* [[การติดตั้งไลบรารี PyUSB]]
 
 
 
==การใช้งานไลบรารี V-USB==
 
การเขียนโค้ดเพื่อเรียกใช้งานไลบรารี V-USB ภายใต้สภาพแวดล้อมของ Arduino มีขั้นตอนหลัก ๆ ดังนี้
 
* สร้างไฟล์ <tt>usbconfig.h</tt> เพื่อบอกไลบรารี V-USB ถึงคุณลักษณะของอุปกรณ์ USB ที่เราต้องการให้บอร์ด MCU จำลองตัวเองขึ้นมา เนื่องจากการตั้งค่าต่าง ๆ ถูกระบุไว้ในรูปมาโครเป็นจำนวนมาก วิธีที่สะดวกและเสี่ยงต่อความผิดพลาดน้อยที่สุดคือคัดลอกเนื้อหามาจากไฟล์ <tt>usbconfig-prototype.h</tt> ที่อยู่ในไดเรคตอรี <tt>usbdrv</tt> ที่ได้จากการติดตั้ง V-USB ตามขั้นตอนก่อนหน้านี้ การตั้งค่าหลัก ๆ ที่สำคัญได้แก่
 
** <code>USB_CFG_VENDOR_ID</code> และ <code>USB_CFG_DEVICE_ID</code> ใช้กำหนดค่า Vendor ID (VID) และ Product ID (PID) ให้กับอุปกรณ์ USB ตัวเลขคู่นี้จะถูกตีความโดยระบบปฏิบัติการว่าเป็นอุปกรณ์ USB ประเภทใด เช่นเครื่องพิมพ์ เมาส์ คียบอร์ด ฯลฯ เพื่อที่ตัวระบบปฏิบัติการจะได้จัดหาตัวขับเคลื่อนอุปกรณ์ (device driver) มาใช้งานได้อย่างเหมาะสม ในตัวอย่างนี้มีการกำหนดค่า VID และ PID ให้เป็น 0x16c0 และ 0x05dc ตามลำดับ ซึ่งเป็นการไม่ระบุประเภทอุปกรณ์ ดูข้อมูลเพิ่มเติมจากหัวข้อ [[#เกี่ยวกับหมายเลข VID/PID]]
 
** <code>USB_CFG_VENDOR_NAME</code> ใช้กำหนดชื่อผู้ผลิตอุปกรณ์ที่จะปรากฏให้เห็นผ่านระบบปฏิบัติการ ระบุในรูปรายการอักขระคั่นด้วยคอมม่า พร้อมทั้งระบุความยาวชื่อให้กับมาโคร <code>USB_CFG_VENDOR_NAME_LEN</code> ในที่นี้เราจะกำหนดชื่อผู้ผลิตเป็น <tt>cpe.ku.ac.th</tt> เพื่อให้สอดคล้องกับแนวปฏิบัติของไลบรารี V-USB
 
** <code>USB_CFG_DEVICE_NAME</code> ใช้กำหนดชื่อของอุปกรณ์ที่จะปรากฏให้เห็นผ่านระบบปฏิบัติการ ระบุในรูปรายการอักขระคั่นด้วยคอมม่า พร้อมทั้งระบุความยาวชื่อให้กับมาโคร <code>USB_CFG_DEVICE_NAME_LEN</code> ในที่นี้ให้กำหนดชื่อในรูป <tt>ID xxxxxxxxxx</tt> โดยที่ <tt>xxxxxxxxxx</tt> แทนรหัสนิสิต 10 หลักของตน
 
* สร้าง Arduino Sketch ขึ้นมาใหม่ แล้วพิมพ์คำสั่งต่อไปนี้ที่ส่วนหัวของไฟล์เพือดึงเอาไลบรารี V-USB มาใช้งาน
 
<syntaxhighlight lang="C">
 
#include <usbdrv.h>
 
* นิยามฟังก์ชัน <tt>setup()</tt> เพื่อกำหนดหน้าที่ของขาอินพุทเอาท์พุทตามปกติ และเพิ่มโค้ดสำหรับสั่งไลบรารี V-USB ให้เตรียมการเบื้องต้นลงไปด้วยดังนี้
 
<syntaxhighlight lang="C">
 
void setup()
 
{
 
  // ตั้งค่าอินพุท/เอาท์พุทตามปกติ
 
  // :
 
 
 
  // สั่งให้ V-USB เตรียมตัวขั้นต้น
 
  usbInit();
 
  usbDeviceDisconnect();
 
  delay(300);
 
  usbDeviceConnect();
 
}
 
</syntaxhighlight>
 
* นิยามฟังก์ชัน <tt>loop()</tt> ให้มีการเรียกใช้ฟังก์ชัน <tt>usbPoll()</tt> ของไลบรารี V-USB โดยให้แน่ใจว่าฟังก์ชันนี้ต้องถูกเรียกซ้ำภายในระยะเวลาไม่เกิน 50 มิลลิวินาทีอย่างต่อเนื่อง ไม่เช่นนั้นอุปกรณ์จะตอบสนองต่อคำร้องขอจากโฮสท์ไม่ทันและมีผลทำให้โฮสท์ตัดการเชื่อมต่อในที่สุด
 
<syntaxhighlight lang="C">
 
void loop()
 
{
 
  // ประมวลผลตามต้องการ แต่ต้องให้แล้วเสร็จภายใน 50 มิลลิวินาที
 
  // :
 
 
 
  // สั่ง V-USB ให้เฝ้าดูสัญญาณการร้องขอจากโฮสท์
 
  usbPoll();
 
}
 
</syntaxhighlight>
 
* เมื่อพบว่ามีคำร้องขอจากโฮสท์ ไลบรารี V-USB จะเรียกหาฟังก์ชัน <tt>usbFunctionSetup()</tt> เพื่อประมวลผลคำร้องขอนั้น เป็นหน้าที่ของเราที่ต้องสร้างฟังก์ชันนี้ขึ้นมา
 
<syntaxhighlight lang="C">
 
usbMsgLen_t usbFunctionSetup(uint8_t data[8])
 
{
 
  usbRequest_t *rq = (usbRequest_t*)data;
 
 
 
  // ประมวลผลข้อมูลภายในคำร้องขอผ่านทางตัวแปร rq
 
  // :
 
}
 
</syntaxhighlight>
 
 
 
== โครงสร้างของคำร้องขอ USB ที่รับจากฝั่งคอมพิวเตอร์ ==
 
ตามสถาปัตยกรรม USB นั้นการสื่อสารจะถูกเริ่มจากการที่ฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ส่งคำร้องขอไปยังฝั่งอุปกรณ์เสมอไม่ว่าจะต้องการอ่านหรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม ข้อมูลคำร้องขอมีขนาด 8 ไบต์ ซึ่งมีโครงสร้างดังนี้ (นิยามโครงสร้างนี้เป็นส่วนหนึ่งของไลบรารี V-USB จึงไม่ต้องเขียนขึ้นมาเอง)
 
<syntaxhighlight lang="C">
 
typedef struct usbRequest{
 
    uchar      bmRequestType;  /* 1 ไบต์ */
 
    uchar      bRequest;      /* 1 ไบต์ */
 
    usbWord_t  wValue;        /* 2 ไบต์ */
 
    usbWord_t  wIndex;        /* 2 ไบต์ */
 
    usbWord_t  wLength;        /* 2 ไบต์ */
 
}usbRequest_t;
 
</syntaxhighlight>
 
* <code>bmRequestType</code> ประกอบด้วยฟิลด์ย่อย 3 ฟิลด์ดังต่อไปนี้
 
:* บิต 7 ทิศทางการส่งข้อมูล (Data Phase Transfer Direction)
 
::* 0 = จากคอมพิวเตอร์ไปอุปกรณ์ USB (Host to Device)
 
::* 1 = จากอุปกรณ์ USB มายังคอมพิวเตอร์ (Device to Host)
 
:* บิต 6..5 ประเภทคำร้องขอ (Type)
 
::* 0 = Standard
 
::* 1 = Class
 
::* 2 = Vendor
 
::ฟังก์ชัน <code>usbFunctionSetup</code> ที่เราต้องเขียนขึ้นนั้นจะถูกเรียกใช้เมื่อค่าในฟิลด์ Type นี้มีค่า 2 (Vendor) เท่านั้น
 
:* บิต 4..0 ผู้รับ (Recipient)
 
::* 0 = Device
 
::* 1 = Interface
 
::* 2 = Endpoint
 
::* 3 = Other
 
* <code>bRequest</code> ระบุหมายเลขคำร้องขอ คำร้องขอตามมาตรฐานของ USB นั้นมีประเภทเป็น Standard ซึ่งจะถูกประมวลผลจากไลบรารี V-USB อัตโนมัติ เราจึงไม่ต้องสนใจในส่วนนี้ ส่วนที่เราต้องรับผิดชอบคือคำร้องขอแบบ Vendor ซึ่งต้องถูกออกแบบไว้ล่วงหน้าแล้วว่าอุปกรณ์ USB ของเราจะรองรับคำร้องขอหมายเลขอะไรบ้าง โดยในฟังก์ชัน <code>usbFunctionSetup</code> ของเราต้องประมวลผลคำร้องขอเหล่านี้ได้ถูกต้อง
 
* <code>wValue</code> และ <code>wIndex</code> ทั้งคู่เป็นฟิลด์ที่ไม่มีความหมายใดในกรณีที่คำร้องขอเป็นแบบ Vendor ดังนั้นเราจึงมีอิสระเต็มที่ในการใช้งานฟิลด์ทั้งคู่นี้เป็นตัวส่งรายละเอียดของคำร้องขอ ซึ่งส่งได้สูงสุด 4 ไบต์
 
* <code>wLength</code> กำหนดขนาดของข้อมูลเพิ่มเติมที่จะส่งจากฝั่งโฮสท์หรือจากอุปกรณ์ USB หากไม่มีข้อมูลเพิ่มเติม ค่านี้จะถูกเซ็ตเป็นศูนย์
 
 
 
คำร้องขอนี้จะถูกส่งมายังโค้ดของเราผ่านมาทางฟังก์ชัน <tt>usbFunctionSetup()</tt> ดังนั้นสิ่งที่เราต้องทำคือตรวจสอบข้อมูลเหล่านี้ภายในคำร้องขอ แล้วตอบสนองไปยังโฮสท์ ซึ่งเป็นไปได้สองกรณีคือ
 
# <b>ไม่มีข้อมูลส่งกลับให้โฮสท์</b> ให้ใช้คำสั่ง <code>return 0</code> ออกจากฟังก์ชันตามปกติ
 
# <b>มีข้อมูลส่งกลับให้โฮสท์</b> ให้ตั้งค่าตัวแปร <code>usbMsgPtr</code> (V-USB ประกาศไว้ให้แล้ว) ให้ชี้ไปยังตำแหน่งหน่วยความจำที่เก็บข้อมูลที่ต้องการส่งคืนโฮสท์ จากนั้นใช้คำสั่ง <code>return <i>len</i></code> โดยที่ <i>len</i> คือจำนวนไบต์ของข้อมูลที่ต้องการส่งกลับ ซึ่งส่งคืนได้ทีละไม่เกิน 8 ไบต์ ระวังว่าข้อมูลนี้ต้องยังอยู่ในหน่วยความจำแม้จะออกจากฟังก์ชัน <tt>usbFunctionSetup</tt> ไปแล้ว ตัวแปรที่ใช้เก็บข้อมูลส่งกลับจึงต้องถูกประกาศเป็นแบบโกลบอลหรือแบบสแตติก
 
 
 
ส่วนของโค้ดด้านล่างส่งค่า 12 และ 34 (ฐานสิบ) กลับไปยังโฮสท์เมื่อได้รับคำร้องขอหมายเลข 38 (ฐานสิบ) และไม่ส่งข้อมูลใด ๆ กลับไปหากคำร้องขอเป็นหมายเลขอื่น
 
<syntaxhighlight lang="C">
 
usbMsgLen_t usbFunctionSetup(uint8_t data[8])
 
{
 
  usbRequest_t *rq = (usbRequest_t*)data;
 
  static uint8_t value[2];
 
 
 
  if (rq->bRequest == 38)
 
  {
 
    value[0] = 12;
 
    value[1] = 34;
 
    usbMsgPtr = &value;
 
    return sizeof(value);
 
  }
 
  return 0; // ไม่ส่งข้อมูลกลับโฮสท์หากเป็นคำร้องขออื่น ๆ
 
}
 
</syntaxhighlight>
 
 
 
==ตัวอย่างโปรแกรม==
 
ดาวน์โหลดตัวอย่างโปรแกรม [http://www.cpe.ku.ac.th/~cpj/204223/usb-generic-arduino.tgz usb-generic-arduino.tgz] แล้วแตกเอาไว้ในไดเรคตอรีที่เก็บ sketch ของ Arduino
 
 
 
===เฟิร์มแวร์ฝั่งดีไวซ์===
 
โค้ดเฟิร์มแวร์อยู่ในไฟล์ <tt>usb_generic</tt> ซึ่งคอมไพล์ได้แต่ยังทำงานไม่สมบูรณ์ เฟิร์มแวร์ตัวอย่างนี้รองรับคำร้องขอ 2 หมายเลขคือ หมายเลข 0 เป็นการควบคุมสถานะ LED บนบอร์ดพ่วง และหมายเลข 1 เป็นการตรวจสอบสถานะของสวิตช์บนบอร์ดพ่วง ให้ปฏิบัติตามขั้นตอนดังนี้ในการอัพโหลดเฟิร์มแวร์ลงบอร์ด
 
* แก้ไขไฟล์ <tt>usbconfig.h</tt> ในส่วนที่นิยามมาโคร <tt>USB_CFG_DEVICE_NAME</tt> เอาไว้เพื่อตั้งชื่ออุปกรณ์ให้เป็น <tt style="color:green;">ID <i>รหัสนิสิต</i></tt>
 
<syntaxhighlight lang="C">
 
#define USB_CFG_DEVICE_NAME    'I','D',' ','1','2','3','4','5','6','7','8','9','0'  <-- แก้เป็นรหัสนิสิตของตน
 
#define USB_CFG_DEVICE_NAME_LEN 13
 
</syntaxhighlight>
 
* (สำหรับบางคน) แก้ไขไฟล์ <tt>Makefile</tt> เพื่อระบุไดเรคตอรีต่าง ๆ ให้สอดคล้องกับเครื่องที่ตนใช้งาน
 
* เปิดเทอร์มินัลเพื่ออัพโหลดเฟิร์มแวร์โดยใช้คำสั่ง
 
make ispload
 
 
 
* เมื่อเฟิร์มแวร์เริ่มต้นทำงาน ระบบปฏิบัติการจะมองเห็นบอร์ดไมโครคอนโทรลเลอร์เป็นอุปกรณ์ USB ทันที จะเห็นได้จากเอาท์พุทของคำสั่ง <tt>lsusb</tt> บน Ubuntu ที่ปรากฏรายการของอุปกรณ์ที่มี VID/PID เป็น 16c0:05dc <u>แม้จะไม่ได้อยู่ในโหมดบูทโหลดเดอร์</u> (สำหรับ Mac OS X ให้ใช้คำสั่ง <tt>system_profiler</tt> แทน)
 
 
 
===แอพลิเคชันฝั่งโฮสท์===
 
ขณะที่เฟิร์มแวร์ทำงานอยู่นั้นเราจะมองไม่เห็นผลลัพธ์การทำงานใด ๆ เนื่องจากเฟิร์มแวร์ถูกเขียนไว้ให้ตอบสนองต่อการสั่งงานผ่านคอมพิวเตอร์เท่านั้น ภายในไฟล์ตัวอย่างมีไฟล์ชื่อ <tt>practicum.py</tt> ซึ่งเป็นโมดูลไพทอนที่เราจะนำมาใช้ติดต่อกับบอร์ดไมโครคอนโทรลเลอร์ผ่านภาษาไพทอน ทดลองเปิดไพทอนเชลล์แล้วโหลดโมดูลมาใช้งาน โดยฟังก์ชันหลักที่เรียกใช้จากโมดูลคือ <tt>findDevices()</tt> ซึ่งคืนค่าเป็นลิสต์ของอุปกรณ์ USB ทุกตัวที่มี VID/PID เป็น 16c0:05dc ที่ต่ออยู่กับคอมพิวเตอร์ ณ ขณะนั้น
 
 
 
$ python
 
>>> from practicum import findDevices,McuBoard
 
>>> devices = findDevices()
 
>>> devices
 
[<usb.Device object at 0xd91d70>]
 
 
 
จากนั้นสร้างอ็อบเจกต์ของคลาส <tt>McuBoard</tt> ขึ้นมาจากอุปกรณ์ตัวแรกในลิสต์
 
 
 
>>> b = McuBoard(devices[0])
 
>>> b.getVendorName()
 
'cpe.ku.ac.th'
 
>>> b.getDeviceName()
 
'ID 1234567890'  <- ต้องขึ้นเป็นรหัสนิสิตของตน
 
 
 
ทดลองส่งคำร้องขอหมายเลข 0 (ควบคุมสถานะ LED) เพื่อให้ LED หมายเลข 2 บนบอร์ดพ่วงติดสว่าง ใช้เมท็อต <code>usbWrite</code> ในคลาส <code>McuBoard</code> ดังนี้
 
 
 
>>> b.usbWrite(0, index=2, value=1)
 
 
 
คำสั่งด้านล่างมีผลทำให้ LED หมายเลข 2 ดับ และ LED หมายเลข 1 ติดขึ้นมาแทน
 
>>> b.usbWrite(0, index=2, value=0)
 
>>> b.usbWrite(0, index=1, value=1)
 
 
 
ทดลองอ่านสถานะของสวิตช์โดยส่งคำร้องหมายเลข 1 ไปยังบอร์ด MCU
 
>>> b.usbRead(1, length=1)
 
(0,)
 
ค่าที่เมทอด <code>usbRead</code> คืนกลับมาจะเป็นทูเปิลที่มีสมาชิกหนึ่งตัว ตามที่ระบุในเฟิร์มแวร์
 
 
 
ทดลองกดสวิตช์บนบอร์ดพ่วงค้างไว้ แล้วส่งคำร้องไปยังบอร์ด MCU ใหม่ ผลลัพธ์ที่ได้ควรเป็นดังนี้
 
>>> b.usbRead(1, length=1)
 
(1,)
 
 
 
ใช้คำสั่ง <tt>help</tt> เพื่อดูรายละเอียดการใช้งานคลาส <tt>McuBoard</tt>
 
>>> help(McuBoard)
 
 
 
== เกี่ยวกับหมายเลข VID/PID ==
 
ชุดตัวเลข VID/PID ที่กำหนดให้กับอุปกรณ์ USB ไม่ควรตั้งเอาเองตามใจชอบเนื่องจากระบบปฏิบัติการจะอาศัยตัวเลขคู่นี้ในการเลือกซอฟต์แวร์ไดรเวอร์ที่จะมาควบคุมอุปกรณ์ โดยทั่วไปการจะได้มาซึ่งเลข VID/PID เพื่อใช้กับอุปกรณ์ที่เราสร้างขึ้นจำเป็นต้องสมัครเป็นสมาชิกของ [http://www.usb.org USB Implementers Forum] (ค่าสมาชิกปีละ 4,000 เหรียญสหรัฐ) หรือซื้อตัวเลข VID มาจากผู้ที่เป็นสมาชิกอีกทีหนึ่ง
 
 
 
อย่างไรก็ตาม Object Development ผู้พัฒนาไลบรารี V-USB ได้เตรียมชุดตัวเลข VID/PID ไว้ให้เราใช้งานโดยไม่เสียค่าใช้จ่าย หมายเลข <tt>16C0:xxxx</tt> ที่เราเลือกนำมาใช้งานก็ได้มาจากตัวเลขในชุดดังกล่าว รายละเอียดเพิ่มเติมเกี่ยวกับการกำหนดค่า VID และ PID ให้กับอุปกรณ์ USB รวมถึงหลักเกณฑ์การปฏิบัติในการผลิตอุปกรณ์ USB สู่สาธารณะ สามารถศึกษาเพิ่มเติมได้จากเนื้อหาในไฟล์ <tt>USB-ID-FAQ.txt</tt> และไฟล์ <tt>USB-IDs-for-free.txt</tt> ในไดเรคตอรี <tt>usbdrv</tt> ที่ได้จากการติดตั้งไลบรารี V-USB รวมถึงเอกสาร [http://www.voti.nl/docs/usb-pid.html How to obtain an USB VID/PID for your project]
 
 
 
== ข้อมูลเพิ่มเติม ==
 
* [http://vusb.wikidot.com/ V-USB Documentation Wiki]
 
* [http://www.beyondlogic.org/usbnutshell/usb1.htm USB in a NutShell]
 
* [http://usb4java.org/ usb4java] - ไลบรารีภาษาจาวาสำหรับติดต่อกับอุปกรณ์ USB
 

รุ่นแก้ไขปัจจุบันเมื่อ 11:17, 10 ตุลาคม 2558