背景
最近有一个串口通信的需求,在一个串口实现消息帧的解析和帧指令的发送。
本来串口从硬件上就是一个全双工的设计,但在程序实现上需要小心谨慎,因为可能会出现收发冲突的情况,甚至有时候虽然收发不冲突,但是收到错误的数据。这两个坑在笔者设计串口通信程序时都踩过。
本文就介绍一种可以同时实现收发的串口通信类,并且预留了消息解析和指令发送的接口。
具体实现细节
基本思路
基本思路是在串口类中的打开串口方法中开启两个线程,分别用于串口读写。
m_hListenThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)this->ListenThread, this, 0, NULL);
m_hCommandThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)this->CommandThread, this, 0, NULL);
SetThreadPriority(m_hListenThread, THREAD_PRIORITY_ABOVE_NORMAL);
并且把协议解析的优先级设置到较高的水平。
串口打开
//打开串口,设置波特率等参数
char* strCOM = "COM2";
hSerial = CreateFile(strCOM,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hSerial == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
printf("Error 1\n");
return false;
}
printf("Error 2\n");
return false;
}
DCB dcbSerialParams = { 0 };
DCB dcbSerial = { 0 };
dcbSerial.DCBlength = sizeof(dcbSerialParams);
if (!GetCommState(hSerial, &dcbSerialParams))
{
printf("Error 3\n");
return false;
}
dcbSerialParams.BaudRate = 38400;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if (!SetCommState(hSerial, &dcbSerialParams))
{
printf("Error 4\n");
return false;
}
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 1;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 5;
if (!SetCommTimeouts(hSerial, &timeouts))
{
printf("Error 5\n");
return false;
}
串口发送线程
串口发送线程,只要newCommand被置位,就发送消息
UINT WINAPI CSerialPort::CommandThread(void* pParam)
{
CSerialPort *pSerialPort = reinterpret_cast<CSerialPort*>(pParam);
cout << "发送串口命令线程启动成功" << endl;
while (!pSerialPort->s_bExit)
{
if (pSerialPort->newCommand)
{
cout << "发送一次指令" << endl;
u8 tqk_data[12] = { 0x00, 0x00, 0x00, 0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
//根据协议赋值
pSerialPort->WriteBuffer(tqk_data, 12); //WriteBuffer函数实现在后面
pSerialPort->newCommand = false;
}
Sleep(5);
}
return true;
}
WriteBuffer函数实现
bool CSerialPort::WriteBuffer(u8* lpBuf, DWORD dwToWrite)
{
//int_T nInputPorts = ssGetNumInputPorts(S);
//HANDLE* ptrHandle = (HANDLE*)ssGetDWork(S, nInputPorts);
//HANDLE hSerial = ptrHandle[0];
//HANDLE hThreadRead = ptrHandle[1];
//HANDLE hThreadWrite = ptrHandle[2];
OVERLAPPED osWrite = { 0 };
DWORD dwWritten;
DWORD dwRes;
bool fRes;
// Create this write operation's OVERLAPPED structure's hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
return FALSE;
// Issue write.
if (!WriteFile(hSerial, lpBuf, dwToWrite, &dwWritten, &osWrite))
{
if (GetLastError() != ERROR_IO_PENDING)
{
// WriteFile failed, but isn't delayed. Report error and abort.
fRes = FALSE;
printf("Serial port Writing error!\n");
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch (dwRes)
{
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hSerial, &osWrite, &dwWritten, FALSE))
fRes = FALSE;
else
if (dwToWrite > dwWritten)
printf("Serial port Writing timeout!\n");
// Write operation completed successfully.
fRes = TRUE;
break;
default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure's event handle.
fRes = FALSE;
break;
}
}
else
{
// WriteFile completed immediately.
fRes = TRUE;
}
CloseHandle(osWrite.hEvent);
return fRes;
}
串口接收线程
Data_Receive_Pre是协议解析函数,每次传输一个字节进去。
因为线程函数必须是类的静态成员,所以需要把类自身指针传进去,在协议解析函数中给类中的属性赋值。
Data_Receive_Pre函数需要根据自身协议实现,可以参考mavlink和匿名的协议解析。
UINT WINAPI CSerialPort::ListenThread(void* pParam)
{
CSerialPort *pSerialPort = reinterpret_cast<CSerialPort*>(pParam);
cout << "串口监听线程启动成功" << endl;
char szBuff[2] = { 0 };
DWORD dwBytesRead = 0;
DWORD dwRes;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = { 0 };
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
while (!pSerialPort->s_bExit)
{
if (!fWaitingOnRead)
{
// Issue read operation.
if (!ReadFile(pSerialPort->hSerial, szBuff, 1, &dwBytesRead, &osReader))
{
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
printf("Serial port Reading error!\n");
else
{
fWaitingOnRead = TRUE;
// printf("Serial port is waiting for data!\n");
}
}
else
{
//cout << "0" << (int)szBuff[0] << endl;
Data_Receive_Pre(szBuff[0], pSerialPort);
}
}
else
{
dwRes = WaitForSingleObject(osReader.hEvent, INFINITE);
switch (dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(pSerialPort->hSerial, &osReader, &dwBytesRead, FALSE))
// Error in communications; report it.
printf("Serial port Reading error after Overlapped!\n ");
else
{
if (dwBytesRead>0)
{
Data_Receive_Pre(szBuff[0], pSerialPort);
}
// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
// printf("%d Byte Data received after Overlapped!\n", (int)dwBytesRead);
}
break;
case WAIT_TIMEOUT:
printf("STILL Serial port is waiting for data!\n ");
break;
default:
break;
}
}
}
return 0;
}
以上就能做到串口收发同时。