ผลต่างระหว่างรุ่นของ "การจำลองบอร์ด MCU เป็นอุปกรณ์ USB"
Chaiporn (คุย | มีส่วนร่วม) |
Chaiporn (คุย | มีส่วนร่วม) |
||
| แถว 1: | แถว 1: | ||
| − | |||
| − | |||
ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมแฟลชเท่านั้น วิกินี้อธิบายถึงขั้นตอนการทำให้ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ เพื่อที่จะสามารถสื่อสารกับโปรแกรมที่ทำงานอยู่บนเครื่องคอมพิวเตอร์ได้ | ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมแฟลชเท่านั้น วิกินี้อธิบายถึงขั้นตอนการทำให้ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ เพื่อที่จะสามารถสื่อสารกับโปรแกรมที่ทำงานอยู่บนเครื่องคอมพิวเตอร์ได้ | ||
| แถว 75: | แถว 73: | ||
avr-gcc -Wall -Os -DF_CPU=12000000L -Iusbdrv -I. -mmcu=atmega168 -o main.elf main.o usbdrv/usbdrv.o usbdrv/usbdrvasm.o | avr-gcc -Wall -Os -DF_CPU=12000000L -Iusbdrv -I. -mmcu=atmega168 -o main.elf main.o usbdrv/usbdrv.o usbdrv/usbdrvasm.o | ||
| − | + | == โครงสร้างของคำร้องขอ USB ที่รับจากฝั่งคอมพิวเตอร์ == | |
| + | ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกเริ่มจากการที่ฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ส่งคำร้องขอ (USB Request) ไปยังฝั่งอุปกรณ์ USB เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม ข้อมูลคำร้องขอมีโครงสร้างตามที่นิยามไว้ในไฟล์ <code>usbdrv/usbdrv.h</code> ดังนี้ | ||
| + | typedef struct usbRequest{ | ||
| + | uchar bmRequestType; /* 1 ไบท์ */ | ||
| + | uchar bRequest; /* 1 ไบท์ */ | ||
| + | usbWord_t wValue; /* 2 ไบท์ */ | ||
| + | usbWord_t wIndex; /* 2 ไบท์ */ | ||
| + | usbWord_t wLength; /* 2 ไบท์ */ | ||
| + | }usbRequest_t; | ||
| + | * <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 หากไม่มีข้อมูลเพิ่มเติม ค่านี้จะถูกเซ็ตเป็นศูนย์ | ||
| + | |||
| + | ดูรายละเอียดเพิ่มเติมได้จาก [http://www.beyondlogic.org/usbnutshell/usb6.htm USB in a NutShell] | ||
| + | |||
| + | == ตัวอย่างโปรแกรม == | ||
| + | |||
| + | :โหลดไฟล์ [http://www.cpe.ku.ac.th/~cpj/204223/usb-example.tgz usb-example.tgz] ซึ่งเก็บไฟล์ทั้งหมดที่ใช้ในตัวอย่างนี้ | ||
| + | |||
| + | เพื่อให้เห็นภาพของการใช้งานไลบรารี V-USB มากขึ้น เราลองสร้างตัวอย่างเฟิร์มแวร์อย่างง่ายขึ้นมาพร้อมทั้งใช้ภาษาไพธอนทดลองสั่งงานจากฝั่งคอมพิวเตอร์ ในที่นี้เราจะให้ตัวเฟิร์มแวร์จำลองตัวเป็นอุปกรณ์ USB ที่รองรับการสั่งงานจากโฮสท์ 2 คำร้องขอ ดังนี้ | ||
| + | * '''คำร้องขอ SET_LED''' สั่งให้ LED ดวงที่ระบุติดหรือดับ มีรายละเอียดของคำร้องขอดังนี้ | ||
| + | :* กำหนดให้หมายเลขคำร้องขอ (request number) คือ 0 | ||
| + | :* ส่งรายละเอียดมาให้ 2 ไบท์ ไบท์แรกระบุตำแหน่งของ LED บนบอร์ดพ่วง (0, 1 หรือ 2) ส่วนไบท์ที่สองระบุว่าจะให้ LED ดวงดังกล่าวติดหากมีค่า 0 หรือดับหากมีค่าอื่นที่ไม่ใช่ศูนย์ เนื่องจากข้อมูลมีขนาดเพียงสองไบท์ เราจะใส่ข้อมูลนี้ลงไปในฟิลด์ <code>wValue</code> ที่ถูกส่งไปพร้อมกับคำร้องขอโดยตรง | ||
| + | :* แม้ไม่มีข้อมูลอื่นเพิ่มเติมส่งจากคอมพิวเตอร์ไปยังอุปกรณ์ USB แต่เราจะระบุทิศทางการไหลของข้อมูลไว้เป็น Host to Device | ||
| + | * '''คำร้องขอ GET_SWITCH''' สั่งให้บอร์ด MCU รายงานสถานะการกดปุ่มสวิตช์กลับมา มีรายละเอียดของคำร้องขอดังนี้ | ||
| + | :* กำหนดให้หมายเลขคำร้องขอ (request number) คือ 1 | ||
| + | :* ทิศทางการไหลของข้อมูลเป็น Device to Host | ||
| + | :* รับข้อมูลกลับมา 1 ไบท์ บอกสถานะของสวิตช์ (0 คือไม่ถูกกด 1 คือถูกกด) | ||
| + | |||
| + | ===การตั้งค่าให้อุปกรณ์ USB=== | ||
| + | เปิดไฟล์ <code>usbconfig.h</code> เพื่อปรับค่าให้สอดคล้องกับโปรเจ็ค | ||
| + | * '''VID/PID:''' อุปกรณ์ USB ทุกตัวจะต้องถูกกำหนดค่า Vendor ID (VID) และ Product ID (PID) ให้ ซึ่งแต่ละตัวเลขมีขนาด 16 บิต ในโปรเจ็คนี้ให้กำหนดค่า VID และ PID ให้เป็น 0x16c0 และ 0x05dc ตามลำดับ โดยดูให้แน่ใจว่าในไฟล์ <code>usbconfig.h</code> มีบรรทัดเหล่านี้ | ||
| + | #define USB_CFG_VENDOR_ID 0xc0, 0x16 /* VID = 0x16c0 */ | ||
| + | : | ||
| + | #define USB_CFG_DEVICE_ID 0xdc, 0x05 /* PID = 0x05dc */ | ||
| + | :อ่านหลักเกณฑ์การตั้งค่า VID/PID เพิ่มเติมได้จากหัวข้อ [[#เกี่ยวกับหมายเลข VID/PID]] | ||
| + | |||
| + | * '''Vendor Name:''' กำหนดค่า USB Vendor Name ให้เป็น <code>cpe.ku.ac.th</code> โดยเปลี่ยนนิยามของมาโคร <code>USB_CFG_VENDOR_NAME</code> และ <code>USB_CFG_VENDOR_NAME_LEN</code> ดังนี้ | ||
| + | #define USB_CFG_VENDOR_NAME 'c','p','e','.','k','u','.','a','c','.','t','h' | ||
| + | #define USB_CFG_VENDOR_NAME_LEN 12 | ||
| + | |||
| + | * '''Device Name:''' กำหนดค่า USB Device Name ให้เป็น ''Practicum Group <หมายเลขกลุ่ม>'' โดยเปลี่ยนนิยามของมาโคร <code>USB_CFG_DEVICE_NAME</code> และ <code>USB_CFG_DEVICE_NAME_LEN</code> ตัวอย่างเช่น กลุ่ม 99 ให้แก้ไขมาโครดังนี้ | ||
| + | #define USB_CFG_DEVICE_NAME 'P','r','a','c','t','i','c','u','m',' ','G','r','o','u','p',' ','9','9' | ||
| + | #define USB_CFG_DEVICE_NAME_LEN 18 | ||
| + | |||
| + | ===โปรแกรมฝั่งเฟิร์มแวร์=== | ||
| + | * เพื่อเป็นแนวปฏิบัติที่ดีในการเขียนโปรแกรม เราจะนิยามคำร้องขอเหล่านี้ไว้ที่ส่วนหัวของ <code>main.c</code> ดังนี้ | ||
| + | #define VENDOR_RQ_SET_LED 0 | ||
| + | #define VENDOR_RQ_GET_SWITCH 1 | ||
| + | |||
| + | * เราสร้างฟังก์ชันจัดการอินพุท/เอาท์พุทของพอร์ท D เพื่อความสะดวกในการใช้งานไว้ดังนี้ | ||
| + | uint8_t in_d(uint8_t pin) | ||
| + | { | ||
| + | // กำหนดให้ขาที่ระบุของพอร์ท D ทำหน้าที่เป็นอินพุท | ||
| + | DDRD &= ~(1<<pin); | ||
| + | // ดึงให้ขามีลอจิก 1 หากขาถูกปล่อยลอยไว้ | ||
| + | PORTD |= (1<<pin); | ||
| + | // อ่านสถานะลอจิกของขาที่ระบุ | ||
| + | return ((PIND & (1<<pin))>>pin); | ||
| + | } | ||
| + | |||
| + | void out_d(uint8_t pin, uint8_t val) | ||
| + | { | ||
| + | // กำหนดให้ขาที่ระบุของพอร์ท D ทำหน้าที่เป็นเอาท์พุท | ||
| + | DDRD |= 1<<pin; | ||
| + | // เซ็ตลอจิกของขาเป็น 1 ถ้า val ไม่ใช่ศูนย์ ไม่เช่นนั้นเซ็ตให้เป็น 0 | ||
| + | if (val) | ||
| + | PORTD |= 1<<pin; | ||
| + | else | ||
| + | PORTD &= ~(1<<pin); | ||
| + | } | ||
| + | |||
| + | * ภายในฟังก์ชัน <code>usbFunctionSetup</code> สร้างโค้ดสำหรับประมวลผลคำร้องขอที่รับมาจากโฮสท์ดังนี้ | ||
| + | usbMsgLen_t usbFunctionSetup(uchar data[8]) | ||
| + | { | ||
| + | usbRequest_t *rq = (void *)data; | ||
| + | static uchar dataBuffer[1]; /* ข้อมูลนี้ต้องไม่ถูกเขียนทับหลังจาก usbFunctionSetup รีเทิร์น */ | ||
| + | |||
| + | /* ประมวลผลตามหมายเลขคำสั่งที่อยู่ใน bRequest */ | ||
| + | if (rq->bRequest == VENDOR_RQ_SET_LED) | ||
| + | { | ||
| + | uint8_t led_no = rq->wValue.bytes[0]; | ||
| + | uint8_t led_val = rq->wValue.bytes[1]; | ||
| + | out_d(led_no, !led_val); /* กลับลอจิกเพื่อให้ 1 = ติด 0 = ดับ */ | ||
| + | return 0; | ||
| + | } | ||
| + | else if (rq->bRequest == VENDOR_RQ_GET_SWITCH) | ||
| + | { | ||
| + | dataBuffer[0] = !in_d(3); /* กลับลอจิกเพื่อให้ 0 = ปล่อย 1 = กด */ | ||
| + | usbMsgPtr = dataBuffer; /* ระบุว่าข้อมูลส่งกลับอยู่ใน dataBuffer */ | ||
| + | return 1; /* ความยาวข้อมูลส่งกลับเท่ากับ 1 */ | ||
| + | } | ||
| + | return 0; /* ไม่รู้จักคำร้องขอ ไม่ส่งข้อมูลกลับ */ | ||
| + | } | ||
| + | |||
| + | * คอมไพล์เฟิร์มแวร์และอัพโหลดเข้าแฟลชของไมโครคอนโทรลเลอร์ ซึ่งหากได้สร้าง <code>Makefile</code> ตามที่อธิบายไว้ข้างต้นไว้เรียบร้อยแล้ว ให้เสียบบอร์ดไมโครคอนโทรลเลอร์เข้ากับพอร์ท USB กดสวิตช์เพื่อเข้าสู่ Bootloader แล้วพิมพ์คำสั่ง | ||
| + | make flash | ||
| + | |||
| + | === โปรแกรมฝั่งเครื่องคอมพิวเตอร์ === | ||
| + | |||
| + | * (สำหรับ Ubuntu Linux) ติดตั้ง PyUSB เพื่อติดต่อกับอุปกรณ์ USB ผ่านภาษาไพธอน | ||
| + | sudo apt-get install python-usb | ||
| + | |||
| + | * (สำหรับ MacOS X) ดาวน์โหลดซอร์สโค้ดของไลบรารี libusb จาก[http://sourceforge.net/projects/libusb/files/libusb-1.0/ ที่นี่] จากนั้นแตกไฟล์ออกและใช้คำสั่ง cd เข้าไปในไดเรคตอรี่ที่เก็บไฟล์ จึงคอมไพล์และติดตั้งโดยใช้คำสั่งต่อไปนี้ | ||
| + | ./configure | ||
| + | make | ||
| + | sudo make install | ||
| + | |||
| + | :จากนั้นโหลดซอร์สโค้ดของ PyUSB จาก[http://sourceforge.net/projects/pyusb/ ที่นี่] แตกไฟล์และติดตั้งโดยใช้คำสั่ง | ||
| + | sudo python setup.py install | ||
| + | |||
| + | * สร้างไฟล์ชื่อ <code>testusb.py</code> เรียกใช้ไลบรารี usb และนิยามฟังก์ชัน <code>find_board</code> เพื่อค้นหาอุปกรณ์ USB ตัวที่เป็นบอร์ด MCU ของเราจากรายการอุปกรณ์ USB ทั้งหมดที่ต่ออยู่กับเครื่อง | ||
| + | import usb | ||
| + | |||
| + | def find_board(): | ||
| + | for bus in usb.busses(): | ||
| + | for dev in bus.devices: | ||
| + | if dev.idVendor == 0x16c0 and dev.idProduct == 0x05dc: | ||
| + | return dev | ||
| + | return None | ||
| + | :สังเกตว่าการค้นหาบอร์ด MCU ของเรานั้นอาศัยค่า VID และ PID ที่ตั้งไว้แต่แรก ดังนั้นอาจเกิดปัญหาขึ้นหากมีการเสียบบอร์ด MCU มากกว่าหนึ่งบอร์ดเข้ากับคอมพิวเตอร์เพราะฟังก์ชัน <code>find_board</code> จะคืนอุปกรณ์ที่พบว่ามี VID:PID เป็น 16c0:05dc เป็นตัวแรกเสมอ อย่างไรก็ตาม หากต้องการระบุเอาอุปกรณ์ตัวที่ระบุก็สามารถอาศัยข้อมูลจาก vendor name หรือ device name มากำหนดเงื่อนไขเพิ่ม | ||
| + | |||
| + | * ติดต่อกับบอร์ด MCU ผ่านทางไพธอนเชลล์ โดยเรียกโปรแกรม <code>testusb.py</code> แบบอินเทอแรคทีฟ | ||
| + | python -i testusb.py | ||
| + | :<span style="color:red;">'''หมายเหตุ:''' หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร [[การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU]]</span> | ||
| + | |||
| + | * สร้าง handle เพื่อใช้เป็นทางผ่านในการสื่อสารกับบอร์ด MCU โดยใช้คำสั่งดังนี้ | ||
| + | >>> board = find_board() | ||
| + | >>> handle = board.open() | ||
| + | |||
| + | * สร้างคำร้องขอสำหรับคำสั่ง SET_LED ซึ่งระบุชนิดของคำร้องเป็นแบบ Vendor คือผู้สร้างอุปกรณ์กำหนดขึ้นมาเอง และระบุว่าคำร้องนี้มีการไหลของข้อมูลแบบ Host to Device | ||
| + | >>> req = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_OUT | ||
| + | |||
| + | * ส่งคำร้องออกไปยังบอร์ด MCU โดยกำหนดให้หมายเลขคำร้องเป็น 0 (ซึ่งหมายถึง SET_LED) และให้ฟิลด์ <code>value</code> มีค่าเป็น 0x0102 ซึ่งเฟิร์มแวร์ของเราจะตีความว่าเป็นการสั่งให้ LED หมายเลข 2 บนบอร์ดพ่วงติด การร้องขอครั้งนี้ไม่ได้ส่งข้อมูลใด ๆ ไปเพิ่มเติม จึงให้พารามิเตอร์ buf ของ handle.ControlMsg เป็น None ไป | ||
| + | >>> handle.controlMsg(req, 0, None, value=0x0102) | ||
| + | |||
| + | * ลองให้ LED หมายเลข 2 ดับ และ LED หมายเลข 1 ติดขึ้นมาแทน | ||
| + | >>> handle.controlMsg(req, 0, None, value=0x0002) | ||
| + | >>> handle.controlMsg(req, 0, None, value=0x0101) | ||
| + | |||
| + | * ทดลองอ่านสถานะของสวิตช์โดยส่งคำร้องหมายเลข 1 ไปยังบอร์ด MCU | ||
| + | >>> req = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_IN | ||
| + | >>> handle.controlMsg(req, 1, 1) | ||
| + | (0,) | ||
| + | :* เนื่องจากคำร้องนี้เป็นการขอให้ MCU ส่งข้อมูลกลับ จึงต้องมีการระบุในคำร้องโดยใช้ usb.ENDPOINT_IN | ||
| + | :* ในกรณีของการร้องขอข้อมูลจากอุปกรณ์ พารามิเตอร์ buf (พารามิเตอร์ตัวที่สามของเมท็อด controlMsg) เป็นความยาวข้อมูลที่ต้องการร้องขอ ซึ่งเท่ากับหนึ่ง | ||
| + | :* ค่าที่เมทอด controlMsg คืนกลับมาจะเป็น tuple ความยาวหนึ่งเช่นกัน | ||
| + | |||
| + | * ทดลองกดสวิตช์บนบอร์ดพ่วง แล้วส่งคำร้องไปยังบอร์ด MCU ใหม่ ผลลัพธ์ที่ได้ควรเป็นดังนี้ | ||
| + | >>> handle.controlMsg(req, 1, 1) | ||
| + | (1,) | ||
| + | |||
| + | ==== สร้างไพธอนคลาสเพื่อความสะดวกในการใช้งาน ==== | ||
| + | การส่งคำสั่งจากคอมพิวเตอร์ไปยังบอร์ดไมโครคอนโทรลเลอร์ผ่านเมท็อด controlMsg นั่นนอกจากจะไม่สะดวกและเข้าใจยากแล้วยังจะทำให้เกิดความผิดพลาดได้ง่ายเนื่องจากพารามิเตอร์ต่าง ๆ ที่ส่งไปยังบอร์ดจะถูกเข้ารหัสรวมกันไว้ในฟิลด์ value และ index โปรแกรม <code>board.py</code> ในโค้ดตัวอย่างเป็นการรวมคำสั่งควบคุม LED และอ่านค่าสวิตช์ รวมถึงคำสั่งค้นหาบอร์ดเอาไว้ในคลาสเดียวกันเพื่อความสะดวกในการเรียกใช้ ดังแสดง | ||
| + | import usb | ||
| + | |||
| + | #################################### | ||
| + | RQ_SET_LED = 0 | ||
| + | RQ_GET_SWITCH = 1 | ||
| + | |||
| + | #################################### | ||
| + | class Board: | ||
| + | def __init__(self): | ||
| + | #print "Looking for MCU board..." | ||
| + | dev = self.find_board() | ||
| + | if not dev: raise "MCU board not found!" | ||
| + | #print "MCU board found" | ||
| + | self.handle = dev.open() | ||
| + | |||
| + | def find_board(self): | ||
| + | board = None | ||
| + | for bus in usb.busses(): | ||
| + | for dev in bus.devices: | ||
| + | if dev.idVendor == 0x16c0 and dev.idProduct == 0x05dc: | ||
| + | return dev | ||
| + | return None | ||
| + | |||
| + | def set_led(self,pin,val): | ||
| + | reqType = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_OUT | ||
| + | self.handle.controlMsg(reqType, RQ_SET_LED, None, value=val*256+pin) | ||
| + | |||
| + | def get_switch(self): | ||
| + | reqType = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_IN | ||
| + | buf = self.handle.controlMsg(reqType, RQ_GET_SWITCH, 1) | ||
| + | return buf[0] | ||
| + | |||
| + | สังเกตว่าเมท็อด <code>__init__</code> จะดำเนินการค้นหาบอร์ด (ที่รันเฟิร์มแวร์จำลองให้ตัวเองเป็นอุปกรณ์ USB แล้ว) โดยอัตโนมัติหากมีการสร้างอ็อปเจ็กต์จากคลาส Board | ||
| + | >>> from board import Board | ||
| + | >>> b = Board() | ||
| + | |||
| + | ซึ่งเราสามารถควบคุมการติดดับของ LED ผ่านอ็อปเจ็กต์นี้ เช่นการสั่งให้ LED ดวงแรก (ดวงที่ 0) ติด ทำได้โดยใช้คำสั่ง | ||
| + | >>> b.set_led(0,1) | ||
| − | + | หรือหากต้องการตรวจสอบสถานะของสวิตช์ ทำได้โดยใช้คำสั่ง | |
| + | >>> b.get_switch() | ||
| + | 0 | ||
== เกี่ยวกับหมายเลข VID/PID == | == เกี่ยวกับหมายเลข VID/PID == | ||
| แถว 84: | แถว 289: | ||
ข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดค่า VID และ PID ให้กับอุปกรณ์ USB รวมถึงหลักเกณฑ์การปฏิบัติในการผลิตอุปกรณ์ USB สู่สาธารณะ สามารถศึกษาเพิ่มเติมได้จากเนื้อหาในไฟล์ <code>vusb-20100715/USB-ID-FAQ.txt</code> และไฟล์ <code>vusb-20100715/USB-IDs-for-free.txt</code> ที่แจกจ่ายมากับไฟล์ <code>vusb-20100715.tar.gz</code> และเอกสาร [http://www.voti.nl/docs/usb-pid.html How to obtain an USB VID/PID for your project] | ข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดค่า VID และ PID ให้กับอุปกรณ์ USB รวมถึงหลักเกณฑ์การปฏิบัติในการผลิตอุปกรณ์ USB สู่สาธารณะ สามารถศึกษาเพิ่มเติมได้จากเนื้อหาในไฟล์ <code>vusb-20100715/USB-ID-FAQ.txt</code> และไฟล์ <code>vusb-20100715/USB-IDs-for-free.txt</code> ที่แจกจ่ายมากับไฟล์ <code>vusb-20100715.tar.gz</code> และเอกสาร [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] | ||
รุ่นแก้ไขเมื่อ 06:20, 26 กรกฎาคม 2555
ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมแฟลชเท่านั้น วิกินี้อธิบายถึงขั้นตอนการทำให้ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ เพื่อที่จะสามารถสื่อสารกับโปรแกรมที่ทำงานอยู่บนเครื่องคอมพิวเตอร์ได้
ในที่นี้เราจะใช้โอเพนซอร์สไลบรารีชื่อ V-USB (เดิมเรียกว่า AVR-USB) ที่พัฒนาโดยบริษัท Objective Development โดยทำให้บอร์ด MCU ของเราทำงานเป็นอุปกรณ์ที่อยู่ในกลุ่ม custom class device ซึ่งจัดเป็นอุปกรณ์ USB ที่ไม่สังกัดคลาสใด โดยซอฟต์แวร์ฝั่งคอมพิวเตอร์จะอยู่ภายใต้ความควบคุมของเราทั้งหมด
นอกเหนือจาก custom class device แล้ว ไลบรารี V-USB ยังรองรับการโปรแกรมเฟิร์มแวร์ให้สังกัดคลาสอื่นได้อีกหลายคลาส อาทิเช่น HID (Human Interface Device) ซึ่งอยู่ในกลุ่มเดียวกับอุปกรณ์จำพวกแป้นพิมพ์ เมาส์ จอยสติ๊ก และเกมแพด
เนื้อหา
ขั้นตอนการใช้งานไลบรารี V-USB
- ดาวน์โหลดซอร์สโค้ดจาก Objective Development
- แตกไฟล์ .tar.gz ที่ดาวน์โหลดมาโดยใช้คำสั่ง
tar zxf vusb-20100715.tar.gz
- คัดลอกไดเรคตอรี
usbdrv/ที่อยู่ในไดเรคตอรีvusb-20100715/ไปวางในไดเรคตอรีโปรเจ็คของตน - ภายในไดเรคตอรีโปรเจ็คของตนเอง ย้ายไฟล์
usbdrv/usbconfig-prototype.hมาวางไว้ด้านนอก และเปลี่ยนชื่อให้เป็นusbconfig.h
mv usbdrv/usbconfig-prototype.h ./usbconfig.h
- ไฟล์นี้จะเก็บข้อมูลการตั้งค่าเกี่ยวกับอุปกรณ์ USB ที่จะให้ไมโครคอนโทรลเลอร์จำลองตัวเองขึ้นมา
- ในไฟล์หลักของโปรเจ็ค เรียกใช้คำสั่ง
#includeต่อไปนี้ไว้ที่ตอนต้นของโปรแกรม
#include <avr/io.h> #include <avr/interrupt.h> /* for sei() */ #include <util/delay.h> /* for _delay_ms() */ #include <avr/pgmspace.h> /* required by usbdrv.h */ #include "usbdrv.h"
- ในส่วนของฟังก์ชัน
main()ต้องมีโครงสร้างหลักดังนี้ (สามารถใส่โค้ดอื่นเพิ่มได้ตามที่ต้องการ)
int main()
{
usbInit();
usbDeviceDisconnect();
_delay_ms(300); /* fake USB disconnect for > 250 ms */
usbDeviceConnect();
sei(); /* enable interrupts */
while (1) /* main event loop */
{
usbPoll(); /* คำสั่งนี้ต้องถูกเรียกอย่างน้อยที่สุดทุก ๆ 50ms */
}
return 0;
}
- นิยามฟังก์ชัน
usbFunctionSetupเพื่อประมวลผลข้อมูลที่รับมาจากเครื่องคอมพิวเตอร์ผ่านทางพอร์ท USB
usbMsgLen_t usbFunctionSetup(uint8_t data[8])
{
;
}
- ฟังก์ชันนี้จะถูกเรียกทำงานโดยอัตโนมัติจากฟังก์ชัน
usbPollเมื่อทางฝั่งคอมพิวเตอร์ส่งคำร้องขอผ่านมาทางพอร์ท USB หัวข้อ #ตัวอย่างโปรแกรม แสดงตัวอย่างการการเขียนฟังก์ชันนี้เอาไว้
การคอมไพล์และลิ้งค์โปรแกรมรวมกับ V-USB
สร้าง Makefile ต่อไปนี้เพื่อคอมไพล์โปรแกรมและดำเนินการลิ้งค์เข้ากับไลบรารี V-USB ซึ่งตัวอย่าง Makefile นี้สมมติว่าไฟล์หลักของโปรเจ็คคือ main.c
MCU=atmega168
F_CPU=12000000L
TARGET=main.hex
OBJS=usbdrv/usbdrv.o usbdrv/usbdrvasm.o
CFLAGS=-Wall -Os -DF_CPU=$(F_CPU) -Iusbdrv -I. -mmcu=$(MCU)
all: $(TARGET)
flash: $(TARGET)
avrdude -p $(MCU) -c usbasp -U flash:w:$(TARGET)
%.hex: %.elf
avr-objcopy -j .text -j .data -O ihex $< $@
%.elf: %.o $(OBJS)
avr-gcc $(CFLAGS) -o $@ $?
%.o: %.c
avr-gcc -c $(CFLAGS) -o $@ $<
%.o: %.S
avr-gcc $(CFLAGS) -x assembler-with-cpp -c -o $@ $<
clean:
rm -f $(OBJS)
rm -f $(TARGET)
rm -f *~
หากดูในกฎที่ระบุกลไกการสร้างไฟล์ main.elf ขึ้นมาจาก main.o จะเห็นว่าบรรทัดที่มีการเรียกใช้ avr-gcc ได้มีการนำเอา $(OBJS) (ซึ่งได้แก่ไฟล์ usbdrv/usbdrv.o และ usbdrv/usbdrvasm.o) ลิ้งค์รวมเข้าไปด้วย ในที่นี้ตัวแปรพิเศษ $@ แทน target ซึ่งหมายถึง main.elf ส่วนตัวแปรพิเศษ $? แทนรายการของ dependency ทั้งหมด นั่นคือไฟล์ main.elf นั้นถูกสร้างขึ้นโดยการที่ make เรียกคำสั่งด้านล่างนี้อัตโนมัติ
avr-gcc -Wall -Os -DF_CPU=12000000L -Iusbdrv -I. -mmcu=atmega168 -o main.elf main.o usbdrv/usbdrv.o usbdrv/usbdrvasm.o
โครงสร้างของคำร้องขอ USB ที่รับจากฝั่งคอมพิวเตอร์
ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกเริ่มจากการที่ฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ส่งคำร้องขอ (USB Request) ไปยังฝั่งอุปกรณ์ USB เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม ข้อมูลคำร้องขอมีโครงสร้างตามที่นิยามไว้ในไฟล์ usbdrv/usbdrv.h ดังนี้
typedef struct usbRequest{
uchar bmRequestType; /* 1 ไบท์ */
uchar bRequest; /* 1 ไบท์ */
usbWord_t wValue; /* 2 ไบท์ */
usbWord_t wIndex; /* 2 ไบท์ */
usbWord_t wLength; /* 2 ไบท์ */
}usbRequest_t;
bmRequestTypeประกอบด้วยฟิลด์ย่อย 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
- ฟังก์ชัน
usbFunctionSetupที่เราต้องเขียนขึ้นนั้นจะถูกเรียกใช้เมื่อค่าในฟิลด์ Type นี้มีค่า 2 (Vendor) เท่านั้น
- บิต 4..0 ผู้รับ (Recipient)
- 0 = Device
- 1 = Interface
- 2 = Endpoint
- 3 = Other
bRequestระบุหมายเลขคำร้องขอ คำร้องขอตามมาตรฐานของ USB นั้นมีประเภทเป็น Standard ซึ่งจะถูกประมวลผลจากไลบรารี V-USB อัตโนมัติ เราจึงไม่ต้องสนใจในส่วนนี้ ส่วนที่เราต้องรับผิดชอบคือคำร้องขอแบบ Vendor ซึ่งต้องถูกออกแบบไว้ล่วงหน้าแล้วว่าอุปกรณ์ USB ของเราจะรองรับคำร้องขอหมายเลขอะไรบ้าง โดยในฟังก์ชันusbFunctionSetupของเราต้องประมวลผลคำร้องขอเหล่านี้ได้ถูกต้องwValueและwIndexทั้งคู่เป็นฟิลด์ที่ไม่มีความหมายใดในกรณีที่คำร้องขอเป็นแบบ Vendor ดังนั้นเราจึงมีอิสระเต็มที่ในการใช้งานฟิลด์ทั้งคู่นี้เป็นตัวส่งรายละเอียดของคำร้องขอ ซึ่งส่งได้สูงสุด 4 ไบท์wLengthกำหนดขนาดของข้อมูลเพิ่มเติมที่จะส่งจากฝั่งโฮสท์หรือจากอุปกรณ์ USB หากไม่มีข้อมูลเพิ่มเติม ค่านี้จะถูกเซ็ตเป็นศูนย์
ดูรายละเอียดเพิ่มเติมได้จาก USB in a NutShell
ตัวอย่างโปรแกรม
- โหลดไฟล์ usb-example.tgz ซึ่งเก็บไฟล์ทั้งหมดที่ใช้ในตัวอย่างนี้
เพื่อให้เห็นภาพของการใช้งานไลบรารี V-USB มากขึ้น เราลองสร้างตัวอย่างเฟิร์มแวร์อย่างง่ายขึ้นมาพร้อมทั้งใช้ภาษาไพธอนทดลองสั่งงานจากฝั่งคอมพิวเตอร์ ในที่นี้เราจะให้ตัวเฟิร์มแวร์จำลองตัวเป็นอุปกรณ์ USB ที่รองรับการสั่งงานจากโฮสท์ 2 คำร้องขอ ดังนี้
- คำร้องขอ SET_LED สั่งให้ LED ดวงที่ระบุติดหรือดับ มีรายละเอียดของคำร้องขอดังนี้
- กำหนดให้หมายเลขคำร้องขอ (request number) คือ 0
- ส่งรายละเอียดมาให้ 2 ไบท์ ไบท์แรกระบุตำแหน่งของ LED บนบอร์ดพ่วง (0, 1 หรือ 2) ส่วนไบท์ที่สองระบุว่าจะให้ LED ดวงดังกล่าวติดหากมีค่า 0 หรือดับหากมีค่าอื่นที่ไม่ใช่ศูนย์ เนื่องจากข้อมูลมีขนาดเพียงสองไบท์ เราจะใส่ข้อมูลนี้ลงไปในฟิลด์
wValueที่ถูกส่งไปพร้อมกับคำร้องขอโดยตรง - แม้ไม่มีข้อมูลอื่นเพิ่มเติมส่งจากคอมพิวเตอร์ไปยังอุปกรณ์ USB แต่เราจะระบุทิศทางการไหลของข้อมูลไว้เป็น Host to Device
- คำร้องขอ GET_SWITCH สั่งให้บอร์ด MCU รายงานสถานะการกดปุ่มสวิตช์กลับมา มีรายละเอียดของคำร้องขอดังนี้
- กำหนดให้หมายเลขคำร้องขอ (request number) คือ 1
- ทิศทางการไหลของข้อมูลเป็น Device to Host
- รับข้อมูลกลับมา 1 ไบท์ บอกสถานะของสวิตช์ (0 คือไม่ถูกกด 1 คือถูกกด)
การตั้งค่าให้อุปกรณ์ USB
เปิดไฟล์ usbconfig.h เพื่อปรับค่าให้สอดคล้องกับโปรเจ็ค
- VID/PID: อุปกรณ์ USB ทุกตัวจะต้องถูกกำหนดค่า Vendor ID (VID) และ Product ID (PID) ให้ ซึ่งแต่ละตัวเลขมีขนาด 16 บิต ในโปรเจ็คนี้ให้กำหนดค่า VID และ PID ให้เป็น 0x16c0 และ 0x05dc ตามลำดับ โดยดูให้แน่ใจว่าในไฟล์
usbconfig.hมีบรรทัดเหล่านี้
#define USB_CFG_VENDOR_ID 0xc0, 0x16 /* VID = 0x16c0 */ : #define USB_CFG_DEVICE_ID 0xdc, 0x05 /* PID = 0x05dc */
- อ่านหลักเกณฑ์การตั้งค่า VID/PID เพิ่มเติมได้จากหัวข้อ #เกี่ยวกับหมายเลข VID/PID
- Vendor Name: กำหนดค่า USB Vendor Name ให้เป็น
cpe.ku.ac.thโดยเปลี่ยนนิยามของมาโครUSB_CFG_VENDOR_NAMEและUSB_CFG_VENDOR_NAME_LENดังนี้
#define USB_CFG_VENDOR_NAME 'c','p','e','.','k','u','.','a','c','.','t','h' #define USB_CFG_VENDOR_NAME_LEN 12
- Device Name: กำหนดค่า USB Device Name ให้เป็น Practicum Group <หมายเลขกลุ่ม> โดยเปลี่ยนนิยามของมาโคร
USB_CFG_DEVICE_NAMEและUSB_CFG_DEVICE_NAME_LENตัวอย่างเช่น กลุ่ม 99 ให้แก้ไขมาโครดังนี้
#define USB_CFG_DEVICE_NAME 'P','r','a','c','t','i','c','u','m',' ','G','r','o','u','p',' ','9','9' #define USB_CFG_DEVICE_NAME_LEN 18
โปรแกรมฝั่งเฟิร์มแวร์
- เพื่อเป็นแนวปฏิบัติที่ดีในการเขียนโปรแกรม เราจะนิยามคำร้องขอเหล่านี้ไว้ที่ส่วนหัวของ
main.cดังนี้
#define VENDOR_RQ_SET_LED 0 #define VENDOR_RQ_GET_SWITCH 1
- เราสร้างฟังก์ชันจัดการอินพุท/เอาท์พุทของพอร์ท D เพื่อความสะดวกในการใช้งานไว้ดังนี้
uint8_t in_d(uint8_t pin)
{
// กำหนดให้ขาที่ระบุของพอร์ท D ทำหน้าที่เป็นอินพุท
DDRD &= ~(1<<pin);
// ดึงให้ขามีลอจิก 1 หากขาถูกปล่อยลอยไว้
PORTD |= (1<<pin);
// อ่านสถานะลอจิกของขาที่ระบุ
return ((PIND & (1<<pin))>>pin);
}
void out_d(uint8_t pin, uint8_t val)
{
// กำหนดให้ขาที่ระบุของพอร์ท D ทำหน้าที่เป็นเอาท์พุท
DDRD |= 1<<pin;
// เซ็ตลอจิกของขาเป็น 1 ถ้า val ไม่ใช่ศูนย์ ไม่เช่นนั้นเซ็ตให้เป็น 0
if (val)
PORTD |= 1<<pin;
else
PORTD &= ~(1<<pin);
}
- ภายในฟังก์ชัน
usbFunctionSetupสร้างโค้ดสำหรับประมวลผลคำร้องขอที่รับมาจากโฮสท์ดังนี้
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
static uchar dataBuffer[1]; /* ข้อมูลนี้ต้องไม่ถูกเขียนทับหลังจาก usbFunctionSetup รีเทิร์น */
/* ประมวลผลตามหมายเลขคำสั่งที่อยู่ใน bRequest */
if (rq->bRequest == VENDOR_RQ_SET_LED)
{
uint8_t led_no = rq->wValue.bytes[0];
uint8_t led_val = rq->wValue.bytes[1];
out_d(led_no, !led_val); /* กลับลอจิกเพื่อให้ 1 = ติด 0 = ดับ */
return 0;
}
else if (rq->bRequest == VENDOR_RQ_GET_SWITCH)
{
dataBuffer[0] = !in_d(3); /* กลับลอจิกเพื่อให้ 0 = ปล่อย 1 = กด */
usbMsgPtr = dataBuffer; /* ระบุว่าข้อมูลส่งกลับอยู่ใน dataBuffer */
return 1; /* ความยาวข้อมูลส่งกลับเท่ากับ 1 */
}
return 0; /* ไม่รู้จักคำร้องขอ ไม่ส่งข้อมูลกลับ */
}
- คอมไพล์เฟิร์มแวร์และอัพโหลดเข้าแฟลชของไมโครคอนโทรลเลอร์ ซึ่งหากได้สร้าง
Makefileตามที่อธิบายไว้ข้างต้นไว้เรียบร้อยแล้ว ให้เสียบบอร์ดไมโครคอนโทรลเลอร์เข้ากับพอร์ท USB กดสวิตช์เพื่อเข้าสู่ Bootloader แล้วพิมพ์คำสั่ง
make flash
โปรแกรมฝั่งเครื่องคอมพิวเตอร์
- (สำหรับ Ubuntu Linux) ติดตั้ง PyUSB เพื่อติดต่อกับอุปกรณ์ USB ผ่านภาษาไพธอน
sudo apt-get install python-usb
- (สำหรับ MacOS X) ดาวน์โหลดซอร์สโค้ดของไลบรารี libusb จากที่นี่ จากนั้นแตกไฟล์ออกและใช้คำสั่ง cd เข้าไปในไดเรคตอรี่ที่เก็บไฟล์ จึงคอมไพล์และติดตั้งโดยใช้คำสั่งต่อไปนี้
./configure make sudo make install
- จากนั้นโหลดซอร์สโค้ดของ PyUSB จากที่นี่ แตกไฟล์และติดตั้งโดยใช้คำสั่ง
sudo python setup.py install
- สร้างไฟล์ชื่อ
testusb.pyเรียกใช้ไลบรารี usb และนิยามฟังก์ชันfind_boardเพื่อค้นหาอุปกรณ์ USB ตัวที่เป็นบอร์ด MCU ของเราจากรายการอุปกรณ์ USB ทั้งหมดที่ต่ออยู่กับเครื่อง
import usb
def find_board():
for bus in usb.busses():
for dev in bus.devices:
if dev.idVendor == 0x16c0 and dev.idProduct == 0x05dc:
return dev
return None
- สังเกตว่าการค้นหาบอร์ด MCU ของเรานั้นอาศัยค่า VID และ PID ที่ตั้งไว้แต่แรก ดังนั้นอาจเกิดปัญหาขึ้นหากมีการเสียบบอร์ด MCU มากกว่าหนึ่งบอร์ดเข้ากับคอมพิวเตอร์เพราะฟังก์ชัน
find_boardจะคืนอุปกรณ์ที่พบว่ามี VID:PID เป็น 16c0:05dc เป็นตัวแรกเสมอ อย่างไรก็ตาม หากต้องการระบุเอาอุปกรณ์ตัวที่ระบุก็สามารถอาศัยข้อมูลจาก vendor name หรือ device name มากำหนดเงื่อนไขเพิ่ม
- ติดต่อกับบอร์ด MCU ผ่านทางไพธอนเชลล์ โดยเรียกโปรแกรม
testusb.pyแบบอินเทอแรคทีฟ
python -i testusb.py
- หมายเหตุ: หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU
- สร้าง handle เพื่อใช้เป็นทางผ่านในการสื่อสารกับบอร์ด MCU โดยใช้คำสั่งดังนี้
>>> board = find_board() >>> handle = board.open()
- สร้างคำร้องขอสำหรับคำสั่ง SET_LED ซึ่งระบุชนิดของคำร้องเป็นแบบ Vendor คือผู้สร้างอุปกรณ์กำหนดขึ้นมาเอง และระบุว่าคำร้องนี้มีการไหลของข้อมูลแบบ Host to Device
>>> req = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_OUT
- ส่งคำร้องออกไปยังบอร์ด MCU โดยกำหนดให้หมายเลขคำร้องเป็น 0 (ซึ่งหมายถึง SET_LED) และให้ฟิลด์
valueมีค่าเป็น 0x0102 ซึ่งเฟิร์มแวร์ของเราจะตีความว่าเป็นการสั่งให้ LED หมายเลข 2 บนบอร์ดพ่วงติด การร้องขอครั้งนี้ไม่ได้ส่งข้อมูลใด ๆ ไปเพิ่มเติม จึงให้พารามิเตอร์ buf ของ handle.ControlMsg เป็น None ไป
>>> handle.controlMsg(req, 0, None, value=0x0102)
- ลองให้ LED หมายเลข 2 ดับ และ LED หมายเลข 1 ติดขึ้นมาแทน
>>> handle.controlMsg(req, 0, None, value=0x0002) >>> handle.controlMsg(req, 0, None, value=0x0101)
- ทดลองอ่านสถานะของสวิตช์โดยส่งคำร้องหมายเลข 1 ไปยังบอร์ด MCU
>>> req = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_IN >>> handle.controlMsg(req, 1, 1) (0,)
- เนื่องจากคำร้องนี้เป็นการขอให้ MCU ส่งข้อมูลกลับ จึงต้องมีการระบุในคำร้องโดยใช้ usb.ENDPOINT_IN
- ในกรณีของการร้องขอข้อมูลจากอุปกรณ์ พารามิเตอร์ buf (พารามิเตอร์ตัวที่สามของเมท็อด controlMsg) เป็นความยาวข้อมูลที่ต้องการร้องขอ ซึ่งเท่ากับหนึ่ง
- ค่าที่เมทอด controlMsg คืนกลับมาจะเป็น tuple ความยาวหนึ่งเช่นกัน
- ทดลองกดสวิตช์บนบอร์ดพ่วง แล้วส่งคำร้องไปยังบอร์ด MCU ใหม่ ผลลัพธ์ที่ได้ควรเป็นดังนี้
>>> handle.controlMsg(req, 1, 1) (1,)
สร้างไพธอนคลาสเพื่อความสะดวกในการใช้งาน
การส่งคำสั่งจากคอมพิวเตอร์ไปยังบอร์ดไมโครคอนโทรลเลอร์ผ่านเมท็อด controlMsg นั่นนอกจากจะไม่สะดวกและเข้าใจยากแล้วยังจะทำให้เกิดความผิดพลาดได้ง่ายเนื่องจากพารามิเตอร์ต่าง ๆ ที่ส่งไปยังบอร์ดจะถูกเข้ารหัสรวมกันไว้ในฟิลด์ value และ index โปรแกรม board.py ในโค้ดตัวอย่างเป็นการรวมคำสั่งควบคุม LED และอ่านค่าสวิตช์ รวมถึงคำสั่งค้นหาบอร์ดเอาไว้ในคลาสเดียวกันเพื่อความสะดวกในการเรียกใช้ ดังแสดง
import usb
####################################
RQ_SET_LED = 0
RQ_GET_SWITCH = 1
####################################
class Board:
def __init__(self):
#print "Looking for MCU board..."
dev = self.find_board()
if not dev: raise "MCU board not found!"
#print "MCU board found"
self.handle = dev.open()
def find_board(self):
board = None
for bus in usb.busses():
for dev in bus.devices:
if dev.idVendor == 0x16c0 and dev.idProduct == 0x05dc:
return dev
return None
def set_led(self,pin,val):
reqType = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_OUT
self.handle.controlMsg(reqType, RQ_SET_LED, None, value=val*256+pin)
def get_switch(self):
reqType = usb.TYPE_VENDOR | usb.RECIP_DEVICE | usb.ENDPOINT_IN
buf = self.handle.controlMsg(reqType, RQ_GET_SWITCH, 1)
return buf[0]
สังเกตว่าเมท็อด __init__ จะดำเนินการค้นหาบอร์ด (ที่รันเฟิร์มแวร์จำลองให้ตัวเองเป็นอุปกรณ์ USB แล้ว) โดยอัตโนมัติหากมีการสร้างอ็อปเจ็กต์จากคลาส Board
>>> from board import Board >>> b = Board()
ซึ่งเราสามารถควบคุมการติดดับของ LED ผ่านอ็อปเจ็กต์นี้ เช่นการสั่งให้ LED ดวงแรก (ดวงที่ 0) ติด ทำได้โดยใช้คำสั่ง
>>> b.set_led(0,1)
หรือหากต้องการตรวจสอบสถานะของสวิตช์ ทำได้โดยใช้คำสั่ง
>>> b.get_switch() 0
เกี่ยวกับหมายเลข VID/PID
ชุดตัวเลข VID/PID ที่กำหนดให้กับอุปกรณ์ USB ไม่ควรตั้งเอาเองตามใจชอบเนื่องจากระบบปฏิบัติการจะอาศัยตัวเลขคู่นี้ในการเลือกซอฟต์แวร์ไดรเวอร์ที่จะมาควบคุมอุปกรณ์ USB
ข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดค่า VID และ PID ให้กับอุปกรณ์ USB รวมถึงหลักเกณฑ์การปฏิบัติในการผลิตอุปกรณ์ USB สู่สาธารณะ สามารถศึกษาเพิ่มเติมได้จากเนื้อหาในไฟล์ vusb-20100715/USB-ID-FAQ.txt และไฟล์ vusb-20100715/USB-IDs-for-free.txt ที่แจกจ่ายมากับไฟล์ vusb-20100715.tar.gz และเอกสาร How to obtain an USB VID/PID for your project