ผลต่างระหว่างรุ่นของ "การจำลองบอร์ด MCU เป็นอุปกรณ์ USB"
Chaiporn (คุย | มีส่วนร่วม) |
Chaiporn (คุย | มีส่วนร่วม) |
||
แถว 1: | แถว 1: | ||
− | ที่ผ่านมานั้นเราใช้พอร์ท USB | + | ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมแฟลชเท่านั้น วิกินี้อธิบายถึงขั้นตอนการทำให้ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ เพื่อที่จะสามารถสื่อสารกับโปรแกรมที่ทำงานอยู่บนเครื่องคอมพิวเตอร์ได้ |
+ | |||
+ | ใช้งานเป็น [http://vusb.wikidot.com/usb-device-classes custom class device] ซึ่งเป็นประเภทอุปกรณ์... | ||
ในที่นี้เราจะใช้โอเพนซอร์สไลบรารีชื่อ [http://www.obdev.at/products/vusb/index.html V-USB] (หรือ AVRUSB) ที่พัฒนาโดยบริษัท [http://www.obdev.at/index.html Objective Development] | ในที่นี้เราจะใช้โอเพนซอร์สไลบรารีชื่อ [http://www.obdev.at/products/vusb/index.html V-USB] (หรือ AVRUSB) ที่พัฒนาโดยบริษัท [http://www.obdev.at/index.html Objective Development] | ||
+ | |||
+ | == ขั้นตอนการใช้งานไลบรารี V-USB == | ||
+ | * ดาวน์โหลดซอร์สโค้ดจาก [http://www.obdev.at/downloads/vusb/vusb-20090822.tar.gz] | ||
+ | * แตกไฟล์ .tar.gz ที่ดาวน์โหลดมาโดยใช้คำสั่ง | ||
+ | tar zxf vusb-20090822.tar.gz | ||
+ | * คัดลอกไดเรคตอรี <code>usbdrv</code> ที่อยู่ในไดเรคตอรี <code>vusb-2009-0822</code> ไปวางในไดเรคตอรีโปรเจ็คของตน | ||
+ | * ภายในไดเรคตอรีโปรเจ็คของตนเอง ย้ายไฟล์ <code>usbconfig-prototype.h</code> มาวางไว้ด้านนอก และเปลี่ยนชื่อให้เป็น <code>usbconfig.h</code> | ||
+ | mv usbdrv/usbconfig-prototype.h ../usbconfig.h | ||
+ | :ไฟล์นี้จะเก็บข้อมูลการตั้งค่าเกี่ยวกับอุปกรณ์ USB ที่จะให้ไมโครคอนโทรลเลอร์จำลองตัวเองขึ้นมา | ||
+ | * ในไฟล์หลักของโปรเจ็ค เรียกใช้คำสั่ง <code>#include</code> ต่อไปนี้ไว้ที่ตอนต้นของโปรแกรม | ||
+ | #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" | ||
+ | * ในส่วนของฟังก์ชัน <code>main()</code> ต้องมีโครงสร้างหลักดังนี้ (สามารถใส่โค้ดอื่นเพิ่มได้ตามที่ต้องการ) | ||
+ | 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; | ||
+ | } | ||
+ | * นิยามฟังก์ชัน <code>usbFunctionSetup</code> เพื่อประมวลผลข้อมูลที่รับมาจากเครื่องคอมพิวเตอร์ผ่านทางพอร์ท USB | ||
+ | usbMsgLen_t usbFunctionSetup(uchar data[8]) | ||
+ | { | ||
+ | : | ||
+ | } | ||
+ | :ฟังก์ชันนี้จะถูกเรียกทำงานโดยอัตโนมัติจากฟังก์ชัน <code>usbPoll</code> เมื่อทางฝั่งคอมพิวเตอร์ส่งคำสั่งผ่านมาทางพอร์ท USB | ||
+ | |||
+ | == การคอมไพล์และลิ้งค์โปรแกรมรวมกับ V-USB == | ||
+ | สร้าง Makefile ต่อไปนี้เพื่อคอมไพล์โปรแกรมและดำเนินการลิ้งค์เข้ากับไลบรารี V-USB ซึ่งตัวอย่าง Makefile นี้สมมติว่าไฟล์หลักของโปรเจ็คคือ <code>main.c</code> | ||
+ | MCU=atmega168 | ||
+ | F_CPU=16000000L | ||
+ | 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 atmega168 -c usbasp -U flash:w:$(TARGET) | ||
+ | |||
+ | %.hex: %.elf | ||
+ | avr-objcopy -j .text -O ihex $< $@ | ||
+ | |||
+ | %.elf: %.o $(OBJS) | ||
+ | avr-gcc $(CFLAGS) -o $@ $? | ||
+ | |||
+ | %.o: %.c | ||
+ | avr-gcc -c $(CFLAGS) $< | ||
+ | |||
+ | %.o: %.S | ||
+ | avr-gcc $(CFLAGS) -x assembler-with-cpp -c -o $@ $< | ||
+ | |||
+ | clean: | ||
+ | rm -f $(OBJS) | ||
+ | rm -f $(TARGET) | ||
+ | rm -f *~ | ||
+ | |||
+ | สังเกตดูในกฎการสร้างไฟล์ <code>main.elf</code> ขึ้นมาจาก <code>main.o</code> บรรทัดที่มีการเรียกใช้ <code>avr-gcc</code> ได้มีการนำเอา <code>$(OBJS)$</code> (ซึ่งได้แก่ไฟล์ | ||
+ | <code>usbdrv/usbdrv.o</code> และ <code>usbdrv/usbdrvasm.o</code>) ลิ้งค์รวมเข้าไปด้วย ในที่นี้ตัวแปรพิเศษ <code>$@</code> แทน target ซึ่งหมายถึง | ||
+ | <code>main.elf</code> ส่วนตัวแปรพิเศษ <code>$?</code> แทนรายการของ dependency ทั้งหมด | ||
+ | |||
+ | == โครงสร้างของคำสั่ง USB ที่รับจากฝั่งคอมพิวเตอร์ == | ||
+ | ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกเริ่มจากฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ไปยังฝั่งอุปกรณ์ USB เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม | ||
+ | ข้อมูลคำสั่งที่ถูกส่งจากโฮสท์ เรียกว่า USB Request มีโครงสร้างตามที่นิยามไว้ในไฟล์ <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 | ||
+ | :* บิต 4..0 ผู้รับ (Recipient) | ||
+ | ::* 0 = Device | ||
+ | ::* 1 = Interface | ||
+ | ::* 2 = Endpoint | ||
+ | ::* 3 = Other | ||
+ | * <code>bRequest</code> ระบุหมายเลขคำสั่ง ซึ่งต้องถูกออกแบบไว้ล่วงหน้าแล้วว่าอุปกรณ์ USB ของเราจะรองรับคำสั่งอะไรบ้าง โดยเฟิร์มแวร์ต้องประมวลผลคำสั่งเหล่านี้ได้ถูกต้อง | ||
+ | * <code>wValue</code> และ <code>wIndex</code> | ||
+ | * <code>wLength</code> กำหนดขนาดของข้อมูลเพิ่มเติมที่จะส่งจากฝั่งโฮสท์หรือจากอุปกรณ์ USB หากไม่มีข้อมูลเพิ่มเติม ค่านี้จะถูกเซ็ตเป็นศูนย์ | ||
+ | |||
+ | อุปกรณ์ USB | ||
+ | |||
+ | == ตัวอย่างโปรแกรม == | ||
+ | เพื่อให้เห็นภาพของการใช้งานไลบรารี V-USB มากขึ้น เราลองสร้างตัวอย่างเฟิร์มแวร์อย่างง่ายขึ้นมาพร้อมทั้งโปรแกรมที่ทดลองสั่งงานจากฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ด้วยภาษาไพธอน | ||
+ | ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกกำหนดโดยฝั่งโฮสท์เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม | ||
+ | ดังนั้นหน้าที่ของเฟิร์มแวร์คือตีความคำสั่งที่ส่งมาจากโฮสท์และเตรียมข้อมูลที่จะส่งกลับไปให้โฮสท์ | ||
+ | |||
+ | เราจะให้ตัวเฟิร์มแวร์จำลองตัวเป็นอุปกรณ์ USB ที่รองรับการสั่งงานจากโฮสท์ 2 คำสั่ง ดังนี้ | ||
+ | * '''คำสั่ง SET_LED''' สั่งให้ LED ดวงที่ระบุติดหรือดับ มีรายละเอียดของคำสั่งดังนี้ | ||
+ | :* กำหนดให้หมายเลขคำสั่ง (request number) คือ 0 | ||
+ | :* การใช้งานจะระบุให้อุปกรณ์ USB เป็นเอาท์พุท | ||
+ | :* ส่งพารามิเตอร์มาให้ 2 ไบท์ ไบท์แรกระบุตำแหน่งของ LED (0, 1 หรือ 2) ส่วนไบท์ที่สองระบุว่าจะให้ LED ดวงดังกล่าวติด (หากมีค่า 0) หรือดับ (หากมีค่าอื่นที่ไม่ใช่ศูนย์) | ||
+ | * '''คำสั่ง GET_SWITCH''' สั่งให้บอร์ด MCU รายงานสถานะการกดปุ่มสวิตช์กลับมา มีรายละเอียดของคำสั่งดังนี้ | ||
+ | :* กำหนดให้หมายเลขคำสั่ง (request number) คือ 1 | ||
+ | :* การใช้งานจะระบุให้อุปกรณ์ USB เป็นอินพุท | ||
+ | :* ไม่มีพารามิเตอร์ใดส่งไป แต่ตัวบอร์ด MCU ส่งรายงานกลับมา 1 ไบท์ บอกสถานะของสวิตช์ (0 คือไม่ถูกกด 1 คือถูกกด) | ||
+ | |||
+ | ===การตั้งค่าให้อุปกรณ์ USB=== | ||
+ | * อุปกรณ์ USB ทุกตัวจะต้องมีการระบุค่า Vendor ID (VID) และ Product ID (PID) อย่างละ 16 บิต | ||
+ | ซึ่งไม่สามารถตั้งเองได้ตามใจชอบเนื่องจากระบบปฏิบัติการจะอาศัยตัวเลขคู่นี้ในการเลือกโปรแกรมไดรเวอร์ที่จะมาควบคุมอุปกรณ์ USB | ||
+ | http://www.voti.nl/docs/usb-pid.html | ||
+ | ตั้งค่า VID ให้เป็น 0x16c0 | ||
+ | |||
+ | ===โปรแกรมฝั่งเฟิร์มแวร์=== | ||
+ | เพื่อเป็นแนวปฏิบัติที่ดีในการเขียนโปรแกรม เราจะนิยามคำสั่งเหล่านี้ไว้ที่ส่วนหัวของ <code>main.c</code> ดังนี้ | ||
+ | #define SET_LED 0 | ||
+ | #define GET_SWITCH 1 | ||
+ | |||
+ | ภายในฟังก์ชัน <code>usbFunctionSetup</code> สร้างโค้ดสำหรับประมวลผลคำสั่งที่รับมาจากโฮสท์ดังนี้ | ||
+ | usbMsgLen_t usbFunctionSetup(uchar data[8]) | ||
+ | { | ||
+ | usbRequest_t *rq = (void *)data; | ||
+ | static uchar dataBuffer[4]; /* buffer must stay valid when usbFunctionSetup returns */ | ||
+ | |||
+ | if(rq->bRequest == SET_LED) | ||
+ | { | ||
+ | dataBuffer[0] = rq->wValue.bytes[0]; | ||
+ | dataBuffer[1] = rq->wValue.bytes[1]; | ||
+ | dataBuffer[2] = rq->wIndex.bytes[0]; | ||
+ | dataBuffer[3] = rq->wIndex.bytes[1]; | ||
+ | usbMsgPtr = dataBuffer; /* tell the driver which data to return */ | ||
+ | return 4; | ||
+ | }else if(rq->bRequest == CUSTOM_RQ_SET_STATUS){ | ||
+ | if(rq->wValue.bytes[0] & 1){ /* set LED */ | ||
+ | LED_PORT_OUTPUT |= _BV(LED_BIT); | ||
+ | }else{ /* clear LED */ | ||
+ | LED_PORT_OUTPUT &= ~_BV(LED_BIT); | ||
+ | } | ||
+ | }else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){ | ||
+ | dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0); | ||
+ | usbMsgPtr = dataBuffer; /* tell the driver which data to return */ | ||
+ | return 1; /* tell the driver to send 1 byte */ | ||
+ | } | ||
+ | return 0; /* default for not implemented requests: return no data back to host */ | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | == การโปรแกรมฝั่งเครื่องคอมพิวเตอร์ == | ||
+ | ติดตั้ง PyUSB เพื่อให้ติดต่อกับอุปกรณ์ USB โดยใช้ภาษาไพธอนได้ | ||
+ | sudo apt-get install python-usb | ||
+ | |||
+ | == ข้อมูลเพิ่มเติม == | ||
+ | * [http://vusb.wikidot.com/ V-USB Documentation Wiki] | ||
+ | * [http://www.beyondlogic.org/usbnutshell/usb1.htm USB in a NutShell] |
รุ่นแก้ไขเมื่อ 04:11, 26 สิงหาคม 2552
ที่ผ่านมานั้นเราใช้พอร์ท USB เป็นเพียงแหล่งจ่ายพลังงานและโปรแกรมแฟลชเท่านั้น วิกินี้อธิบายถึงขั้นตอนการทำให้ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB ความเร็วต่ำ เพื่อที่จะสามารถสื่อสารกับโปรแกรมที่ทำงานอยู่บนเครื่องคอมพิวเตอร์ได้
ใช้งานเป็น custom class device ซึ่งเป็นประเภทอุปกรณ์...
ในที่นี้เราจะใช้โอเพนซอร์สไลบรารีชื่อ V-USB (หรือ AVRUSB) ที่พัฒนาโดยบริษัท Objective Development
เนื้อหา
ขั้นตอนการใช้งานไลบรารี V-USB
- ดาวน์โหลดซอร์สโค้ดจาก [1]
- แตกไฟล์ .tar.gz ที่ดาวน์โหลดมาโดยใช้คำสั่ง
tar zxf vusb-20090822.tar.gz
- คัดลอกไดเรคตอรี
usbdrv
ที่อยู่ในไดเรคตอรีvusb-2009-0822
ไปวางในไดเรคตอรีโปรเจ็คของตน - ภายในไดเรคตอรีโปรเจ็คของตนเอง ย้ายไฟล์
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(uchar data[8]) { : }
- ฟังก์ชันนี้จะถูกเรียกทำงานโดยอัตโนมัติจากฟังก์ชัน
usbPoll
เมื่อทางฝั่งคอมพิวเตอร์ส่งคำสั่งผ่านมาทางพอร์ท USB
การคอมไพล์และลิ้งค์โปรแกรมรวมกับ V-USB
สร้าง Makefile ต่อไปนี้เพื่อคอมไพล์โปรแกรมและดำเนินการลิ้งค์เข้ากับไลบรารี V-USB ซึ่งตัวอย่าง Makefile นี้สมมติว่าไฟล์หลักของโปรเจ็คคือ main.c
MCU=atmega168 F_CPU=16000000L 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 atmega168 -c usbasp -U flash:w:$(TARGET) %.hex: %.elf avr-objcopy -j .text -O ihex $< $@ %.elf: %.o $(OBJS) avr-gcc $(CFLAGS) -o $@ $? %.o: %.c avr-gcc -c $(CFLAGS) $< %.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 ทั้งหมด
โครงสร้างของคำสั่ง USB ที่รับจากฝั่งคอมพิวเตอร์
ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกเริ่มจากฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ไปยังฝั่งอุปกรณ์ USB เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม
ข้อมูลคำสั่งที่ถูกส่งจากโฮสท์ เรียกว่า USB Request มีโครงสร้างตามที่นิยามไว้ในไฟล์ 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
- บิต 4..0 ผู้รับ (Recipient)
- 0 = Device
- 1 = Interface
- 2 = Endpoint
- 3 = Other
bRequest
ระบุหมายเลขคำสั่ง ซึ่งต้องถูกออกแบบไว้ล่วงหน้าแล้วว่าอุปกรณ์ USB ของเราจะรองรับคำสั่งอะไรบ้าง โดยเฟิร์มแวร์ต้องประมวลผลคำสั่งเหล่านี้ได้ถูกต้องwValue
และwIndex
wLength
กำหนดขนาดของข้อมูลเพิ่มเติมที่จะส่งจากฝั่งโฮสท์หรือจากอุปกรณ์ USB หากไม่มีข้อมูลเพิ่มเติม ค่านี้จะถูกเซ็ตเป็นศูนย์
อุปกรณ์ USB
ตัวอย่างโปรแกรม
เพื่อให้เห็นภาพของการใช้งานไลบรารี V-USB มากขึ้น เราลองสร้างตัวอย่างเฟิร์มแวร์อย่างง่ายขึ้นมาพร้อมทั้งโปรแกรมที่ทดลองสั่งงานจากฝั่งคอมพิวเตอร์ (ฝั่งโฮสท์) ด้วยภาษาไพธอน ในสถาปัตยกรรม USB นั้นการสื่อสารจะถูกกำหนดโดยฝั่งโฮสท์เสมอไม่ว่าจะมีการอ่านข้อมูลจากอุปกรณ์ USB หรือเขียนข้อมูลไปยังอุปกรณ์ USB ก็ตาม ดังนั้นหน้าที่ของเฟิร์มแวร์คือตีความคำสั่งที่ส่งมาจากโฮสท์และเตรียมข้อมูลที่จะส่งกลับไปให้โฮสท์
เราจะให้ตัวเฟิร์มแวร์จำลองตัวเป็นอุปกรณ์ USB ที่รองรับการสั่งงานจากโฮสท์ 2 คำสั่ง ดังนี้
- คำสั่ง SET_LED สั่งให้ LED ดวงที่ระบุติดหรือดับ มีรายละเอียดของคำสั่งดังนี้
- กำหนดให้หมายเลขคำสั่ง (request number) คือ 0
- การใช้งานจะระบุให้อุปกรณ์ USB เป็นเอาท์พุท
- ส่งพารามิเตอร์มาให้ 2 ไบท์ ไบท์แรกระบุตำแหน่งของ LED (0, 1 หรือ 2) ส่วนไบท์ที่สองระบุว่าจะให้ LED ดวงดังกล่าวติด (หากมีค่า 0) หรือดับ (หากมีค่าอื่นที่ไม่ใช่ศูนย์)
- คำสั่ง GET_SWITCH สั่งให้บอร์ด MCU รายงานสถานะการกดปุ่มสวิตช์กลับมา มีรายละเอียดของคำสั่งดังนี้
- กำหนดให้หมายเลขคำสั่ง (request number) คือ 1
- การใช้งานจะระบุให้อุปกรณ์ USB เป็นอินพุท
- ไม่มีพารามิเตอร์ใดส่งไป แต่ตัวบอร์ด MCU ส่งรายงานกลับมา 1 ไบท์ บอกสถานะของสวิตช์ (0 คือไม่ถูกกด 1 คือถูกกด)
การตั้งค่าให้อุปกรณ์ USB
- อุปกรณ์ USB ทุกตัวจะต้องมีการระบุค่า Vendor ID (VID) และ Product ID (PID) อย่างละ 16 บิต
ซึ่งไม่สามารถตั้งเองได้ตามใจชอบเนื่องจากระบบปฏิบัติการจะอาศัยตัวเลขคู่นี้ในการเลือกโปรแกรมไดรเวอร์ที่จะมาควบคุมอุปกรณ์ USB http://www.voti.nl/docs/usb-pid.html ตั้งค่า VID ให้เป็น 0x16c0
โปรแกรมฝั่งเฟิร์มแวร์
เพื่อเป็นแนวปฏิบัติที่ดีในการเขียนโปรแกรม เราจะนิยามคำสั่งเหล่านี้ไว้ที่ส่วนหัวของ main.c
ดังนี้
#define SET_LED 0 #define GET_SWITCH 1
ภายในฟังก์ชัน usbFunctionSetup
สร้างโค้ดสำหรับประมวลผลคำสั่งที่รับมาจากโฮสท์ดังนี้
usbMsgLen_t usbFunctionSetup(uchar data[8]) { usbRequest_t *rq = (void *)data; static uchar dataBuffer[4]; /* buffer must stay valid when usbFunctionSetup returns */ if(rq->bRequest == SET_LED) { dataBuffer[0] = rq->wValue.bytes[0]; dataBuffer[1] = rq->wValue.bytes[1]; dataBuffer[2] = rq->wIndex.bytes[0]; dataBuffer[3] = rq->wIndex.bytes[1]; usbMsgPtr = dataBuffer; /* tell the driver which data to return */ return 4; }else if(rq->bRequest == CUSTOM_RQ_SET_STATUS){ if(rq->wValue.bytes[0] & 1){ /* set LED */ LED_PORT_OUTPUT |= _BV(LED_BIT); }else{ /* clear LED */ LED_PORT_OUTPUT &= ~_BV(LED_BIT); } }else if(rq->bRequest == CUSTOM_RQ_GET_STATUS){ dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0); usbMsgPtr = dataBuffer; /* tell the driver which data to return */ return 1; /* tell the driver to send 1 byte */ } return 0; /* default for not implemented requests: return no data back to host */ }
การโปรแกรมฝั่งเครื่องคอมพิวเตอร์
ติดตั้ง PyUSB เพื่อให้ติดต่อกับอุปกรณ์ USB โดยใช้ภาษาไพธอนได้
sudo apt-get install python-usb