All data on USB bus is transferred under host control. Even though some transfers are called IN and some other OUT, they all start at the host. There are four types of transfers, the most interesting of which is control transfer. This type of transfer is used all the time in communication with every class of devices. In this article, I give a short description on programming control transfers.
Control transfer consists of three steps( or stages ): setup, data, and status. Some of them carry necessary data in setup packet itself and don’t have data stage at all. Good example of such short request is “Set address” – the information to transfer is just one byte and is sent by filling a certain setup packet field.
Even though USB specification defines several different destinations for the transfer, such as device, interface and endpoint, on the lowest level each packet address consists of two parts – device and endpoint. Some endpoints are IN or OUT only. Other, such as “default control pipe” AKA endpoint 0, are bi-directional. Before each transfer can be sent out, peripheral address must be loaded into MAX3421E PERADDR register.
The first step of every control transfers is called setup stage, during which host sends special packet called setup packet to the peripheral. Control transfer low-level bus signaling is complex, that’s why there is special FIFO register for setup packet, called SUDFIFO. Eight bytes of setup packet are loaded into this register. After that, endpoint number and setup token are loaded into HXFR register. Loading of this register starts the transfer. The setup packet is sent out and host checks for transfer results. If peripheral is busy, it returns NAK. If peripheral is very busy or dead, it won’t answer at all. There are other errors, but they usually mean that the firmware tries to send a wrong packet and needs to be fixed.
What happens on MAX3421E side after setup packet was sent out? As soon as it receives an answer from the peripheral, it sets HXFRDNIRQ (“Transfer done”) bit in HIRQ register. The result is loaded into HRSL register. Then it’s up to firmware to decide what to do next. Take a look at XferDispatchPkt function in transfer.c.
This function dispatches a packet of any type – setup, IN, or OUT. After sending a transfer request and getting a result, it checks if result is a NAK. NAK means the device is alive but not ready to answer. Receiving NAK during transfer is quite typical and is usually not a big deal, unless we get too many. There is no restriction on number of NAKs in USB specification. Device can send them for hours. Some devices are (incorrectly) programmed to return NAK instead of STALL, which is a condition when request can not be satisfied. For this reason, function counts the number of NAKs and returns if certain limit (in my code default is set to 200) is reached.
Another tracked situation is a timeout. After sending a packet, function waits certain time for HXFRDNIRQ to assert, then tries to resend the packet. Since it’s totally possible that peripheral device will never answer, function aborts after several (default 3) attempts are made.
Any other result code is returned as function return code. Result zero means success, non-zero means something is wrong.
Like I said before, some control transfers have data stage, and some don’t. In case data stage is present (Get_Descriptor request, for example), the next request after setup is ordinary bulk-IN request. There are also rare cases when bulk-OUT request is necessary.
The XferInTransfer function does just that. Before calling it in control transfer we need to tweak data toggle mechanism; since first packet sent to the endpoint was not a bulk one, the toggle value for this endpoint will still be zero. If we try to send the packet with this toggle value, the result code would be toggle error.
At present, the toggle value is set in XferCtrlReq function. Take a look at the beginning of big switch statement under cases for getting various descriptors. At the time of writing, USB data structures for connected devices have not been fully designed; the right way to do toggles is to store them in endpoint-related structure, load before transmission, and update after transmission is completed.
Let’s get back to XferInTransfer. The function sends IN request first, and then keeps reading receive FIFO checking several end transmission conditions on the way. It is crucial to know maximum packet size for the device beforehand; one of the indicators that device sent all the data is a packet which is less in size than the maximum packet size. This criteria, however important, is not the only one. Function also checks if the expected number of bytes has been received. In the ideal world, number of bytes requested in the setup packet will be equal to the number of bytes received, for example, during enumeration the very first packet is Get_descriptor request with number of bytes set to 8 and maximum packet size also set to 8; we shall expect to receive 8 bytes, however, I often see devices that return the whole descriptor( 18 bytes ) instead.
Last stage is status – a packet which finishes the control transfer. It is also an indicator for the device to stop transmitting during data stage. MAX3421E supports two special tokens for status packets – HS-IN and HS-OUT. HS are empty bulk transfers in opposite direction. HS-IN is used to complete CONTROL-WRITE requests, such as Set_Address. HS-OUT is used for CONTROL-READ requests like Get_Descriptor.
Status stage is very simple. All you need to do is load tokINHS or tokOUTHS and analyse the return code of XferDispatchPkt.
Control transfers are the most complex of all USB transfer types. Take a look at XferCtrlReq function to get better idea of data flow, different stages and status tokens.
Oleg.
Related posts:
- Lightweight USB host. Part 6 – introduction to HID.
- Lightweight USB Host. Part 3 – accessing MAX3421E.
- Lightweight USB Host. Part 1 – Motivation.
- Lightweight USB Host. Part 2 – Hardware.
- Lightweight USB Host. Part 4 – The code.
- Arduino USB host – Pre-prototyping.
- Arduino USB Host – Peripherals.
- Towards an FT232 Driver for the USB Host Shield- Part 0
- PS3 and Wiimote Game Controllers on the Arduino Host Shield: Part 3
- Arduino USB Host – USB Descriptors.


Hi,
Great post, but my question is about what wordpress plugin are you using for related posts (seens to work very well)? Thanks!
I’m using YARPP
Hi,
Where can I find information about the return codes of XferDispatchPkt? I am trying to use the USB host shield for a different model of Mad Catz PS3 controller than the one your example was developed for and I am getting an rcode=4 value from rcode = dispatchPkt( tokINHS, ep, nak_limit ); from inside USB::ctrlStatus( byte ep, boolean direction, unsigned int nak_limit ).
Thanks,
David
David,
Return codes are MAX3421E USB transfer result codes. They are described in Maxim Application Note 3785, “Maxim 3421E Programming Guide”. Maxim used to have this appnote posted on their site; for some reason, it is not available at the moment. However, copies are available on the net, googling for “maxim an3785″ brings several useful results, here is one -> http://www.hdl.co.jp/ftpdata/utl-001/AN3785.pdf . Page 10 contains short list of result codes, page 36 has all codes. Rcode 4 means NAK, which for HID devices may mean many things, form “slow device” to “new information is not available”, depending on device configuration. See this page for information about communicating to HID devices -> http://www.circuitsathome.com/communicating-arduino-with-hid-devices-part-1
Oleg.
Hi, I’m using your tutorials to learnd about USB communications in Arduino.
I have to send this string to a weather station (recognized as HID): 20 00 08 01 00 00 00 00
How I have to proceed?
Thanks you very much!!
What is this string – a packet or a report? It looks like a request for the report number one; in this case, take a look how this is done in Arduino code here -> http://www.circuitsathome.com/communicating-arduino-with-hid-devices-part-1
I had read somewhere that this string was necessary to start
sending data from the meteorological station. I have seen, however, that the data is sent without sending this string. So for now I do not study this aspect.
I get this kind of data, I know I have to “interpret”them. As
you seem?
For example I get:
[...]
1 FFFFFFFF 0 30 63 1 0 0
7 0 42 30 FFFFFF98 0 0 0
5 30 0 30 6A 1 0 0
1 FFFFFFFF 0 30 6A 1 0 0
7 0 42 30 FFFFFF98 0 0 0
5 30 0 30 6A 1 0 0
1 FFFFFFFF 0 30 6A 1 0 0
7 0 42 30 FFFFFF98 0 0 0
5 30 0 30 6A 1 0 0
1 FFFFFFFF 0 30 6A 1 0 0
7 0 42 31 FFFFFF93 0 0 0
5 30 0 30 66 1 0 0
1 FFFFFFFF 0 30 66 1 0 0
7 0 42 30 FFFFFF98 0 0 0
5 30 0 30 6A 1 0 0
1 FFFFFFFF 0 30 6A 1 0 0
7 0 60 7D FFFFFFFF 0 0 0
5 0 0 0 FFFFFFDC 1 0 0
1 FFFFFFFF 0 0 FFFFFFDC 1 0 0
7 0 42 30 FFFFFF98 0 0 0
5 30 0 30 6A 1 0 0
[...]
As I read on the net, these values may be correct, the only values
which I think are wrong: FFFFFFFF and FFFFFFXX. What do you think?
I connected the Arduino to the USB of the PC and then apply it with a 9V battery
additional current from the jack.
The output looks like HID reports. Take a look at this page -> http://www.circuitsathome.com/communicating-arduino-with-hid-devices-part-1 You may also want to run descriptor_parser from here -> https://github.com/felis/USB_Host_Shield/tree/master/examples to see what your device’s report descriptor looks like.
Here description_parser output:
—————————————————–
Start
Device addressed… Requesting device descriptor.
Device descriptor:
Descriptor Length: 12
USB version: 1.10
Class: 00 Use class information in the Interface Descriptor
Subclass: 00
Protocol: 00
Max.packet size: 08
Vendor ID: 0FDE
Product ID: CA01
Revision ID: 0302
Mfg.string index: 00
Prod.string index: 01 Length: 4 Contents:
Serial number index: 00
Number of conf.: 01
Configuration number 0
Total configuration length: 34 bytes
Configuration descriptor:
Total length: 0022
Number of interfaces: 01
Configuration value: 01
Configuration string: 00
Attributes: 80
Max.power: 32 100ma
Interface descriptor:
Interface number: 00
Alternate setting: 00
Endpoints: 01
Class: 03 HID (Human Interface Device)
Subclass: 00
Protocol: 00
Interface string: 00
HID descriptor:
Descriptor length: 09 9 bytes
HID version: 1.10
Country Code: 0 Not Supported
Class Descriptors: 1
Class Descriptor Type: 22 Report
Class Descriptor Length:34 bytes
HID report descriptor:
Length: 2 Type: Global Tag: Usage Page Undefined Data: 00 Data: FF
Length: 1 Type: Local Tag: Usage Data: 01
Length: 1 Type: Main Tag: Collection Application (mouse, keyboard) Data: 01
Length: 1 Type: Local Tag: Usage Data: 01
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 00
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 08
Length: 1 Type: Main Tag: Input Data,Array,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 00
Length: 1 Type: Local Tag: Usage Data: 02
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 00
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 08
Length: 1 Type: Main Tag: Output Data,Variable,Absolute,No Wrap,Linear,Preferred State,No Null Position,Non-volatile(Ignore for Input), Data: 02
Length: 0 Type: Main Tag: End Collection
Endpoint descriptor:
Endpoint address: 01 Direction: IN
Attributes: 03 Transfer type: Interrupt
Max.packet size: 0008
Polling interval: 0A 10 ms
—————————————————–
Here there is the decimal output of your “MAX3421E USB Host controller keyboard communication” example:
1 -1 -36 0 0 -36 1 0
2 0 66 0 0 -36 1 0
1 48 66 0 0 -36 1 0
2 -108 0 0 0 -36 1 0
2 0 0 0 0 -36 1 0
2 48 0 0 0 -36 1 0
2 48 102 0 0 -36 1 0
1 1 102 0 0 -36 1 0
1 -1 102 0 0 -36 1 0
2 0 66 0 0 -36 1 0
1 48 66 0 0 -36 1 0
2 -108 0 0 0 -36 1 0
2 0 0 0 0 -36 1 0
2 48 0 0 0 -36 1 0
Can I use this keyboard example to get correctly data of my weather station?
It looks like normal HID, not a “boot” variety. Boot code may work but I’d rather use like in example linked from my previous comment in this thread.