Electronic – Okay for USB to send data without specific OUT command first

picprotocolusb

I have been going back and forth between writing software for my device and the PC application trying to get the protocol right.

I have a pic18f4550 talking over interrupt transfers. It needs to regularly send the status of up to 128 switches, and also maintain the status of pin outputs based on commands from the PC (on/off, pulse, blink, etc).

Much of the examples I have encountered place IN-transfers inside of a block that makes sure an OUT-transfer always happens first, like this libUSBDotNet example. If there is no OUT data ready then no IN request will be made either. The template firmware was initially set up to handle something similar – It would always wait for there to be data in the OUT buffer, handle that, then set up the IN buffer and send.

When I got started I thought it was overrated, because my device was always going to send the switch status if it had the information and always ready to the commands should there be any. I adjusted my application to send OUT transfers and IN requests independently, and the firmware to handle each independently. But as my code gets more complex, I wonder if I was missing something.

If I ensured the OUT-IN back-to-back structure, I could reserve one or two initial OUT bytes to include a command to send the switch update. It would also allow me to easily stop requesting the data. Of course I could leave it as is and have certain commands reserved to set flags that would enable/disable certain behavior on the data collection/transfer side. I still don't know what might be necessary at the end of this project, so I guess I am looking for advise for best practice, and pros/cons of approaches. Or maybe I'm way overthinking this.

Thanks.

EDIT: Also, ping-pong buffer mode is turned off on my chip, so there are dedicated buffers for the IN EP and OUT EP, if I am not mistaken.

C# code (i've ported this into pyUSB as well). Compare to the libUSBDotNet sample I linked to. Run every 10ms with a timer

private static void RunUSB(Object source, ElapsedEventArgs e)
    {
        ErrorCode ec = ErrorCode.None;

        if (true) // for now always do this.  In future only if we have commands to send.
        {
            int bytesWritten;
            ec = writer.Write (commands, 0, 3, 2000, out bytesWritten);
            if (ec != ErrorCode.None)
                throw new Exception (UsbDevice.LastErrorString);
        }

        int bytesRead;
        byte[] readBuffer = new byte[64];
        ec = reader.Read(readBuffer, 2000, out bytesRead);
        if (bytesRead != 0) 
        {
            //handle input data
        }
    }  

Firmware code (using mStack)

// initialize endpoint 1 IN buffer
unsigned char *INPacket = usb_get_in_buffer(1);
memset(INPacket, 0, EP_1_IN_LEN);

    while (1) {
        if (usb_is_configured()) 
        {
            // get data from PC
            if (usb_out_endpoint_has_data(1))
            {
                unsigned char OUTLen;
                const unsigned char *OUTPacket;
                /* Data received from host */
                OUTLen = usb_get_out_buffer(1, &OUTPacket);

                // do stuff with OUT data
                // rearm for out data
                usb_arm_out_endpoint(1);
            }

            // send data to PC            
            if (!usb_in_endpoint_halted(1))
            {
                /* Wait for EP 1 IN to become free then send. This of
                 * course only works using interrupts. */
                if(!usb_in_endpoint_busy(1))
                {
                    // fill INPacket with data
                    usb_send_in_buffer(1, byteCount);
                } else {
                    // send something
                    INPacket[0] = 0;
                    usb_send_in_buffer(1, 1);
                }
            }
        }
    }

Note that the if (!usb_in_endpoint_halted(1)) was originally inside of the if (usb_out_endpoint_has_data(1)) block

Best Answer

You don't get to decide OUT or IN, the host does. This question therefore makes no sense.

If you have something to send to the host, you have to wait for a IN token. The hardware SIE takes care of this automatically though. You set up the buffers with the appropriate descriptors, and wait for the hardware to transfer the data.

On this part, you can even set up two buffers such that the hardware transfers into or out of the first, then automatically swtiches to the second when the first one gets full/empty. This is called "ping pong buffer mode" in the datasheet.

Before doing a USB project, you really should read the USB spec first. Without that, you'll be forever poking around in the dark wondering why certain things were done certain ways and whether they are really necessary. The USB SIE chapter in the datasheet gives reasonable background, but it's no substitute for the USB spec.