C++串口同步和异步的读取与串口设备编程

本文主要讲述如何对串口进行高效率的读写,当串口中数据到达时立即读取进行处理,以及如何将该方法运用到串口设备编程中。为了使得程序更加清晰文中的代码去除了异常处理的情况。文中加粗的文字相应的比较重要,需要多注意。当然文中会有错误,欢迎评论指正。


文章中代码下载地址 http://pan.baidu.com/s/1pLsP9wB


1、COM口WindowsAPI函数

CreateFile("COM1", ...); //打开串口设备
SetupComm      //设置串口发送接收缓存
GetCommState //配置串口、设置波特率、停止位、校验位等待
PurgeComm     //清空发送接收缓存
SetCommTimeouts   //设置发送接收超时
ClearCommError       //清除COM口错误、查询发送和 接收缓存中的字节数
SetCommMask    //设置监听事件,设置后可以调用 WaitCommEvent  等待事件,若是以同步方式打开串口此函数会清除之前触发的事件
WaitCommEvent  //等待监听事件: 当 SetCommMask  注册的事件到达则会立即返回,
                             //如果是以同步方式打开串口需要调用 SetCommMask  清除事件,
                             //否则再次调用 WaitCommEvent 会立即返回

ReadFile(hCom, ReadBuf, ReadLen, &ReadSize, NULL) //读取缓存, 当缓存中已有ReadLen个字节数据则立即返回,没有则会一直等到
                                                                                           // SetCommTimeouts  中设置的超时过去则反回。
WriteFile  //写入数据

以上函数具体介绍请参考微软官方文档MSDN 地址 https://msdn.microsoft.com/en-us/library/windows/desktop/aa363194(v=vs.85).aspx


2、串口的读取

读取串口时希望串口中一有数据则立即读取到结果并返回。不管是同步还是异步都有两种方式实现,都是一种利用WaitCommEvent 等待EV_RXCHAR事件,令一种利用ReadFile函数的特性( 当缓存中已有ReadLen个字节数据则立即返回)。
WaitCommEvent 方法:则先注册EV_RXCHAR事件,如果读取缓存中有数据则会触发该事件,应用程序可以得到通知然后再调用ReadFile读取。
ReadFile的方法:则需要先读取一个字节 ReadFile(hCom, buf, 1, &ReadSize, NULL),返回TRUE后则通过ClearCommError 查询有多少数据需要读取,然后再次调用ReadFile将其余的数据读取出来。

由于同步方式打开串口时调用WaitCommEvent等待时,不能在其他线程调用WriteFile进行写入操作,且WaitCommEvent无超时参数所以该方法对于同步串口读取基本无实用价值。

3、COM口同步读写


由于通过ReadFile等待数据的读取方法

打开并配置串口
HANDLE InitCOM(LPCTSTR Port)
{
	HANDLE hCom = INVALID_HANDLE_VALUE;
	hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
		0/*同步方式打开串口*/, NULL);
	if (INVALID_HANDLE_VALUE == hCom)
	{
		return INVALID_HANDLE_VALUE;
	}
	SetupComm(hCom, 4096, 4096);//设置缓存
	
	DCB dcb;

	GetCommState(hCom, &dcb);//设置串口
	dcb.DCBlength = sizeof(dcb);
	dcb.BaudRate = CBR_9600;
	dcb.StopBits = ONESTOPBIT;
	SetCommState(hCom, &dcb);

	PurgeComm(hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);//清空缓存

	COMMTIMEOUTS ct;
	//设置读取超时时间,及ReadFlie最长等待时间
	ct.ReadIntervalTimeout = 0;
	ct.ReadTotalTimeoutConstant = 5000;
	ct.ReadTotalTimeoutMultiplier = 500;

	ct.WriteTotalTimeoutMultiplier = 500;
	ct.WriteTotalTimeoutConstant = 5000;

	SetCommTimeouts(hCom, &ct);//设置超时

	return hCom;
}


数据读取
bool ComRead(HANDLE hCom, LPBYTE buf, int &len)
{
	DWORD ReadSize = 0;
	BOOL rtn = FALSE;

	//设置读取1个字节数据,当缓存中有数据到达时则会立即返回,否则直到超时
	rtn = ReadFile(hCom, buf, 1, &ReadSize, NULL);

	//如果是超时rtn=true但是ReadSize=0,如果有数据到达,会读取一个字节ReadSize=1
	if (rtn == TRUE && 1 == ReadSize)
	{
		DWORD Error;
		COMSTAT cs = {0};
		int ReadLen = 0;
		//查询剩余多少字节未读取,存储于cs.cbInQue中
		ClearCommError(hCom, &Error, &cs);
		ReadLen = (cs.cbInQue > len) ? len : cs.cbInQue;
		if (ReadLen > 0)
		{
			//由于之前等待时以读取一个字节,所欲buf+1
			rtn = ReadFile(hCom, buf+1, ReadLen, &ReadSize, NULL);
			len = 0;
			if (rtn)
			{
				len = ReadLen + 1;
			}
		}
	}
	PurgeComm(hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);
	return rtn != FALSE;
}

数据写入
bool ComWrite(HANDLE hCom, LPBYTE buf, int &len)
{
	PurgeComm(hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);
	BOOL rtn = FALSE;
	DWORD WriteSize = 0;
	rtn = WriteFile(hCom, buf, len, &WriteSize, NULL);

	len = WriteSize;
	return rtn != FALSE;
}


由于同步串口WaitCommEvent等待数据读取的方法基本无实用价值,所以不讨论。

4、COM口异步读写

因为异步读取是在后台进行,数据到达一般需要单独的线程等待,所以本文采用了一个类进行说明。

WaitCommEvent等待数据读取的方法

异步读取类声明
class ComAsy
{
public: 
	ComAsy();
	~ComAsy();
	bool InitCOM(LPCTSTR Port);//打开窗口
	void UninitCOM(); //关闭串口并清理

	//写入数据
	bool ComWrite(LPBYTE buf, int &len);

	//读取线程
	static unsigned int __stdcall OnRecv(void*);

private:
	HANDLE m_hCom;
	OVERLAPPED m_ovWrite;//用于写入数据
	OVERLAPPED m_ovRead;//用于读取数据
	OVERLAPPED m_ovWait;//用于等待数据
	volatile bool m_IsOpen;//串口是否打开
	HANDLE m_Thread;//读取线程句柄
};

ComAsy::ComAsy():
			m_hCom(INVALID_HANDLE_VALUE),
			m_IsOpen(false),
			m_Thread(NULL)
{
	memset(&m_ovWait, 0, sizeof(m_ovWait));
	memset(&m_ovWrite, 0, sizeof(m_ovWrite));
	memset(&m_ovRead, 0, sizeof(m_ovRead));
}

ComAsy::~ComAsy()
{
	UninitCOM();
}


初始化并配置串口
bool ComAsy::InitCOM(LPCTSTR Port)
{
	m_hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,//设置异步标识
		NULL);
	if (INVALID_HANDLE_VALUE == m_hCom)
	{
		return false;
	}
	SetupComm(m_hCom, 4096, 4096);//设置发送接收缓存

	DCB dcb;
	GetCommState(m_hCom, &dcb);
	dcb.DCBlength = sizeof(dcb);
	dcb.BaudRate = CBR_9600;
	dcb.StopBits = ONESTOPBIT;
	SetCommState(m_hCom, &dcb);//配置串口

	PurgeComm(m_hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

	COMMTIMEOUTS ct;
	ct.ReadIntervalTimeout = MAXDWORD;//读取无延时,因为有WaitCommEvent等待数据
	ct.ReadTotalTimeoutConstant = 0;  //
	ct.ReadTotalTimeoutMultiplier = 0;//

	ct.WriteTotalTimeoutMultiplier = 500;
	ct.WriteTotalTimeoutConstant = 5000;

	SetCommTimeouts(m_hCom, &ct);

	//创建事件对象
	m_ovRead.hEvent = CreateEvent(NULL, false, false, NULL);
	m_ovWrite.hEvent = CreateEvent(NULL, false, false, NULL);
	m_ovWait.hEvent = CreateEvent(NULL, false, false, NULL);

	SetCommMask(m_hCom, EV_ERR | EV_RXCHAR);//设置接受事件

	//创建读取线程
	m_Thread = (HANDLE)_beginthreadex(NULL, 0, &ComAsy::OnRecv, this, 0, NULL);
	m_IsOpen = true;
	return true;
}

写入数据,由于写入数据一般不会有太高的性能要求,所以异步写入时如果数据在后台写入,则会等待写入完成后再退出,此时相当于同步的写入。
bool ComAsy::ComWrite(LPBYTE buf, int &len)
{
	BOOL rtn = FALSE;
	DWORD WriteSize = 0;

	PurgeComm(m_hCom, PURGE_TXCLEAR|PURGE_TXABORT);
	m_ovWait.Offset = 0;
	rtn = WriteFile(m_hCom, buf, len, &WriteSize, &m_ovWrite);

	len = 0;
	if (FALSE == rtn && GetLastError() == ERROR_IO_PENDING)//后台读取
	{
		//等待数据写入完成
		if (FALSE == ::GetOverlappedResult(m_hCom, &m_ovWrite, &WriteSize, TRUE))
		{
			return false;
		}
	}

	len = WriteSize;
	return rtn != FALSE;
}

读取数据
unsigned int __stdcall ComAsy::OnRecv( void* LPParam)
{
	ComAsy *obj = static_cast<ComAsy*>(LPParam);

	DWORD WaitEvent = 0, Bytes = 0;
	BOOL Status = FALSE;
	BYTE ReadBuf[4096];
	DWORD Error;
	COMSTAT cs = {0};

	while(obj->m_IsOpen)
	{
		WaitEvent = 0;
		obj->m_ovWait.Offset = 0;
		Status = WaitCommEvent(obj->m_hCom,&WaitEvent, &obj->m_ovWait );
		//WaitCommEvent也是一个异步命令,所以需要等待
		if (FALSE == Status && GetLastError() == ERROR_IO_PENDING)//
		{
			//如果缓存中无数据线程会停在此,如果hCom关闭会立即返回False
			Status = GetOverlappedResult(obj->m_hCom, &obj->m_ovWait,  &Bytes, TRUE);
		}
		ClearCommError(obj->m_hCom, &Error, &cs);
		if (TRUE == Status //等待事件成功
			&& WaitEvent&EV_RXCHAR//缓存中有数据到达
			&& cs.cbInQue > 0)//有数据
		{
			Bytes = 0;
			obj->m_ovRead.Offset = 0;
			memset(ReadBuf, 0, sizeof(ReadBuf));
			//数据已经到达缓存区,ReadFile不会当成异步命令,而是立即读取并返回True
			Status = ReadFile(obj->m_hCom, ReadBuf, sizeof(ReadBuf), &Bytes, &obj->m_ovRead);
			if (Status != FALSE)
			{
				cout<<"Read:"<<(LPCSTR)ReadBuf<<"   Len:"<< Bytes<<endl;
			}
			PurgeComm(obj->m_hCom, PURGE_RXCLEAR|PURGE_RXABORT);
		}

	}
	return 0;
}

关闭串口
void ComAsy::UninitCOM()
{
	m_IsOpen = false;
	if (INVALID_HANDLE_VALUE != m_hCom)
	{
		CloseHandle(m_hCom);
		m_hCom = INVALID_HANDLE_VALUE;
	}
	if (NULL != m_ovRead.hEvent)
	{
		CloseHandle(m_ovRead.hEvent);
		m_ovRead.hEvent = NULL;
	}
	if (NULL != m_ovWrite.hEvent)
	{
		CloseHandle(m_ovWrite.hEvent);
		m_ovWrite.hEvent = NULL;
	}
	if (NULL != m_ovWait.hEvent)
	{
		CloseHandle(m_ovWait.hEvent);
		m_ovWait.hEvent = NULL;
	}
	if (NULL != m_Thread)
	{
		WaitForSingleObject(m_Thread, 5000);//等待线程结束
		CloseHandle(m_Thread);
		m_Thread = NULL;
	}
}

对于异步读取一般都采用WaitCommEvent的方式等待数据,采用ReadFile的方式等待数据也可以,只需要设置一个很大的超时时间,然后通过读取1个字节等待。

5、串口设备开发

一般的串口设备都是上位机发送一个命令设备返回命令执行的结果,同步窗口读取非常适合这种模式。一般先WriteFile发送一个命令,然后ReadFile读取结果。将超时参数设为设备动作返回需要的最长时间,这样就在发送命令后知道命令的执行结果。
采用同步方式读取可以封装一个设备类,类的结构大致如下。
class Device
{
public:
	Device();
	~Device();
	bool Init(LPCTSTR Port, ...);
	void UnInit();

	bool Option1(LPCTSTR Param1,...)
	{
		m_cs.Lock();//每一个操作前先锁定设备
		WriteFile(m_hCom, ...);//发送命令

		ReadFile(m_hCom, ...);//获取命令结果
		m_cs.Unlock();
		return true;
	}
	bool Option2(LPCTSTR Param1,...);
	//...


	//查询状态线程,每隔一段时间查询一次状态,以便知道设备是否在线还是离线
	static unsigned int __stdcall QueryStatus(void*);


private:
	HANDLE m_hCom;
	bool m_IsOpen;
	int m_DeviceStatus;

	CCriticalSection m_cs;//

	HANDLE m_ThreadStatus;
};



对于会主动向上抛数据的设备,采用异步的方式更为合适,因为异步方式会一直等待读取数据。





————————————————————————————————————————————————————————————————————————

串口异步读取的另一种方法



串口初始化, 与之前的异步方法一致,只是在设置超时是将ReadIntervalTimeout设置为2ms, 这样这样ReadFile异步读取时,会返回FALSE,并且GetLastError() == ERROR_IO_PENDING,然后调用GetOverlappedResult等待数据到达,如果有数据到达且时间超过2ms则会返回,否则会一直等待。
串口未关闭的情况下,GetOverlappedResult返回需要两个条件,一是超时或者读取的字节以到达ReadFile中指定的字节数,二是有数据到达,只有同时两个条件满足才会返回。

代码如下:
初始化代码:
bool ComAsy::InitCOM(LPCTSTR Port)
{
	m_hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,//设置异步标识
		NULL);
	if (INVALID_HANDLE_VALUE == m_hCom)
	{
		return false;
	}
	SetupComm(m_hCom, 4096, 4096);//设置发送接收缓存

	DCB dcb;
	GetCommState(m_hCom, &dcb);
	dcb.DCBlength = sizeof(dcb);
	dcb.BaudRate = CBR_9600;
	dcb.StopBits = ONESTOPBIT;
	SetCommState(m_hCom, &dcb);//配置串口

	PurgeComm(m_hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

	COMMTIMEOUTS ct;
    //读取延时设为2ms,这样ReadFile异步读取时,会返回FALSE,并且GetLastError() == ERROR_IO_PENDING。
	//如果设为MAXDWORD,ReadFile异步读取时,会返回TRUE
	//然后调用GetOverlappedResult等待数据到达,如果有数据到达且时间超过2ms则会返回,否则会一直等待。
	ct.ReadIntervalTimeout = 2;
	ct.ReadTotalTimeoutConstant = 0;  //
	ct.ReadTotalTimeoutMultiplier = 0;//

	ct.WriteTotalTimeoutMultiplier = 500;
	ct.WriteTotalTimeoutConstant = 5000;

	SetCommTimeouts(m_hCom, &ct);

	//创建事件对象
	m_ovRead.hEvent = CreateEvent(NULL, false, false, NULL);
	m_ovWrite.hEvent = CreateEvent(NULL, false, false, NULL);
	m_ovWait.hEvent = CreateEvent(NULL, false, false, NULL);

	SetCommMask(m_hCom, EV_ERR | EV_RXCHAR);//设置接受事件

	//创建读取线程
	m_Thread = (HANDLE)_beginthreadex(NULL, 0, &ComAsy::OnRecv, this, 0, NULL);
	m_IsOpen = true;
	return true;
}



读取线程代码:
unsigned int __stdcall ComAsy::OnRecv( void* LPParam)
{
	ComAsy *obj = static_cast<ComAsy*>(LPParam);

	DWORD Bytes = 0;
	BOOL Status = FALSE;
	BYTE ReadBuf[4096];
	DWORD Error;
	COMSTAT cs = {0};

	while(obj->m_IsOpen)
	{

		ClearCommError(obj->m_hCom, &Error, &cs);

		Bytes = 0;
		obj->m_ovRead.Offset = 0;
		memset(ReadBuf, 0, sizeof(ReadBuf));
		Status = ReadFile(obj->m_hCom, ReadBuf, sizeof(ReadBuf), &Bytes, &obj->m_ovRead);
		//数据已经到达缓存区,读取会立即返回,并返回True, 否则返回False
		if (Status == FALSE && GetLastError() == ERROR_IO_PENDING)
		{
			//如果有数据到达 且 时间超过ReadIntervalTimeout则会返回,否则会一直等待
			Status = GetOverlappedResult(obj->m_hCom, &obj->m_ovRead, &Bytes, TRUE);
		}
		if (FALSE != Status && Bytes > 0)
		{
			cout<<"Read:"<<(LPCSTR)ReadBuf<<"   Len:"<< Bytes<<endl;
		}
		PurgeComm(obj->m_hCom, PURGE_RXCLEAR|PURGE_RXABORT);

	}

	return 0;
}

感谢支持

  • 27
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: 串口通信是一种通过串行接口进行数据传输的通信方式。异步通信是串口通信中的一种传输方式,即不同步地传输数据。 在C语言中,可以编写一个异步通信的demo程序来演示串口通信。 首先,需要包含相关的头文件,如<termios.h>用于串口配置,<fcntl.h>用于文件控制,<unistd.h>用于文件操作等。 接下来,需要定义串口的文件路径,如"/dev/ttyS0"表示串口设备文件。 然后,可以使用open函数打开串口设备文件,并设置相关的串口参数,如波特率、数据位、校验位、停止位等。 之后,可以使用read函数读取串口接收到的数据,并使用write函数向串口发送数据。 最后,使用close函数关闭串口设备文件。 下面是一个简单的异步通信demo程序的代码示例: #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> int main() { int fd; char buf[100]; fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd == -1) { perror("open"); return -1; } struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= CLOCAL | CREAD; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_iflag &= ~(IXON | IXOFF | IXANY); options.c_cflag &= ~CSTOPB; tcsetattr(fd, TCSANOW, &options); write(fd, "Hello", 5); sleep(1); read(fd, buf, sizeof(buf)); printf("Received: %s\n", buf); close(fd); return 0; } 这段程序实现了通过串口向外发送"Hello",然后从串口接收数据,并打印接收到的数据。 在实际应用中,可以根据具体需求修改程序中的串口设备文件路径和参数设置,并进行相应的错误处理。 ### 回答2: 串口通信是一种用于在计算机和外部设备之间传输数据的标准通信方式。在串口通信中,数据按照位的形式一位一位地传输,而不是以字节的形式。异步通信是串口通信中的一种常见模式,它以不等时间间隔传输数据。 在C语言中实现一个串口通信的异步通信demo,可以使用以下步骤: 1. 引入所需的头文件,如stdio.h、fcntl.h、unistd.h和termios.h。这些头文件提供了访问串口通信所需的函数和数据类型。 2. 打开串口文件。使用open函数打开串口设备文件,如/dev/ttyS0。如果打开成功,该函数将返回文件描述符。 3. 配置串口属性。使用tcgetattr和tcsetattr函数获取和设置串口的属性,包括波特率、数据位、停止位和校验位等。 4. 设置串口为非阻塞模式。使用fcntl函数将串口文件描述符设置为非阻塞模式,这样可以实现异步通信。 5. 读取和写入数据。使用read和write函数从串口读取和写入数据。可以使用循环来实现连续的数据传输。 6. 关闭串口文件。使用close函数关闭串口文件。 以下是一个简单的异步串口通信demo的示例代码: ```c #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> int main() { int fd; char data; // 打开串口文件 fd = open("/dev/ttyS0", O_RDWR); if (fd == -1) { perror("Error opening serial port"); return 1; } // 配置串口属性 struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~CRTSCTS; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; tcsetattr(fd, TCSANOW, &options); // 设置串口为非阻塞模式 fcntl(fd, F_SETFL, O_NONBLOCK); // 读取和写入数据 while(1) { // 从串口读取数据 if (read(fd, &data, 1) > 0) { printf("Received: %c\n", data); } // 向串口中写入数据 data = 'A'; write(fd, &data, 1); sleep(1); } // 关闭串口文件 close(fd); return 0; } ``` 上述代码实现了一个简单的异步串口通信demo,其中打开了/dev/ttyS0串口文件,配置了波特率为9600,然后不断读取和写入数据。这只是一个简单的示例,实际应用中需要根据具体需求进行适当的修改和完善。 ### 回答3: 串口通信是指通过串行接口进行数据传输的一种通信方式。在计算机领域中,串口通信通常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。 异步通信demo c是一个使用C语言编写的示例程序,用于演示如何实现串口异步通信。该示例程序通过调用相关的库函数和API,实现了串口的打开、设置波特率、写入数据和读取数据等操作。用户可以根据需要进行修改和扩展,以满足具体的通信需求。 在串口通信中,异步通信是指数据传输的起始和停止时刻不依赖于时钟信号,在传输数据时,发送和接收两端的时钟信号可以有一定的差异。异步通信通过在数据传输中插入起始位和停止位来同步数据的传输。与之相对的是同步通信,同步通信需要在发送和接收两端保持相同的时钟信号,以实现数据的同步传输。 串口通信的优点包括可靠性高、传输距离远、抗干扰能力强等。异步通信demo c通过提供示例代码和相关函数库,简化了程序员对串口通信的开发和调试过程,提高了开发效率和可靠性。 总之,串口通信和异步通信demo c是一种常用的通信方式和相应的示例程序,通过串口连接计算机与外部设备进行数据传输,并通过异步通信方式实现数据的同步和可靠传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值