Page 1 of 4

Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 2:07 pm
by alexblack
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:

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.

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 2:18 pm
by Giovanni
Hi,

Thanks for finding but before proceeding please try the USB driver in the repository trunk, it is much different now and more efficient and probably does not require that workaround. Don't be afraid about the experimental code because the version in the repository is usually very stable.

Resetting the queues in order to signal the disconnection is the right approach, the disconnection detection can be done in several other ways:
1) SOF callback detection (there is no SOF when the device is disconnected).
2) 5V signal detection if there is a dedicated pin, the EXT driver could be used here.

Edit: usbSetupTransfer() is used to setup the driver state machine to start a transaction over EP0, you used it correctly, usbSetupTransfer(usbp, NULL, 0, NULL) prepares a zero bytes transfer.

Giovanni

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 2:29 pm
by alexblack
Thank for fast answer. I will try trunk now.

Giovanni wrote:Resetting the queues in order to signal the disconnection is the right approach, the disconnection detection can be done in several other ways:
1) SOF callback detection (there is no SOF when the device is disconnected).
2) 5V signal detection if there is a dedicated pin, the EXT driver could be used here.
Giovanni


2) Not applicable to me, because 5V always present from internal DC-DC parallel to USB 5V pin.

1) Three times SOF interrupt (from STM32 manual) is SUSP interrupt which have callback in USB Driver - USB_EVENT_SUSPEND - I used it.

And what about 'break' character - I need it my serial protocol?

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 4:08 pm
by alexblack
I try to compile my project with ChibiOS version from 'trunk' (I used 2.4.2 before) but there are errors, because in new version there is no such functions/macros 'chIOGetAndClearFlags' and BaseChannel type too, used in Serial driver. BaseChannel i replace to BaseSequentialStream but can not quickly find a replacement for chIOGetAndClearFlags, please help me!

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 5:04 pm
by Giovanni
Hi,

Now flags handling are integrated in the Kernel Events subsystem, you have to use chEvtGetAndClearFlags() on the EventListener object, it works pretty much in the same way. We did this because that "GetAndClearFlags" function was duplicated in various places doing about the same thing.

Code: Select all

flagsmask_t chEvtGetAndClearFlags(EventListener *elp)


And what about 'break' character - I need it my serial protocol?


It makes no sense for a virtual serial port over USB, it is a physical line state (continuous zeros).

Giovanni

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 5:50 pm
by alexblack
Maybe it should be discussed in a separate topic or it already exist?
But there are many changes in new version and I want to know how to quickly migrate from one version to another, and what exactly has changed ..
For example, now UART LLD has 2 versions and files changed its location. Which version to choose? chIQInit has new parameters etc.

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 5:56 pm
by alexblack
Giovanni wrote:
And what about 'break' character - I need it my serial protocol?

It makes no sense for a virtual serial port over USB, it is a physical line state (continuous zeros).


But some protocols need it, for example 'LIN' and my protocol has it too. I have devices with USART and new with USB, I do not want to change protocol, because i will need rewrote many programs...
For example FTDI FT232 chip emulate all lines, I think Virtual Com driver must do it too.

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 6:26 pm
by alexblack
Giovanni wrote:Now flags handling are integrated in the Kernel Events subsystem, you have to use chEvtGetAndClearFlags() on the EventListener object, it works pretty much in the same way. We did this because that "GetAndClearFlags" function was duplicated in various places doing about the same thing.
Giovanni

Now it turns out that the flags will be sent asynchronously with respect to the data stream, as the events are asynchronous? Then there are problems with protocols such as LIN, as BREAK signal is used to mark the start of the parcel. I'll have to check it somehow ...

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 6:38 pm
by alexblack
Finally I compiled my project. USB driver works. Since USB cable detection not part of driver I still need some code to do this (i used that fact that if no packets from host so USB cable unplugged but it will conflict with sleep mode of host, but for me it is ok). Instead of customization of chIQreset and chOQreset I can use it directly - all fine.

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:
    chSysLockFromIsr();

    /* Enables the endpoints specified into the configuration.
       Note, this callback is invoked from an ISR so I-Class functions
       must be used.*/
    usbInitEndpointI(usbp, USB_CDC_DATA_REQUEST_EP, &ep1config);
    usbInitEndpointI(usbp, USB_CDC_INTERRUPT_REQUEST_EP, &ep2config);

    /* Resetting the state of the CDC subsystem.*/
    sduConfigureHookI(usbp);

    chSysUnlockFromIsr();
    return;
  case USB_EVENT_SUSPEND:
    if (usbp->state == USB_ACTIVE)
    {
      // USB cable unplugged
      chSysLockFromIsr();
      _usb_reset(usbp);
      // Reset queues and unlock waiting threads
      chIQResetI(&SDU1.iqueue);
      chOQResetI(&SDU1.oqueue);
      chSysUnlockFromIsr();
    }
    return;
  case USB_EVENT_WAKEUP:
    return;
  case USB_EVENT_STALLED:
    return;
  }
  return;
}



Thank you for support...

Re: Serial over USB driver on STM32, cable unplug detecting

Posted: Thu Nov 22, 2012 7:56 pm
by alexblack
I just compiled all project with new version of ChibiOS and it locked in Porthalt with panic message "SV#10".
The problem was with virtual timer callback.

Code: Select all

static void vtSockets_callback(void *para)
{
  // The associated function is invoked by an interrupt handler within the I-Locked state.

  chVTSetI(&vtSockets, MS2ST(1000), vtSockets_callback, NULL);
 
  for (int i=MAX_SOCKET_COUNT-1; i>0; i--)
  {
    int tmr = SOCKETS[i].timeout;
    if (tmr > 0) SOCKETS[i].timeout = --tmr;
  }
}


I inserted pair of chSysLockFromIsr() chSysUnlockFromIsr() here:

Code: Select all

static void vtSockets_callback(void *para)
{
  // The associated function is invoked by an interrupt handler within the I-Locked state.
  chSysLockFromIsr();
  chVTSetI(&vtSockets, MS2ST(1000), vtSockets_callback, NULL);
 
  for (int i=MAX_SOCKET_COUNT-1; i>0; i--)
  {
    int tmr = SOCKETS[i].timeout;
    if (tmr > 0) SOCKETS[i].timeout = --tmr;
  }
  chSysUnlockFromIsr();
}


Why with ChibiOS 2.4.2 it works without locks and now I must lock? And generally how can I understand from which zone callbacks called?