ผลต่างระหว่างรุ่นของ "การจำลองบอร์ด MCU เป็นอุปกรณ์ USB"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 75: แถว 75:
 
These topics are so cnfuosing but this helped me get the job done.
 
These topics are so cnfuosing but this helped me get the job done.
  
== ตัวอย่างโปรแกรม ==
+
Stay infoamritve, San Diego, yeah boy!
 
 
:โหลดไฟล์ [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
 
 
 
Ya learn somehitng new everyday. It's true I guess!
 
  
 
== เกี่ยวกับหมายเลข VID/PID ==
 
== เกี่ยวกับหมายเลข VID/PID ==

รุ่นแก้ไขเมื่อ 04:24, 17 กุมภาพันธ์ 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.

Stay infoamritve, San Diego, yeah boy!

เกี่ยวกับหมายเลข 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.