串行数据采集程序从缓冲区读取

时间:2017-07-25 07:39:17

标签: visual-c++ mfc serial-port

我在Visual C ++ 2008中开发了一个应用程序,用于从COM端口定期(50ms)读取数据。为了定期读取数据,我将读取函数放在OnTimer函数中,因为我不希望GUI的其余部分挂起,所以我在一个线程中调用了这个定时器函数。我已将代码放在下面。

应用程序运行正常,但它显示以下意外行为:在数据源(硬件设备甚至数据模拟器)停止发送数据后,我的应用程序继续接收数据一段时间,该比例与读取功能运行了多长时间(编辑:此超出期间与发送数据的时间段相同)。因此,如果我立即启动和停止数据流,这将反映在我的GUI上,但如果我启动数据流并在十秒后停止,我的GUI将继续显示数据10秒钟(已编辑)。

在用完所有调试尝试后,我做了以下观察:

  1. 如上所述,这个多余的操作时间与硬件发送数据的时间成正比。
  2. 传入数据的频率为50毫秒,因此要接收10秒的数据,我的GUI必须接收大约200多个数据包。
  3. 我声明的唯一缓冲区是abBuffer,它只是一个固定大小的字节数组。我不认为这会增加大小,所以这些数据存储在某个地方。
  4. 如果我更改数据包中的某些内容,可以理解的是,这种更改会在延迟后显示在GUI上(由于上述要点)。但这意味着在COM端口接收的数据存储在一些可变大小的缓冲区中,我的读取函数正从该缓冲区读取数据。
  5. 我已经计划了阅读和处理时间。后者是瞬时的,而前者很少(1000次读取中的3次(没有可辨别的模式))需要16ms。这完全在GUI每次读取的50ms窗口内。
  6. 以下是我的主题和计时器代码:

    UINT CMyCOMDlg::StartThread(LPVOID param)
    {
        THREADSTRUCT *ts = (THREADSTRUCT*)param;
    
        ts->_this->SetTimer(1,50,0);
    
        return 0;
    
    }
    
    //Timer function that is called at regular intervals
    void CMyCOMDlg::OnTimer(UINT_PTR nIDEvent)
    {       
    
        if(m_bCount==true)
        {
            DWORD NoBytesRead;
            BYTE  abBuffer[45];
    
            if(ReadFile((m_hComm),&abBuffer,45,&NoBytesRead,0))
            {
                if(NoBytesRead==45)
                {
                    if(abBuffer[0]==0x10&&abBuffer[1]==0x10||abBuffer[0]==0x80&&abBuffer[1]==0x80)
                    {
                        fnSetData(abBuffer);
                    }
                    else
                    {
                        CString value;                      
                        value.Append("Header match failed");
                        SetDlgItemText(IDC_RXRAW,value);    
                    }
                }
                else
                {
                    CString value;
                    value.Append(LPCTSTR(abBuffer),NoBytesRead);
                    value.Append("\r\nInvalid Packet Size");
                    SetDlgItemText(IDC_RXRAW,value);
                }
            }
            else
            {
                DWORD dwError2 = GetLastError();
                CString error2;
                error2.Format(_T("%d"),dwError2);
                SetDlgItemText(IDC_RXRAW,error2);
            }
    
            fnClear();
        }
        else
        {
            KillTimer(1);
        }
    
        CDialog::OnTimer(nIDEvent);
    
    }
    

    m_bCount只是我用来杀死计时器的标志,ReadFile函数是标准的Windows API调用。 ts是一个包含指向主对话框类的指针的结构,即this

    任何人都可以想到这可能发生的原因吗?我已经尝试了很多东西,而且我的代码也做得很少,我无法弄清楚这种意外行为发生在哪里。

    编辑:

    我正在添加下面使用的COM端口设置和超时:

                dcb.BaudRate = CBR_115200;
                dcb.ByteSize = 8;
                dcb.StopBits = ONESTOPBIT;
                dcb.Parity = NOPARITY;
                SetCommState(m_hComm, &dcb);
    
                _param->_this=this;
    
                COMMTIMEOUTS timeouts;
                timeouts.ReadIntervalTimeout=1;
                timeouts.ReadTotalTimeoutMultiplier = 0;
                timeouts.ReadTotalTimeoutConstant = 10;
                timeouts.WriteTotalTimeoutMultiplier = 1;
                timeouts.WriteTotalTimeoutConstant = 1;
                SetCommTimeouts(m_hComm, &timeouts);
    

1 个答案:

答案 0 :(得分:0)

您正在OnTimer()函数中一次处理一条消息。由于定时器间隔为1秒,但数据源每50毫秒继续发送一次消息,因此您的应用程序无法及时处理所有消息。

您可以添加 while 循环,如下所示:

while(true)
{
    if(::ReadFile(m_hComm, &abBuffer, sizeof(abBuffer), &NoBytesRead, 0))
    {
        if(NoBytesRead == sizeof(abBuffer))
        {
            ...
        }
        else
        {
            ...
            break;
        }
    }
    else
    {
        ...
        break;
    }
}

但您的代码中还有另一个问题。如果您的软件在数据源仍在发送消息时检查消息,则 NoBytesRead 可能小于45.您可能希望将数据存储到消息缓冲区中,如 CString std :: queue< unsigned char>

如果邮件末尾没有包含 NULL 的邮件,则将邮件传递给 CString 对象是不安全的。

此外,如果第一个字节从0x80开始, CString 会将其视为多字节字符串。它可能会导致错误。如果消息不是文字文本字符串,请考虑使用其他数据格式,如 std :: vector< unsigned char>

顺便说一句,您不需要在单独的线程中调用 SetTimer()。踢定时器不需要时间。另外,我建议您在 OnTimer()函数之外的某处调用 KillTimer(),以便代码更直观。

如果数据源持续不断地发送数据,则在打开/关闭COMM端口时可能需要使用 PurgeComm()