Domanda

Sto usando un microcontrollore STM32F105 con la libreria USB STM32_USB-FS-Device_Lib_V3.2.1 e si sono adattati l'esempio VCP per i nostri scopi (integrazione con RTOS e API seriale).

Il problema è che se il cavo USB è collegato, ma la porta non è aperta sul host Windows, dopo pochi minuti il ??dispositivo finisce permanentemente rientrare nel ISR USB finché la porta è aperta e quindi tutto inizia a lavorare normalmente.

Ho strumentato gestore di interrupt e può vedere che quando si verifica il guasto, chiuso il gestore ISR e poi subito rientra. Ciò si verifica perché all'uscita dal interrompere la bandiera IEPINT in OTG_FS_GINTSTS non è chiaro. L'OTG_FS_DAINT in questo momento contiene 0x00000002 (IEPINT1 set), mentre DIEPINT1 ha 0x00000080 (TXFE). La linea a OTGD_FS_Handle_InEP_ISR () che cancella TXFE è chiamata, ma il bit non fa trasparente o diventa immediatamente riaffermata. Quando la porta COM sull'host viene riaperto, lo stato di OTG_FS_GINTSTS e OTG_FS_DAINT alla fine del interrupt è sempre zero, e ulteriori interrupt si verificano a velocità normale. Si noti che il problema si verifica se i dati vengono uscita ma l'host non ha aperto porta. Se sia la porta è aperta o non vi sono dati in uscita, il sistema funziona a tempo indeterminato. Credo che i più dati in uscita la prima si verifica il problema, ma che è aneddotica al momento.

Il codice VCP ha una variabile di stato che assume i seguenti valori enumerati:

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

e usiamo lo stato configurato per determinare se mettere i dati nel buffer del driver per l'invio. Tuttavia lo stato configurato viene impostato quando il cavo è collegato non quando l'host ha la porta aperta e un'applicazione connessa. Vedo che quando Windows si apre la porta, c'è una raffica di interrupt così sembra che un po 'di comunicazione avviene su questo evento; Mi chiedo se è possibile rilevare se l'host ha aperto la porta,.

ho bisogno di una delle due cose, forse:

  1. Per evitare che il codice USB di rimanere bloccati nel ISR in prima istanza
  2. Per determinare se l'host ha la porta aperta dalla fine dispositivo, e spingere solo i dati per l'invio quando è aperto.
È stato utile?

Soluzione

Parte (1) - di impedire l'interruzione di lock-up - è stato facilitato da una libreria di bug fix USB dal supporto ST; non stava schiarendo correttamente l'interrupt TxEmpty.

Dopo alcune ricerche e l'assistenza di supporto ST, ho deciso una soluzione a parte (2) - rilevare se la porta host è aperto. Convenzionalmente, quando viene aperta una porta la linea di controllo del modem DTR è asserito. Questa informazione viene passata ad un dispositivo di classe CDC, in modo da poter usare questo per raggiungere il mio obiettivo. E 'possibile per un'applicazione per modificare il comportamento di DTR, ma questo non dovrebbe accadere in una qualsiasi delle applicazioni client che possono connettersi a questo dispositivo, in questo caso. Tuttavia c'è un piano di back-up che assume implicitamente la porta ad essere aperti se la linea di codifica (baud, framing) sono impostati. In questo caso non v'è alcun mezzo per rilevare chiusura ma almeno non impedirà un'applicazione convenzionale di lavorare con il mio dispositivo, anche se poi induce a bloccarsi quando si disconnette.

Per quanto riguarda il codice esempio VCP della ST specificamente ho fatto le seguenti modifiche al usb_prop.c:

1) Aggiunta la seguente funzione:

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

2) Modificato Virtual_Com_Port_NoData_Setup () di manipolazione del SET_CONTROL_LINE_STATE così:

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) Per consentire l'utilizzo con applicazioni che non operano DTR convenzionalmente Ho anche modificato Virtual_Com_Port_Data_Setup () manipolazione di SET_LINE_CODING così:

  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;
  }

Altri suggerimenti

Dopo tanta ricerca e una sorta di reverse engineering ho finalmente trovato il metodo per rilevare il terminale aperto e anche di terminazione. Ho scoperto che nella classe CDC ci sono tre nodi di dati, uno è un nodo di controllo e gli altri due sono dati In e Out dei dati nodes.Now quando si apre un codice di un terminale viene inviato al nodo di controllo e anche quando lo si chiude . tutto quello che dobbiamo fare è quello di ottenere quei codici e da loro inizio e fermare la nostra attività di trasmissione dei dati. il codice che viene inviato è rispettivamente 0x21 e 0x22 per aprire e chiudere il terminal.In l'usb_cdc_if.c c'è una funzione che ricevono e interpretare tali codici (v'è un caso interruttore e il cmd variabile è il codice parliamo) funzione .che è CDC_Control_FS. Eccoci, ora tutto quello che dobbiamo fare è quello di espandere questa funzione in modo che interpretare il 0x22 e 0x21. Ecco, ora sapete nell'applicazione se la porta è aperta o meno.

ho bisogno di una delle due cose, forse:

  1. Per evitare che il codice USB di rimanere bloccati nel ISR in prima istanza
  2. Per determinare se l'host ha la porta aperta dalla fine dispositivo, e spingere solo i dati per l'invio quando è aperto.

Si dovrebbe cercare di fare l'opzione 1 invece di 2. Su Windows e Linux, è possibile aprire una porta COM e utilizzarlo senza impostare i segnali di controllo, che significa che non c'è infallibile, cross-platform modo per rilevare che la porta COM è aperto.

Un dispositivo ben programmato non si lascerà smetterà di funzionare solo perché l'host USB fermato polling per i dati; questa è una cosa normale che dovrebbe essere gestita correttamente. Ad esempio, è possibile cambiare il codice in modo che si coda solo fino dati da inviare all'host USB se c'è buffer di spazio disponibile per l'endpoint. Se non v'è spazio di buffer libero, si potrebbe avere qualche speciale codice di gestione degli errori.

Ho trovato un'altra soluzione con l'adozione CDC_Transmit_FS. Si può ora essere utilizzato come uscita per printf sovrascrivendo funzione _write.

In primo luogo esso controlla lo stato della connessione, quindi tenta di inviare più di endport USB in un ciclo occupato, che ripete l'invio se USB è occupato.

ho scoperto se dev_state non è USBD_STATE_CONFIGURED il connettore USB è scollegato. Se la spina è collegato ma non porta VCP è aperta tramite mastice o termiti, il secondo controllo fallisce.

Questa implementazione funziona bene per me per l'applicazione RTOS e CubeMX HAL. Il loop occupato non sta bloccando più thread a bassa priorità.

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 viene utilizzato da _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;
    }
}

Saluti Bernhard

Ho lo stesso obbligo di rilevare porta PC di apertura / chiusura. L'ho visto implementato come segue:

Apri rilevato da:

  • DTR affermato
  • CDC trasferimento di massa

Chiudi rilevato da:

  • DTR deasserito
  • USB "unplugged", il sonno etc

Questo sembra funzionare abbastanza bene, anche se il test più approfondita saranno necessari per confermare che funziona robustamente.

Disclaimer: io uso codice generato dal Cube, e di conseguenza funziona con i driver HAL. Le soluzioni proposte, qui prima, non lo fanno il lavoro per me, così ho trovato uno. Non è bene, ma funziona per alcuni scopi.

Una delle segno indiretto di porta non aperta sorge quando si tenta di pacchetti di trasmissione da CDC_Transmit_FS, e poi aspettare fino TxState viene impostato a 0. Se la porta non è aperta non succede mai. Quindi la mia soluzione è quella di risolvere alcuni 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
    }
}

Il problema è anche che se si cerca di aprire la porta, dopo che il dispositivo ha tentato di inviare un pacchetto, sopraelevazione essere fatto. Perciò tutta l'uso io di routine:

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;
}

La domanda è, quanto grande timeout deve essere, non la trasmissione di interruzione, mentre la porta è aperta. Ho impostato BUSY_TIMEOUT a 3000, ed ora funziona.

ho riparato verificando una variabile hUsbDeviceFS.ep0_state . Si uguale 5 se collegato e 4 se non collegato o è stato scollegato. Ma. Ci sono alcuni problema nel HAL. E 'uguale 5 quando il programma avviato. I prossimi passi è stato risolto al inizio di un programma

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

...

Non ho alcun desiderio di imparare l'HAL - Spero che questo post sarà vedere dagli sviluppatori e risolverà il HAL. Mi ha aiutato a risolvere il mio problema.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top