ผลต่างระหว่างรุ่นของ "การจำลองบอร์ด MCU เป็นอุปกรณ์ USB"
แถว 158: | แถว 158: | ||
make flash | make flash | ||
− | + | Ya learn somehitng new everyday. It's true I guess! | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== เกี่ยวกับหมายเลข VID/PID == | == เกี่ยวกับหมายเลข VID/PID == |
รุ่นแก้ไขเมื่อ 23:25, 14 กุมภาพันธ์ 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
These topics are so cnfuosing but this helped me get the job done.
ตัวอย่างโปรแกรม
- โหลดไฟล์ 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
Ya learn somehitng new everyday. It's true I guess!
เกี่ยวกับหมายเลข 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
Frnkaly I think that's absolutely good stuff.