logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

m_marko 19 Maj 2009 11:07 83987 181
  • #1 6547975
    m_marko
    Poziom 12  
    Witam,

    Temat ten jest na tyle ciekawy, że wart jest szerszego opisania. Wcześniej stosowałem układy firmy FTDI w najprostszej postaci czyli jako wirtualny port szeregowy COM. Są również mikrokontrolery AVR z pełnym portem USB, niemniej wiąże to z większym wydatkiem, a wtedy można z powodzeniem sięgnąć po bardziej zaawansowane układy 32 bitowe.

    Natomiast w większości przypadków w moich aplikacjach potrzebuję wysyłać stosunkową niedużą paczkę danych. Port szeregowy jest stosunkowo łatwo oprogramować z poziomu uC jak i PC (szczególnie przy korzystaniu z gotowych bibliotek). W przypadku USB nie musi być o wiele trudniej. USB 1.1 (low speed) umożliwia przesył danych z prędkością 1.5Mb co jak na moje potrzeby jest w zupełności wystarczające.

    Czas temu natknąłem się na AVR USB (obecnie V-USB), a jest to softwarowa implementacja portu szeregowego USB 1.1 w mikrokontrolerach AVR. Link do strony poniżej (skąd można ściągnąć najnowszą wersję biblioteki):
    http://www.obdev.at/products/vusb/index.html

    Większość przykładowych projektów na w/w stronie dotyczy ATmega8, ja używam ATmega88 ale nie ma problemu z uruchomieniem biblioteki na innych AVR (wymagane 2kB pamięci FLASH i 128 RAM, oraz zegar min. 12MHz).

    HID
    Większość przykładów oparte jest na HID (Human Input Devices) dzięki czemu nie ma potrzeby tworzenia specjalnych driverów pod systemy operacyjne (np. Windows, Linux). Mikrokontroler z zaszytym interfejscem HID będzie automatycznie rozpoznawany przez system operacyjny, który zainstaluje odpowiednie sterowniki.

    Więcej o HID: http://www.usb.org/developers/hidpage

    Osobiście uruchomiłem myszkę, joystick, oraz urządzenie wymieniające dane na ATmega88. Wystarczy zdefiniować w raporcie HID ilość przycisków, osi i urządzenie gotowe.

    Najbardziej interesujący jest HID jako urządzenie wymieniające dane gdyż w ten sposób mogę łatwo wysyłać i odbierać dane do/z mikrokontrolera, np. sterowanie zapalaniem diod led z komputera PC i odczytywanie temperatury.

    Jako program do testowania komunikacji zastosowałem "gotowca" pod Visual C# (przykład generic_hid_cs). Do ściągnięcia pod adresem (plus inne informacje o HID):
    http://www.lvr.com/hidpage.htm

    VID/PID
    Każde urządzenie z portem USB posiada identyfikator VID (USB Vendor ID - numer identyfikacyjny producenta) oraz PID (numer identyfikacyjny produktu). Oczywiście numery te posiadają firmy, które produkują urządzenia pod USB i raczej są nieosiągalne dla przeciętnego użytkownika, chyba, że zechce uzyskać takowe (kosztowna droga). Rozwiązaniem jest zastosowanie numerów podawanych przez twórcę biblioteki V-USB.

    Wymagania
    Biblioteka jest napisana w języku C (z kilkoma wstawkami assemblerowymi). Przy czym użytkownik nie musi wnikać głęboko w kod. Praktycznie najważniejsze elementy znajdują się w pliku main.c (report descriptor), oraz w usbconfig.h (konfiguracja sprzętowa).
    Środowiska w których można skompilować bibliotekę to:
    AVR Studio (z kompilatorem C), WinAVR, CodeVision AVR (nie testowałem)
    osobiście korzystam głównie z AVR Studio, ale myślę, że każde inne środowisko z kompilatorem języka C powinno tutaj wystarczyć.
    Mikrokontroler musi posiadać min. 2kB pamięci FLASH, min 128 bajtów RAM, oraz zegar min. 12MHz.
    W przypadku tworzenia własnego urządzenia USB, lub też modyfikacji HID dobrze jest znać po trochu opis standardu USB. W większości przypadków wystarczają załączone do biblioteki przykłady z ewentualnymi małymi korektami.

    Na koniec
    Temat jest bardzo rozległy i nie sposób opisać go w kilku słowach. Niemniej powyższa biblioteka jest bardzo mocnym argumentem za stosowaniem małych mikrokontrolerów (szczególnie AVR - nie znalazłem nigdzie indziej urządzenia pod USB za kilka zł). No i to, że zastosowanie USB w AVR nie musi być takie trudne.

    Przykłady
    Pomimo iż biblioteka V-USB posiada kilka przykładów, zdecydowałem się umieścić kilka nowych. Na razie dodaję przykład joysticka.
    Joystick 16p - przykład joysticka z 16 przyciskami (plus osie X i Y), program napisany pod AVR Studio 4.16. Program pod ATmega88.

    Historia:
    2009.05.29 Dodano wpis Wymagania
    2009.07.05 Dodano przykłady
  • #2 6548542
    dawid512
    Poziom 32  
    Czyżby opisany tu projekt to znane już avr cdc? Jedyna różnica jaką widzę to dodany tryb HID.
  • #3 6551769
    m_marko
    Poziom 12  
    Nie znam dobrze projektu cdc, lecz na stronie twórcy AVR-CDC http://www.recursion.jp/avrcdc pisze, że oparł swój projekt na powyższej bibliotece AVR-USB.

    HID jest jedną z możliwości jakie oferuje USB, napisałem o tym ponieważ z powodzeniem to stosuję. Twórca biblioteki podaje, że jest w pełni kompatybilne ze standardem USB 1.1 a więc można z tą biblioteką zrobić dowolne urządzenie na USB niekoniecznie z wykorzystaniem HID, ale w tym przypadku wymagane jest wykonanie odpowiedniego sterownika pod system operacyjny.

    Pozdrawiam
  • #4 6561441
    krzysiekj18
    Poziom 19  
    Witam
    Temat bardzo interesujący, jednak jestem początkujący jeżeli chodzi o komunikację procesora z komputerem, i tutaj moje pytanie czy za pomocą bascom-a można zaprogramować procesor avr do takiej komunikacji jak przedstawiona powyżej, jeżeli nie to przy pomocy jakiego języka się to odbywa ???
    Oraz moja prośba czy może mi ktoś udostępnić gotową (jeżeli to możliwe z opisami ) aplikację którą uruchamia się z VisulaC+ oraz aplikację którą trzeba wgrać na procka aby była możliwa komunikacja.
    Dodam jeszcze, że zależy mi najbardziej na odczytywaniu i ustawianiu stanów wejść i wyjść procesora ( w tym stanów wejść/wyjść analogowych ).
  • #5 6563336
    megaman123
    Poziom 13  
    Hej !

    Widzę ,ze rozgryzłeś tą bibliotekę :) . Dokopałeś się w jaki sposób usbHidReportDescriptor wypełniać ? ( tablica używana w programie , wypełniać w sensie - co znaczą poszczególne bajty). Symulując myszkę wysyłałeś naraz pakiet 3+ więcej bajtów ? Zakładam ,że używając funkcji usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
    Przymierzam się właśnie od dłuższego czasu ,zeby stworzyć klawiaturę , ale niestety nie potrafię zrozumieć kolejność wykonywanych czynności podczas wysyłania danych ( zapewne , poprzez brak znajomości protokołu USB )

    Pozdrawiam
  • #6 6564009
    m_marko
    Poziom 12  
    Witam,

    Jeżeli chodzi o Bascom to może to być trudna droga, dawno temu jak Bascom ujrzał światło dzienne zainteresowałem się tym tematem, ale napisałem kilka programików i moje doświadczenia w tym temacie się kończą. Jeśli Bascom ma możliwość kompilacji plików języka C to może.

    Jeśli chodzi o działający przykład to z biblioteki V-USB załadowałem przykład hid-data na ATmega88 (jestem teraz na wyjeździe kilkudniowym i jak tylko wrócę to załaduję ten sam przykład pod ATmega88), następnie ściągnąłem aplikację pod Visual C#:
    http://www.lvr.com/files/generic_hid_cs_451.zip

    Po załadowaniu przykładu do uC (ważne aby ustawić prawidłową wartość zegara oraz konfigurację portów - plik usbconfig.h) i podłączeniu pod port USB (sposób podłączenia jest również podany na stronie projektu V-USB) odpalam przykładowy program GenericHID, w którym muszę ustawić następujące parametry:
    1. VID/PID musi być zgodny z tym co jest ustawione w uC
    2. Należy wybrać opcję "Exchange Feature Reports"
    Nacisnąć przycisk "Once" lub "Continous" i voila, następuje wymiana danych z uC. Przy czym ilość danych (bajtów) jest zdefiniowana w samym raporcie HID, który jest zaimplementowany w AVR. Powyższy plik zawiera również skompilowaną wersję programu, więc jak ktoś nie ma to może sobie odpalić plik GenericHID,exe (wymagany .Net 2.0 lub późniejszy).

    Wspomnieć muszę, że system powinien rozpoznać podłączony uC jako urządzenie HID.

    O raportach HID napiszę jeszcze parę słów jak tylko znajdę chwilkę czasu. Choć zastrzegam, że sam wciąż "uczę" się korzystania z HID i jeśli popełnię jakieś błędy to z góry przepraszam.

    Pozdrawiam.

    Update:
    25.05.2009 Załączyłem program pomiar.zip, który to jest przykładem z V-USB pod ATmega88 (AVR Studio)

    Nie ma się co przejmować zawiłym kodem biblioteki. Najważniejsza część jest w main.c, gdzie znajduje się następujący raport HID:
    
    PROGMEM char usbHidReportDescriptor[22] = {    /* USB report descriptor */
        0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
        0x09, 0x01,                    // USAGE (Vendor Usage 1)
        0xa1, 0x01,                    // COLLECTION (Application)
        0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
        0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
        0x75, 0x08,                    //   REPORT_SIZE (8)
        0x95, 0x10,                    //   REPORT_COUNT (16)
        0x09, 0x00,                    //   USAGE (Undefined)
        0xb2, 0x02, 0x01,              //   FEATURE (Data,Var,Abs,Buf)
        0xc0                           // END_COLLECTION
    };
    


    Uwaga jeśli nastąpi zmiana długości powyższej tablicy to należy zaktualizować odpowiedni wpis w pliku usbconfig.h:
    
    #define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH    22
    


    Zdefiniowany raport (Feature) informuje, że urządzenie będzie wysyłać i odbierać 16 bajtów. Aby zmienić liczbę wymienianych bajtów np. na 32, wystarczy wykonać następującą zmianę w definicji raportu:

    
    PROGMEM char usbHidReportDescriptor[22] = {    /* USB report descriptor */
        0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
        0x09, 0x01,                    // USAGE (Vendor Usage 1)
        0xa1, 0x01,                    // COLLECTION (Application)
        0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
        0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
        0x75, 0x08,                    //   REPORT_SIZE (8)
        0x95, 0x20,                    //   REPORT_COUNT (32) *ZMIANA*
        0x09, 0x00,                    //   USAGE (Undefined)
        0xb2, 0x02, 0x01,              //   FEATURE (Data,Var,Abs,Buf)
        0xc0                           // END_COLLECTION
    };
    


    Najbardziej istotnymi elementami raportu (z naszego punktu widzenia), są:
    LOGICAL_MINIMUM, LOGICAL_MAXIMUM - określa zakres w jakim może zmienić się pojedyncza przesyłana wartość (w naszym przypadku od 0 do 255),
    REPORT_SIZE - wielkość przesyłanej wartości (w tym przypadku 8 oznacza 8 bitów),
    REPORT_COUNT - ilość przesyłanych wartości (w tym przypadku 32 - oznacza 32 bajty)

    Możliwe jest zdefiniowanie np. wartości 16, 32, 64 bitowych itd.
    Możliwe jest mieszanie typów zmiennych poprzez odpowiednią rozbudowę samego raportu.
  • #7 6574751
    r06ert
    Poziom 25  
    Witam, zainteresowałem się tematem :)

    m_marko napisał:

    1. VID/PID musi być zgodny z tym co jest ustawione w uC


    a co jest ustawione w twoim programie "pomiar"? W pliku usbconfig.h znalazłem takie oto linijki
    
    #define  USB_CFG_VENDOR_ID       0xc0, 0x16
    [...]
    #define  USB_CFG_DEVICE_ID       0xdf, 0x05
    

    Wpisałem to samo w odpowiednie pola programu "Generic HID Tester" i klops :) Nie może znaleźć urządzenia.

    Robie to tak, skompilowałem twój program ale pod ATmega8, zegar 12MHz, wgrałem też wsad do eeproma (plik .eep). Podłączyłem pod USB (wyskoczył komunikat że nierozpoznanie urządzenie, może mieć problem etc.)i uruchomiłem aplikacje "Generic HID Tester".

    Z góry dzięki za pomoc. Pozdrawiam :)
  • #8 6582537
    m_marko
    Poziom 12  
    Witam,

    Wcześniej niestety nie mogłem odpowiedzieć.

    Zamieszczam poniżej na szybko sklecony schemat na szybko skleconego obwodu :D

    V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

    Oraz ustawień fuse bits:
    V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

    Po załadowaniu softu (poprzedni post - Pomiar.zip), układ po podłączeniu do Windowsa XP (lub innego systemu, łącznie z Linuksem) powinien być zidentyfikowany jako urządzenie HID. Poniżej obrazek z Windows XP:
    V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

    Następnie po uruchomieniu aplikacji Generic Hid Tester (patrz również poprzedni post), powinniśmy uzyskać następujący efekt po naciśnięciu przycisku "Send receive data Once":
    V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

    lub taki po naciśnięciu przycisku "Find my device":
    V-USB (AVR USB) Tanie urządzenie USB w oparciu o AVR

    Update 28.05.2009
    Mam nadzieję, że powyższe obrazki rozwieją wątpliwości odnośnie zastosowania biblioteki V-USB w AVR.
    Schemat elektroniczny proszę traktować tylko jako przykład. Diody D1 i D2 mają za zadanie obniżyć napięcie do pożądanego przez standard USB 3.3V. Przy obniżonym napięciu zasilania układ ATmega88 może pracować z zegarem max. 12MHz (choć tutaj pracuje poprawnie przy 20MHz).
  • #9 6592894
    ksz
    Poziom 15  
    @megaman123 Odnośnie tablicy deskryptora HID, usb.org udostępnia programik generujący takową tablicę, HIDTool bodajże..
  • #11 6651789
    Sparrowhawk
    Poziom 22  
    Witam

    Robię projekt w oparciu o ten "przepis". Mam kilka pytań:

    1. Czy jeżeli pobieram 3 ośmiobitowe liczby z PC, to do komputera też mogę wysłać 3 bajtową informację. Opieram się na tym, że ustawienia HID Raport są dla transmisji w dwie strony.

    2. Jeśli już wyślę na USB te 3 liczby, to pod jaką zmienną się kryją w programie? Czy aby je odczytać trzeba wywołać funkcję USB...WRITE?
  • #12 6652556
    m_marko
    Poziom 12  
    Witam,

    Zaimplementowany HID w programie Pomiar.zip określa bufor 16 bajtów do odczytu i zapisu danych do/z AVR. Nie wiem czy można zdefiniować asymetryczny bufor czyli inny rozmiar bufora odczytu inny zapisu.

    Jeżeli mają być odbierane i wysyłane 3 bajty to należy umieścić odpowiednią modyfikację w raporcie HID. Tą samą informację należy umieścić w funkcji usbFunctionSetup:
    
    ...
    bytesRemaining = 16; // należy wstawić 3
    ...
    bytesRemaining = 16; // należy wstawić 3
    ...
    


    Powyższa funkcja jest wywoływana za każdym razem jak komputer PC odpytuje urządzenie. USB driver na AVR jest tak skonfigurowany, że dane wysyłane do AVR są przekazywane poprzez funkcję usbFunctionWrite, natomiast dane wysyłane do PC poprzez funkcję usbFunctionRead.

    Funkcja wysyłająca dane do PC wygląda następująco
    
    uchar usbFunctionRead(uchar *data, uchar len)
    

    *data jest wskaźnikiem do bufora nadawczego, natomiast len - wielkością bufora (maksymalnie 8 )

    Jeśli zdefiniowana tablica bufora w HID jest duża to wtedy dane są wysyłane paczkami (maksymalnie 8 bajtów w jednej paczce).

    Np. wysyłamy bufor 17 znaków najpierw wywoływana jest funkcja usbFunctionRead pakujemy 8 bajtów do bufora *data (len=8 ), następnym razem znowu 8 bajtów (len=8 ) i ostatecznie pozostaje nam 1 bajt do wysłania (len=1).

    Analogicznie wygląda sprawa z funkcją odbierającą dane z PC
    
    uchar usbFunctionRead(uchar *data, uchar len)
    

    *data jest wskaźnikiem do bufora odbiorczego, natomiast len - wielkością bufora (maksymalnie 8 ).

    Np. odbieramy bufor 17 znaków najpierw otrzymujemy 8 (len=8 ), potem znowu 8 (len=8 ) i ostatecznie 1 (len=1).

    Powyższe funkcje usbFunctionRead i usbFunctionWrite umożliwiają obrabianie danych przed ich wysłaniem albo zaraz po ich odebraniu. Jest możliwość ustawienia drivera w AVR tak, aby dane były pobierane ze zdefiniowanej tablicy w pamięci RAM. Nie konfigurowałem jednak tego ale wiem, że w pliku konfiguracyjnym należy odznaczyć opcje
    USB_CFG_IMPLEMENT_FN_READ na 0 i
    USB_CFG_IMPLEMENT_FN_WRITE na 0
    oraz umieścić wskaźnik do pamięci w funkcji usbFunctionSetup (jak? to jeszcze nie wiem).

    Pozdrawiam.
  • #13 6695586
    Sparrowhawk
    Poziom 22  
    #include <avr/io.h>
    #include <avr/wdt.h>
    #include <avr/interrupt.h>  /* for sei() */
    #include <util/delay.h>     /* for _delay_ms() */
    #include <avr/eeprom.h>
    #include <avr/pgmspace.h>   /* required by usbdrv.h */
    #include "usbdrv.h"
    #include "oddebug.h"        /* This is also an example for using debug macros */
    
    
    #define EnkA0 (PINB &_BV(PINB0))
    #define EnkB0 (PINB &_BV(PINB1))
    #define EnkA1 (PINB &_BV(PINB2))
    #define EnkB1 (PINB &_BV(PINB3))
    #define EnkA2 (PINB &_BV(PINB4))
    #define EnkB2 (PINB &_BV(PINB5))
    
    volatile uint8_t ADRES;
    uchar LED[3] = {2,3,4};
    int WAIT[3] = {1,1,1};
    
    uchar inputbuff[3];
    uchar outbuff[3];
    
    PROGMEM char usbHidReportDescriptor[21] = {    /* USB report descriptor */
        0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
        0x09, 0x01,                    // USAGE (Vendor Usage 1)
        0xa1, 0x01,                    // COLLECTION (Application)
        0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
        0x25, 0x01,		               //   LOGICAL_MAXIMUM (1)
        0x75, 0x08,                    //   REPORT_SIZE (8)
        0x95, 0x03,                    //   REPORT_COUNT (3)
        0x09, 0x00,                    //   USAGE (Undefined)
        0xb2, 0x02, 0x01,              //   FEATURE (Data,Var,Abs,Buf)
        0xc0                           // END_COLLECTION
    };
    /* Since we define only one feature report, we don't use report-IDs (which
     * would be the first byte of the report). The entire report consists of 128
     * opaque data bytes.
     */
    
    /* The following variables store the status of the current data transfer */
    static uchar    currentAddress;
    static uchar    bytesRemaining;
    
    /* ------------------------------------------------------------------------- */
    
    /* usbFunctionRead() is called when the host requests a chunk of data from
     * the device. For more information see the documentation in usbdrv/usbdrv.h.
     */
    
    
    uchar   usbFunctionRead(uchar *data, uchar len)
    {
        if(len > bytesRemaining)
            len = bytesRemaining;
        eeprom_read_block(data, (uchar *)0 + currentAddress, len);
        currentAddress += len;
        bytesRemaining -= len;
        return len;
    }
    
    
    /* usbFunctionWrite() is called when the host sends a chunk of data to the
     * device. For more information see the documentation in usbdrv/usbdrv.h.
     */
    
    uchar   usbFunctionWrite(uchar *data, uchar len)
    {
        if(bytesRemaining == 0)
            return 1;               /* end of transfer */
        if(len > bytesRemaining)
            len = bytesRemaining;
        eeprom_write_block(data, (uchar *)0 + currentAddress, len);
        currentAddress += len;
        bytesRemaining -= len;
        return bytesRemaining == 0; /* return 1 if this was the last chunk */
    }
    
    
    /* ------------------------------------------------------------------------- */
    
    usbMsgLen_t usbFunctionSetup(uchar data[8])
    {
    usbRequest_t    *rq = (void *)data;
    
        if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* HID class request */
            if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
                /* since we have only one report type, we can ignore the report-ID */
                bytesRemaining = 3;
                currentAddress = 0;
                return USB_NO_MSG;  /* use usbFunctionRead() to obtain data */
            }else if(rq->bRequest == USBRQ_HID_SET_REPORT){
                /* since we have only one report type, we can ignore the report-ID */
                bytesRemaining = 3;
                currentAddress = 0;
                return USB_NO_MSG;  /* use usbFunctionWrite() to receive data from host */
            }
        }else{
            /* ignore vendor type requests, we don't use any */
        }
        return 0;
    }
    
    
    void WYSW(uchar LEDS[3])
    {
    	DDRC = 0xFF;
    	PORTC = ADRES;
    	DDRA = 0xFF;
    	PORTA = LEDS[ADRES];
    }
    
    void ENKD(void)
    {
    	if((!WAIT[0])&&(!EnkA0)&&(EnkB0))
    	{
    		LED[0]--;
    		if(LED[0] < 0) 
    		LED[0] = 9;
    		WAIT[0] = 1;
    	}
    	if((!WAIT[0])&&(EnkA0)&&(!EnkB0))
    	{
    		LED[0]++;
    		if(LED[0] > 9) 
    		LED[0] = 0;
    		WAIT[0] = 1;
    	}
    	if((EnkA0)&&(EnkB0))
    	{
    		WAIT[0] = 0;
    	}
    	if((!WAIT[1])&&(!EnkA1)&&(EnkB1))
    	{
    		LED[1]--;
    		if(LED[1] < 0) 
    		LED[1] = 9;
    		WAIT[1] = 1;
    	}
    	if((!WAIT[1])&&(EnkA1)&&(!EnkB1))
    	{
    		LED[1]++;
    		if(LED[1] > 9) 
    		LED[1] = 0;
    		WAIT[1] = 1;
    	}
    	if((EnkA1)&&(EnkB1))
    	{
    		WAIT[1] = 0;
    	}
    
    	if((!WAIT[2])&&(!EnkA2)&&(EnkB2))
    	{
    		LED[2]--;
    		if(LED[2] < 0) 
    		LED[2] = 9;
    		WAIT[2] = 1;
    	}
    	if((!WAIT[2])&&(EnkA2)&&(!EnkB2))
    	{
    		LED[2]++;
    		if(LED[2] > 9) 
    		LED[2] = 0;
    		WAIT[2] = 1;
    	}
    	if((EnkA2)&&(EnkB2))
    	{
    		WAIT[2] = 0;
    	}
    }
    
    SIGNAL (SIG_OVERFLOW0)
    {
    	TCNT0 = 6;
    	if(ADRES == 10)
    	{
    		ADRES = 0;
    	}
    	else
    	{
    		ADRES++;
    	}
    	WYSW(LED);
    	ENKD();
    }
    
    int main(void)
    {
    	PORTB = 0xFF;
    	TIMSK = _BV(TOIE0);
    	TCCR0 = _BV(CS00)|_BV(CS01);
    	TCNT0 = 6;
    	
    	outbuff[0] = LED[0];
    	outbuff[1] = LED[1];
    	outbuff[2] = LED[2];
    
    	uchar   i;
    
        wdt_enable(WDTO_1S);
        /* Even if you don't use the watchdog, turn it off here. On newer devices,
         * the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
         */
        DBG1(0x00, 0, 0);       /* debug output: main starts */
        /* RESET status: all port bits are inputs without pull-up.
         * That's the way we need D+ and D-. Therefore we don't need any
         * additional hardware initialization.
         */
        odDebugInit();
        usbInit();
        usbDeviceDisconnect();  /* enforce re-enumeration, do this while interrupts are disabled! */
        i = 0;
        while(--i)
    	{             /* fake USB disconnect for > 250 ms */
            wdt_reset();
            _delay_ms(1);
        }
        usbDeviceConnect();
        sei();
        DBG1(0x01, 0, 0);       /* debug output: main loop starts */
        for(;;)
    	{                /* main event loop */
            DBG1(0x02, 0, 0);   /* debug output: main loop iterates */
            wdt_reset();
            usbPoll();
        }
        return 0;
    
    }

    Mam urządzenie na ATMEGA32 (16MHz), do którego mam podłączone 3 wyświetlacze LED, na których mogę za pomocą 3 enkoderów ustawić wartość od 0 do 10. I teraz chciałbym aby za pomocą funkcji HIDTOOL READ można było odczytać wskazywaną wartość do PC. Jednak czegokolwiek bym nie zmienił w HIDREPORT zawsze funkcja HIDTOOL READ (W skompilowanym programie HIDTOOL) zwraca mi tablicę szesnastu 8 bitowych liczb zapisanych w systemie szesnastkowym.
  • #14 6713343
    m_marko
    Poziom 12  
    Witam,

    Wprowadziłem pewne modyfikacje do programu. Testowałem na ATmega88. Mikrokontroler odbiera dane z PC po czym je "obrabia" za pomocą funkcji FncData. Można tam dorzucić sterowanie wyjściami uC.

    Analogicznie w funkcji usbFunctionWrite można utworzyć funkcję do wysyłania danych.

    Korzystając z programu GenericHid Cs (opisany parę postów wyżej), sprawdziłem komunikację i następuje prawidłowa wymiana 3 bajtów z PC.

    Zmieniłem logical Maximum z 1 na 255 (zwiększyła się przez to wielkość reportu HID). Lepiej bity pakować do bajtów i wysyłać bufor jako pełne bajty.


    
    #include <avr/io.h>
    #include <avr/wdt.h>
    #include <avr/interrupt.h> /* for sei() */
    #include <util/delay.h> /* for _delay_ms() */
    #include <avr/eeprom.h>
    #include <avr/pgmspace.h> /* required by usbdrv.h */
    #include <string.h>
    #include "usbdrv.h"
    #include "oddebug.h" /* This is also an example for using debug macros */
    
    
    #define buffMask (uchar)0x03 // maska bufora, tylko wartości (2^n-1), np. 0x01,0x03,0x07,0x0F itd
    
    uchar inbuff[3];	// bufor wejściowy >= buffMask
    uchar outbuff[3];   // bufor wyjściowy >= buffMask
    
    void FncData(void);
    
    PROGMEM char usbHidReportDescriptor[22] = { /* USB report descriptor */
    	0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
    	0x09, 0x01, // USAGE (Vendor Usage 1)
    	0xa1, 0x01, // COLLECTION (Application)
    	0x15, 0x00, // LOGICAL_MINIMUM (0)
    	0x26, 0xFF, 0x00, // LOGICAL_MAXIMUM (255)
    	0x75, 0x08, // REPORT_SIZE (8)
    	0x95, 0x03, // REPORT_COUNT (3)
    	0x09, 0x00, // USAGE (Undefined)
    	0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
    	0xc0 // END_COLLECTION
    };
    /* Since we define only one feature report, we don't use report-IDs (which
    * would be the first byte of the report). The entire report consists of 128
    * opaque data bytes.
    */
    
    /* The following variables store the status of the current data transfer */
    static uchar currentAddress;
    static uchar bytesRemaining;
    
    /* ------------------------------------------------------------------------- */
    
    /* usbFunctionRead() is called when the host requests a chunk of data from
    * the device. For more information see the documentation in usbdrv/usbdrv.h.
    */
    
    uchar usbFunctionRead(uchar *data, uchar len)
    {
    	if(len > bytesRemaining)
    	len = bytesRemaining;
    	// lepiej wyłączyć odczyt danych z EEPROM jeśli jej nie potrzebujemy 
    	//eeprom_read_block(data, (uchar *)0 + currentAddress, len); 
    
    	// skopiowanie danych z outbuff do data
    	memcpy(data,outbuff,len&buffMask);
    
    	FncData(); // obsługa przychodzących danych
    
    	// poniższe zmienne mają zastosowanie w przypadku bufora większego od 8 bajtów
    	currentAddress += len;
    	bytesRemaining -= len;
    	return len;
    }
    
    
    /* usbFunctionWrite() is called when the host sends a chunk of data to the
    * device. For more information see the documentation in usbdrv/usbdrv.h.
    */
    
    uchar usbFunctionWrite(uchar *data, uchar len)
    {
    	if(bytesRemaining == 0)
    	return 1; /* end of transfer */
    	if(len > bytesRemaining)
    	len = bytesRemaining;
    	// lepiej wyłączyć zapis danych do EEPROM jeśli jej nie potrzebujemy
    	// eeprom_write_block(data, (uchar *)0 + currentAddress, len);
    
    	// skopiowanie danych do inbuff
    	memcpy(inbuff,data,len&buffMask);
    	
    	// poniższe zmienne mają zastosowanie w przypadku bufora większego od 8 bajtów
    	currentAddress += len;
    	bytesRemaining -= len;
    	return bytesRemaining == 0; /* return 1 if this was the last chunk */
    }
    
    
    /* ------------------------------------------------------------------------- */
    
    usbMsgLen_t usbFunctionSetup(uchar data[8])
    {
    	usbRequest_t *rq = (void *)data;
    
    	if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS)
    	{ /* HID class request */
    		if(rq->bRequest == USBRQ_HID_GET_REPORT)
    		{ /* wValue: ReportType (highbyte), ReportID (lowbyte) */
    		/* since we have only one report type, we can ignore the report-ID */
    			bytesRemaining = 3;
    			currentAddress = 0;
    			return USB_NO_MSG; /* use usbFunctionRead() to obtain data */
    		}else if(rq->bRequest == USBRQ_HID_SET_REPORT){
    			/* since we have only one report type, we can ignore the report-ID */
    			bytesRemaining = 3;
    			currentAddress = 0;
    			return USB_NO_MSG; /* use usbFunctionWrite() to receive data from host */
    		}
    	}else{
    	/* ignore vendor type requests, we don't use any */
    	}
    	return 0;
    }
    
    // przykładowa funkcja obsługująca przychodzące i wychodzące dane
    void FncData(void)
    {
    	outbuff[0]=inbuff[2];
    	outbuff[1]=inbuff[0];
    	outbuff[2]=inbuff[1];
    }
    
    
    int main(void)
    {
    	uchar i;
    
    	wdt_enable(WDTO_1S);
    	/* Even if you don't use the watchdog, turn it off here. On newer devices,
    	* the status of the watchdog (on/off, period) is PRESERVED OVER RESET!
    	*/
    	DBG1(0x00, 0, 0); /* debug output: main starts */
    	/* RESET status: all port bits are inputs without pull-up.
    	* That's the way we need D+ and D-. Therefore we don't need any
    	* additional hardware initialization.
    	*/
    	odDebugInit();
    	usbInit();
    	usbDeviceDisconnect(); /* enforce re-enumeration, do this while interrupts are disabled! */
    	i = 0;
    	while(--i)
    	{ /* fake USB disconnect for > 250 ms */
    		wdt_reset();
    		_delay_ms(1);
    	}
    	usbDeviceConnect();
    	sei();
    	DBG1(0x01, 0, 0); /* debug output: main loop starts */
    	for(;;)
    	{ /* main event loop */
    
    		DBG1(0x02, 0, 0); /* debug output: main loop iterates */
    		wdt_reset();
    		usbPoll();
    	}
    	return 0;
    
    }
    
  • #15 7188851
    Wilku
    Poziom 17  
    Właśnie próbuję stworzyć na tych bibliotekach urządzenie, które będzie zachowywało się jak klawiatura z kilkoma przyciskami (to akurat już zrobiłem), dodatkowo muszę mieć możliwość przesyłania do niego danych. Nie za bardzo wiem jak dla tego urządzenia stworzyć ReportDescriptor. Niby w HID Descriptor Tool jest opis każdego pola, ale jak to złożyć w całość? Jeśli chodzi o przykład podany powyżej. Jak w ogóle wysłać do niego dane z Generic HID Tester? Są tam dwa pola w ramce 'Bytes to Send'. Jaka jest ich funkcja dokładnie? Próbowałem tam kombinować, ale coś nie idzie, układ nie odsyła danych.
  • #16 7195711
    m_marko
    Poziom 12  
    Na początku jest to z pewnością zagmatwane. Postaram się to wyjaśnić najprościej jak potrafię.

    Cała konfiguracja połączenia znajduje się w opisie Hid. Urządzenie informuje system jakie dane jest w stanie przyjąć.

    Poniższy przykład Hid Descriptor informuje komputer, że urządzenie służy tylko do wymiany danych z komputerem.

    
    PROGMEM char usbHidReportDescriptor[22] = { /* USB report descriptor */
    	0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
    	0x09, 0x01, // USAGE (Vendor Usage 1)
    	0xa1, 0x01, // COLLECTION (Application)
    	0x15, 0x00, // LOGICAL_MINIMUM (0)
    	0x26, 0xFF, 0x00, // LOGICAL_MAXIMUM (255)
    	0x75, 0x08, // REPORT_SIZE (8)
    	0x95, 0x03, // REPORT_COUNT (3)
    	0x09, 0x00, // USAGE (Undefined)
    	0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
    	0xc0 // END_COLLECTION
    };
    


    Informujemy w jakim zakresie następuje zmiana pojedynczej danej wejściowej (LOGICAL_MINIMUM i LOGICAL_MAXIMUM), następnie rozmiar zmiennej (8 bitów), ilość zmiennych (REPORT_COUNT).

    Tak więc powyższy przykład definiuje 3 bajty, które będą wysyłane do urządzenia oraz 3 bajty, które będą odbierane z urządzenia.

    Ustawiając REPORT_COUNT np. na 4 zwiększamy liczbę bajtów do 4. Jeśli ustawimy LOGICAL_MINIMUM na 0 a LOGICAL_MAXIMUM na 65535, oraz rozmiar na 16 bitów, REPORT_COUNT na 3 to będziemy wymieniać 6 bajtów lub 3 zmienne integer (o rozmiarze 16 bitów) - jak kto woli. Jest to najprostszy sposób dopasowania Hid do swoich potrzeb.

    W programie Generic Hid znajduje się funkcja ExchangeFeatureReports(), która pobiera wartości z pól "Bytes To Send" i wysyła do urządzenia, a następnie odczytuje wartości odebrane z urządzenia.

    W skrócie opiszę wybrane elementy funkcji.

    Sprawdzenie czy jest to raport Feature HID:
    Cytat:

    if ( ( MyHid.Capabilities.FeatureReportByteLength > 0 ) )


    Utworzenie bufora wyjściowego o długości zdefiniowanej w samym raporcie (REPORT_COUNT):
    Cytat:

    outFeatureReportBuffer = new Byte[ MyHid.Capabilities.FeatureReportByteLength ];


    Skopiowanie danych z obszaru Bytes To Send:
    Cytat:

    outFeatureReportBuffer[ 0 ] = 0;
    outFeatureReportBuffer[ 1 ] = Convert.ToByte( cboByte0.SelectedIndex );
    outFeatureReportBuffer[ 2 ] = Convert.ToByte( cboByte1.SelectedIndex );


    Wysłanie danych do urządzenia:
    Cytat:

    success = myOutFeatureReport.Write( outFeatureReportBuffer, hidHandle );


    Analogicznie wygląda w przypadku odebrania danych.

    Tworzona jest tablica na przyjęcie danych z urządzenia:
    Cytat:

    inFeatureReportBuffer = new Byte[ MyHid.Capabilities.FeatureReportByteLength ];


    Następnie odczytanie raportu (czyli danych).
    Cytat:

    myInFeatureReport.Read( hidHandle, readHandle, writeHandle, ref myDeviceDetected, ref inFeatureReportBuffer, ref success );


    Należy pamiętać aby w programie działającym pod PC umieścić kod sprawdzający ilość bajtów jakie można wymieniać jednorazowo z urządzeniem.

    Pozdrawiam.
  • #17 7763466
    Azonic_2006
    Poziom 17  
    Witam!!!
    Trochę odkopywanie tematu ale chciałem się zapytać w jaki sposób odbierać raporty biblioteką V-USB a dokładnie która funkcja za to odpowiada. Jeżeli można to prosił bym o jakiś przykład, najlepiej klawiatury z diodami num lock itd.
    Pozdrawiam!!!
  • #18 7772015
    prokopcio
    Poziom 29  
    witam.
    tak sobie czytam ten interesujący temat i mam pytanie - czy kontroler wykrywany jako urządzenie HID może być traktowany jako inne urządzenie niż klawiatura, mysz etc... ? chodzi mi o to czy można np. wysłać z DELPHI w prosty sposób kilka bajtów i poczekać na odpowiedź - również kilku-bajtową... czy to jednak działa inaczej? czy można na podstawie takiego projektu stworzyć powiedzmy interfejs "ala rs232" który w systemie nie będzie widziany jako COM tylko urządzenie HID nie wymagające sterowników a z aplikacji i tak wyślemy co tam chcemy, tyle, że nie przez wirtualny port COM...
  • #19 7772246
    acid12
    Poziom 21  
    urządzenia HID nie muszą byc myszą, klawiaturą. Przykładem niech będą programatory dla avr pod USB widziane jako urządzenia HID.

    ale jak spod PC sie komunikować z czyms takim to nie wiem, kiedyś sie zastanawiałem i postanowiłem wtedy że prościej jest zrobic (dla mnie amatora) wirtualny port COM ;)
  • #20 7794005
    beznadziejny_elektronik
    Poziom 10  
    Hmm... Ja mam pytanie co do tego pliku "pomiar". NIe jestem zaawansowany w tych sprawach wiec pytam:
    - Gdy załadowałem na uC "Pomiar.hex" to komp wykrył nieznane urządzenie.
    - Gdy chciałem sobie skompilować plik "main.c" to wyskakiwały błedy żę nie moze znalezc pliku czy katalogu i takie tam, potem przenioslem pliki z tego katalogu (co go nie moglo znalezc) do katalogu z plikiem main.c. Do pisałem do kodu cos takiego:

    #include "usbdrv.c"
    #include "oddebug.c"
    #define F_CPU 12000000

    i bledy wyskakuja takie:

    AVR Studio 4.16:

    Build started 6.3.2010 at 23:35:58
    avr-gcc -mmcu=atmega128 -Wl,-Map=main.map main.o -o main.elf
    main.o: In function `usbBuildTxBlock':
    C:\Documents and Settings\PC\Pulpit\Pomiar\default/../usbdrv.c:536: undefined reference to `usbCrc16Append'
    main.o: In function `usbGenericSetInterrupt':
    C:\Documents and Settings\PC\Pulpit\Pomiar\default/../usbdrv.c:240: undefined reference to `usbCrc16Append'
    make: *** [main.elf] Error 1
    Build failed with 2 errors and 0 warnings...

    WinAVR2009:

    > "make.exe" all
    make.exe: *** No rule to make target `all'. Stop.

    > Process Exit Code: 2
    > Time Taken: 00:01

    Dokładniej robie układ bardzo podobny do podanego powyzej przez "Marco" na atmega8 ze stronki:

    http://www.recursion.jp/avrcdc/cdc-io.html

    (przedostatni schemat na tej stronce) - oczywiscie skorzystał bym z plikow podanych przez Osamu Tamure (ktore dalo sie wgrac), ale nie mam pojecia jakim programem i w jaki sposób mam sie porozumiec z komputerem za pomoca USB z uC.

    Z gory dziekuje za pomoc!
  • #21 7818059
    marek_Łódź
    Poziom 36  
    beznadziejny_elektronik napisał:

    undefined reference to `usbCrc16Append'
    undefined reference to `usbCrc16Append'
    Pewnie kwestia ustawienia ścieżek dla kompilatora/linkera. Źródła nie wymagają mieszania w nich (przynajmniej tak było w starszych wersjach), a zgubiony powyżej nagłówek opisany jest w pliku usbdrv.h i prawidłowa lokalizacja tego pliku powinna te błędy usunąć.

    Cytat:
    oczywiscie skorzystał bym z plikow podanych przez Osamu Tamure (ktore dalo sie wgrac), ale nie mam pojecia jakim programem i w jaki sposób mam sie porozumiec z komputerem za pomoca USB z uC.
    Poprawnie zrealizowany-zaprogramowany układ zgłasza się jako wirtualny port szeregowy (oczywiście po zainstalowaniu w PC drivera CDC). Do pierwszych testów wystarcza dowolny terminal na porcie szeregowym np. hyperterminal.
  • #22 7842122
    sottek
    Poziom 10  
    A jak się to wszystko ma do uC z sprzętowym USB np. atmega32U4?
    Czy można też korzystać z tej biblioteki?
    Czy też w tym przypadku istnieje "łatwiejszy" sposób realizacji?
    Pytam bo tak mnie natchnęło na wykorzystanie tych uC, a nie wiem jak to u nich realizować.
  • #23 7842142
    marek_Łódź
    Poziom 36  
    To jest czysto programowe rozwiązanie, więc nijak się ma do procesorów ze sprzętowym USB. Inaczej mówiąc to całkiem różne procedury.
  • #24 8487064
    mirekk36
    Poziom 42  
    Temat może i stary, ale jak to mówią "jary" ;)

    kolega m_marko odwalił KAWAŁ ODBREJ ROBOTY, szok (leci ode mnie słona pczaka punktów dla kolegi)

    jutro będę testował bo teraz po tym przeczytaniu tego wątku w końcu załapałem o co chodzi mniej więcej.

    Jeśli autor jeszcze tu zagląda to małe pytanko mam:

    Czy dobrze zrozumiałem, że nawet jeśli ustawimy sobie większy bufor do wymiany danych pomiędzy PC a uC niż 8 bajtów - to i tak będzie to zawsze wysyłane w paczkach ? - tak wynika chyba też z kodu w pomiar .zip

    ale - jak będzie wyglądał odbiór takich paczek po stronie PC ??? przylecą zawsze po kolei jedna za drugą ??? czy może tak jak w UDP - w dowolnej kolejności?
  • #25 8853727
    jacynka84
    Poziom 26  
    Witam.
    Otóż jestem bardzo początkujący w C, i chciałbym abyście mi napisali jakieś komentarze do kodu tego joya z pierwszego postu co bym wiedział co do czego i co decyduje na przykład czemu to jest joy a nie dysk, i co decyduje w kodzie ile mamy osi i przycisków i ile tego jest.
    I jak by wyglądał kod gdybym chciał wykorzystać do tego jakiś uC z wbudowanym USB, jest ktoś łaskawy co za punkty mi napisze jakieś wyjaśnienia?
    Prawie w ogóle nie rozumiem kodu z pierwszego postu...
  • #26 9036985
    jacynka84
    Poziom 26  
    Witam, zmajstrowałem układ, na ATmega88 i 20Mhz, ale mam również 18Mhz kwarc - gdzie dokładnie zmieniać w plikach z pierwszego postu kwarc z jakim ma pracować M88, bo w Main.c nie ma takiej linijki...
    I jak mam ustawić fusy Clock divide ?
    na razie mi wykrywa "nieznane urządzenie".
  • #27 9037098
    Azonic_2006
    Poziom 17  
    Witam!!!
    Zegar można zmienić w pliku makefile z tym że chyba nie uda ci się go pogonić na 18Mhz. Clock divide wyłączone.
    W załączniku podsyłam plik make gdzie jest schemacik jak ustawić fusy.
    Plik nie jest mojego autorstwa.
    Usuń ".h" z nazwy bo inaczej wysłać się nie chciało.
    Pozdrawiam!!!
    Azonic_2006
  • #28 9040192
    jacynka84
    Poziom 26  
    Dzięki za plik, ale nic to nie dało.
    Użyłem 16Mhz kwarc, D- mam na PortD.0, D+ pod PortD.1 i 2.
    Zmieniałem linię podłączeń usb w plikach USB config, i też nic to nie dało...
    gdzie powinny być podłączone te linie dokładnie?
    Mam ATmega88, nie 8,kompiluję i programuję go pod AVRstudio4.

    Dodano po 1 [godziny] 13 [minuty]:

    A jednak działa!
    Wykrywa prawidłowo, no to teraz będę kombinował.
    Mam takie pytanie, cóż za zmienna i jak jest przekazywane do Win że to ma akurat tyle przycisków a nie mniej czy więcej, jak przechowywane są w zmiennej stany poszczególnych przycisków i osi??
  • #29 9041950
    Azonic_2006
    Poziom 17  
    Witam!!!
    Jak wykrywa to GIT.
    Po pierwsze widzę że robisz po na chybił trafił a tak się nie da.
    Musisz zapoznać się ze specyfikacją USB, a dokładnie z urządzeniami HID.
    Za ilość osi i przycisków odpowiada deskryptor który jest wysyłany podczas podłączenia (enumeracja) i dlatego komputer rozpoznaje np. myszkę od pada.
    Podaj źródła to spróbuję ci pokazać mniej więcej co jest co, ale polecam poczytać jednak o USB.

    Zaraz zrobię paczkę programów, które udało mi się zdobyć w czasie swojej przygody z USB.

    [Edit]
    Dodaję w załączniki programy i dokumentacje USB, a dodatkowo polecam tą stronę.

    Pozdrawiam!!!
    Azonic_2006
    Załączniki:
  • #30 9042337
    jacynka84
    Poziom 26  
    Dzięki.
    W raporcie hid gdzie określamy co to jest i ile ma przycisków czy osi są dwie kolumny, o ile rozumiem po oc jest druga, o tyle za grosz nie rozumiem po co jest pierwsza i co znaczą w niej te numery. np. przy osiach mam 0x09 - po co to ??
    Teraz usiłuję w raporcie dołożyć jedną oś - i nic, a tylko dokładam jedną linijkę i poprawiam ilość bajtów.
REKLAMA