【发布时间】:2017-07-26 11:22:17
【问题描述】:
我在 Visual C++ 2008 中开发了一个应用程序,用于从 COM 端口定期(50 毫秒)读取数据。为了定期读取数据,我将读取函数放在OnTimer 函数中,并且因为我不希望 GUI 的其余部分挂起,所以我从线程中调用了这个计时器函数。我已经把代码放在下面了。
应用程序运行良好,但显示以下意外行为:在数据源(硬件设备甚至数据模拟器)停止发送数据后,我的应用程序继续接收数据一段时间,该时间与读取功能已经运行了多长时间(EDIT:此超出时间段与发送数据的时间段相同)。因此,如果我立即启动和停止数据流,这将反映在我的 GUI 上,但如果我启动数据流并在 10 秒后停止它,我的 GUI 将继续显示数据 10 秒以上(已编辑)。
在用尽了所有的调试尝试后,我做了以下观察:
- 如上所述,这个超长的运行时间与硬件发送数据的时间成正比。
- 传入数据的频率为 50 毫秒,因此要接收 10 秒的数据,我的 GUI 必须再接收大约 200 个数据包。
- 我声明的唯一缓冲区是
abBuffer,它只是一个固定大小的字节数组。我认为这不会增加大小,因此这些数据存储在某个地方。 - 如果我更改数据包中的某些内容,可以理解,此更改会在延迟后显示在 GUI 上(由于上述几点)。但这意味着在 COM 端口接收到的数据存储在某个可变大小的缓冲区中,我的读取函数正在从中读取数据。
- 我已经对读取和处理周期进行了计时。后者是瞬时的,而前者很少(1000 次读取中的 3 次(遵循不可识别的模式))需要 16 毫秒。这完全在 GUI 每次读取的 50 毫秒窗口内。
以下是我的线程和定时器代码:
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);
【问题讨论】:
-
来自 COM 端口的数据由操作系统存储在系统缓冲区中。您的应用程序不直接从硬件读取数据,而只是从中间系统缓冲区中获取数据。 “传入数据的频率是 50 毫秒” -- 频率是一个速率(例如每秒 X 次),但您只是提到了一个时间间隔。
-
@sawdust 我很抱歉没有澄清频率。每秒 20 次(连续数据包之间为 50 毫秒)。我假设
ReadFileAPI 调用将从端口读取。即使不是这种情况,如果在发送数据时立即开始读取,为什么会导致延迟?会不会和定时器功能有关? -
@sawdust 另外,关于系统缓冲区,如果硬件在我的应用程序开始读取之前发送数据,是否也会读取应用程序正式开始读取之前的所有数据?
-
"如果硬件在之前发送数据..." -- 操作系统通常会在设备打开时分配此系统缓冲区,因此之前接收到的不是"读”。更有可能是数据速率。当您可以简单地使用阻塞读取来等待读取完成时,使用计时器读取是多余的。这假设数据包实际上每 50 毫秒发送一次。但是,由于您看到了额外的数据运行,这意味着您施加的每秒 20 次的读取速率比发送数据的实际速率要慢。因此,程序正在慢慢地产生回流。
-
@sawdust 谢谢你的回复。我检查了系统调用读取函数之间的平均间隔。事实证明,这大约是 60 毫秒。所以 Windows 只是确保间隔大于我指定的时间。有什么办法可以改进这个实现。例如,如果读取功能在第一次读取后会结束,我将如何使用
WaitCommEvent定期读取?
标签: visual-c++ mfc serial-port