Question

J'utilise un microcontrôleur STM32F105 avec la bibliothèque USB STM32_USB-FS-Device_Lib_V3.2.1 et ont adapté l'exemple de VCP pour nos besoins (intégration avec RTOS et API série).

Le problème est que si le câble USB est connecté, mais le port est ouvert sur l'hôte Windows, après quelques minutes, les extrémités de l'appareil en permanence réintègrent le ISR USB jusqu'à ce que le port est ouvert puis tout commence fonctionne normalement.

J'instrumenté gestionnaire d'interruption et peut voir que lorsque le défaut se produit, la sortie du gestionnaire ISR, puis immédiatement est réadmis. Cela se produit parce que la sortie de l'interruption du drapeau IEPINT dans OTG_FS_GINTSTS est pas claire. Le OTG_FS_DAINT à ce moment contient 0x00000002 (set IEPINT1), tandis que DIEPINT1 a 0x00000080 (TXFE). La ligne OTGD_FS_Handle_InEP_ISR () qui efface TXFE est appelé, mais le bit ne soit pas clair ou devient immédiatement réaffirmée. Lorsque le port COM sur l'hôte est rouverte, l'état de OTG_FS_GINTSTS et OTG_FS_DAINT à la fin de l'interruption est toujours égale à zéro, et d'autres interruptions se produisent au taux normal. Notez que le problème ne se produit que si des données sont sorties, mais l'hôte n'a pas ouvert de port. Si soit le port est ouvert ou aucune donnée est sortie, le système fonctionne indéfiniment. Je crois que plus les données qui est sortie plus tôt le problème se produit, mais est anecdotique à l'heure actuelle.

Le code VCP a une grandeur d'état qui prend les valeurs énumérées ci-après:

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

et nous utilisons l'état CONFIGURES pour déterminer si de mettre les données dans la mémoire tampon du pilote pour l'envoi. Cependant, l'état configuré est activé lorsque le câble est attaché pas lorsque l'hôte a l'orifice ouvert et une application connectée. Je vois que lorsque Windows n'ouvre le port, il y a une rafale d'interruptions il semble que certaines communications se produit sur cet événement; Je me demande s'il est donc possible de détecter si l'hôte a ouvert le port,.

J'ai besoin de deux choses peut-être:

  1. Pour éviter que le code USB de se coincer dans la région désignée en première instance
  2. Pour déterminer si l'hôte a ouvert le port de l'extrémité de l'appareil, et seulement pousser des données pour l'envoi lorsqu'il est ouvert.
Était-ce utile?

La solution

Partie (1) - empêcher le blocage d'interruption - a été facilitée par une bibliothèque USB correction de bogue de support ST; il n'a pas été correctement éclaircissait l'interruption TxEmpty.

Après quelques recherches et de l'assistance de ST soutien, j'ai déterminé une solution à une partie (2) - détecter si le port d'accueil est ouvert. Classiquement, lorsqu'un port est ouvert la ligne de commande du modem DTR est affirmé. Ces informations sont transmises à un appareil de classe CDC, donc je peux l'utiliser pour atteindre mon but. Il est possible pour une application de modifier le comportement de DTR, mais cela ne devrait pas se produire dans l'une des applications clientes qui sont susceptibles de se connecter à ce dispositif dans ce cas. Cependant, il y a un plan de sauvegarde qui suppose implicitement le port ouvert si la ligne de codage (transmission, cadrage) sont réglées. Dans ce cas, il n'y a aucun moyen de détection de fermeture, mais au moins il ne sera pas empêcher une application non conventionnelle de travailler avec mon appareil, même si elle provoque alors à se bloquer lorsqu'il se déconnecte.

En ce qui concerne le code exemple VCP ST Plus précisément, j'ai apporté les modifications suivantes à usb_prop.c:

1) Ajout de la fonction suivante:

#include <stdbool.h>
static bool host_port_open = false ;
bool Virtual_Com_Port_IsHostPortOpen()
{
    return bDeviceState == CONFIGURED && host_port_open ;
}

2) de modification Virtual_Com_Port_NoData_Setup () de manutention de SET_CONTROL_LINE_STATE ainsi:

else if (RequestNo == SET_CONTROL_LINE_STATE)
{
  // Test DTR state to determine if host port is open
  host_port_open = (pInformation->USBwValues.bw.bb0 & 0x01) != 0 ;
  return USB_SUCCESS;
}

3) Pour permettre une utilisation avec des applications qui ne la manipulation de SET_LINE_CODING ainsi sans DTR conventionnelle, j'ai également modifié Virtual_Com_Port_Data_Setup ():

  else if (RequestNo == SET_LINE_CODING)
  {
    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
    {
      CopyRoutine = Virtual_Com_Port_SetLineCoding;

      // If line coding is set the port is implicitly open 
      // regardless of host's DTR control.  Note: if this is 
      // the only indicator of port open, there will be no indication 
      // of closure, but this will at least allow applications that 
      // do not assert DTR to connect.
      host_port_open = true ;

    }
    Request = SET_LINE_CODING;
  }

Autres conseils

After so much searching and a kind of reverse engineering I finally found the method for detecting the open terminal and also it's termination. I found that in the CDC class there is three Data nodes , one is a control node and the other two are data In and data Out nodes.Now when you open a terminal a code is sent to the control node and also when you close it. all we need to do is to get those codes and by them start and stop our data transmission tasks. the code that is sent is respectively 0x21 and 0x22 for opening and closing the terminal.In the usb_cdc_if.c there is a function that receive and interpret those codes (there is a switch case and the variable cmd is the code we are talking about).that function is CDC_Control_FS . Here we are, Now all we need to do is to expand that function so that it interpret the 0x22 and 0x21 . there you are , now you know in your application whether the port is open or not.

I need one of two things perhaps:

  1. To prevent the USB code from getting stuck in the ISR in the first instance
  2. To determine whether the host has the port open from the device end, and only push data for sending when open.

You should attempt to do option 1 instead of 2. On Windows and Linux, it is possible to open a COM port and use it without setting the control signals, which means there is no fool-proof, cross-platform way to detect that the COM port is open.

A well programmed device will not let itself stop functioning just because the USB host stopped polling for data; this is a normal thing that should be handled properly. For example, you might change your code so that you only queue up data to be sent to the USB host if there is buffer space available for the endpoint. If there is no free buffer space, you might have some special error handling code.

I found another solution by adopting CDC_Transmit_FS. It can now be used as output for printf by overwriting _write function.

First it checks the connection state, then it tries to send over USB endport in a busy loop, which repeats sending if USB is busy.

I found out if dev_state is not USBD_STATE_CONFIGURED the USB plug is disconnected. If the plug is connected but no VCP port is open via PuTTY or termite, the second check fails.

This implementation works fine for me for RTOS and CubeMX HAL application. The busy loop is not blocking low priority threads anymore.

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
    uint8_t result = USBD_OK;


    // Check if USB interface is online and VCP connection is open.
    // prior to send:
    if ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)
            || (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN))
    {
        // The physical connection fails.
        // Or: The phycical connection is open, but no VCP link up.
        result = USBD_FAIL;
    }
    else
    {

        USBD_CDC_SetTxBuffer(hUsbDevice_0, Buf, Len);

        // Busy wait if USB is busy or exit on success or disconnection happens
        while(1)
        {

            //Check if USB went offline while retrying
            if ((hUsbDevice_0->dev_state != USBD_STATE_CONFIGURED)
                        || (hUsbDevice_0->ep0_state == USBD_EP0_STATUS_IN))
            {
                result = USBD_FAIL;
                break;
            }

            // Try send
            result = USBD_CDC_TransmitPacket(hUsbDevice_0);
            if(result == USBD_OK)
            {
                break;
            }
            else if(result == USBD_BUSY)
            {
                // Retry until USB device free.
            }
            else
            {
                // Any other failure
                result = USBD_FAIL;
                break;
            }

        }
    }

    return result;
}

CDC_Transmit_FS is used by _write:

// This function is used by printf and puts.
int _write(int file, char *ptr, int len)
{
    (void) file; // Ignore file descriptor
    uint8_t result;

    result = CDC_Transmit_FS((uint8_t*)ptr, len);
    if(result == USBD_OK)
    {
        return (int)len;
    }
    else
    {
        return EOF;
    }
}

Regards Bernhard

I have the same requirement to detect PC port open/close. I have seen it implemented it as follows:

Open detected by:

  • DTR asserted
  • CDC bulk transfer

Close detected by:

  • DTR deasserted
  • USB "unplugged", sleep etc

This seems to be working reasonably well, although more thorough testing will be needed to confirm it works robustly.

Disclaimer: I use code generated by Cube, and as a result it works with HAL drivers. Solutions, proposed here before, don't work for me, so I have found one. It is not good, but works for some purposes.

One of indirect sign of not opened port arises when you try to transmit packet by CDC_Transmit_FS, and then wait till TxState is set to 0. If port is not opened it never happens. So my solution is to fix some timeout:

uint16_t count = 0;
USBD_CDC_HandleTypeDef *hcdc =
        (USBD_CDC_HandleTypeDef*) USBD_Device.pClassData;

while (hcdc->TxState != 0) {
    if (++count > BUSY_TIMEOUT) { //number of cycles to wait till it makes decision
        //here it's clear that port is not opened
    }
}

The problem is also, that if one tries to open port, after device has tried to send a packet, it cant be done. Therefore whole routine I use:

uint8_t waitForTransferCompletion(void) {

    uint16_t count = 0;
    USBD_CDC_HandleTypeDef *hcdc =
             (USBD_CDC_HandleTypeDef*) USBD_Device.pClassData;

    while (hcdc->TxState != 0) {
        if (++count > BUSY_TIMEOUT) { //number of cycles to wait till it makes decision
            USBD_Stop(&USBD_Device); // stop and
            MX_USB_DEVICE_Init(); //            init device again
            HAL_Delay(RESET_DELAY); // give a chance to open port
            return USBD_FAIL; // return fail, to send last packet again
        }
    }

    return USBD_OK;
}

The question is, how big timeout has to be, not to interrupt transmission while port is opened. I set BUSY_TIMEOUT to 3000, and now it works.

I fixed it by checking of a variable hUsbDeviceFS.ep0_state. It equal 5 if connected and 4 if do not connected or was disconnected. But. There are some issue in the HAL. It equal 5 when program started. Next steps fixed it at the begin of a program

        /* USER CODE BEGIN 2 */
            HAL_Delay(500);
            hUsbDeviceFS.ep0_state = 4;

...

I do not have any wishes to learn the HAL - I hope this post will be seeing by developers and they will fix the HAL. It helped me to fix my issue.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top