从USB Virtual Com Port设备检测打开的PC COM端口

问题描述 投票:5回答:7

我正在使用带有STM32_USB-FS-Device_Lib_V3.2.1 USB库的STM32F105微控制器,并根据我们的目的调整了VCP示例(与RTOS和串行API集成)。

问题是如果连接了USB电缆,但是Windows主机上没有打开端口,几分钟后设备会永久重新进入USB ISR,直到端口打开然后一切正常开始工作。

我已经检测了中断处理程序并且可以看到当故障发生时,ISR处理程序退出然后立即重新进入。发生这种情况是因为在退出中断时,OTG_FS_GINTSTS中的IEPINT标志不清楚。此时OTG_FS_DAINT包含0x00000002(IEPINT1设置),而DIEPINT1包含0x00000080(TXFE)。调用清除TXFE的OTGD_FS_Handle_InEP_ISR()中的行,但该位不清除或立即重新置位。当主机上的COM端口重新打开时,中断结束时OTG_FS_GINTSTS和OTG_FS_DAINT的状态始终为零,并且以正常速率发生进一步的中断。请注意,只有在输出数据但主机没有打开端口时才会出现此问题。如果端口打开或没有输出数据,系统将无限期运行。我相信输出的数据越多,问题就越早出现,但目前这是轶事。

VCP代码有一个状态变量,它接受以下枚举值:

  UNCONNECTED,
  ATTACHED,
  POWERED,
  SUSPENDED,
  ADDRESSED,
  CONFIGURED

我们使用CONFIGURED状态来确定是否将数据放入驱动程序缓冲区以进行发送。但是,当连接电缆而不是主机打开端口并连接应用程序时,将设置CONFIGURED状态。我看到当Windows打开端口时,会出现一连串的中断,所以似乎在这个事件上发生了一些通信;我想知道是否有可能检测主机是否打开了端口。

我或许需要两件事之一:

  1. 防止USB代码在第一个实例中卡在ISR中
  2. 确定主机是否从设备端打开端口,并且仅在打开时推送数据以进行发送。
embedded usb device cdc stm32
7个回答
7
投票

部分(1) - 防止中断锁定 - 来自ST支持的USB库错误修复;它没有正确清除TxEmpty中断。

经过ST支持的一些研究和帮助,我已经确定了第(2)部分的解决方案 - 检测主机端口是否打开。通常,当端口打开时,DTR调制解调器控制线被断言。此信息将传递给CDC类设备,因此我可以使用它来实现我的目标。应用程序可能会更改DTR的行为,但在这种情况下,任何可能连接到此设备的客户端应用程序都不会发生这种情况。但是,如果设置了线路编码(波特,帧),则会有一个备用计划隐含地假定端口是开放的。在这种情况下,没有检测闭合的方法,但至少它不会阻止非常规应用程序使用我的设备,即使它在断开连接时导致它崩溃。

关于ST的VCP示例代码,我特别对usb_prop.c进行了以下更改:

1)增加了以下功能:

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

2)修改了对SET_CONTROL_LINE_STATE的Virtual_Com_Port_NoData_Setup()处理:

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)为了允许与不按常规操作DTR的应用程序一起使用,我还修改了对SET_LINE_CODING的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;
  }

1
投票

经过这么多的搜索和一种逆向工程,我终于找到了检测开放式终端的方法,也终止了它。我发现在CDC类中有三个数据节点,一个是控制节点,另外两个是数据输入和数据输出节点。现在当你打开终端时,代码被发送到控制节点,当你关闭它时。我们需要做的就是获取这些代码并由他们启动和停止我们的数据传输任务。发送的代码分别是0x21和0x22,用于打开和关闭终端。在usb_cdc_if.c中有一个接收和解释这些代码的函数(有一个switch case,变量cmd是我们正在讨论的代码) 。该功能是CDC_Control_FS。我们在这里,现在我们需要做的就是扩展该函数,使其解释0x22和0x21。你在那里,现在你知道你的应用程序端口是否打开。


1
投票

我或许需要两件事之一:

  1. 防止USB代码在第一个实例中卡在ISR中
  2. 确定主机是否从设备端打开端口,并且仅在打开时推送数据以进行发送。

您应该尝试执行选项1而不是2.在Windows和Linux上,可以打开COM端口并在不设置控制信号的情况下使用它,这意味着没有万无一失的跨平台方法来检测COM端口已打开。

一个编程良好的设备不会因为USB主机停止轮询数据而让自己停止运行;这是正常的事情应该妥善处理。例如,您可以更改代码,以便只有在端点可用的缓冲区空间时才排队要发送到USB主机的数据。如果没有空闲缓冲区空间,则可能有一些特殊的错误处理代码。


1
投票

我通过采用CDC_Transmit_FS找到了另一种解决方案。现在可以通过覆盖_write函数将其用作printf的输出。

首先它检查连接状态,然后尝试通过USB端口在忙碌循环中发送,如果USB忙,则重复发送。

我发现如果dev_state不是USBD_STATE_CONFIGURED则USB插头已断开连接。如果插头已连接但没有通过PuTTY或白蚁打开VCP端口,则第二次检查失败。

对于RTOS和CubeMX HAL应用程序,此实现适用于我。 busy循环不再阻止低优先级线程。

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

_crite使用CDC_Transmit_FS:

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

关心伯恩哈德


0
投票

我有同样的要求检测PC端口打开/关闭。我看到它实现如下:

检测到开放:

  • DTR声称
  • CDC批量转让

关闭检测到:

  • DTR无效
  • USB“拔掉插头”,睡觉等

这似乎工作得相当好,虽然需要进行更彻底的测试以确认其运行稳健。


0
投票

免责声明:我使用Cube生成的代码,因此它可以与HAL驱动程序一起使用。之前提出的解决方案对我不起作用,所以我找到了一个。它不好,但可以用于某些目的。

当您尝试通过CDC_Transmit_FS传输数据包,然后等待TxState设置为0时,会出现未打开端口的间接符号之一。如果端口未打开,则永远不会发生。所以我的解决方案是修复一些超时:

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

问题还在于,如果试图打开端口,在设备尝试发送数据包之后,就无法完成。因此我使用的整个程序:

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

问题是,超时必须有多大,而不是在端口打开时中断传输。我将BUSY_TIMEOUT设置为3000,现在它可以工作了。


0
投票

我通过检查变量hUsbDeviceFS.ep0_state来修复它。如果连接则等于5,如果未连接或断开则等于4。但。 HAL存在一些问题。程序启动时它等于5。接下来的步骤在程序开始时修复它

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

...

我没有任何希望学习HAL的愿望 - 我希望开发人员能够看到这篇文章,他们将修复HAL。它帮助我解决了我的问题。

© www.soinside.com 2019 - 2024. All rights reserved.