Pregunta

Estoy usando un microcontrolador STM32F105 con la biblioteca STM32_USB-FS-DeVice_LIB_V3.2.1 USB y he adaptado el ejemplo VCP para nuestros propósitos (integración con RTO y API serie).

El problema es que si el cable USB está conectado, pero el puerto no está abierto en el host de Windows, después de unos minutos, el dispositivo termina volviendo a ingresar permanentemente el USB ISR hasta que el puerto se abre y luego todo comienza a funcionar normalmente.

He instrumentado Handler de interrupción y puedo ver que cuando ocurre la falla, el controlador ISR sale y luego se vuelve a entrar inmediatamente. Esto ocurre porque en la salida de la interrupción del indicador IEPINT en OTG_FS_GINTSTS no está claro. El OTG_FS_DAINT en este momento contiene 0x00000002 (set IEPINT1), mientras que DIEPINT1 tiene 0x00000080 (TXFE). La línea en OTGD_FS_HANDLE_INEP_ISR () que borra TXFE se llama, pero el bit no se borra o se vuelve a asignar inmediatamente. Cuando se vuelve a abrir el puerto COM en el host, el estado de OTG_FS_GINTSTS y OTG_FS_DAINT al final de la interrupción siempre es cero, y se producen interrupciones adicionales a la velocidad normal. Tenga en cuenta que el problema solo ocurre si se emiten datos, pero el host no tiene el puerto abierto. Si el puerto está abierto o no se emiten datos, el sistema se ejecuta indefinidamente. Creo que cuanto más datos se producen, cuanto antes ocurre el problema, pero en la actualidad es anecdótico.

El código VCP tiene una variable de estado que toma los siguientes valores enumerados:

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

Y usamos el estado configurado para determinar si se debe poner datos en el búfer del controlador para enviar. Sin embargo, el estado configurado se establece cuando el cable está conectado no cuando el host tiene el puerto abierto y una aplicación conectada. Veo que cuando Windows abre el puerto, hay una explosión de interrupciones, por lo que parece que se produce cierta comunicación en este evento; Por lo tanto, me pregunto si es posible detectar si el host tiene el puerto abierto.

Necesito una de las dos cosas quizás:

  1. Para evitar que el código USB se atasque en el ISR en primera instancia
  2. Para determinar si el host tiene el puerto abierto desde el extremo del dispositivo y solo presiona datos para enviar cuando está abierto.
¿Fue útil?

Solución

La Parte (1), evitando el bloqueo de la interrupción, fue facilitada por una solución de error de la biblioteca USB desde ST Support; No estaba limpiando correctamente la interrupción txempty.

Después de algunas investigaciones y asistencia del soporte de ST, he determinado una solución a la parte (2), detectando si el puerto host está abierto. Convencionalmente, cuando se abre un puerto, se afirma la línea de control del módem DTR. Esta información se pasa a un dispositivo de clase CDC, por lo que puedo usar esto para lograr mi objetivo. Es posible que una aplicación cambie el comportamiento de DTR, pero esto no debería suceder en ninguna de las aplicaciones de los clientes que probablemente se conecten a este dispositivo en este caso. Sin embargo, hay un plan de respaldo que asume implícitamente que el puerto está abierto si se establece la codificación de línea (baudios, enmarcado). En este caso, no hay medios para detectar el cierre, pero al menos no evitará que una aplicación no convencional trabaje con mi dispositivo, incluso si luego hace que se bloquee cuando se desconecta.

Con respecto al código de ejemplo VCP de ST específicamente, he realizado los siguientes cambios en USB_PROP.C:

1) Se agregó la siguiente función:

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

2) Manejo de Virtual_Com_Port_Nodata_Setup () modificado de set_control_line_state así:

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) Para permitir el uso con aplicaciones que no operan DTR convencionalmente, también he modificado virtual_com_port_data_setup () manejo de set_line_coding así:

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

Otros consejos

Después de tanta búsqueda y un tipo de ingeniería inversa, finalmente encontré el método para detectar el terminal abierto y también su terminación. Descubrí que en la clase CDC hay tres nodos de datos, uno es un nodo de control y los otros dos son datos de datos y nodos de datos. Ahora, cuando abre un terminal, se envía un código al nodo de control y también cuando lo cierra. . Todo lo que necesitamos hacer es obtener esos códigos y, por ellos, comenzar y detener nuestras tareas de transmisión de datos. El código que se envía es respectivamente 0x21 y 0x22 para abrir y cerrar el terminal. En el USB_CDC_IF.C hay una función que recibe e interpreta esos códigos (hay un caso de conmutador y el CMD variable es el código del que estamos hablando) . Esa función es CDC_CONTROL_FS. Aquí estamos, ahora todo lo que necesitamos hacer es expandir esa función para que interprete el 0x22 y el 0x21. Ahí está, ahora sabe en su aplicación si el puerto está abierto o no.

Necesito una de las dos cosas quizás:

  1. Para evitar que el código USB se atasque en el ISR en primera instancia
  2. Para determinar si el host tiene el puerto abierto desde el extremo del dispositivo y solo presiona datos para enviar cuando está abierto.

Debe intentar hacer la opción 1 en lugar de 2. En Windows y Linux, es posible abrir un puerto COM y usarlo sin configurar las señales de control, lo que significa El puerto com está abierto.

Un dispositivo bien programado no dejará que dejara de funcionar solo porque el host USB dejó de sondear para obtener datos; Esto es algo normal que debe manejarse correctamente. Por ejemplo, puede cambiar su código para que solo haga cola los datos que se enviarán al host USB si hay espacio búfer disponible para el punto final. Si no hay espacio de búfer gratuito, es posible que tenga algún código especial de manejo de errores.

Encontré otra solución adoptando CDC_TRANSMIT_FS. Ahora se puede usar como salida para printf sobrescribiendo la función _Write.

Primero verifica el estado de conexión, luego intenta enviar a USB Endport en un bucle ocupado, que repite el envío si USB está ocupado.

Descubrí si dev_state no es usbd_state_configurado El enchufe USB está desconectado. Si el enchufe está conectado pero no se abre el puerto VCP a través de Putty o Termite, la segunda verificación falla.

Esta implementación funciona bien para mí para la aplicación RTOS y Cubemx HAL. El bucle ocupado ya no está bloqueando hilos de baja prioridad.

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 es utilizado por _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;
    }
}

Saludos Bernhard

Tengo el mismo requisito para detectar el puerto de PC abierto/cierre. Lo he visto implementado de la siguiente manera:

Abierto detectado por:

  • Dtr afirmó
  • Transferencia a granel de CDC

Close detectado por:

  • Dtr desserted
  • USB "desenchufado", sueño, etc.

Esto parece estar funcionando razonablemente bien, aunque se necesitarán pruebas más exhaustivas para confirmar que funciona de manera robusta.

Descargo de responsabilidad: uso el código generado por Cube y, como resultado, funciona con los controladores HAL. Las soluciones, propuestas aquí antes, no funcionan para mí, así que he encontrado una. No es bueno, pero funciona para algunos fines.

Uno de signo indirecto del puerto no abierto surge cuando intenta transmitir el paquete por CDC_TRANSMIT_FS, y luego espere hasta que TXState esté configurado en 0. Si el puerto no se abre, nunca sucede. Entonces mi solución es arreglar algo de tiempo de espera:

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

El problema también es que si uno intenta abrir el puerto, después de que el dispositivo haya intentado enviar un paquete, no se puede hacer. Por lo tanto, la rutina completa que uso:

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 pregunta es cuán grande debe ser el tiempo de espera, no para interrumpir la transmisión mientras se abre el puerto. Establecí Buscado_TimeOut a 3000, y ahora funciona.

Lo arreglé comprobando una variable hUsbDeviceFS.ep0_state. Igual a 5 si está conectado y 4 si no está conectado o se desconectó. Pero. Hay algún problema en el HAL. Iguala 5 cuando comenzó el programa. Los próximos pasos lo arreglaron al comienzo de un programa

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

...

No tengo ningún deseo de aprender el HAL, espero que esta publicación sea vista por los desarrolladores y arreglarán el HAL. Me ayudó a solucionar mi problema.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top