【关于串口通信CSerialPort类BUG问题??????】

大树学长 2014-10-25 11:12:41
承接我上一个还没解决的XP下蓝屏的问题;
首先我用的是CSerialPort类
我测试每次传输数据过快就会蓝屏
在波特率为2400——57600时传输数据达到500次每秒 XP系统下绝对会蓝屏
1、定时器测试30毫秒级别每秒也就相应33次左右,传输正常。
2、按钮测试数据传输每秒125次级别完全正常。
3、当用手工测试数据传输最高每秒400次到500次时如果波特率越低就越容易蓝屏,57600以上的波特率没有试过。
4、我把这个类单独拿出来测试,用for循环发送数据,也会蓝屏

Win7系统因为写有保护所有没有蓝屏的现象。

网上解释:
CSerialPort类是一个非常经典的VC串口类但有人发现用CSerialPort连续发送大量数据时却不正常,其原因是CSerialPort是一个异步串口类从其源代码中就可以看出
m_hComm = CreateFile(szPort,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
上面是CSerialPort打开串口的代码可以看到倒数第二个参数dwFlagsAndAttributes它指示了串口进行异步或同步操作,这里的值设为FILE_FLAG_OVERLAPPED即异步发送。同步操作时API函数会阻塞直到操作完成以后才能返回,在多线程方式中虽然不会阻塞主线程,但是仍然会阻塞监听线程而异步,重叠操作方式,API函数会立即返回操作在后台进行,避免线程的阻塞。
所以在连续发送数据时,由于是异步发送在执行到m_serial.WriteToPort(chSend1); 时,并不马上发送串口数据而是要等进入CSerialPort的线程之后再发送,如果是同步操作,则程序停在那里等发送完成。所以有可能前一个“发送任务”还未完成,但后一个“发送任务”又来了,这样将“冲”掉上次发送过程。
解决方法
有两种
1、将多次发送变为一次发送这需要将多次发送的数据打包到一个数据包里发送。
2、第一种方法并不一定适合所有应用场合此时只能使用同步串口类同步串口类在启动发送时将会立即发送数据并等待发送完毕后才会返回。
有人问可不可以将CreateFile倒数第二个参数改成同步操作参数,但问题没有这么简单,因为CSerialPort类还有其它地方的许多代码与异步串口类有关,所以这些地方也需要改,这样还不如直接使用另外现成的同步串口类。


问题:如果设置CreateFile倒数第二个参数改成同步操作参数,其他的与异步串口有关的代码怎么改???




...全文
232 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
大树学长 2014-10-28
  • 打赏
  • 举报
回复
引用 13 楼 schlafenhamster 的回复:
波特率 9600 时 大概 1ms 一个字节。
按照这个我每秒钟最高响应500次,那就是每秒钟传输500字节,大概2ms一个字节。奇怪了
笨笨仔 2014-10-27
  • 打赏
  • 举报
回复
如果你的数据发送数率较高,可选用115200波特率试试,或改用以太网。因为你所说的似乎都是发送,也就是说你并不关心接收端,仅对发送而言,你的每次发送前只要保证发送缓冲区空就行了,应该不会出现问题。为了保证发送正确,可建立发送事件处理线程,通过“发送缓冲区空”事件,决定新数据的发送开始。 事件例子:

//***************************************************************
//				全局变量
//***************************************************************
DWORD		ThreadProcEvent(LPVOID pParam);		// 事件响应函数(线程)

HANDLE		hCom;								// 串口的句柄
HANDLE		hThreadEvent;						// 事件线程句柄
bool		fEventRun;							// 事件函数执行标志
DWORD		dwWinThreadID;						// 窗口线程ID
DWORD		dwThreadID;							// 事件线程ID
OVERLAPPED	Eol={0};							// 事件线程使用的结构
OVERLAPPED	Wol={0};							// 写操作使用的结构
OVERLAPPED	Rol={0};							// 读操作使用的结构
bool		fStopMsg;							// 停止事件线程向主线程发送消息标志
// ************		串口事件响应程序	**************
DWORD ThreadProcEvent(LPVOID pParam)
{
	DWORD dwEvtMask,dwRes;
	Eol.hEvent=CreateEvent(NULL,							// 设置为无信号状态
				FALSE,										// 自动复原
				FALSE,
				NULL);
	while(fEventRun)
	{
		WaitCommEvent(hCom,
			&dwEvtMask,
			&Eol);
		dwRes=WaitForSingleObject(Eol.hEvent,
					TO_EVENT_TIMEOUT);
		switch(dwRes)
		{
		case WAIT_OBJECT_0:										// 得到监视结果
			switch(dwEvtMask)
			{
			case EV_RXCHAR:										// 接收到数据
				if(!fStopMsg)
				{
					fStopMsg=true;
					::PostThreadMessage(dwWinThreadID,			// 向窗口线程发收到数据的消息
							WM_COMMDATA_MESSAGE,
							(WPARAM)EV_RXCHAR,1);				// 发送接收缓冲区有数据事件消息
				}
				break;
			case EV_TXEMPTY:									// 发送缓冲区已空
				// 发送缓冲区空
				::PostThreadMessage(dwWinThreadID,			// 向窗口线程发收到数据的消息
						WM_COMMDATA_MESSAGE,
						(WPARAM)EV_TXEMPTY,1);				// 发送缓冲区已空事件消息
				break;
			}
			break;
		}
	}
	return true;
}
//////////////////////////////////////		全局END		/////////////////////////////////////////
bool CCommThread::fEvent=false;						// 初始化线程启动标志
schlafenhamster 2014-10-27
  • 打赏
  • 举报
回复
"什么办法能将接受数据的效率增加5倍左右" 提高 波特率
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 6 楼 wxhxj0268 的回复:
没有应答机制的发送就象TCP/IP协议中的UDP广播一样,是不安全的,你不能保证对方完整的接收到数据。 如果你发送的数据收到与否或正确与否均无关系,也可以不使用应答机制,但这种情况很少见。 在串行通信的异步机制中,使用的事件激发,因此,在程序中应该加入事件线程,而发送的间隔应该由硬件的接收能力决定,这个参数可以通过实验决定,也可以通过芯片的能力参数决定。
我只需要完整的接受到最后一次的数据 前面的完不完整的 没关系 比如采用PostMessage的就可以 我这是一个连续发送的过程 但最终显示的是最后一次的数据 解决方法我百度过 要么将这个异步的改为同步的,或者将发送的数据 弄到一个大的数据中一次性发送。 如果有什么办法能将接受数据的效率增加5倍左右 我这个问题就可以解决了
笨笨仔 2014-10-27
  • 打赏
  • 举报
回复
没有应答机制的发送就象TCP/IP协议中的UDP广播一样,是不安全的,你不能保证对方完整的接收到数据。 如果你发送的数据收到与否或正确与否均无关系,也可以不使用应答机制,但这种情况很少见。 在串行通信的异步机制中,使用的事件激发,因此,在程序中应该加入事件线程,而发送的间隔应该由硬件的接收能力决定,这个参数可以通过实验决定,也可以通过芯片的能力参数决定。
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 3 楼 wxhxj0268 的回复:
不受控且无间隔的发送,一个不安全的程序。
有点,但现在我接手的这个程序我已经是第三个人了,程序是别人写的,不然也不会这么麻烦,而且我现在还完全不懂串口这一快的东西, 网上说 1. CommThread comm事件循环里用的是::SendMessage, 这个会阻塞,对应的事件处理函数返回后才会继续。 2. comm事件循环里调用 ReceiveChar接收几个字节就 调用 SendMessage(WM_COMM_RXCHAR) 几次, 而且在循环里等锁,进了两次临界区,就更加慢了。 改动应该是 1. 改用 PostMessage, 但别的地方也需要改动 2. WM_COMM_RXCHAR 不应该一次只接收一个字节,但是要加一个读缓冲,其他地方改动也很大 WM_COMM_RXCHAR 怎么加一个缓冲????????
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 1 楼 schlafenhamster 的回复:
CSerialPort类 不是有源码吗, 改改看. 一次没发完, 不能再发. 如果发送是 线程, 那么 线程创建,退出时要时间的.
1. CommThread comm事件循环里用的是::SendMessage, 这个会阻塞,对应的事件处理函数返回后才会继续。 2. comm事件循环里调用 ReceiveChar接收几个字节就 调用 SendMessage(WM_COMM_RXCHAR) 几次, 而且在循环里等锁,进了两次临界区,就更加慢了。 改动应该是 1. 改用 PostMessage, 但别的地方也需要改动 2. WM_COMM_RXCHAR 不应该一次只接收一个字节,但是要加一个读缓冲,其他地方改动也很大 WM_COMM_RXCHAR 怎么加一个缓冲????????
schlafenhamster 2014-10-27
  • 打赏
  • 举报
回复
波特率 9600 时 大概 1ms 一个字节。
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 9 楼 wxhxj0268 的回复:
如果你的数据发送数率较高,可选用115200波特率试试,或改用以太网。因为你所说的似乎都是发送,也就是说你并不关心接收端,仅对发送而言,你的每次发送前只要保证发送缓冲区空就行了,应该不会出现问题。为了保证发送正确,可建立发送事件处理线程,通过“发送缓冲区空”事件,决定新数据的发送开始。 事件例子:

//***************************************************************
//				全局变量
//***************************************************************
DWORD		ThreadProcEvent(LPVOID pParam);		// 事件响应函数(线程)

HANDLE		hCom;								// 串口的句柄
HANDLE		hThreadEvent;						// 事件线程句柄
bool		fEventRun;							// 事件函数执行标志
DWORD		dwWinThreadID;						// 窗口线程ID
DWORD		dwThreadID;							// 事件线程ID
OVERLAPPED	Eol={0};							// 事件线程使用的结构
OVERLAPPED	Wol={0};							// 写操作使用的结构
OVERLAPPED	Rol={0};							// 读操作使用的结构
bool		fStopMsg;							// 停止事件线程向主线程发送消息标志
// ************		串口事件响应程序	**************
DWORD ThreadProcEvent(LPVOID pParam)
{
	DWORD dwEvtMask,dwRes;
	Eol.hEvent=CreateEvent(NULL,							// 设置为无信号状态
				FALSE,										// 自动复原
				FALSE,
				NULL);
	while(fEventRun)
	{
		WaitCommEvent(hCom,
			&dwEvtMask,
			&Eol);
		dwRes=WaitForSingleObject(Eol.hEvent,
					TO_EVENT_TIMEOUT);
		switch(dwRes)
		{
		case WAIT_OBJECT_0:										// 得到监视结果
			switch(dwEvtMask)
			{
			case EV_RXCHAR:										// 接收到数据
				if(!fStopMsg)
				{
					fStopMsg=true;
					::PostThreadMessage(dwWinThreadID,			// 向窗口线程发收到数据的消息
							WM_COMMDATA_MESSAGE,
							(WPARAM)EV_RXCHAR,1);				// 发送接收缓冲区有数据事件消息
				}
				break;
			case EV_TXEMPTY:									// 发送缓冲区已空
				// 发送缓冲区空
				::PostThreadMessage(dwWinThreadID,			// 向窗口线程发收到数据的消息
						WM_COMMDATA_MESSAGE,
						(WPARAM)EV_TXEMPTY,1);				// 发送缓冲区已空事件消息
				break;
			}
			break;
		}
	}
	return true;
}
//////////////////////////////////////		全局END		/////////////////////////////////////////
bool CCommThread::fEvent=false;						// 初始化线程启动标志
谢谢谢谢!! 我把那个类单独拿出来测试一下,尽量提高下效率
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 8 楼 schlafenhamster 的回复:
"什么办法能将接受数据的效率增加5倍左右" 提高 波特率
STC-ISP那个单片机烧录软件,好像在烧录到115200这种级别的波特率有问题,最多一般只用到57600
大树学长 2014-10-27
  • 打赏
  • 举报
回复
引用 8 楼 schlafenhamster 的回复:
"什么办法能将接受数据的效率增加5倍左右" 提高 波特率
引用 8 楼 schlafenhamster 的回复:
"什么办法能将接受数据的效率增加5倍左右" 提高 波特率
是的,我就用的提高波特率的办法,从9600提高到了57600,但测试的结果是,当数据的发送量增加4倍左右 还是会蓝屏,115200测试的上位机和下位机连接接不上,可能.hex文件还是有点问题 我再试试看吧 但是我还是想解决掉根本问题
笨笨仔 2014-10-26
  • 打赏
  • 举报
回复
不受控且无间隔的发送,一个不安全的程序。
笨笨仔 2014-10-26
  • 打赏
  • 举报
回复
发送缓冲区爆了吧?溢出了。
schlafenhamster 2014-10-26
  • 打赏
  • 举报
回复
CSerialPort类 不是有源码吗, 改改看. 一次没发完, 不能再发. 如果发送是 线程, 那么 线程创建,退出时要时间的.

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧