I'm going bananas trying to figure out what method to use to send info between the host and USB device (honestly this USB spec seems like the most overly complicated way to accomplish the simplest task - Rube Goldberg would shake his head).
I want to make linux host software that will "sit there" forever and spend 99.9% of the time waiting for info from the USB device.
After tons of research, it appears to me that the only reasonable method to do this is using INTERRUPT transfers. My reasons are (correct me if I'm wrong):
-Control transfers would require the host program to st in a loop and waste CPU
-Bulk transfers don't work unless Mercury and Jupiter align *and* Virgo is in the seventh house.
So that only leaves INTERRUPT transfers as a method whee the host will "block" while it waits.
The question then is... How do you tell the AVR firmware that you are using INTERRUPT transfers rather than BULK transfers? I understand this is important so that the firmware tells the host USB controller to use INTERRUPT polling rather than BULK.
I had a look through most of the example projects trying to find one that had host software that "sits there" waiting for information. However they all seem to have host software that just does one "read" and that's it. There were data loggers and the like, where I thought they would be good candidates, but no luck.
Thanks
How do you enable INTERRUPT transfers?
Re: How do you enable INTERRUPT transfers?
Hi bt101 -
I was just about to post a question about using Interrupt - out, however since I've got interrupt - in working I thought I'd try and answer your question.
Firstly, interrupt-in transfers don't quite work in this way. Its true that you can have blocking semantics in the API - for example, libusb you can use the synchronous API and say 'wait for something to come through from the device and block until it does', however behind the scenes, the host is not spening 99.9% of its time doing nothing. in actual fact, USB interrupt-in transfers poll. In the USB endpoint descriptor the device can specify how often to poll.
So, if you want to do something fairly basic, its probably simplest to use a control transfer and do your own polling.
However given the above and assuming that you do want to do an interrupt-in transfer, then these are the steps you need to follow:
1. In your AVR firmware:
a. in usbconfig.h set USB_CFG_HAVE_INTRIN_ENDPOINT to 1
b. in usbconfig.h set USB_CFG_SUPPRESS_INTR_CODE to 0 (default)
c. in usbconfig.h set USB_CFG_INTR_POLL_INTERVAL to at least 10
d. When you want to send data to the host, put it in a buffer, and call 'usbSetInterrupt()'. Use usbInterruptIsReady() to check to see if the last transfer has completed.
for example, here is my FlushUSBSendBuffer() method I'm using that copies data from a ring buffer and calls the above two methods:
At the host end (libusb-1.0) you call 'libusb_interrupt_transfer()' to fetch the data in from the end point:
set TIMEOUT = 0 if you want to wait for ever.
I hope that all makes sense.
I was just about to post a question about using Interrupt - out, however since I've got interrupt - in working I thought I'd try and answer your question.
Firstly, interrupt-in transfers don't quite work in this way. Its true that you can have blocking semantics in the API - for example, libusb you can use the synchronous API and say 'wait for something to come through from the device and block until it does', however behind the scenes, the host is not spening 99.9% of its time doing nothing. in actual fact, USB interrupt-in transfers poll. In the USB endpoint descriptor the device can specify how often to poll.
So, if you want to do something fairly basic, its probably simplest to use a control transfer and do your own polling.
However given the above and assuming that you do want to do an interrupt-in transfer, then these are the steps you need to follow:
1. In your AVR firmware:
a. in usbconfig.h set USB_CFG_HAVE_INTRIN_ENDPOINT to 1
b. in usbconfig.h set USB_CFG_SUPPRESS_INTR_CODE to 0 (default)
c. in usbconfig.h set USB_CFG_INTR_POLL_INTERVAL to at least 10
d. When you want to send data to the host, put it in a buffer, and call 'usbSetInterrupt()'. Use usbInterruptIsReady() to check to see if the last transfer has completed.
for example, here is my FlushUSBSendBuffer() method I'm using that copies data from a ring buffer and calls the above two methods:
Code: Select all
// Device -> Host
// Flush up to 8 bytes at a time out to host. You can call this as much as you like
// and it will only process data if ready and there is data waiting.
BOOL
FlushUSBSendBuffer() {
if (usbInterruptIsReady() && (sb_end!=sb_start)) {
int i=0;
while(TRUE) {
sendBufferCurrent[i] = sendBuffer[sb_start];
i++;
sb_start=(sb_start+1) & SB_IDXMASK;
if (sb_start==sb_end) break;
if (i==8) break;
}
usbSetInterrupt(sendBufferCurrent, i);
return TRUE;
}
return FALSE;
}
At the host end (libusb-1.0) you call 'libusb_interrupt_transfer()' to fetch the data in from the end point:
Code: Select all
char *buffer = (char *) malloc(8);
int transferred
result = libusb_interrupt_transfer(device_handle, (1 | LIBUSB_ENDPOINT_IN), buffer, 8, &transferred, TIMEOUT);
set TIMEOUT = 0 if you want to wait for ever.
I hope that all makes sense.
Re: How do you enable INTERRUPT transfers?
Thanks for the info.
I should clarify that when I say that my program will do nothing for 99.9% of the time, I'm literally refering to "my" program. It does almost nothing but wait. So I would imagine that it would be better if it blocked, rather than sitting in a loop burning cycles. So that would rule out Control Transfers.
So it sounds like I'm going down the right road to use Interrupt Transfers.
The only problem (I shoulda brough this up in the original question) is that the V-USB instructions for Interrupt and Bulk transfers are lumped together. This page:
http://vusb.wikidot.com/driver-api
They don't explain how to make sure that your device shows up to the host as an "interrupt" device rather than a "Bulk" device. My understanding is that Bulk devices are a bad idea/don't work.
I'll try the settings that you mentioned, and I assume that they make the device appear as an Interrupt Device:
a. in usbconfig.h set USB_CFG_HAVE_INTRIN_ENDPOINT to 1
b. in usbconfig.h set USB_CFG_SUPPRESS_INTR_CODE to 0 (default)
c. in usbconfig.h set USB_CFG_INTR_POLL_INTERVAL to at least 10
It would be nice if it were published what setting makes the device appear as a "Bulk" device so I can make sure to avoid it.
I should clarify that when I say that my program will do nothing for 99.9% of the time, I'm literally refering to "my" program. It does almost nothing but wait. So I would imagine that it would be better if it blocked, rather than sitting in a loop burning cycles. So that would rule out Control Transfers.
So it sounds like I'm going down the right road to use Interrupt Transfers.
The only problem (I shoulda brough this up in the original question) is that the V-USB instructions for Interrupt and Bulk transfers are lumped together. This page:
http://vusb.wikidot.com/driver-api
They don't explain how to make sure that your device shows up to the host as an "interrupt" device rather than a "Bulk" device. My understanding is that Bulk devices are a bad idea/don't work.
I'll try the settings that you mentioned, and I assume that they make the device appear as an Interrupt Device:
a. in usbconfig.h set USB_CFG_HAVE_INTRIN_ENDPOINT to 1
b. in usbconfig.h set USB_CFG_SUPPRESS_INTR_CODE to 0 (default)
c. in usbconfig.h set USB_CFG_INTR_POLL_INTERVAL to at least 10
It would be nice if it were published what setting makes the device appear as a "Bulk" device so I can make sure to avoid it.
Re: How do you enable INTERRUPT transfers?
Sure, I understand. Interrupt transfers are the closest you will get to zero cycles.
To answer your questions in two parts -
How to make an in endpoint interrupt not bulk
- Note low-speed usb devices aren't meant to have bulk transfers, just interrupt transfers, so I'd expect this to be default behaviour. Is this what you mean by a bad idea?
- Looking at 'usbdrv.c' under the Configuration Descriptor section, both endpoint descriptors that could be defined are hard coded as interrupt endpoints (0x03).
So the answer is that endpoints are interrupt not bulk by default. How to make a bulk endpoint? I think that you need to create a custom configuration descriptor to do this, and define your own endpoints.
Second question, How to be sure what vusb currently using. What OSs do you have to hand? If you can lay your hands on anything running linux, plug your device into it and look at /proc/bus/usb/devices. This gives you a complete breakdown of the devices, interfaces, configurations and endpoints on the bus. You can see the current endpoints, their types, their IDs, direction and poll intervals from this. Its really useful to do this to give confidence that your configuration is correct. I'd be interested to know if you can do the same thing on the max, the system info gives much less information.
I'm just learning this myself, here's some more info that I'm not 100% sure of but I think to be the case, would be good if someone who knows this stuff more than me could confirm / deny my assertions...
- There's not a lot of difference between an bulk and interrupt endpoints - a bulk endpoint is just like an interrupt endpoint but with additional instructions to the host to ignore the poll interval and just poll the device as rapidly as possible.
- An endpoint is uni-directional. If you need to communicate with the same thing on the device in two directions you need two endpoints one for each direction. You can use the same endpoint number for an in and an out endpoint however as there is an additional bit (0x80) that sets the direction. 0x81 and 0x01 are therefore two separate endpoints- one's in and one's out, but the fact they both are endpoint '1' isn't special in any way.
- If you need to do anything in vusb with endpoints beyond a simple control or a couple of interrupt-in endpoints you need to build your own configuration descriptor (this stalled me for ages as it wasn't really clear to me that to add an interrupt-out endpoint I needed to do any more than make some changes in usbconfig.h and implement the appropriate functions.
Hope that all makes some sense!
Cheers,
Daniel
To answer your questions in two parts -
How to make an in endpoint interrupt not bulk
- Note low-speed usb devices aren't meant to have bulk transfers, just interrupt transfers, so I'd expect this to be default behaviour. Is this what you mean by a bad idea?
- Looking at 'usbdrv.c' under the Configuration Descriptor section, both endpoint descriptors that could be defined are hard coded as interrupt endpoints (0x03).
So the answer is that endpoints are interrupt not bulk by default. How to make a bulk endpoint? I think that you need to create a custom configuration descriptor to do this, and define your own endpoints.
Second question, How to be sure what vusb currently using. What OSs do you have to hand? If you can lay your hands on anything running linux, plug your device into it and look at /proc/bus/usb/devices. This gives you a complete breakdown of the devices, interfaces, configurations and endpoints on the bus. You can see the current endpoints, their types, their IDs, direction and poll intervals from this. Its really useful to do this to give confidence that your configuration is correct. I'd be interested to know if you can do the same thing on the max, the system info gives much less information.
I'm just learning this myself, here's some more info that I'm not 100% sure of but I think to be the case, would be good if someone who knows this stuff more than me could confirm / deny my assertions...
- There's not a lot of difference between an bulk and interrupt endpoints - a bulk endpoint is just like an interrupt endpoint but with additional instructions to the host to ignore the poll interval and just poll the device as rapidly as possible.
- An endpoint is uni-directional. If you need to communicate with the same thing on the device in two directions you need two endpoints one for each direction. You can use the same endpoint number for an in and an out endpoint however as there is an additional bit (0x80) that sets the direction. 0x81 and 0x01 are therefore two separate endpoints- one's in and one's out, but the fact they both are endpoint '1' isn't special in any way.
- If you need to do anything in vusb with endpoints beyond a simple control or a couple of interrupt-in endpoints you need to build your own configuration descriptor (this stalled me for ages as it wasn't really clear to me that to add an interrupt-out endpoint I needed to do any more than make some changes in usbconfig.h and implement the appropriate functions.
Hope that all makes some sense!
Cheers,
Daniel
Re: How do you enable INTERRUPT transfers?
Hi,
take a look at my project V-USB-MIDI. I created a custom config and modified the endpoint descriptor to use bulk or int transfers at different poll rates:
viewtopic.php?f=8&t=1352&start=15#p9034
Ciao Martin
take a look at my project V-USB-MIDI. I created a custom config and modified the endpoint descriptor to use bulk or int transfers at different poll rates:
viewtopic.php?f=8&t=1352&start=15#p9034
Ciao Martin
Re: How do you enable INTERRUPT transfers?
Thanks for the help. I finally got it working. I thought I'd share the host code.
I dunno about anybody else but I prefer perl as it is easy to use and develop.
A lot of the examples have tons of C code spread over multiple files and they are extremely difficult to follow.
The below code will read 8 interrupt bytes into the buffer from the device. Note that this code does not do any "checking" to ensure the device is connected. However, it is easy to follow:
I dunno about anybody else but I prefer perl as it is easy to use and develop.
A lot of the examples have tons of C code spread over multiple files and they are extremely difficult to follow.
The below code will read 8 interrupt bytes into the buffer from the device. Note that this code does not do any "checking" to ensure the device is connected. However, it is easy to follow:
Code: Select all
use Device::USB;
$vendor = 0x16c0;
$product = 0x05df;
my $usb = Device::USB->new();
my $dev = $usb->find_device( $vendor, $product );
my $endpoint = 1;
my $buf;
$dev->interrupt_read($endpoint,$buf,8,0);