General joystick HID question

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
Ruckus

General joystick HID question

Post by Ruckus » Mon Nov 05, 2007 2:42 am

I've been playing around with a joystick program that someone on the web developed for the ATMEGA series. The HID Report Discriptor was broken, and I was able to fix it and get windows to recognise it. I guess I'm learning :)

Anyway my question is what if the interupt in endpoint needs to send more than 8 bytes of data. This program only sent 8 bytes so it wasn't a problem. However, trying to learn, I created a HID Report that had several axis's and alot of buttons, but I don't understand how to send
more than 8 bytes of data.

I read the notes in the usbdrv.h and usbdrv.c, but it was a bit vague on what is needed. Anyone have any examples or a discription of what is needed :)

Grendel
Rank 4
Rank 4
Posts: 167
Joined: Sat Dec 16, 2006 9:53 pm
Location: Oregon, USA
Contact:

Post by Grendel » Mon Nov 05, 2007 10:16 am

W/o going into detail (too late here..) -- you'll have to define multiple reports in the descriptor. Each report can carry 7 bytes data max (8th byte is the report ID). Details should be in the USB specs.

Rukus

Post by Rukus » Mon Nov 05, 2007 7:52 pm

Grendel wrote:W/o going into detail (too late here..) -- you'll have to define multiple reports in the descriptor. Each report can carry 7 bytes data max (8th byte is the report ID). Details should be in the USB specs.


Thanks, I spent some time looking at the specification. I see how that a report can have a ID. I'll try messing with it when I get home.

Rukus

Post by Rukus » Tue Nov 06, 2007 2:52 am

Rukus wrote:
Grendel wrote:W/o going into detail (too late here..) -- you'll have to define multiple reports in the descriptor. Each report can carry 7 bytes data max (8th byte is the report ID). Details should be in the USB specs.


Thanks, I spent some time looking at the specification. I see how that a report can have a ID. I'll try messing with it when I get home.


Ok, I've got the joystick report split up using report_id. I'm able to address each report Id by placeing the report Id into the first variable of
the array replyBuf and then filling the other 6 parts of the array with my joystick axis data. I then call usbSetInterrupt(replyBuf, 7);

I want to call usbSetInterrupt again to send my button data by changing the reportID and then filling the array with my button data, but I don't want to step on the data from the previous call.

Is there any way to know that the previous interupt data has been sent so I can send the second part? I though of using usbTxLen1 variable that is used by the driver to see if anything was left in the buffer, but its only used by usbdrv.c and the actual driver and not available globaly

Seems that after I learn one thing, I end up with more questions :lol:

I'm moving along and learning alot. Still a long way to go.

Rukus

Post by Rukus » Tue Nov 06, 2007 3:04 am

Found it: usbInterruptIsReady() :D

Rukus

Post by Rukus » Tue Nov 06, 2007 5:31 am

Ok....I've been playing with the HID Reports and messing with the A/D converters and now i've got a 6 Axis, 24 button joystick working now. :D

I have 2 seperate reports, one for the joystick axis's (they're only 8bit at the moment) and 1 report for the 24 buttons.

Thanks for the help.

ozel
Posts: 8
Joined: Tue Nov 06, 2007 6:17 am
Location: Munich, Bavaria

Post by ozel » Tue Nov 06, 2007 6:46 am

I think I ran into a similar problem... But I don't really know how to implement the suggestions above.
This is my current state:
I modified the descriptors of the (infrared) HID mouse examples to act as a absolute mouse (it shall become a touchscreen controller). Now I want to send the X and Y coordinates in a higher resolution (16bit) than 8 bit, because logical_maximum exceeds 255. Using two input reports with each 8bit report_size per Usage(Y) and Usage(X) didn't work. The host HID driver (Mac OS X) still just uses one, 8bit input report for each axis.
And I don't really know how I should handle a higher and a lower 8bit value together with my reportBuffer. Which would be the lower, which the higher one?

Ruckus, do I understand you right, that you used three report_ids to get 3 times 8 bits for 24 buttons send to the host?
I really hope you also plan to increase the joystick axis resolution... :-)

Regards,
Oli

Guest

Post by Guest » Tue Nov 06, 2007 9:31 am

ozel wrote:I think I ran into a similar problem... But I don't really know how to implement the suggestions above.
This is my current state:
I modified the descriptors of the (infrared) HID mouse examples to act as a absolute mouse (it shall become a touchscreen controller). Now I want to send the X and Y coordinates in a higher resolution (16bit) than 8 bit, because logical_maximum exceeds 255. Using two input reports with each 8bit report_size per Usage(Y) and Usage(X) didn't work. The host HID driver (Mac OS X) still just uses one, 8bit input report for each axis.
And I don't really know how I should handle a higher and a lower 8bit value together with my reportBuffer. Which would be the lower, which the higher one?

Ruckus, do I understand you right, that you used three report_ids to get 3 times 8 bits for 24 buttons send to the host?
I really hope you also plan to increase the joystick axis resolution... :-)

Regards,
Oli


I only used 2 reports....1 for the axis information and 1 for the buttons. This was using 2 seperate report ID's. This is a bit different than what you are talking about.

I'm not at home at the moment, but I think you want to have your Report_Size (16) and Report_Count (2). This should give you 2 - 16 bit words. Your Logical_Minimum (0) and Logical Maximum (32767) should provide you with what you want if I understand this correctly. You wouldn't need to have 2 seperate reportID's.

p.s
The resolution is only 8 bit at the moment....I didn't need to complicate things anymore than that as I already have enough questions to be answered :lol:

Rukus

Post by Rukus » Tue Nov 06, 2007 9:33 am

I keep forgetting to enter my user name :)

ozel
Posts: 8
Joined: Tue Nov 06, 2007 6:17 am
Location: Munich, Bavaria

Post by ozel » Tue Nov 06, 2007 3:36 pm

Hm, I tried using resport_size (16) and report_count (2), but then I don't know how to save my 16 bit values in the reportBuffer. Because each item is just 8bit (uchar). And when there is the handover in usbFunctionSetup() like "usbMsgPtr = reportBuffer", the Buffer needs to be only 8bit wide, because usbMsgPtr is only 8bit wide.
Can I fill reportBuffer with an upper and lower 8bit value? How are the rules for this in AVR-USB?

I understand, that Avr-USB can only send 8bit at a time, because of the USB low speed spec, but how would one send two 8bits after one another to the host and get it there interpreted as one 16bit value?

I hope this doesn't sound too stupid.
I'm okay with reading the HID spec, but I think my problem relates more to the understanding of AVR-USB internals, right?

Although my code is really basic I put together the relevant pices and commented my knowledge gaps:

Code: Select all

static uchar  reportBuffer[5];    // buffer for HID reports, this is increased  from 3 to 5, because of 2 more 8bit values, right way?
                                  // the descriptor below only introduces 3 reports:  one 8 bit for the button, one 16bit for the X coordinate and another 16bit for the Y coordinate
static uchar    idleRate;           /* in 4 ms units */

// absolute mouse hid descriptor, length is 62
char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] PROGMEM = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x75, 0x05,                    //     REPORT_SIZE (5)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0x00, 0x04,              //     LOGICAL_MAXIMUM (1024)
//  0x75, 0x08,                    //     REPORT_SIZE (8)
    0x75, 0x10,                    //     REPORT_SIZE (16)
//  0x95, 0x02,                    //     REPORT_COUNT (2)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Rel) absolute

    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0x00, 0x03,              //     LOGICAL_MAXIMUM (768)
//  0x75, 0x08,                    //     REPORT_SIZE (8)
    0x75, 0x10,                    //     REPORT_SIZE (16)
//  0x95, 0x02,                    //     REPORT_COUNT (2)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Rel) absolute
   
//  0x81, 0x06,                    //     INPUT (Data,Var,Rel) relative
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};


static char buildReport(void)
{
   // retval 1 means, that something changed and we must send report
   char retval = 0;
   
   // button pressed? -> one absolute coordinate pair should be send
   if ( (MPIN & _BV(RC)) == ACTIVE(_BV(RC)) ) {      
     // the test coordinate x=y=500  (somewhere in the lower middle of the screen) should be approached here, 500 dec is 0x01F4 hex
     // set X
     reportBuffer[1] = 0xf4;  //how do I handover my 16 bit value in 8bit chunks here?
     reportBuffer[2] = 0x01; // it doesn't work like this, only one of the two 8 bit values gets interpreted at the host, its not seen as one single 16bit value

     // set Y
     reportBuffer[3] = 0xf4;  // here's the same problem
     reportBuffer[4] = 0x01;
       retval = 1;
    }
   return retval;
}

uchar   usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    usbMsgPtr = reportBuffer; //because of this, reportBuffer needs to be 8bit wide and can't be changed to 16bit wide, right?

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* we only have one report type, so don't look at wValue */
            buildReport();
            return sizeof(reportBuffer);
        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){
            usbMsgPtr = &idleRate;
            return 1;
        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){
            idleRate = rq->wValue.bytes[1];
        }
    }else{
        /* no vendor specific requests implemented */
    }
   return 0;
}


I'm really stuck here...
Many thanks in advance, for any help.

Ciao,
Oli
p.s. this touchscreen controller project will be published and documented as open source of course. I know this is required, but how many do it actually? ;)

Rukus
Rank 1
Rank 1
Posts: 24
Joined: Tue Nov 06, 2007 10:18 am

Post by Rukus » Tue Nov 06, 2007 3:54 pm

Posted: Tue Nov 06, 2007 2:36 pm Post subject:

--------------------------------------------------------------------------------

Hm, I tried using resport_size (16) and report_count (2), but then I don't know how to save my 16 bit values in the reportBuffer. Because each item is just 8bit (uchar). And when there is the handover in usbFunctionSetup() like "usbMsgPtr = reportBuffer", the Buffer needs to be only 8bit wide, because usbMsgPtr is only 8bit wide.
Can I fill reportBuffer with an upper and lower 8bit value? How are the rules for this in AVR-USB?

I understand, that Avr-USB can only send 8bit at a time, because of the USB low speed spec, but how would one send two 8bits after one another to the host and get it there interpreted as one 16bit value?


The size of 16 and count of 2 is right, you just have to split the 16 bit values into a High and low byte and put them into the message pointer. Over the years I've messed with so many processors, that I don't recall if the avr is high/low or low/high order (little endian or bigendian)

edit note: this would be 16 and 2 if you declared them at the same time with 1 input statement (I didn't see your report section before I answered).....If you have 2 separate input statements then it would be 16 and 1 for each (as in your case as shown). Declaring size and count twice for each input is redundent and will refer to the most recent declaration. At least this is what I've noticed in my testing.

The limitiation isn't that Avr-USB cant send 8bits at a time, it is that it can only send 8 bytes per packet. You could put 4 - 16 bit words into the buffer then signal the interupt. That is if you are using the default report_ID. If you break the HID report up into multiple report_ID's, then you can only send 7 bytes, because the first byte has to indicate what report_ID this message is for.

I'll have a further look at this when I get home.
Last edited by Rukus on Tue Nov 06, 2007 5:37 pm, edited 1 time in total.

christian
Objective Development
Objective Development
Posts: 1443
Joined: Thu Nov 09, 2006 11:46 am

Post by christian » Tue Nov 06, 2007 5:28 pm

Just a quick note on report sizes: AVR-USB can handle longer reports than 8 bytes with a trick. If you want to send (say) 12 bytes, use usbSetInterrupt() for the first 8 bytes, then wait for usbInterruptIsReady() and then send the remaining 4 bytes with usbSetInterrupt().

This is not really intuitive nor a good API, but it works, as far as I remember.

Guest

Post by Guest » Tue Nov 06, 2007 5:35 pm

Rukus wrote:
Posted: Tue Nov 06, 2007 2:36 pm Post subject:

--------------------------------------------------------------------------------

Hm, I tried using resport_size (16) and report_count (2), but then I don't know how to save my 16 bit values in the reportBuffer. Because each item is just 8bit (uchar). And when there is the handover in usbFunctionSetup() like "usbMsgPtr = reportBuffer", the Buffer needs to be only 8bit wide, because usbMsgPtr is only 8bit wide.
Can I fill reportBuffer with an upper and lower 8bit value? How are the rules for this in AVR-USB?

I understand, that Avr-USB can only send 8bit at a time, because of the USB low speed spec, but how would one send two 8bits after one another to the host and get it there interpreted as one 16bit value?


The size of 16 and count of 2 is right, you just have to split the 16 bit values into a High and low byte and put them into the message pointer. Over the years I've messed with so many processors, that I don't recall if the avr is high/low or low/high order (little endian or bigendian)

edit note: this would be 16 and 2 if you declared them at the same time with 1 input statement (I didn't see your report section before I answered).....If you have 2 separate input statements then it would be 16 and 1 for each (as in your case as shown). Declaring size and count twice for each input is redundent and will refer to the most recent declaration. At least this is what I've noticed in my testing.

The limitiation isn't that Avr-USB cant send 8bits at a time, it is that it can only send 8 bytes per packet. You could put 4 - 16 bit words into the buffer then signal the interupt. That is if you are using the default report_ID. If you break the HID report up into multiple report_ID's, then you can only send 7 bytes, because the first byte has to indicate what report_ID this message is for.

I'll have a further look at this when I get home.

Rukus
Rank 1
Rank 1
Posts: 24
Joined: Tue Nov 06, 2007 10:18 am

Post by Rukus » Tue Nov 06, 2007 6:15 pm

Ok, I got this to work without any problems. This may or not be proper form, however it is working.

Code: Select all

static char   hidDescrReport[] PROGMEM = {
    // Report descriptor
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x30,                    //   USAGE (X)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0x00, 0x04,              //   LOGICAL_MAXIMUM (1024)
    0x75, 0x10,                    //   REPORT_SIZE (16)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x09, 0x31,                    // USAGE (Y)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0x00, 0x03,              //   LOGICAL_MAXIMUM (768)
    0x75, 0x10,                    //   REPORT_SIZE (16)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0xc0                           // END_COLLECTION
};


I also found that the avr stores 16bit numbers in the low byte/high byte format, so you'll have to swap them. I just tested this with the statement below.

Code: Select all


 uchar AxisBuf[8];
// low byte / high byte format for X and Y
 AxisBuf[0]=0x00;  // byte 0 and byte 1 0x400 = 1024
 AxisBuf[1]=0x04;
 AxisBuf[2]=0x00;
 AxisBuf[3]=0x03;  // byte 2 and byte 3 0x300 = 768

 if(usbInterruptIsReady())
   usbSetInterrupt(AxisBuf, 4);


I didn't add the buttons and other stuff you had, but you get the idea.
Again, I'm just learning this stuff myself so what I've explained is only from what I've learned while testing. Hope this helps.

Rukus
Rank 1
Rank 1
Posts: 24
Joined: Tue Nov 06, 2007 10:18 am

Post by Rukus » Tue Nov 06, 2007 6:17 pm

christian wrote:Just a quick note on report sizes: AVR-USB can handle longer reports than 8 bytes with a trick. If you want to send (say) 12 bytes, use usbSetInterrupt() for the first 8 bytes, then wait for usbInterruptIsReady() and then send the remaining 4 bytes with usbSetInterrupt().

This is not really intuitive nor a good API, but it works, as far as I remember.


Thanks christian. It does work as I was sending HID Reports that were larger than 8 bytes lastnight.

Post Reply