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

时间:2011-03-17 12:15:56

标签: embedded usb device cdc stm32

我正在使用带有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. 确定主机是否从设备端打开端口,并且仅在打开时推送数据以进行发送。

6 个答案:

答案 0 :(得分: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 :(得分:1)

  

我可能需要两件事之一:

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

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

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

答案 2 :(得分:1)

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

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

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

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

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

此致 哈德

答案 3 :(得分:0)

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

检测到开启:

  • DTR断言
  • CDC批量转帐

检测到关闭:

  • DTR取消断言
  • USB“拔掉插头”,睡觉等

这似乎运作得相当好,但需要进行更全面的测试才能确认其运行稳健。

答案 4 :(得分: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,现在可以正常工作。

答案 5 :(得分:0)

我通过检查变量 SELECT CASE WHEN ac.actor_id < te.actor_id THEN ac.first_name ELSE te.first_name END , CASE WHEN ac.actor_id > te.actor_id THEN te.first_name ELSE ac.first_name END , fi.title , fi.film_id FROM Film_Actor as fa INNER JOIN Actor as ac ON fa.actor_id = ac.actor_id INNER JOIN Film as fi ON fa.film_id = fi.film_id INNER JOIN ( SELECT a_c.first_name, a_c.last_name, a_c.actor_id FROM Film_Actor as f_a INNER JOIN Actor as a_c ON f_a.actor_id = a_c.actor_id) te ON te.actor_id=fa.actor_id 来修复它。 如果连接则等于5,如果未连接或断开则等于4。 但。 HAL存在一些问题。程序启动时它等于5。 接下来的步骤在程序开始时将其修复

hUsbDeviceFS.ep0_state

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