糟糕的串口/ USB码(c++) -修复建议?

时间:2022-02-25 15:09:26

I don't have much experience with Serial I/O, but have recently been tasked with fixing some highly flawed serial code, because the original programmer has left the company.

我对串行I/O没有太多的经验,但最近的任务是修复一些有严重缺陷的串行代码,因为最初的程序员已经离开了公司。

The application is a Windows program that talks to a scientific instrument serially via a virtual COMM port running on USB. Virtual COMM port USB drivers are provided by FTDI, since they manufacture the USB chip we use on the instrument.

该应用程序是一个Windows程序,通过在USB上运行的虚拟通讯端口与科学仪器进行串行通信。虚拟通讯端口USB驱动程序是由FTDI提供的,因为他们生产我们在仪器上使用的USB芯片。

The serial code is in an unmanaged C++ DLL, which is shared by both our old C++ software, and our new C# / .Net (WinForms) software.

串行代码位于一个非托管的c++ DLL中,它由我们的旧c++软件和我们的新c# / . net (WinForms)软件共享。

There are two main problems:

主要有两个问题:

Fails on many XP systems

在许多XP系统上失败

When the first command is sent to the instrument, there's no response. When you issue the next command, you get the response from the first one.

当第一个命令发送到该工具时,没有响应。当您发出下一个命令时,您将得到第一个命令的响应。

Here's a typical usage scenario (full source for methods called is included below):

下面是一个典型的使用场景(下面包含调用方法的完整源代码):

char szBuf [256];   

CloseConnection ();

if (OpenConnection ())
{   
    ClearBuffer ();

    // try to get a firmware version number
    WriteChar ((char) 'V');
    BOOL versionReadStatus1 = ReadString (szBuf, 100);
        ...
    }

On a failing system, the ReadString call will never receive any serial data, and times out. But if we issue another, different command, and call ReadString again, it will return the response from the first command, not the new one!

在失败的系统上,ReadString调用将永远不会接收到任何串行数据,并超时。但是如果我们发出另一个不同的命令,并再次调用ReadString,它将返回第一个命令的响应,而不是新的命令!

But this only happens on a large subset of Windows XP systems - and never on Windows 7. As luck would have it, our XP dev machines worked OK, so we did not see the problem until we started beta testing. But I can also reproduce the problem by running an XP VM (VirtualBox) on my XP dev machine. Also, the problem only occurs when using the DLL with the new C# version - works fine with the old C++ app.

但这种情况只发生在Windows XP系统的很大一部分上,而不会发生在Windows 7上。幸运的是,我们的XP开发机器运行良好,所以直到我们开始测试时才发现问题。但是我也可以在XP开发机器上运行XP VM (VirtualBox)来重现这个问题。而且,只有在使用新的c#版本的DLL时,问题才会出现。

This seemed to be resolved when I added a Sleep(21) to the low level BytesInQue method before calling ClearCommError, but this exacerbated the other problem - CPU usage. Sleeping for less than 21 ms would make the failure mode reappear.

在调用ClearCommError之前,我向低级别BytesInQue方法添加了一个Sleep(21),这似乎解决了这个问题,但这又加剧了另一个问题——CPU的使用。睡眠时间少于21毫秒会使故障模式重新出现。

High CPU usage

高CPU使用率

When doing serial I/O CPU use is excessive - often above 90%. This happens with both the new C# app and the old C++ app, but is much worse in the new app. Often makes the UI very non-responsive, but not always.

当进行串行I/O CPU使用时是过度的—通常超过90%。这在新的c#应用程序和旧的c++应用程序中都有发生,但在新的应用程序中情况要糟糕得多。

Here's the code for our Port.cpp class, in all it's terrible glory. Sorry for the length, but this is what I'm working with. Most important methods are probably OpenConnection, ReadString, ReadChar, and BytesInQue.

这是我们港口的代码。cpp级,这是可怕的荣耀。不好意思,这是我要处理的。最重要的方法可能是OpenConnection、ReadString、ReadChar和BytesInQue。

// 
// Port.cpp: Implements the CPort class, which is 
//               the class that controls the serial port. 
// 
// Copyright (C) 1997-1998 Microsoft Corporation 
// All rights reserved. 
// 
// This source code is only intended as a supplement to the 
// Broadcast Architecture Programmer's Reference. 
// For detailed information regarding Broadcast 
// Architecture, see the reference. 
// 
#include <windows.h> 
#include <stdio.h> 
#include <assert.h> 
#include "port.h" 

// Construction code to initialize the port handle to null. 
CPort::CPort() 
{ 
    m_hDevice = (HANDLE)0;

    // default parameters
    m_uPort = 1;
    m_uBaud = 9600;
    m_uDataBits = 8;
    m_uParity = 0;
    m_uStopBits = 0; // = 1 stop bit 
    m_chTerminator = '\n';
    m_bCommportOpen = FALSE;
    m_nTimeOut = 50;
    m_nBlockSizeMax = 2048;

} 

// Destruction code to close the connection if the port  
// handle was valid. 
CPort::~CPort() 
{ 
    if (m_hDevice) 
     CloseConnection(); 
} 

// Open a serial communication port for writing short  
// one-byte commands, that is, overlapped data transfer  
// is not necessary. 
BOOL CPort::OpenConnection() 
{ 
    char szPort[64]; 

    m_bCommportOpen = FALSE;

    // Build the COM port string as "COMx" where x is the port. 
    if (m_uPort > 9)
        wsprintf(szPort, "\\\\.\\COM%d", m_uPort); 
    else
        wsprintf(szPort, "COM%d", m_uPort); 

    // Open the serial port device. 
    m_hDevice = CreateFile(szPort, 
                            GENERIC_WRITE | GENERIC_READ, 
                            0, 
                            NULL,          // No security attributes 
                            OPEN_EXISTING, 
                            FILE_ATTRIBUTE_NORMAL,
                            NULL); 

    if (m_hDevice == INVALID_HANDLE_VALUE)
    { 
         SaveLastError ();
         m_hDevice = (HANDLE)0; 
         return FALSE; 
    } 

    return SetupConnection(); // After the port is open, set it up. 
} // end of OpenConnection() 


// Configure the serial port with the given settings. 
// The given settings enable the port to communicate 
// with the remote control. 
BOOL CPort::SetupConnection(void) 
{ 
    DCB dcb;  // The DCB structure differs betwwen Win16 and Win32. 

    dcb.DCBlength = sizeof(DCB); 

    // Retrieve the DCB of the serial port. 
    BOOL bStatus = GetCommState(m_hDevice, (LPDCB)&dcb); 

    if (bStatus == 0)
    {   
         SaveLastError ();

         return FALSE;
    }


    // Assign the values that enable the port to communicate. 
    dcb.BaudRate          = m_uBaud;       // Baud rate 
    dcb.ByteSize          = m_uDataBits;   // Data bits per byte, 4-8 
    dcb.Parity            = m_uParity;     // Parity: 0-4 = no, odd, even, mark, space 
    dcb.StopBits          = m_uStopBits;   // 0,1,2 = 1, 1.5, 2 


    dcb.fBinary           = TRUE;        // Binary mode, no EOF check : Must use binary mode in NT 
    dcb.fParity           = dcb.Parity == 0 ? FALSE : TRUE;       // Enable parity checking 
    dcb.fOutX             = FALSE;       // XON/XOFF flow control used 
    dcb.fInX              = FALSE;       // XON/XOFF flow control used 
    dcb.fNull             = FALSE;       // Disable null stripping - want nulls 
    dcb.fOutxCtsFlow      = FALSE; 
    dcb.fOutxDsrFlow      = FALSE; 
    dcb.fDsrSensitivity = FALSE; 
    dcb.fDtrControl = DTR_CONTROL_ENABLE; 
    dcb.fRtsControl =  RTS_CONTROL_DISABLE ; 

    // Configure the serial port with the assigned settings. 
    // Return TRUE if the SetCommState call was not equal to zero.
    bStatus = SetCommState(m_hDevice, &dcb);

    if (bStatus == 0)
    {       
         SaveLastError ();
         return FALSE;   
    }

    DWORD dwSize;
    COMMPROP *commprop;
    DWORD dwError;

    dwSize = sizeof(COMMPROP) + sizeof(MODEMDEVCAPS) ;
    commprop = (COMMPROP *)malloc(dwSize);
    memset(commprop, 0, dwSize);

    if (!GetCommProperties(m_hDevice, commprop))
    {
        dwError = GetLastError();
    } 

    m_bCommportOpen = TRUE;

    return TRUE; 
} 


void CPort::SaveLastError ()
{
    DWORD dwLastError = GetLastError ();

    LPVOID lpMsgBuf;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                  FORMAT_MESSAGE_FROM_SYSTEM | 
                  FORMAT_MESSAGE_IGNORE_INSERTS,
                  NULL,
                  dwLastError,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                  (LPTSTR) &lpMsgBuf,
                  0,
                  NULL);

    strcpy (m_szLastError,(LPTSTR)lpMsgBuf);
    // Free the buffer.
    LocalFree( lpMsgBuf );

}

void CPort::SetTimeOut (int nTimeOut)
{
    m_nTimeOut = nTimeOut;

}


// Close the opened serial communication port. 

void CPort::CloseConnection(void)
{ 
   if (m_hDevice != NULL &&
       m_hDevice != INVALID_HANDLE_VALUE)
   {
        FlushFileBuffers(m_hDevice);
        CloseHandle(m_hDevice);  ///that the port has been closed. 
   }
   m_hDevice = (HANDLE)0; 

   // Set the device handle to NULL to confirm
   m_bCommportOpen = FALSE;
} 

int CPort::WriteChars(char * psz)
{ 
    int nCharWritten = 0;

    while (*psz)
    {
     nCharWritten +=WriteChar(*psz);
     psz++;
    }

    return nCharWritten;
}

// Write a one-byte value (char) to the serial port. 
int CPort::WriteChar(char c) 
{ 
    DWORD dwBytesInOutQue = BytesInOutQue ();

    if (dwBytesInOutQue > m_dwLargestBytesInOutQue)
        m_dwLargestBytesInOutQue = dwBytesInOutQue;

    static char szBuf[2]; 

    szBuf[0] = c; 
    szBuf[1] = '\0'; 


    DWORD dwBytesWritten; 

    DWORD dwTimeOut = m_nTimeOut; // 500 milli seconds

    DWORD start, now;

    start = GetTickCount();

    do
    {
         now = GetTickCount();
         if ((now - start) > dwTimeOut )
         {
              strcpy (m_szLastError, "Timed Out");

              return 0;
         }

         WriteFile(m_hDevice, szBuf, 1, &dwBytesWritten, NULL); 
    }
    while (dwBytesWritten == 0);

    OutputDebugString(TEXT(strcat(szBuf, "\r\n")));

    return dwBytesWritten; 
}

int CPort::WriteChars(char * psz, int n)
{ 

    DWORD dwBytesWritten; 

    WriteFile(m_hDevice, psz, n, &dwBytesWritten, NULL); 

    return dwBytesWritten; 
}

// Return number of bytes in RX queue
DWORD CPort::BytesInQue ()
{
    COMSTAT    ComStat ; 
    DWORD      dwErrorFlags; 
    DWORD      dwLength; 

    // check number of bytes in queue 
    ClearCommError(m_hDevice, &dwErrorFlags, &ComStat ) ; 

    dwLength = ComStat.cbInQue; 


    return dwLength;

}

DWORD CPort::BytesInOutQue ()
{
    COMSTAT    ComStat ; 
    DWORD      dwErrorFlags; 
    DWORD      dwLength; 

    // check number of bytes in queue 
    ClearCommError(m_hDevice, &dwErrorFlags, &ComStat ); 

    dwLength = ComStat.cbOutQue ; 

    return dwLength;

}


int CPort::ReadChars (char* szBuf, int nMaxChars)
{
    if (BytesInQue () == 0)
        return 0;

    DWORD dwBytesRead; 

    ReadFile(m_hDevice, szBuf, nMaxChars, &dwBytesRead, NULL); 

    return (dwBytesRead); 
}


// Read a one-byte value (char) from the serial port. 
int CPort::ReadChar (char& c)
{
    static char szBuf[2]; 

    szBuf[0] = '\0'; 
    szBuf[1] = '\0'; 

    if (BytesInQue () == 0)
        return 0;

    DWORD dwBytesRead; 

    ReadFile(m_hDevice, szBuf, 1, &dwBytesRead, NULL); 

    c = *szBuf; 

    if (dwBytesRead == 0)
        return 0;

    return dwBytesRead; 
}


BOOL CPort::ReadString (char *szStrBuf , int nMaxLength)
{
    char str [256];
    char str2 [256];

    DWORD dwTimeOut = m_nTimeOut; 

    DWORD start, now;

    int nBytesRead;
    int nTotalBytesRead = 0;

    char c = ' ';

    static char szCharBuf [2];

    szCharBuf [0]= '\0';
    szCharBuf [1]= '\0';

    szStrBuf [0] = '\0';

    start = GetTickCount();

    while (c != m_chTerminator)
    {        
         nBytesRead = ReadChar (c);
         nTotalBytesRead += nBytesRead;

         if (nBytesRead == 1 && c != '\r' && c != '\n')
         {             
              *szCharBuf = c;

              strncat (szStrBuf,szCharBuf,1);

              if (strlen (szStrBuf) == nMaxLength)
                    return TRUE;

              // restart timer for next char
              start = GetTickCount();
         }

         // check for time out
         now = GetTickCount();
         if ((now - start) > dwTimeOut )
         {
              strcpy (m_szLastError, "Timed Out");

              return FALSE;
         }
    } 

    return TRUE;
}         


int CPort::WaitForQueToFill (int nBytesToWaitFor)
{
    DWORD start = GetTickCount();

    do 
    {
         if (BytesInQue () >= nBytesToWaitFor)
            break;

         if (GetTickCount() - start > m_nTimeOut)
            return 0;

    } while (1);

    return BytesInQue ();
}

int CPort::BlockRead (char * pcInputBuffer, int nBytesToRead)
{
    int nBytesRead = 0;
    int charactersRead;

    while (nBytesToRead >= m_nBlockSizeMax)
    {
        if (WaitForQueToFill (m_nBlockSizeMax) < m_nBlockSizeMax)
            return nBytesRead;

        charactersRead = ReadChars (pcInputBuffer, m_nBlockSizeMax);
        pcInputBuffer += charactersRead;
        nBytesRead += charactersRead;
        nBytesToRead -= charactersRead;
    }

    if (nBytesToRead > 0)
    {
        if (WaitForQueToFill (nBytesToRead) < nBytesToRead)
            return nBytesRead;

        charactersRead = ReadChars (pcInputBuffer, nBytesToRead);
        nBytesRead += charactersRead;
        nBytesToRead -= charactersRead;

    }

    return nBytesRead;
}

Based on my testing and reading, I see several suspicious things in this code:

根据我的测试和阅读,我在这个代码中看到了一些可疑的东西:

  1. COMMTIMEOUTS is never set. MS docs say "Unpredictable results can occur if you fail to set the time-out values". But I tried setting this, and it didn't help.

    从来没有设置逗号超时。MS docs说“如果你没有设置超时值,就会出现不可预测的结果”。但我试着设置这个,但没有用。

  2. Many methods (e.g. ReadString) will go into a tight loop and hammer the port with repeated reads if they don't get data immediately . This seems to explain the high CPU usage.

    许多方法(例如ReadString)将进入一个紧密的循环,如果不能立即获得数据,将重复读取端口。这似乎解释了高CPU使用率。

  3. Many methods have their own timeout handling, using GetTickCount(). Isn't that what COMMTIMEOUTS is for?

    许多方法都有自己的超时处理,使用GetTickCount()。这难道不是暂停的原因吗?

  4. In the new C# (WinForms) program, all these serial routines are called directly from the main thread, from a MultiMediaTimer event. Maybe should be run in a different thread?

    在新的c# (WinForms)程序中,所有这些串行例程都直接从主线程,从一个多介质事件中调用。也许应该在不同的线程中运行?

  5. BytesInQue method seems to be a bottleneck. If I break to debugger when CPU usage is high, that's usually where the program stops. Also, adding a Sleep(21) to this method before calling ClearCommError seems to resolve the XP problem, but exacerbates the CPU usage problem.

    BytesInQue方法似乎是一个瓶颈。如果我在CPU使用率很高时中断调试,那通常是程序停止的地方。另外,在调用ClearCommError之前添加一个Sleep(21)到这个方法似乎解决了XP问题,但是加剧了CPU的使用问题。

  6. Code just seems unnecessarily complicated.

    代码看起来太复杂了。

My Questions

我的问题

  1. Can anyone explain why this only works with a C# program on a small number of XP systems?

    有人能解释为什么这只适用于一小部分XP系统的c#程序吗?

  2. Any suggestions on how to rewrite this? Pointers to good sample code would be most welcome.

    有什么建议吗?最好是指向好的示例代码的指针。

3 个解决方案

#1


7  

There are some serious problems with that class and it makes things even worse that there is a Microsoft copyright on it.

这个类有一些严重的问题,更糟糕的是它有微软的版权。

There is nothing special about this class. And it makes me wonder why it even exists except as an Adapter over Create/Read/WriteFile. You wouldnt even need this class if you used the SerialPort class in the .NET Framework.

这门课没什么特别的。它让我想知道为什么它会存在,除了作为一个适配器存在于Create/Read/WriteFile之上。如果在. net框架中使用SerialPort类,甚至不需要这个类。

Your CPU usage is because the code goes into an infinite loop while waiting for the device to have enough available data. The code might as well say while(1); If you must stick with Win32 and C++ you can look into Completion Ports and setting the OVERLAPPED flag when invoking CreateFile. This way you can wait for data in a separate worker thread.

您使用CPU是因为代码在等待设备拥有足够可用数据时进入无限循环。代码也可以是while(1);如果您必须使用Win32和c++,那么您可以在调用CreateFile时查看完成端口并设置重叠标志。这样,您可以在一个单独的worker线程中等待数据。

You need to be careful when communicating to multiple COM ports. It has been a long time since I've done C++ but I believe the static buffer szBuff in the Read and Write methods is static for ALL instances of that class. It means if you invoke Read against two different COM ports "at the same time" you will have unexpected results.

在与多个COM端口通信时需要小心。我已经很长时间没有使用c++了,但是我相信读写方法中的静态缓冲区szBuff对该类的所有实例都是静态的。这意味着如果您同时对两个不同的COM端口调用Read,您将得到意想不到的结果。

As for the problems on some of the XP machines, you will most certainly figure out the problem if you check GetLastError after each Read/Write and log the results. It should be checking GetLastError anyways as it sometimes isn't always an "error" but a request from the subsystem to do something else in order to get the result you want.

对于某些XP机器上的问题,如果您在每次读取/写入之后检查GetLastError并记录结果,那么您肯定会发现问题。它应该检查GetLastError,因为它有时并不总是一个“错误”,而是子系统请求做一些其他的事情以得到您想要的结果。

You can get rid of the the whole while loop for blocking if you set COMMTIMEOUTS correctly. If there is a specific timeout for a Read operation use SetCommTimeouts before you perform the read.

如果您正确地设置了逗号超时,您可以消除阻塞的整个while循环。如果读操作有特定的超时,请在执行读操作之前使用SetCommTimeouts。

I set ReadIntervalTimeout to the max timeout to ensure that the Read won't return quicker than m_nTimeOut. This value will cause Read to return if the time elapses between any two bytes. If it was set to 2 milliseconds and the first byte came in at t, and the second came in at t+1, the third at t+4, ReadFile would of only returned the first two bytes since the interval between the bytes was surpassed. ReadTotalTimeoutConstant ensures that you will never wait longer than m_nTimeOut no matter what.

我将ReadIntervalTimeout设置为最大超时,以确保读取不会比m_nTimeOut返回得更快。如果时间在两个字节之间流逝,这个值将导致Read返回。如果它被设置为2毫秒,第一个字节在t中出现,第二个在t+1中出现,第三个在t+4, ReadFile只返回前两个字节,因为字节之间的间隔被超越了。ReadTotalTimeoutConstant确保无论如何都不会等待超过m_nTimeOut。

maxWait = BytesToRead * ReadTotalTimeoutMultiplier + ReadTotalTimeoutConstant. Thus (BytesToRead * 0) + m_nTimeout = m_nTimeout

maxWait = BytesToRead * readtotaltimeout乘数+ ReadTotalTimeoutConstant。因此(BytesToRead * 0) + m_nTimeout = m_nTimeout

BOOL CPort::SetupConnection(void) 
{
      // Snip...
      COMMTIMEOUTS comTimeOut;                   
      comTimeOut.ReadIntervalTimeout = m_nTimeOut; // Ensure's we wait the max timeout
      comTimeOut.ReadTotalTimeoutMultiplier = 0;
      comTimeOut.ReadTotalTimeoutConstant = m_nTimeOut;
      comTimeOut.WriteTotalTimeoutMultiplier = 0;
      comTimeOut.WriteTotalTimeoutConstant = m_nTimeOut;
      SetCommTimeouts(m_hDevice,&comTimeOut);
}

// If return value != nBytesToRead check check GetLastError()
// Most likely Read timed out.
int CPort::BlockRead (char * pcInputBuffer, int nBytesToRead)
{
      DWORD dwBytesRead; 
      if (FALSE == ReadFile(
             m_hDevice, 
             pcInputBuffer, 
             nBytesToRead, 
             &dwBytesRead, 
             NULL))
      {
           // Check GetLastError
           return dwBytesRead;
      }
      return dwBytesRead;
}

I have no idea if this is completely correct but it should give you an idea. Remove the ReadChar and ReadString methods and use this if your program relies on things being synchronous. Be careful about setting high time outs also. Communications are fast, in the milliseconds.

我不知道这是否完全正确,但它应该给你一个想法。删除ReadChar和ReadString方法,如果您的程序依赖于同步的内容,则使用此方法。也要注意设置高超时。通信速度很快,以毫秒为单位。

#2


1  

Here's a terminal program I wrote years ago (probably at least 15 years ago, now that I think about it). I just did a quick check, and under Windows 7 x64, it still seems to work reasonably well -- connects to my GPS, read, and displays the data coming from it.

这是我几年前写的一个终端程序(可能至少是15年前写的,现在我想起来了)。我刚刚做了一个快速检查,在Windows 7 x64下,它仍然运行得很好——连接到我的GPS,读取并显示来自它的数据。

If you look at the code, you can see that I didn't spend much time selecting the comm timeout values. I set them all to 1, intending to experiment with longer timeouts until the CPU usage was tolerable. To make a long story short, it uses so little CPU time I've never bothered. For example, on the Task Manager's CPU usage graph, I can't see any difference between it running and not. I've left it running collecting data from the GPS for a few hours at a time, and the Task Manager still says its total CPU usage is 0:00:00.

如果您查看代码,您会发现我没有花太多时间选择comm超时值。我将它们都设置为1,以实验更长的超时,直到CPU使用率可以忍受为止。长话短说,它占用了我很少的CPU时间。例如,在任务管理器的CPU使用情况图中,我看不出运行和不运行之间有什么区别。我让它每次运行几个小时,从GPS收集数据,任务管理器仍然说它的总CPU使用量是0:00:00。

Bottom line: I'm pretty sure it could be more efficient -- but sometimes good enough is good enough. Given how heavily I don't use it any more, and the chances of ever adding anything like file transfer protocols, making it more efficient probably won't ever get to the top of the pile of things to do.

底线:我很肯定它会更有效率——但是有时候足够好就足够了。考虑到我不再大量地使用它,以及添加文件传输协议等任何东西的可能性,使它更高效可能永远都无法完成最重要的工作。

#include <stdio.h>
#include <conio.h>
#include <string.h>

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

void system_error(char *name) {
// Retrieve, format, and print out a message from the last error.  The 
// `name' that's passed should be in the form of a present tense noun 
// (phrase) such as "opening file".
//
    char *ptr = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        0,
        GetLastError(),
        0,
        (char *)&ptr,
        1024,
        NULL);

    fprintf(stderr, "\nError %s: %s\n", name, ptr);
    LocalFree(ptr);
}

int main(int argc, char **argv) {

    int ch;
    char buffer[64];
    HANDLE file;
    COMMTIMEOUTS timeouts;
    DWORD read, written;
    DCB port;
    HANDLE keyboard = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE screen = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode;
    char port_name[128] = "\\\\.\\COM3";
    char init[] = "";

    if ( argc > 2 )
        sprintf(port_name, "\\\\.\\COM%s", argv[1]);

    // open the comm port.
    file = CreateFile(port_name,
        GENERIC_READ | GENERIC_WRITE,
        0, 
        NULL, 
        OPEN_EXISTING,
        0,
        NULL);

    if ( INVALID_HANDLE_VALUE == file) {
        system_error("opening file");
        return 1;
    }

    // get the current DCB, and adjust a few bits to our liking.
    memset(&port, 0, sizeof(port));
    port.DCBlength = sizeof(port);
    if (!GetCommState(file, &port))
        system_error("getting comm state");
    if (!BuildCommDCB("baud=19200 parity=n data=8 stop=1", &port))
        system_error("building comm DCB");
    if (!SetCommState(file, &port))
        system_error("adjusting port settings");

    // set short timeouts on the comm port.
    timeouts.ReadIntervalTimeout = 1;
    timeouts.ReadTotalTimeoutMultiplier = 1;
    timeouts.ReadTotalTimeoutConstant = 1;
    timeouts.WriteTotalTimeoutMultiplier = 1;
    timeouts.WriteTotalTimeoutConstant = 1;
    if (!SetCommTimeouts(file, &timeouts))
        system_error("setting port time-outs.");

    // set keyboard to raw reading.
    if (!GetConsoleMode(keyboard, &mode))
        system_error("getting keyboard mode");
    mode &= ~ ENABLE_PROCESSED_INPUT;
    if (!SetConsoleMode(keyboard, mode))
        system_error("setting keyboard mode");

    if (!EscapeCommFunction(file, CLRDTR))
        system_error("clearing DTR");
    Sleep(200);
    if (!EscapeCommFunction(file, SETDTR))
        system_error("setting DTR");

    if (!WriteFile(file, init, sizeof(init), &written, NULL))
        system_error("writing data to port");

    if (written != sizeof(init))
        system_error("not all data written to port");

    // basic terminal loop:
    do {
        // check for data on port and display it on screen.
        ReadFile(file, buffer, sizeof(buffer), &read, NULL);
        if (read)
            WriteFile(screen, buffer, read, &written, NULL);

        // check for keypress, and write any out the port.
        if ( kbhit() ) {
            ch = getch();
            WriteFile(file, &ch, 1, &written, NULL);
        }
    // until user hits ctrl-backspace.
    } while ( ch != 127);

    // close up and go home.
    CloseHandle(keyboard);
    CloseHandle(file);
    return 0;
}

#3


0  

I would add

我想补充

Sleep(2);

to the while loop in CPort::WaitForQueToFill()

对CPort中的while循环::WaitForQueToFill()

This will give the OS a chance to actually place some bytes in the queue.

这将使操作系统有机会在队列中实际放置一些字节。

#1


7  

There are some serious problems with that class and it makes things even worse that there is a Microsoft copyright on it.

这个类有一些严重的问题,更糟糕的是它有微软的版权。

There is nothing special about this class. And it makes me wonder why it even exists except as an Adapter over Create/Read/WriteFile. You wouldnt even need this class if you used the SerialPort class in the .NET Framework.

这门课没什么特别的。它让我想知道为什么它会存在,除了作为一个适配器存在于Create/Read/WriteFile之上。如果在. net框架中使用SerialPort类,甚至不需要这个类。

Your CPU usage is because the code goes into an infinite loop while waiting for the device to have enough available data. The code might as well say while(1); If you must stick with Win32 and C++ you can look into Completion Ports and setting the OVERLAPPED flag when invoking CreateFile. This way you can wait for data in a separate worker thread.

您使用CPU是因为代码在等待设备拥有足够可用数据时进入无限循环。代码也可以是while(1);如果您必须使用Win32和c++,那么您可以在调用CreateFile时查看完成端口并设置重叠标志。这样,您可以在一个单独的worker线程中等待数据。

You need to be careful when communicating to multiple COM ports. It has been a long time since I've done C++ but I believe the static buffer szBuff in the Read and Write methods is static for ALL instances of that class. It means if you invoke Read against two different COM ports "at the same time" you will have unexpected results.

在与多个COM端口通信时需要小心。我已经很长时间没有使用c++了,但是我相信读写方法中的静态缓冲区szBuff对该类的所有实例都是静态的。这意味着如果您同时对两个不同的COM端口调用Read,您将得到意想不到的结果。

As for the problems on some of the XP machines, you will most certainly figure out the problem if you check GetLastError after each Read/Write and log the results. It should be checking GetLastError anyways as it sometimes isn't always an "error" but a request from the subsystem to do something else in order to get the result you want.

对于某些XP机器上的问题,如果您在每次读取/写入之后检查GetLastError并记录结果,那么您肯定会发现问题。它应该检查GetLastError,因为它有时并不总是一个“错误”,而是子系统请求做一些其他的事情以得到您想要的结果。

You can get rid of the the whole while loop for blocking if you set COMMTIMEOUTS correctly. If there is a specific timeout for a Read operation use SetCommTimeouts before you perform the read.

如果您正确地设置了逗号超时,您可以消除阻塞的整个while循环。如果读操作有特定的超时,请在执行读操作之前使用SetCommTimeouts。

I set ReadIntervalTimeout to the max timeout to ensure that the Read won't return quicker than m_nTimeOut. This value will cause Read to return if the time elapses between any two bytes. If it was set to 2 milliseconds and the first byte came in at t, and the second came in at t+1, the third at t+4, ReadFile would of only returned the first two bytes since the interval between the bytes was surpassed. ReadTotalTimeoutConstant ensures that you will never wait longer than m_nTimeOut no matter what.

我将ReadIntervalTimeout设置为最大超时,以确保读取不会比m_nTimeOut返回得更快。如果时间在两个字节之间流逝,这个值将导致Read返回。如果它被设置为2毫秒,第一个字节在t中出现,第二个在t+1中出现,第三个在t+4, ReadFile只返回前两个字节,因为字节之间的间隔被超越了。ReadTotalTimeoutConstant确保无论如何都不会等待超过m_nTimeOut。

maxWait = BytesToRead * ReadTotalTimeoutMultiplier + ReadTotalTimeoutConstant. Thus (BytesToRead * 0) + m_nTimeout = m_nTimeout

maxWait = BytesToRead * readtotaltimeout乘数+ ReadTotalTimeoutConstant。因此(BytesToRead * 0) + m_nTimeout = m_nTimeout

BOOL CPort::SetupConnection(void) 
{
      // Snip...
      COMMTIMEOUTS comTimeOut;                   
      comTimeOut.ReadIntervalTimeout = m_nTimeOut; // Ensure's we wait the max timeout
      comTimeOut.ReadTotalTimeoutMultiplier = 0;
      comTimeOut.ReadTotalTimeoutConstant = m_nTimeOut;
      comTimeOut.WriteTotalTimeoutMultiplier = 0;
      comTimeOut.WriteTotalTimeoutConstant = m_nTimeOut;
      SetCommTimeouts(m_hDevice,&comTimeOut);
}

// If return value != nBytesToRead check check GetLastError()
// Most likely Read timed out.
int CPort::BlockRead (char * pcInputBuffer, int nBytesToRead)
{
      DWORD dwBytesRead; 
      if (FALSE == ReadFile(
             m_hDevice, 
             pcInputBuffer, 
             nBytesToRead, 
             &dwBytesRead, 
             NULL))
      {
           // Check GetLastError
           return dwBytesRead;
      }
      return dwBytesRead;
}

I have no idea if this is completely correct but it should give you an idea. Remove the ReadChar and ReadString methods and use this if your program relies on things being synchronous. Be careful about setting high time outs also. Communications are fast, in the milliseconds.

我不知道这是否完全正确,但它应该给你一个想法。删除ReadChar和ReadString方法,如果您的程序依赖于同步的内容,则使用此方法。也要注意设置高超时。通信速度很快,以毫秒为单位。

#2


1  

Here's a terminal program I wrote years ago (probably at least 15 years ago, now that I think about it). I just did a quick check, and under Windows 7 x64, it still seems to work reasonably well -- connects to my GPS, read, and displays the data coming from it.

这是我几年前写的一个终端程序(可能至少是15年前写的,现在我想起来了)。我刚刚做了一个快速检查,在Windows 7 x64下,它仍然运行得很好——连接到我的GPS,读取并显示来自它的数据。

If you look at the code, you can see that I didn't spend much time selecting the comm timeout values. I set them all to 1, intending to experiment with longer timeouts until the CPU usage was tolerable. To make a long story short, it uses so little CPU time I've never bothered. For example, on the Task Manager's CPU usage graph, I can't see any difference between it running and not. I've left it running collecting data from the GPS for a few hours at a time, and the Task Manager still says its total CPU usage is 0:00:00.

如果您查看代码,您会发现我没有花太多时间选择comm超时值。我将它们都设置为1,以实验更长的超时,直到CPU使用率可以忍受为止。长话短说,它占用了我很少的CPU时间。例如,在任务管理器的CPU使用情况图中,我看不出运行和不运行之间有什么区别。我让它每次运行几个小时,从GPS收集数据,任务管理器仍然说它的总CPU使用量是0:00:00。

Bottom line: I'm pretty sure it could be more efficient -- but sometimes good enough is good enough. Given how heavily I don't use it any more, and the chances of ever adding anything like file transfer protocols, making it more efficient probably won't ever get to the top of the pile of things to do.

底线:我很肯定它会更有效率——但是有时候足够好就足够了。考虑到我不再大量地使用它,以及添加文件传输协议等任何东西的可能性,使它更高效可能永远都无法完成最重要的工作。

#include <stdio.h>
#include <conio.h>
#include <string.h>

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

void system_error(char *name) {
// Retrieve, format, and print out a message from the last error.  The 
// `name' that's passed should be in the form of a present tense noun 
// (phrase) such as "opening file".
//
    char *ptr = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        0,
        GetLastError(),
        0,
        (char *)&ptr,
        1024,
        NULL);

    fprintf(stderr, "\nError %s: %s\n", name, ptr);
    LocalFree(ptr);
}

int main(int argc, char **argv) {

    int ch;
    char buffer[64];
    HANDLE file;
    COMMTIMEOUTS timeouts;
    DWORD read, written;
    DCB port;
    HANDLE keyboard = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE screen = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode;
    char port_name[128] = "\\\\.\\COM3";
    char init[] = "";

    if ( argc > 2 )
        sprintf(port_name, "\\\\.\\COM%s", argv[1]);

    // open the comm port.
    file = CreateFile(port_name,
        GENERIC_READ | GENERIC_WRITE,
        0, 
        NULL, 
        OPEN_EXISTING,
        0,
        NULL);

    if ( INVALID_HANDLE_VALUE == file) {
        system_error("opening file");
        return 1;
    }

    // get the current DCB, and adjust a few bits to our liking.
    memset(&port, 0, sizeof(port));
    port.DCBlength = sizeof(port);
    if (!GetCommState(file, &port))
        system_error("getting comm state");
    if (!BuildCommDCB("baud=19200 parity=n data=8 stop=1", &port))
        system_error("building comm DCB");
    if (!SetCommState(file, &port))
        system_error("adjusting port settings");

    // set short timeouts on the comm port.
    timeouts.ReadIntervalTimeout = 1;
    timeouts.ReadTotalTimeoutMultiplier = 1;
    timeouts.ReadTotalTimeoutConstant = 1;
    timeouts.WriteTotalTimeoutMultiplier = 1;
    timeouts.WriteTotalTimeoutConstant = 1;
    if (!SetCommTimeouts(file, &timeouts))
        system_error("setting port time-outs.");

    // set keyboard to raw reading.
    if (!GetConsoleMode(keyboard, &mode))
        system_error("getting keyboard mode");
    mode &= ~ ENABLE_PROCESSED_INPUT;
    if (!SetConsoleMode(keyboard, mode))
        system_error("setting keyboard mode");

    if (!EscapeCommFunction(file, CLRDTR))
        system_error("clearing DTR");
    Sleep(200);
    if (!EscapeCommFunction(file, SETDTR))
        system_error("setting DTR");

    if (!WriteFile(file, init, sizeof(init), &written, NULL))
        system_error("writing data to port");

    if (written != sizeof(init))
        system_error("not all data written to port");

    // basic terminal loop:
    do {
        // check for data on port and display it on screen.
        ReadFile(file, buffer, sizeof(buffer), &read, NULL);
        if (read)
            WriteFile(screen, buffer, read, &written, NULL);

        // check for keypress, and write any out the port.
        if ( kbhit() ) {
            ch = getch();
            WriteFile(file, &ch, 1, &written, NULL);
        }
    // until user hits ctrl-backspace.
    } while ( ch != 127);

    // close up and go home.
    CloseHandle(keyboard);
    CloseHandle(file);
    return 0;
}

#3


0  

I would add

我想补充

Sleep(2);

to the while loop in CPort::WaitForQueToFill()

对CPort中的while循环::WaitForQueToFill()

This will give the OS a chance to actually place some bytes in the queue.

这将使操作系统有机会在队列中实际放置一些字节。