10 posts / 0 new
Author
Message

I'm having a problem with processing USB management tasks quickly enough alongside my main program. I've set up a virtual serial terminal on my ATmega using LUFA. I started by adapting the VirtualSerial example, which includes this main loop:

int main(void)
{
SetupHardware();

GlobalInterruptEnable();

for (;;)
{
}
}

The above code is not exactly what I now have, since I also have a bunch of DHCP processing stuff in there, but it basically resembles that structure. I can post the code if it's really necessary (it's not proprietary, just messy!).

According to the function's documentation, the USB_USBTask() function has to be called at least every 30ms (taken from /LUFA/Drivers/USB/Core/USBTask.h):

		/** This is the main USB management task. The USB driver requires this task to be executed
*  continuously when the USB system is active (device attached in host mode, or attached to a host
*  in device mode) in order to manage USB communications. This task may be executed inside an RTOS,
*  fast timer ISR or the main user application loop.
*
*  The USB task must be serviced within 30ms while in device mode, or within 1ms while in host mode.
*  The task may be serviced at all times, or (for minimum CPU consumption):
*
*    - In device mode, it may be disabled at start-up, enabled on the firing of the \ref EVENT_USB_Device_Connect()
*      event and disabled again on the firing of the \ref EVENT_USB_Device_Disconnect() event.
*
*    - In host mode, it may be disabled at start-up, enabled on the firing of the \ref EVENT_USB_Host_DeviceAttached()
*      event and disabled again on the firing of the \ref EVENT_USB_Host_DeviceEnumerationComplete() or
*      \ref EVENT_USB_Host_DeviceEnumerationFailed() events.
*
*  If in device mode (only), the control endpoint can instead be managed via interrupts entirely by the library
*  by defining the INTERRUPT_CONTROL_ENDPOINT token and passing it to the compiler via the -D switch.
*
*
*  \ingroup Group_USBManagement
*/
void USB_USBTask(void);

The problem I'm having is that if I send too many USB messages to my host (a simple serial terminal on my PC) in my main loop, or take too many measurements with my sensors, the program no longer responds to USB commands and I get errors. My syslog reads the following:

Sep 29 12:03:24 sean-desktop kenel: [  884.931194] usb 3-11: new full-speed USB device number 12 using xhci_hcd
Sep 29 12:03:24 sean-desktop kernel: [  885.051173] usb 3-11: device descriptor read/64, error -71
Sep 29 12:03:24 sean-desktop kernel: [  885.283145] usb 3-11: device descriptor read/64, error -71
Sep 29 12:03:24 sean-desktop kernel: [  885.511171] usb 3-11: new full-speed USB device number 13 using xhci_hcd
Sep 29 12:03:24 sean-desktop kernel: [  885.631175] usb 3-11: device descriptor read/64, error -71
Sep 29 12:03:25 sean-desktop kernel: [  885.859167] usb 3-11: device descriptor read/64, error -71
Sep 29 12:03:25 sean-desktop kernel: [  886.087170] usb 3-11: new full-speed USB device number 14 using xhci_hcd
Sep 29 12:03:25 sean-desktop kernel: [  886.087275] usb 3-11: Device not responding to setup address.
Sep 29 12:03:25 sean-desktop kernel: [  886.295296] usb 3-11: Device not responding to setup address.
Sep 29 12:03:25 sean-desktop kernel: [  886.503182] usb 3-11: device not accepting address 14, error -71
Sep 29 12:03:25 sean-desktop kernel: [  886.623182] usb 3-11: new full-speed USB device number 15 using xhci_hcd
Sep 29 12:03:25 sean-desktop kernel: [  886.623287] usb 3-11: Device not responding to setup address.
Sep 29 12:03:26 sean-desktop kernel: [  886.831360] usb 3-11: Device not responding to setup address.
Sep 29 12:03:26 sean-desktop kernel: [  887.043191] usb 3-11: device not accepting address 15, error -71
Sep 29 12:03:26 sean-desktop kernel: [  887.043213] usb usb3-port11: unable to enumerate USB device

in contrast to the following syslog entries when I don't send so many USB messages:

Sep 29 12:10:29 sean-desktop kernel: [ 1310.781837] usb 3-11: new full-speed USB device number 16 using xhci_hcd
Sep 29 12:10:30 sean-desktop kernel: [ 1311.332620] usb 3-11: New USB device found, idVendor=03eb, idProduct=2044
Sep 29 12:10:30 sean-desktop kernel: [ 1311.332621] usb 3-11: New USB device strings: Mfr=1, Product=2, SerialNumber=220
Sep 29 12:10:30 sean-desktop kernel: [ 1311.332622] usb 3-11: Product: Sensor
Sep 29 12:10:30 sean-desktop kernel: [ 1311.332623] usb 3-11: Manufacturer: Sean
Sep 29 12:10:30 sean-desktop kernel: [ 1311.332624] usb 3-11: SerialNumber: 7863002383135190E060
Sep 29 12:10:30 sean-desktop kernel: [ 1311.333197] cdc_acm 3-11:1.0: ttyACM0: USB ACM device

I figure that my code is taking too long to call USB_USBTask(), more than the required 30ms, and so my host thinks the device is not responding and kills the connection.

My question is: what do I have to do to move the USB management task out of my main loop? I read in the documentation that it's possible to define an interrupt routine to process USB tasks, which sounds like it would be better than having the task called manually as part of my main loop. I've not got much experience with interrupt driven programming, so some pointers would be helpful! Cheers.

Last Edited: Fri. Sep 29, 2017 - 11:11 AM

So what have you done to verify that it IS less than 30ms between calls to the task()? I'd suggest running a timer and logging each call to the task function then later determine if the step from one to the next is ever more than 30ms. If it is then clearly you are doing too much work elsewhere. If so the usual "solution" is to break long jobs into smaller steps and use a state machine to step through. So if you currently have:

void some_function(void) {
// very long stuff
}

you make it:

typedef enum {
IDLE,
NEXT_BIT,
ANOTHER_BIT
}work_t;

void some_function(void) {
static work_t state = IDLE;
switch (state) {
case IDLE:
// do first bit
state = NEXT_BIT;
break;
case NEXT_BIT:
// do next bit
state = ANOTHER_BIT;
break;
case ANOTHER_BIT:
// do final bit
state = IDLE;
break;
}
}

Now that function splits the work into 3 parts and you call it 3 times to do the complete job. The key thing is that each bit is shorter

So what have you done to verify that it IS less than 30ms between calls to the task()?

It's just a hunch based on experiments I've made by adding more or less USB communication in my loop. Right now, I've got a bunch of print statements (that are piped over the virtual serial port to my host), and adding more than ~4 in a single iteration results in the misbehaviour I reported above. I could time it, but I figure that processing USB tasks via interrupts is in any case a better approach.

I'd suggest running a timer and logging each call to the task function then later determine if the step from one to the next is ever more than 30ms.

Thanks for the code suggestion, but, frankly, I can't believe that the only way to get this to work is to split my main code into batches! In the Arduino framework, for example, USB serial messages are correctly communicated to the host regardless of how long the main loop takes to run - what is it doing that I'm not? I figure it must be using interrupts in the background to keep the host synced, but I don't know where to start with those.

seands wrote:

...In the Arduino framework, for example, USB serial messages are correctly communicated to the host regardless of how long the main loop takes to run - what is it doing that I'm not? I figure it must be using interrupts in the background to keep the host synced, but I don't know where to start with those.

An Uno uses a separate chip for USB - serial communication (FTDI, AtmegaXU2...), so the atmega328P chip doesn't handle the USB protocol at all.  A Leonardo for example uses an atmega32U4 chip which has build-in USB support, so the USB handling happens in a module, not the main processor.

seands wrote:
what is it doing that I'm not?
As ccrause says the Arudino's have a separate chip doing the USB so the main chip can block execution as long as it likes.

But in embedded it is never a good idea for anything to be allowed to block execution for long periods. Especially so for ISR()s. If your code really does follow the pattern of:

	for (;;)
{
}

then presumably everything else you are doing is occurring in ISR code? Not a great idea.

clawson wrote:

seands wrote:

what is it doing that I'm not?

As ccrause says the Arudino's have a separate chip doing the USB so the main chip can block execution as long as it likes.

I'm actually using an ATmega32U4, so I will look into using the native USB controller instead of LUFA.

clawson wrote:
But in embedded it is never a good idea for anything to be allowed to block execution for long periods. Especially so for ISR()s. If your code really does follow the pattern of:

	for (;;)
{
}

then presumably everything else you are doing is occurring in ISR code? Not a great idea.

My code looks like this:

        uint8_t i = 0;

for (;;)
{
i++;

if (i >= UINT_MAX) {
i = 0;
}

if (i % 10000 == 0) {
print_sensor_data_to_usb();
print_dhcp_data_to_usb();
print_sensor_data_to_usb();
}

}

I found this minimal USB code for the ATmega32U4-based Teensy 2.0 for debugging purposes (which is what I'm using it for). It seems to use the built-in USB controller, which, like ccrause said might mean I avoid the USB tasks needing processed in the main microcontroller loop.

In any form of USb stack the USb will likely need "regular service". You might be able to do this from a timer ISR though which would simply steal cycles from your other tasks as necessary. (never really looked into why Dean's USB_USBTask() cannot be called from an interrupt context but I guess he has reasons?)

                    print_sensor_data_to_usb();
print_dhcp_data_to_usb();
print_sensor_data_to_usb();

?

Not knowing about the internals of those but speculating they are blocking too long, could you test this simple change which will serve the USB tasks more frequently:

                if (i % 10000 == 0) {
}
else if (i % 10000 == 1) {
print_sensor_data_to_usb();
}
else if (i % 10000 == 2) {
print_dhcp_data_to_usb();
}
else if (i % 10000 == 3) {
print_sensor_data_to_usb();
}

This will allow the USB tasks to "sneak in" between each print_xxx_data_to_usb() call.

That is just for a quick test. If it behaves better there is probably a more robust and elegant way to do this "multi-tasking"/scheduling.

Also,

1) your variable i is uint8_t (i.e. max value is 255) but you test on modulus 10000, and...

2) ...you wrap at UINT_MAX (not sure of it's value in avr-gcc but I would expect it to be far larger than 255)

3) Even if the variable was an uint16_t (max value 65535) the modulus on 10000 will give you "jitter" in the period of servicing the sensor readings and printings to USB. I.e. at 0, 10000, 20000, ... 60000, 0, 10000 which means subsequent periods of 10000, 10000, 10000, 10000, 10000, 10000, 5536, 10000, ...

Why not simply have an uint16_t that wraps at 10000 and service/call the functions when it is == 0?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Fri. Sep 29, 2017 - 02:40 PM

I see. Since I prefer having an interrupt do the processing regularly, I think I'll use the Teensy debug code I linked above. I already got it to work. For reference, it defines two interrupts:

// USB Device Interrupt - handle all device-level events
// the transmit buffer flushing is triggered by the start of frame
//
ISR(USB_GEN_vect);

// USB Endpoint Interrupt - endpoint 0 is handled here.  The
// other endpoints are manipulated by the user-callable
// functions, and the start-of-frame interrupt.
//
ISR(USB_COM_vect);

In my main code I don't need to do anything USB related to maintain the connection as it is handled by these interrupts. Perfect!

Cheers for the help.