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.
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 -> https://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 -> https://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 -> https://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.
We are using MAX3421E on our embedded platform with Xilinx FPGA.
The microblaze softcore on FPGA interfaces with MAX3421E through SPI. MAX3421E acts as a host and interacts with a 3rd party peripheral device with BULK transfer.
The operations that we are performing are:
a. Enumerate the device (works correctly – EP 2 BULK OUT, EP6 BULK IN)
b. Set configuration to 1 (Works correctly)
c. BULK OUT 8 bytes) (We see that in the usb analyser max3421e is sending OUT with zero bytes)
I appreciate if some one can help us.
Hi,
I am using MAX3421E to act as Host for HID Mouse.
Currently, I am able to
a. enumerate the device & configure to INTERRUPT with 1ms polling interval
b. able to Get HID descriptor as below:
code const hid_report_descriptor HIDREPORTDESC =
{
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x16, 0x01, 0x80, // LOGICAL_MINIMUM (-32768)
0x26, 0xff, 0x7f, // LOGICAL_MAXIMUM (32767)
0x75, 0x10, // REPORT_SIZE (16)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xC0, // End Collection (Physical)
0xC0 // End Collection (Application)
}
Need sample code on how to read the mouse X and mouse Y position from MAX3421E USB host.
I appreciate if some one can help.
There are plenty of examples on the site.
The part that i confuse is, after get the hid_report_descriptor HIDREPORTDESC, from the HID Report Descriptor
i can know the size and location of Mouse X & Y report bytes.
And, now, how to configure SPI master to read to HID report from MAX3421E.
Thanks & appreciate your help.
It is just an IN transfer from device’s interrupt endpoint. You will be getting a report or NAK if nothing have changed since the last poll.
Here is my code to Read IN transfer
Do I need to set any bmRequest on SETUP to read the HID Report?
Here is my code used:
– this work for GetHID Descriptor
– this work for Get Vendor Test
Thanks & appreciate your help.
CTL_Read(byte *pSUD)
{
BYTE resultcode;
WORD bytes_to_read;
bytes_to_read = pSUD[6] + 256*pSUD[7];
// SETUP packet
Hwritebytes(rSUDFIFO,8,pSUD); // Load the Setup data FIFO
resultcode=Send_Packet(tokSETUP,0); // SETUP packet to EP0
if (resultcode) return (resultcode); // should be 0, indicating ACK. Else return error code.
// One or more IN packets (may be a multi-packet transfer)
Hwreg(rHCTL,bmRCVTOG1); // FIRST Data packet in a CTL transfer uses DATA1 toggle.
// last_transfer_size = IN_Transfer(0,bytes_to_read); // In transfer to EP-0 (IN_Transfer function handles multiple packets)
resultcode = IN_Transfer(0,bytes_to_read);
if(resultcode) return (resultcode);
IN_nak_count=nak_count; //this from Send_Packet
// The OUT status stage
resultcode=Send_Packet(tokOUTHS,0);
if (resultcode) return (resultcode); // should be 0, indicating ACK. Else return error code.
return(0); // success!
}
Send_Packet(byte token, byte endpoint)
{
BYTE resultcode=0,retry_count;
retry_count = 0;
nak_count = 0;
//
while(1) // If the response is NAK or timeout, keep sending until either NAK_LIMIT or RETRY_LIMIT is reached.
{ // Returns the HRSL code.
Hwreg(rHXFR,(token|endpoint)); // launch the transfer
while(!(Hrreg(rHIRQ) & bmHXFRDNIRQ)); // wait for the completion IRQ
Hwreg(rHIRQ,bmHXFRDNIRQ); // clear the IRQ
resultcode = (Hrreg(rHRSL) & 0x0F); // get the result
if (resultcode==hrNAK)
{
nak_count++;
if(nak_count==NAK_LIMIT) break;
else continue;
}
if (resultcode==hrTIMEOUT)
{
retry_count++;
if (retry_count==RETRY_LIMIT) break; // hit the max allowed retries. Exit and return result code
else continue;
}
else break; // all other cases, just return the success or error code
}
return(resultcode);
}
IN_Transfer(byte endpoint, WORD INbytes)
{
BYTE resultcode,j;
BYTE pktsize;
unsigned int xfrlen,xfrsize;
xfrsize = INbytes;
xfrlen = 0;
while(1) // use a ‘return’ to exit this loop.
{
resultcode=Send_Packet(tokIN,endpoint); // IN packet to EP-‘endpoint’. Function takes care of NAKS.
if (resultcode) return (resultcode); // should be 0, indicating ACK. Else return error code.
pktsize=Hrreg(rRCVBC); // number of received bytes
for(j=0; j<pktsize; j++) // add this packet's data to XfrData array
XfrData[j+xfrlen] = Hrreg(rRCVFIFO);
Hwreg(rHIRQ,bmRCVDAVIRQ); // Clear the IRQ & free the buffer
xfrlen += pktsize; // add this packet's byte count to total transfer length
//
// The transfer is complete under two conditions:
// 1. The device sent a short packet (L.T. maxPacketSize)
// 2. 'INbytes' have been transferred.
//
if ((pktsize = xfrsize)) // have we transferred ‘length’ bytes?
// return xfrlen;
{
last_transfer_size = xfrlen;
return(resultcode);
}
}
}
You need IN transfer, not a control transfer.
The HID reporting data should be already in MAX3421E (when i send to HOST mode) am I right?
Just need to get in from Revice FIFO (R6) right?
Do you have any link, for the IN Transfer so that i can refer.
Thanks & appreciate your help.
The answer is right in front of you in the code you pasted. You can’t just copy the code from one place to another, you have to actually read and understand it.
1. PERADDR for reading HID report
When reading the HID Descriptor i setting the PERADDR to addr=7
Should i change the IN Transfer HID report to other PERADDR to addr= (maybe 25) or just use back the
2. Setting the Config for reading HID report
Set_address_to_25[8] = {0x00, 0x05, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00}
Set_config_HID_Report[8] = {0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
now able to get the HID report but only once time. I t polling the same reporting. any idea?
Now, the MAX3421E code work & integrate it in to the machine.
Thanks.