利用串口进行实时数据采集的方案

华泽玺 王长林 章冲

(西南交通大学计算机与通信工程学院 四川 成都 610031)

摘 要:本文描述了一种智能设备主动向串口发送数据,应用计算机多线程技术建立串口监听线程,改善传统的握手、数据传输、断开连接的模式,实现串口实时数据采集,并给出了方案的核心程序。
关键词:串口 通信 串口监听 数据采集 实时性
THE METHOD OF USING SERIAL INTERFACE FOR REAL-TIME DATA COLLCTION
Ze-Xi HUA Chang-Lin WANG Chong ZHANG
(School Of Computer and Communication Engineering,
Southwest Jiaotong University , Province Si Chuan , Chengdu 610031 , China)
Abstract: This paper describes a kind intelligent equipment which transmits data to serial interface and uses multithreading technology of computer for building the serial port monitors thread program. It realizes Real-time data collection, improving traditional method of handshake transmitting data and end-connecting. And this paper covers the method of the kernel program.
Key words: serial interface, communication, serial port data monitor, data collection, Real-time character
1 前言
串行接口是计算机通信与智能设备数据采集较为广泛应用的接口设备,成本低、使用方便、较高的性价比是它的优势,但对实时性要求较高的应用系统仍感到有些不足,为此本文提出并实现了一种提高串口数据采集实时性的方案。
2 传统串口通信方式
一般的传统串口通信方式在发送数据前,按照自定义串口通信协议(SPCP)发送方将应用程序将发送的数据进行分帧,然后按下面的步骤进行通信。
(1)握手:由发送端发SYN信号,接收端应答发出ACK信号,发送端发控制信号,如果正确,才可以进行数据的传输。
(2)数据传输:在传输过程中如果出现问题则重新握手,再进行数据传输,如果没有问题,则要收到接受端的应答信号ACK。
(3)断开连接:通过发送ABORT控制帧和应答ACK结束通信。
在我们的实际测试中,如果用智能设备进行数据采集,然后通过串口把数据传入微机,每次完成一次数据采集,经RS-232串口实验,在数据量很小的情况下,传输的时间大约在55ms~75ms之间,这对于有些数据采集的实时性要需是不能满足要求的,为此本文描述一个智能设备主动发送/串口监听的模式,可以大大提高数据采集的实时性。
3 主动发送/串口监听模式
所谓的主动发送是指智能设备传输数据没有握手的过程和应答的过程,只有按照事先给定的通信协议定时不断发送的过程,发送过程中可以带有校验码,这样就可以节省出很多时间,提高数据的实时性。串口监听是计算机通过多线程技术,对串口进行数据监听,进行事件触发,一旦监听到串口有数据来到马上进行接收到缓冲区,再进行一些差错处理,对数据个数,有效性,通过上下数据比较确认数据的正确性等。由于对于不同设备处理情况不同,本文给出了VC编制的核心程序。
3.1 建立智能设备的主动发送模式
这部分是对智能设备采集的数据放入设备内存进行处理,按照给定的通信协议进行整理,按照给定的周期向计算机串口发送,只管这样不停地发下去。
3.2 建立计算机多线程和串口监听模式
这部分是实现这一方案的关键所在,首先我们先建立一个通信的类。实现步骤如下:
·加入宏定义、类定义与成员变量。
·加入构造与串口初始化成员函数(InitPort)。
·加入串口监听成员函数StartMonitoring、RestartMonitoring和StopMonitoring。
·加入串口监听线程静态函数CommThread。
·加入串口读函数ReceiveChar、ReadBlock。
·加入属性获取和错误处理函数。
对端口数据采用事件驱动方式,事件驱动方式通过设置事件通知,用SetCommMask()函数指定了有用事件的后,应用程序可调用WaitCommEvent()函数来等待事件发生。
//创建监听线程
BOOL CSerialPortEx::StartMonitoring()
{
if (!(m_Thread = AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
//重新开始监听线程
BOOL CSerialPortEx::RestartMonitoring()
{
TRACE("Thread resumed\n");
m_Thread->ResumeThread();
return TRUE;
}
//终止监听线程
BOOL CSerialPortEx::StopMonitoring()
{
TRACE("Thread suspended\n");
if(m_bThreadAlive)
{
m_Thread->SuspendThread();
m_bThreadAlive=FALSE;
}
return TRUE;
}
//下面为串口监听线程:
UINT CSerialPortEx::CommThread(LPVOID pParam)
{
CSerialPortEx *port = (CSerialPortEx*)pParam;
// 指示当前线程正在运行
port->m_bThreadAlive = TRUE;
DWORD BytesTransfered = 0;
DWORD Event = 0;
DWORD CommEvent = 0;
DWORD dwError = 0;
COMSTAT comstat;
BOOL bResult = TRUE;
// 清空串口缓冲区
if (port->m_hComm)
PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// 开始无限循环,当该线程alive时该循环一直进行
for (;;)
{ //检测线路状态,等待SetCommMask指定事件之一发生,以异步方式操作串口,所以该调用会立刻返回
bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{ //串口没有数据时的正常返回值
break;
}
case 87:
{//在Windows NT 下,该项值将会返回,原因不明,但它是正常返回值
break;
}
default:
{ //其它返回值表示有错误发生
port->ProcessErrorMessage("WaitCommEvent()");
break;
}
}
}
else
{//WaitCommEvent以后,要用ClearCommError清除事件的Flag,以便进行下一轮
//WaitCommEvent,同时这个API可以获得更详细的事件信息
bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
//确认串口中无字符则进入主监视过程,重新开始循环
if (comstat.cbInQue == 0)
continue;
} // end if bResult
//主监视函数,该函数将阻塞本线程直至等待的某一事件发生
Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);
switch (Event)
{
case 0: //shutdown事件
{ //将中止本线程
port->m_bThreadAlive = FALSE;
AfxEndThread(100);
break;
}
case 1: // 处理串口状态事件
{
GetCommMask(port->m_hComm, &CommEvent);
if (CommEvent & EV_CTS)
::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_CTS_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
if (CommEvent & EV_RXFLAG)
::SendMessage(port->m_pOwner->m_hWnd,WM_COMM_RXFLAG_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
if (CommEvent & EV_BREAK)
::SendMessage(port->m_pOwner->m_hWnd, M_COMM_BREAK_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
if (CommEvent & EV_ERR)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
if (CommEvent & EV_RING)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);
if (CommEvent & EV_RXCHAR)
// 检测是ReadBlock还是ReadChar模式
if(!port->m_bBlockRead)
ReceiveChar(port, comstat);
break;
}
case 2: // 处理写事件
{
// Write character event from port
WriteChar(port);
break;
}
} // end switch
} // 结束串口主循环
return 0;
}
3.3 计算机读取所需数据
通过处理的数据放在一个缓冲区中,而这个缓冲区中的数据则可以认为是直接从设备上采集过来的数据,计算机可以根据数据的需求,随时可以从这个缓冲区中取中取数据,则可以采得的数据为实时数据。从而改善了串口的实时性较差的弱点。
4 适用范围
主动发送/串口监听模式虽然可以改善实时取数据的问题,这种方案充分利用了串口的优势,提高设备的性价比,但有些情况下我们还是不采用这种方案,这是由串口本身的性能所决定的。
第一,串行传输本身比并行传输要慢,如果在一次采集数据量非常大的,实时性要求较高的情况下,不宜采用这种方案。
第二,串口的传输速率相对而言比较慢,如果在工控中采集实时率比较高、精度比较高的情况下,也不宜采用这种处理方案。
5 结束语
随着科技的进步,社会的发展,计算机在工业控制中会发挥越来越重要的作用,利用串口的优势,实现串口数据的实时采集会有它更广泛的用途。
参考文献:
[1] 求是科技,谭思亮,邹超群等.Visual C++串口通信工程开发实例导航.人民邮电出版社.2003年4月
[2] David J.Kruglinski Scot Wingo George Shepherd .Programming Microsoft Visual C++ 6.0 技术内幕(第五版).北京希望电子出版社.2001