接下去是一步比较关键的操作,建立工作者线程,用来监听串口消息,如果发现inbuf中有接收到的字符,及时通知相应处理函数进行处理。
调用MFC全局函数AfxBeginThread建立线程。好的线程应该短小精悍,所以,我在这个线程里面其实什么事也不做,只是起到通知别的函数的作用。
m_pThread=AfxBeginThread(CommProc,
this,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED,// 挂起线程
NULL);
m_pThread就是指向我新创建的线程的指针。
线程函数如下,有点长,但是已经是最简单的线程了:
//串口线程
UINT CommProc(LPVOID lParam)
{
COMSTAT commstat;//这个结构体主要是用来获取端口信息的
DWORD dwError;
DWORD dwMask;
DWORD dwLength;
OVERLAPPED overlapped;
//OVERLAPPED结构体用来设置I/O异步,具体可以参见MSDN
memset(&overlapped, 0, sizeof(OVERLAPPED));
//初始化OVERLAPPED对象
overlapped.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);
//创建CEvent对象
CUartDlg* dlg=(CUartDlg*)lParam;
if(dlg->m_hCom==NULL)
{
AfxMessageBox("串口句柄为空!");
return -1;
}
while(dlg->m_bConnected)
{
ClearCommError(dlg->m_hCom, &dwError, &commstat);
if(commstat.cbInQue)
//如果串口inbuf中有接收到的字符就执行下面的操作
{
WaitForSingleObject(dlg->m_hPostMsgEvent, INFINITE);
//无线等待。。。
ResetEvent(dlg->m_hPostMsgEvent);
//设置CEvent对象为无信号状态
::PostMessage(dlg->m_hWnd, WM_COMMSG, EV_RXCHAR, 0);
//发送特定信息,用来通知特定函数进行处理
continue;
}
if(!WaitCommEvent(dlg->m_hCom, &dwMask, &overlapped))
{
if(GetLastError()==ERROR_IO_PENDING)
//如果操作被挂起,也就说正在读取或这在写,则进行下面的操作
GetOverlappedResult(dlg->m_hCom,
&overlapped, &dwLength, TRUE);
//无限等待这个I/O操作的完成
else
{
CloseHandle(overlapped.hEvent);
return (UINT)-1;
}
}
}
CloseHandle(overlapped.hEvent);
return 0;
}
因为是多线程,所以,要注意的是对临界资源的访问问题,也就是说互斥问题,避免死锁现象的发生。所以,在这个线程中多次使用了事件对象CEvent,通过它来标志串口有没有被占据,和标志是否正在进行读取串口(串口无法同时进行读写操作)。具体的大家可以看我的代码注释 : )
这是我对这个线程发出的消息进行处理的函数:
void CUartDlg::OnComMsg(WPARAM wParam, LPARAM lParam)
{
char buf[MAXBLOCK/4];
CString str;
int nLength;
int nStartChar, nEndChar;
if(!m_bConnected || (wParam & EV_RXCHAR)!=EV_RXCHAR)
// 是否是EV_RXCHAR事件?
{
SetEvent(m_hPostMsgEvent);
// 允许发送下一个线程读取消息
return;
}
nLength=ReadComm(buf,100);
buf[nLength]='\0';
if(nLength)
{
//IDC_EDIT_EDIT是我在一个对话框上一个CEdit控件的ID号,大家可设置成
//自己的控件ID号
GetDlgItem(IDC_EDIT_EDIT)->SetFocus();
CString str(buf);
m_strMessage+=str;
UpdateData(FALSE);
CEdit* pEdit=(CEdit*)GetDlgItem(IDC_EDIT_EDIT);
pEdit->GetSel(nStartChar, nEndChar);
pEdit->SetSel(nStartChar-2, nEndChar-2);
}
SetEvent(m_hPostMsgEvent); // 允许发送下一个线程读取消息
}
第三步,已经建立好了工作者线程,那么接下去我们就可以进行串口的读写操作了。
DWORD CUartDlg::ReadComm(char *buf, DWORD dwLength)
{
COMSTAT comstat;
DWORD dwError;
DWORD length;
DWORD dwByteReaded;
ClearCommError(m_hCom, &dwError, &comstat);
length=min(comstat.cbInQue, dwLength);
if(!ReadFile(m_hCom, buf, length, &dwByteReaded, &m_osRead))
return 0;
return dwByteReaded;
}
这是读串口函数;
DWORD CUartDlg::WriteComm(char *buf, DWORD dwLength)
{
BOOL fState=FALSE;
DWORD length=0;
COMSTAT ComStat;
DWORD dwErrorFlags;
//ClearCommError是用来清除Comm中的错误,从而可以在下面的代码通过
//GetLastError抓取错误
ClearCommError(m_hCom,&dwErrorFlags,&ComStat);
fState=WriteFile(m_hCom,buf,dwLength,&length,&m_osWrite);
if(!fState)
{
if(GetLastError()==ERROR_IO_PENDING)
{
SetEvent(m_osWrite.hEvent);
while(!GetOverlappedResult(m_hCom,&m_osWrite,&length,TRUE))// 等待
{
if(GetLastError()==ERROR_IO_INCOMPLETE)
continue;
}
}
else
length=0;
}
return length;
}
这是写串口函数。这两个函数其实本质是一样的,操作过程也近似,大家可以参考着写。
第四步,好了,现在一个串口程序大致上已经完工了,呵呵,是不是挺繁琐?确实,用windows API函数进行硬件层次的编程都是比较繁琐的。还有一点,就是在结束程序的时候,千万不要忘了关闭串口的句柄,否则容易造成内存泄露的问题!
void CUartDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
if(m_bConnected)
{
m_bConnected=FALSE;
SetEvent(m_hPostMsgEvent);
SetCommMask(m_hCom, 0);
WaitForSingleObject(m_pThread->m_hThread, INFINITE);
m_pThread=NULL;
CloseHandle(m_hCom);
}
CDialog::OnClose();
}
总结:串口通讯应用非常广泛,特别是在硬件设计领域,更是没有串口不行。但是VC爱好者中懂串口编程的不多。我想,还是大家比较喜欢上层的东西吧。
在编写这个程序的时候,大体上已经写的差不多了,可以读取ARM机串口发送过来的字符,并显示出来,但是就是不能发送通过串口发送命令给ARM机。我在网上看了好多信息,也查阅了很多相关书籍,仍然没有找到答案。最后只能自己埋头一句代码一句代码得找,找了两天,还是没有找到,人差不多已经到了崩溃的边缘了,最后突然灵感发现,将错误锁定在dcb参数设定上。原来,我在dcb硬件握手参数上设置错了,那个fOutxCtsFlow参数应该设置成FALSE,否则串口读操作将一直处于阻塞状态。当时我差不多想亲吻每一个人,呵呵,程序员常常是带有一点病态的 : ) 希望大家以后不要犯我这样的错误。
还有,就是因为基于对话框(Dialog-based)的应用程序是不能接收WM_CHAR这个消息的,所以我在进行写串口操作时,重载了PreTranslateMessage这个函数,然后在这个函数内部对消息进行分检处理,起到了很好的效果。大家可以试一试。