为什么串口读取喜欢用timertask 线程,不喜欢开线程

写在前面:
&&&&&&&& 晚上应该继续完成未写完的代码,但Chrome上打开的标签实在太多了,约30个了,必须关掉一些,所以需要把自己看的整理一下然后关掉。本次主要写点MFC环境下多线程串口通信相关的东西,这包括线程创建及控制、串口同步异步操作、内存非法访问(或者说是线程同步)、线程通信、Windows消息响应过程等。
遇到问题:
&&&&&&&& 项目中IO传感器通信模块之前直接写在了主线程中,UI代码和串口通信代码搅合在一起,不利于后期维护,而且有个非常严重的问题,IO通信太忙导致整个系统比较卡,特别是当系统接上超过3个摄像机之后,MFC模态对话框使用Domodal()直接无法打开,卡住了,然后用户就无法操作了,这个问题必须要解决。
解决方案:
&&&&&&&& 单独开辟一个线程来处理所有的串口通信,该IO线程和主线程(负责UI部分)通信,从而更新状态,不能子线程中直接更新UI,参看《》。
具体步骤:
1.创立IO线程并完成消息响应
HANDLE hThread1 = CreateThread( NULL,0,IOControlProc,(LPVOID)(m_pCOMSerialPort),0,&m_dwIOControlThreadId );//创建IO线程
CloseHandle( hThread1 );//关闭线程句柄
CSerialPort
*m_pCOMSerialP//通信串口
DWORD m_dwIOControlThreadId;//线程ID
  IOControlProc线程函数
DWORD WINAPI IOControlProc(LPVOID lpParameter)
CSerialPort *pSerialPort = (CSerialPort*)lpP
UINT_PTR nHUMITimer = 0;
UINT_PTR nIOTimer = 0;
//::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);
nHUMITimer = ::SetTimer(NULL,TEMPHUMICOMM_TIMER,500,(TIMERPROC)OnIOTimer);//温湿度
nIOTimer =::SetTimer(NULL,IOCOMM_TIMER,100,(TIMERPROC)OnIOTimer);//IO
PIOTimerStru pHumiTimer= new IOTimerS
pHumiTimer-&nIdEvent = TEMPHUMICOMM_TIMER;
pHumiTimer-&pSerialPort = pSerialP
PIOTimerStru pIoTimer = new IOTimerS
pIoTimer-&nIdEvent = IOCOMM_TIMER;
pIoTimer-&pSerialPort = pSerialP
PIOTimerStru pIoControl = new IOTimerS
pIoControl-&nIdEvent = TEMPHUMICOMM_TIMER + IOCOMM_TIMER;
pIoControl-&pSerialPort = pSerialP
while(GetMessage(&msg,NULL,0,0))
TranslateMessage(&msg);
if (WM_TIMER == msg.message)
lParam = msg.lP//WM_TIMER回调函数的地址
if (nHUMITimer == msg.wParam)
msg.wParam = (WPARAM)pHumiT
else if (nIOTimer == msg.wParam)
msg.wParam = (WPARAM)pIoT
else if (WM_IOCOMMDATA == msg.message)
msg.message = WM_TIMER;
pIoControl-&wParam = msg.wP
pIoControl-&lParam = msg.lP
msg.wParam = (WPARAM)pIoC
msg.lParam = lP
DispatchMessage(&msg);
delete pHumiT
delete pIoT
delete pIoC
  这里,IO线程有自己的消息循环队列(虽然没有窗口),参看:《》,把这里的代码改成死循环的(参看《》),如下
VOID CALLBACK TimerProc(
HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime
//do some thing
DWORD WINAPI ThreadProc(LPVOID lpParameter)
::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);
while(GetMessage(&msg,NULL,0,0))
TranslateMessage(&msg);
DispatchMessage(&msg);
  我的消息响应函数写成如下
VOID CALLBACK OnIOTimer(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
PIOTimerStru pStru = (PIOTimerStru)idE
if (pStru-&nIdEvent == TEMPHUMICOMM_TIMER)//温湿度模块
static bool bFlag =
if (bFlag)
IOSendData(pStru-&pSerialPort,2,0x0A,0,0x03);
IOSendData(pStru-&pSerialPort,2,0x0A,0,0x04);
bFlag = !bF
else if (pStru-&nIdEvent == IOCOMM_TIMER)//IO控制模块
IOSendData(pStru-&pSerialPort,1,0x10,0,0x03);
else if (pStru-&nIdEvent ==
TEMPHUMICOMM_TIMER + IOCOMM_TIMER)//别的线程发送的指令
WORD highDeviceIndex = HIWORD(pStru-&wParam);//设备号
WORD lowPortIndex = LOWORD(pStru-&wParam);//设备1端口号,设备2指定温度或湿度模块
WORD highParam = HIWORD(pStru-&lParam);//设备1指定状态1或者0,设备2指定整数部分
WORD lowParam = LOWORD(pStru-&lParam);//设备1为0,设备2指定小数部分
IOSendData(pStru-&pSerialPort,(int)highDeviceIndex,(BYTE)highParam,(BYTE)lowParam,(BYTE)lowPortIndex);
  这里要注意一个问题,OnIOTimer中获得的idEvent并不是我们设置的IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,究其原因,参看《》,文中关于原因的解释为&注:只有当hWnd参数为非空时,计时器的ID为设置的 nIDEvent, 系统为你自动生成一个计时器ID,可由返回时值获取.&,可由MSDN得知。
那么我们的这个IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,怎么传递过去呢?查看《》得知,我们可由msg.wParam传递。
那么对于我们自定义的消息TEMPHUMICOMM_TIMER + IOCOMM_TIMER,我也想让OnIOTimer来处理怎么办?直接修改msg.message = WM_TIMER;就可以了嘛?答案是不行的!
为什么呢?参看《》,原来&如果参数lpmsg指向一个WM_TIMER消息,并且WM_TIMER消息的参数IParam不为NULL,则调用IParam指向的函数,而不是调用窗口程序。&,那么我们直接修改msg.lParam为OnIOTimer函数的地址就行了。这样,所有消息响应完成了。
2.多线程访问冲突(线程冲突)遇到一个问题,系统有一个串口通信端口,IO线程直接使用了,然后我在主线程中也发送数据,然后问题出现了,有时候会出现非法访问,跟踪了一下,原来两个线程使用了同一个串口通信缓冲区,主线程往里面压入数据的时候,可能子线程已经释放了该缓冲区,查询文章《》,而我使用的CSerialPort是同步的,我尝试修改类库代码,报错太多,此方法放弃。最终,我的解决方法是把所有的串口IO通信全部交给子线程来做,那么主线程要做的事,可以通过发送消息给子线程,再由子线程代劳,主线程怎么发送消息给子线程?使用API函数PostThreadMessage来完成,第一个参数就是子线程的Id。
子线程收到数据后,进行验证,验证通过后,发消息给主线程,通知它更新界面。《》
3.主线程退出前,关闭子线程
PostThreadMessage(m_dwIOControlThreadId,WM_QUIT,0,0);
Sleep(100);//需要等待关闭掉
GetMessage有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。  
这里可参看《》和《》
至此,经测试问题全部解决,记录一下。
阅读(...) 评论()2008年9月 .NET技术大版内专家分月排行榜第三
2008年9月 .NET技术大版内专家分月排行榜第三
2009年10月 .NET技术大版内专家分月排行榜第三
匿名用户不能发表回复!|串口超时处理原理及实现 - itloverhpu - 博客园
原理:大部分串口都是基于一字节、一字节传输,检测到特定的字符(比如换行或者空格)才判定一帧数据结束,这样的传输机制在自己调试时可以用,但实际运用其实用的很少,最大的坏处是cpu会&死等&特定字符,另外,若是由总线干扰出现的特定的字符,若程序同样判定帧起始(或停止)符,这明显是错误的。我们需要一帧一帧的传输,这样,就需要字节超时处理了,即只要字符与字符之间间隔超过一定的时间,那么就判定字符是一帧的结束。&&&&& 大部分教程没有提到可能是为了降低大家的学习难度,这里提供一份参考代码,STM32的所有串口都加进去了,全部测试通过,也经过实际项目(非精确严格要求)的检验。若不需要用到某些串口,只需要把app_conf.h,文件里的相关宏关闭即可。比如,不需要用到串口3、4、5,那么只需要注释掉app_conf.h里的USING_USART3、USING_USART4、USING_USART5即可。同时,串口相关配置波特率、缓冲区大小等等也在此文件,大家看看注释就明白了。默认全部开启,字节超时时间可设,例如USART1_RECEIVE_OVERTIME这类名字的宏。最后一个配置是printf 输出串口的选择,默认为串口一。&&&&& 这个实现五个串口公用一个systick,不需要每个串口需要单独的硬件定时器;二级缓冲区以加大吞吐量,一个接受缓冲区,接受缓冲区负责接收;一个帧准备缓冲区,帧准备缓冲区有一个准备好的标志USART_ready_buf_ok,应用程序可检测这个标志看是否有一帧数据存进来。还有一个帧长度USART_ready_buf_len以指示准备好的缓冲区的帧长度。代码先发放出去,大家先试试,稍后有时间在说说具体事怎么实现的,年底比较事多,大家见谅。&&& 对了,测试代码是简单地回发,即5个串口回发自己收到的数据帧,你可以做个测试,若字节超时时间设的比较长(在app_conf.h文件的USART1_RECEIVE_OVERTIME宏),那么你在串口调试助手里不停地点发送,等到你停下来,才会回发你刚发的内容。&&&& 还有,这个工程LED灯部分也有些意思,除了亮状态和灭状态,还有第三种状态&&&&闪烁,而且闪烁的次数与闪烁的间隔软件可配置。若有兴趣大家也可以试试,配置也在app_conf.h里,当然,你的板子与我的板子肯定不一样,那么"AunonBoard_led.h"里的各个LED_PORT和LED_PIN宏定义也要修改。&
(二)、软件定时器
&&&&& 软件定时器是由一个硬件定时器实现的多个定时器,在定时不要求非常精确的情况下可以用到,比如串口字节超时等等,特点是需要多少个定时器就可以拥有多少个定时器,不受硬件限制。这里的软件定时器源代码soft_timer.c和soft_timer.h两个文件,在不做任何改动的情况在ARM和51下测试通过,其他平台未知,(没有相应的硬件平台测试)。先放代码和工程,后面有时间在细说。&&&&& 使用方法(平台无关):&&&&& (1)硬件定时器初始化,中断配置什么的不要忘了,具体怎么实现无所谓,只要能不停地周期性中断(stm32的systick最合适了),中断间隔也是软件定时器的最小能分辨的间隔,然后将软件定时器刷新函数void timer_periodic_refresh()--没有参数--加到你的定时器中断服务函数里。&&&&&& (2)定义一个定时器,如:struct soft_timer timer,struct soft_timer 是软件定时器的结构体,定义在soft_timer.h文件中,timer是你的定时器的名字。&&&&& (3)软件定时器链表复位,soft_timer_list_reset(),无参数;&&&&&&&&(4)然后添加刚才你定义的定时器timer添加到定时器链表add_timer(&timer,timer_over_proc,time_count),第一个参数是你要添加的定时器结构体的指针,第二个参数是定时间到了你要调用的超时函数,第三个参数是定时时间,单位是你的周期性中断时间间隔。&&&&& (5)开始启动定时器,&start_timer(&timer),参数是你要启动的定时器的结构体指针。&&&&& 这样,等超时后,就会自动调用timer_over_proc()函数,(像不像我们自己实现的软件定时器中断?)&&&&& 注意,这个实现是一次性定时器,即一次超时后不再触发,若需要周期性触发,那么可以再timer_over_proc()函数里面重装初值--reload_timer(&timer,time_count),第一个r参数是待启动定时器结构体指针,time_count是重装值,单位依旧是你的硬件定时器中断时间间隔,然后再启动定时器start_timer(timer)即可。&&&&& 若还有使用上的问题,可参考两个测试工程的用法,一个stm32,一个51。STM32测试工程以systick建了3个软件定时器,分别以0.3s,0.5s,0.7s的时间间隔闪烁3个led灯。C51工程以定时器0建立3个软件定时器闪烁led灯。具体代码分析,后面有时间在细说。&&&&& STM32的测试结果视频和代码见附件。
(三)软件定时器实现串口字节超时处理
&&&&& 前面第一个工程是以前的一个小项目上用的,原封不动地发了上来,结构除了自个儿比较清晰外可能不会有人知道我再干什么。。。。。。所以写了这个软件定时器的版本,并做了充分的中文注释。欢迎指正!
&&&&& 同前面的一样,每个串口用了两个缓冲区,一个接收缓冲区,一个准备缓冲区。当串口中断函数里面收到一个字符时,放入接收缓冲区,并开始启动定时器,若在超时范围内没有收到下一个字符,超时函数会被自动调用,将接收缓冲区的数据转移至准备缓冲区内,并将准备标志置位,以供应用程序查询。&&&&&& 测试工程里面使用了串口1、2,简单回发收到的数据帧,为了对比,这两个串口的字节超时设置不一样,一个50ms,一个500ms,所以串口2在发送比较快的情况会被认为数据没有结束,直到至少停500ms才会回发。在我的板子上测试通过,由于手上没有串口3、4、5的板子,所以就没添加,需要的朋友可自己添加一下,还是比较容易的。&&&&&&& 写完了,不知道有人试过我这个软件定时器么?以满足下我小小的虚荣心啊,哈哈哈。
在串口通信过程中&超时处理几乎是必须的。&如果用过Modbus协议,它的判断结束的唯一条件就是超时。
本人应用的部分代码 共享下: //检测超时函数 供定时器中断调用-1ms一次// t为超时时间__inline void Chk_TimeOut(u8 t){ if(!uartRMsg.rcOK && (uartRMsg.rcIndex&=8) && (++uartRMsg.rcS &=t) ) //超时之后,开始重新解码
uartRMsg.rcOK =}//接收函数, 供USART中断调用__inline void Recieve_MSG(char ch){
uartRMsg.rcS = 0;
//如果接收到数据,则清除超市检测计数, if(!uartRMsg.rcOK)
uartRMsg.Buf.buf[uartRMsg.rcIndex ++] =
if(uartRMsg.rcIndex&=8 && uartRMsg.Buf.msg.cmd!=16)//其他条件成立的时候也可进入解码程序&此程序为Modbus应用,可改为接收到结束符等&
uartRMsg.rcOK = } }
//供主函数调用void MSGTransfer(void){
if(!uartRMsg.rcOK)
//......数据解析部分}匿名用户不能发表回复!|最近研究了一下MFC下对串口的操作,测试了一下对设备的读写。
1.打开串口
GetDlgItem(IDC_BUTTON_OPEN)-&EnableWindow(FALSE);
m_hComm = CreateFile("COM1",
GENERIC_READ | GENERIC_WRITE,
OPEN_EXISTING,
if (m_hComm == INVALID_HANDLE_VALUE)
TCHAR szBuf[1024];
wsprintf(szBuf,_T("打开COM1失败,代码:%d"),GetLastError());
2.设置串口通讯参数
memset(&dcb,0,sizeof(dcb));
if (!::GetCommState(m_hComm,&dcb))
dcb.BaudRate = 9600;
dcb.fParity = 1;
dcb.Parity = 0;
dcb.ByteSize = 8;
dcb.StopBits = 0;
SetCommState(m_hComm,&dcb);
if (!::SetupComm(m_hComm,1024,1024))
3.设定超时
//设定读超时
m_CommTimeOuts.ReadIntervalTimeout=MAXDWORD;
m_CommTimeOuts.ReadTotalTimeoutMultiplier=0;
m_CommTimeOuts.ReadTotalTimeoutConstant=0;
//在读一次输入缓冲区的内容后读操作就立即返回,
//而不管是否读入了要求的字符。
//设定写超时
m_CommTimeOuts.WriteTotalTimeoutMultiplier=100;
m_CommTimeOuts.WriteTotalTimeoutConstant=500;
::SetCommTimeouts(m_hComm,&(m_CommTimeOuts)); //设置超时
::PurgeComm(m_hComm,PURGE_RXCLEAR | PURGE_TXABORT);
4.开一个线程&1 m_pScanThread = AfxBeginThread(ScanThreadProc,this);&
5.设置定时器
快捷键Ctrl+W在MessageMaps添加消息响应
1 void CTestDAMDADlg::OnTimer(UINT nIDEvent)
// TODO: Add your message handler code here and/or call default
//AfxMessageBox("Begin");
switch (nIDEvent)
case TIMER_2:
//AfxMessageBox("Begin");
OnButton2v();
WriteComm(LENID,offlen);
KillTimer(TIMER_2);
SetTimer(TIMER_4,5000,NULL);
case TIMER_4:
OnButton4v();
WriteComm(LENID,offlen);
KillTimer(TIMER_4);
SetTimer(TIMER_6,5000,NULL);
case TIMER_6:
OnButton6v();
WriteComm(LENID,offlen);
KillTimer(TIMER_6);
SetTimer(TIMER_8,5000,NULL);
case TIMER_8:
OnButton8v();
WriteComm(LENID,offlen);
KillTimer(TIMER_8);
SetTimer(TIMER_10,5000,NULL);
case TIMER_10:
OnButton10v();
WriteComm(LENID,offlen);
KillTimer(TIMER_10);
SetTimer(TIMER_2,5000,NULL);
OnButton6v();
WriteComm(LENID,offlen);
KillTimer(TIMER_2);
SetTimer(TIMER_4,5000,NULL);
CDialog::OnTimer(nIDEvent);
线程里开启定时器&1 dlg-&SetTimer(TIMER_2,5000,NULL);&
6.调用写串口操作
1 BOOL CTestDAMDADlg::WriteComm(BYTE *lpByte,DWORD dwBytes)
DWORD dwBytesWrite = 20;
COMSTAT ComS
DWORD dwErrorF
BOOL bWriteS
ClearCommError(m_hComm,&(dwErrorFlags),&(ComStat));
bWriteStat = WriteFile(m_hComm,lpByte,dwBytes,
&dwBytesWrite,NULL);
if (!bWriteStat)
return FALSE;
return TRUE;
读串口操作类似,这样就完成了定时对串口的读写操作,测试通过!
阅读(...) 评论()}

我要回帖

更多关于 timertask 线程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信