Serial over USB driver on STM32, cable unplug detecting
Posted: Thu Nov 22, 2012 2:07 pm
Hello.
What I have: IAR compiler, ChibiOS 2.4.2, STM32F103 MCU, USB CDC driver - Virtual Com Port, not USB bus powered, MCU powered from external source.
Simple test application:
When I plug USB cable, start PUTTY, all works fine.
Then I unplug USB cable, test thread locks, because USB driver not detecting cable unplug and SDU1.config->usbp->state is USB_ACTIVE forever.
And thread remains locked even after re-connection USB cable. I began to understand the problem and made ​​the following:
But after reconnecting cable the OS halted in PortHalt. I began to understand more and found this in "serial_usb.c":
When I called chIQReset and chOQreset it cleared q_wrptr and q_rdptr and this was the reason of PortHalt.
I did a modified version of reset routines:
and used it instead standard on USB queues. All works fine, plug/unplug detected.
Whether I did right?
Also, I needed to be able to transmit a signal 'break' on Virtual Com Port. I read a little bit of information on USB and did following in file "serial_usb.c":
Just do not understand the meaning of this function: usbSetupTransfer(usbp, NULL, 0, NULL);
Whether I did right? And, if so, make the appropriate changes to the driver to allow others to use.
What I have: IAR compiler, ChibiOS 2.4.2, STM32F103 MCU, USB CDC driver - Virtual Com Port, not USB bus powered, MCU powered from external source.
Simple test application:
Code: Select all
static SerialUSBDriver SDU1;
...
// Activates the USB driver (USB bus pull-up on D+ always hardware connected)
sduObjectInit(&SDU1);
sduStart(&SDU1, &serusbcfg);
for (;;)
{
if (SDU1.config->usbp->state == USB_ACTIVE)
{
chprintf(&SDU1, "USB TEST\n\r");
}
chThdSleepMilliseconds(MS2ST(1000));
}
When I plug USB cable, start PUTTY, all works fine.
Then I unplug USB cable, test thread locks, because USB driver not detecting cable unplug and SDU1.config->usbp->state is USB_ACTIVE forever.
And thread remains locked even after re-connection USB cable. I began to understand the problem and made ​​the following:
Code: Select all
/*
* Handles the USB driver global events.
*/
static void usb_event(USBDriver *usbp, usbevent_t event) {
switch (event) {
case USB_EVENT_RESET:
return;
case USB_EVENT_ADDRESS:
return;
case USB_EVENT_CONFIGURED:
/* Enables the endpoints specified into the configuration.
Note, this callback is invoked from an ISR so I-Class functions
must be used.*/
chSysLockFromIsr();
usbInitEndpointI(usbp, USB_CDC_DATA_REQUEST_EP, &ep1config);
usbInitEndpointI(usbp, USB_CDC_INTERRUPT_REQUEST_EP, &ep2config);
usbInitEndpointI(usbp, USB_CDC_DATA_AVAILABLE_EP, &ep3config);
chSysUnlockFromIsr();
return;
case USB_EVENT_SUSPEND:
if (usbp->state == USB_ACTIVE)
{
// Cable unplugged
chSysLockFromIsr();
_usb_reset(usbp);
// Reset input/output queues to unlock threads
chIQResetI(&SDU1.iqueue);
chOQResetI(&SDU1.oqueue);
chSysUnlockFromIsr();
}
return;
case USB_EVENT_WAKEUP:
return;
case USB_EVENT_STALLED:
return;
}
return;
}
But after reconnecting cable the OS halted in PortHalt. I began to understand more and found this in "serial_usb.c":
Code: Select all
void sduObjectInit(SerialUSBDriver *sdup) {
sdup->vmt = &vmt;
chEvtInit(&sdup->event);
sdup->flags = IO_NO_ERROR;
sdup->state = SDU_STOP;
chIQInit(&sdup->iqueue, sdup->ib, SERIAL_USB_BUFFERS_SIZE, inotify);
chOQInit(&sdup->oqueue, sdup->ob, SERIAL_USB_BUFFERS_SIZE, onotify);
/* This is a dirty trick but those pointers are never used because queues
are accessed in block mode from the low level.*/
sdup->iqueue.q_wrptr = (uint8_t *)sdup;
sdup->oqueue.q_rdptr = (uint8_t *)sdup;
}
When I called chIQReset and chOQreset it cleared q_wrptr and q_rdptr and this was the reason of PortHalt.
I did a modified version of reset routines:
Code: Select all
void USBOQResetI(OutputQueue *oqp) {
chDbgCheckClassI();
//oqp->q_rdptr = oqp->q_wrptr = oqp->q_buffer;
//oqp->q_counter = chQSizeI(oqp);
while (notempty(&oqp->q_waiting))
chSchReadyI(fifo_remove(&oqp->q_waiting))->p_u.rdymsg = Q_RESET;
}
void USBIQResetI(InputQueue *iqp) {
chDbgCheckClassI();
//iqp->q_rdptr = iqp->q_wrptr = iqp->q_buffer;
//iqp->q_counter = 0;
while (notempty(&iqp->q_waiting))
chSchReadyI(fifo_remove(&iqp->q_waiting))->p_u.rdymsg = Q_RESET;
}
and used it instead standard on USB queues. All works fine, plug/unplug detected.
Whether I did right?
Also, I needed to be able to transmit a signal 'break' on Virtual Com Port. I read a little bit of information on USB and did following in file "serial_usb.c":
Code: Select all
bool_t sduRequestsHook(USBDriver *usbp) {
SerialUSBDriver *sdup = usbp->param;
if ((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) {
switch (usbp->setup[1]) {
case CDC_GET_LINE_CODING:
usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
return TRUE;
case CDC_SET_LINE_CODING:
usbSetupTransfer(usbp, (uint8_t *)&linecoding, sizeof(linecoding), NULL);
return TRUE;
case CDC_SET_CONTROL_LINE_STATE:
/* Nothing to do, there are no control lines.*/
usbSetupTransfer(usbp, NULL, 0, NULL);
return TRUE;
/* My addition to support break character !!!! */
case CDC_SEND_BREAK :
chSysLockFromIsr();
chIOAddFlagsI(sdup, SD_BREAK_DETECTED);
chSysUnlockFromIsr();
usbSetupTransfer(usbp, NULL, 0, NULL);
return TRUE;
default:
return FALSE;
}
}
return FALSE;
}
Just do not understand the meaning of this function: usbSetupTransfer(usbp, NULL, 0, NULL);
Whether I did right? And, if so, make the appropriate changes to the driver to allow others to use.