Our custom board is using a Kinetis MKL26Z128VFM4 (KL26 sub-family) which connects to a host PC over USB (the Kinetis appears as a "COMx" serial port on the PC). Our development environment is CodeWarrior using ProcessorExpert with USB code by Erich Styger (http://sourceforge.net/projects/mcuoneclipse/files/PEx%20Components/). Our board includes a switching voltage regulator that regulates 20V power to a heater, and with disturbing regularity, sometimes when we turn on/off power to that regulator, the USB communication goes into an errored state from which we have not figured out how to recover without resetting the Kinetis. I need a way to automatically clear the error state in software and/or re-initialize USB0 so that we can resume communications. Ideally this would not result in dropping the "COMx" port connection in the PC.
In file CDC1.c, there are two flags: start_app and start_transactions. While USB communications is operating normally, both are TRUE. Sometimes when we turn on/off the heater power, both of these flags go FALSE, after which USB communications is inoperative.
I've traced the code trying to figure out how we get into this state, and here's what I see:
In function USB_ISR() (in Generated_Code\usb_dci_kinetis.c), we discover a USB error in "if(ERROR_FLAG(intr_stat))". This is checking bit 1 of the USB0_ISTAT register. According to the USB0_ERRSTAT register, the error seems to be either 0x01 (PIDERR) or 0x08 (DFN8). Other errors may also appear; I have only been able to test this 10 times or so because it takes a good bit of time to set things up & cause the error. Function USB_ISR() assigns the USB0_ERRSTAT value (0x01 or 0x08) to event.errors, and then invokes a chain of calls, many through function pointers, which passes that information up higher & higher, but nobody seems to ever care what bits were set in USB0_ERRSTAT. The chain of calls includes...
USB_ISR()
USB_Device_Call_Service()
g_usb_CB[USB_SERVICE_ERROR]() --> USB_Error_Service()
g_class_callback() --> USB_Class_CDC_Event()
g_cdc_class_callback() --> CDC1_App_Callback()
At the end of CDC1_App_Callback(), it sets those flags (start_app and start_transactions) to FALSE and then returns. As mentioned, I don't think any code anywhere actually checks the USB0_ERRSTAT bits to handle any error different from any other error, or to differentiate between different types of errors.
In our application, I tried calling the following to see if I could reset the USB communications (which I stole from code in PE_low_level_init() in Cpu.c)...
while (TRUE)
{
// Run CDC app task periodically and check for USB connection with host
byte err = CDC1_App_Task(out, sizeof(out));
if (err == ERR_OK)
g_USB_isEnumerated = TRUE;
else if (err == ERR_BUSOFF)
{
if (g_USB_isEnumerated)
{ // USB *has* been enumerated but has encountered errors; attempt to recover
USB0_Init(); // same as in PE_low_level_init()
Tx1_Init(); // same as in PE_low_level_init()
Rx1_Init(); // same as in PE_low_level_init()
//(void)USB1_Init(); // same as in PE_low_level_init()
++numberOfRecoveryAttempts;
}
g_USB_isEnumerated = FALSE;
.
.
.
...unfortunately when I do that, execution gets stuck in USB0_Init() somewhere near where it sets USB0_USBTRC0. The USB0 interrupt keeps firing & from then on, that's all we do: service USB0 interrupts.
How can I automatically recover from USB0 error conditions without resetting the Kinetis?
Thank you,
-Mark Minich
======== UPDATE: ========
In Generated_Code\CDC1.c, lines I commented out are prefixed with "//CCCC", and lines I added are suffixed by "//NNNN".....
int USB_ignoredErrorCount; // NNNN
void CDC1_App_Callback(byte controller_ID, byte event_type, void *val)
{
.
.
.
} else if (event_type == USB_APP_ERROR) { /* detach? */
//CCCC start_app = FALSE;
//CCCC start_transactions = FALSE;
++USB_ignoredErrorCount; // NNNN
}
}
...and now everything works swimmingly. I've turned heater power on/off over 100 times, and the code has ignored 6 errors, and comm with the PC is still going fine. I have not verified if we dropped bytes or had corrupted bytes on comm link, but at least communications did not terminate due to the errors.
Shouldn't the Freescale or ProcessorExpert code be updated to simply track error stats, but not terminate comms?
======== ANOTHER UPDATE: ========
While that worked great for the SerialPort class in .NET Framework 4 on Windows 7, it does not work at all on .NET Framework 4 on Windows XP. On WinXP, now after several times turning the heater on & off, the Kinetis board thinks it's fine, but the SerialPort class in .NET Framework 4 on Windows XP is all screwed up, complaining with a System::Exception^ about either "A device attached to the system is not functioning" or "The device is not connected".
Hello Mark,
One way is to disable the USB pullup, wait, and re_init the USB module, which can emulator a USB unplug and re-plug.
The USB error bits are mostly used as statistic data as indications to the quality of the USB communications. In most cases, it not all, if USB error happens, the USB engine together with the stack is able to recover the error properly through reset or re-sending data by the host. However, sometimes, the USB communications will hang due to poor host or device USB stack. In you case, it seems the issue is also caused by EMI.
Hi Derek,
Thanks for the reply. I'm unfamiliar with the details of USB pullups, and barely familiar with USB in general. There are several registers in the KL26 Sub-Family Ref Manual that talk about enabling/disabling pullups/pulldowns. Are you familiar with the Kinetis KL26 sub-familily enough to tell me what registers I'd need to write to to do what you're talking about? Or can you point me to some web page that discusses it? BTW, I'm using USB in CDC mode so it appears to the PC as a "COM" port. I don't know if that affects how pullups are used.
Thanks,
-Mark
Hello Mark,
For a high or full-speed USB device plugged in a PC, it will be powered and it should enable the USB D+ pullup to let the PC know its presence. When the USB pullup is disabled, it has the same effect of unplug the device. The PC will treat both situations as unplug.
For USB stack 4.1.1, the USB_DCI_Init (called by _usb_device_init) routine will enable the pullup as below:
USB0_CONTROL = USB_CONTROL_DPPULLUPNONOTG_MASK; // for Non-OTG device
But the USB_DCI_DeInit routine does not disable the pullup.
One issue of this implementation will cause the USB device to be re-enumerated, which may cause problem for the communication between the host and the device.
Hi Mark,
thanks for your detailed analysis, and posting the updates with that possible solution!
I took over that code from the FSL USB stack and wrapped it into that Processor Expert component. I had seen as well that the stack (better: the application layer in the examples) are only very minimial. I'm travelling this week, but I happily can incorporate your changes. I will need some time to do some analysis on my end. Maybe to have an option 'ignore errors' which would correspond to your solution?
Erich
Erich,
This problem has gotten stranger & stranger. It turns out it has nothing to do with which version of Windows, but it may have something to do with the USB chipset on the PC. Then again, maybe not; it's hard to tell because the problem also seems very sensitive to the exact orientation of the USB cable and other hardware, and minor things like putting a USB hub in the middle seem to "fix" the problem sometimes and also exacerbate the problem at other times. In some configurations I get only one USB error per hundred or more heater on/off cycles, other times I get a USB error every 3rd on/off cycle or so.
At this point I think that ignoring the errors is not the right way to go... at least not by itself. I do think that the USB driver code should not disable itself upon error detection, but somehow the fact that errors occurred (and perhaps the type of errors and also the error count) needs to be visible to the application. But I think what I really need is to be able to fully re-initialize the USB port after I've detected some number of errors so that it appears to the PC as if it's been unplugged & plugged back in, in order to trigger the PC to re-recognize the Kinetis board. I'm not totally familiar with your code or Freescale's low-level driver to know if that's possible with the current API?
Thank you,
-Mark
Hi Mark,
yes, initialization of the full USB block should be possible. There might be still some things missing compared to power on reset, but I don't know.
As for your init sequence you have above:
USB0_Init(); // same as in PE_low_level_init()
Tx1_Init(); // same as in PE_low_level_init()
Rx1_Init(); // same as in PE_low_level_init()
//(void)USB1_Init(); // same as in PE_low_level_init()
At that time the USB interrupts are still enabled, right?
So I would add
USB1_Deinit();
at the beginning so interrupts get disabled.
I realize that I should better add TX/RX init() and deinit() into the higher level init() and deinit() too, just to make it clean.
I hope this helps.
Erich
Hi Erich,
There is no USB1_Deinit() that I can find. Did you mean USB1_usb_int_dis()? If I go into the PEx FSL_USB_Stack Component Inspector window, Methods tab, the only method I can enable/disable is the Init method, but in USB1.c there are three methods: USB1_Init(), USB1_usb_int_dis(), and USB1_usb_int_en().
-Mark
Hi Mark,
are you using the March 22nd 2015 release of the components available on SourceForge (McuOnEclipse - Browse /PEx Components at SourceForge.net )?
Erich