Windows下的串口同通信及类的封装
最近用到了串口编程,所以写篇随笔将其记录下来。
Windows下串口通信的基本流程
先来梳理一下在Windows下对串口读写数据操作的基本流程。在Windows下(linux也是)串口的读写是以文件的方式去操作的,所以使用WindowsAPI:CreateFile来打开串口,代码为:
LPCTSTR Comname = _T("COM3");
HANDLE hSerial;
//API原型就不列了,可以去看文档,为了体流程这里使用方法。
//要注意的是第一个参数Comname为要打开的串口名,第六参数FILE_FLAG_OVERLAPPED为使用异步方式打开串口。
hSerial = CreateFile(Comname, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (hSerial == INVALID_HANDLE_VALUE)
{
printf("open fail \n");
CloseHandle(hSerial);
return -1;
}
打开串口后我们对串口设置缓冲去的大小:
//第一个参数为打开的串口,后面分别为设备内部读和写缓冲区的大小。
SetupComm(hSerial, 1024, 1024);
接下来开始设置串口的参数:
DCB dcb;
GetCommState(hSerial, &dcb);//获取当前的配置保存到dcb
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = CBR_115200;//设置串口波特率
dcb.ByteSize = 8;//设置一个字节的长度
dcb.StopBits = ONESTOPBIT;//设置停止位
dcb.Parity = NOPARITY;//设置校验位
SetCommState(hSerial, &dcb); //最后把存有配置信息的dcb设置给串口
然后对串口设置超时
COMMTIMEOUTS timeout; //创建一个端口超时结构体
timeout.ReadIntervalTimeout = MAXDWORD;
timeout.ReadTotalTimeoutConstant = 0;
timeout.ReadTotalTimeoutMultiplier = 0;
timeout.WriteTotalTimeoutConstant = 500;
timeout.WriteTotalTimeoutMultiplier = 5000;
SetCommTimeouts(hSerial, &timeout);//设定好参数后将其设置给串口
PurgeComm(hSerial, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR);//最后对串口进行一下清空操作。
由于我们使用异步的方式打开的串口,所以需要一个OVERLAPPED结构,先创建它后面会用到
OVERLAPPED ov_write, ov_read;
memset(&ov_write, 0, sizeof(OVERLAPPED));//初始化
memset(&ov_read, 0, sizeof(OVERLAPPED));
ov_write.hEvent = CreateEvent(NULL, false, false, NULL);//创建事件
ov_read.hEvent = CreateEvent(NULL, false, false, NULL);
然后为串口设置言关心的事件
SetCommMask(hSerial, EV_RXCHAR);//绑定串口接收到字符的事件
然后就是获取串口接收到的内容了,这里新开一个线程去后台监听串口是否收到数据,当有数据时将数据取出来,首先需要写一个读取串口的方法:
void readData(HANDLE serial, OVERLAPPED* ov_r)
{
DWORD dwGetEventMask = 0;
DWORD TrByte = 0;
BOOL Status = 0;
DWORD CError = 0;
COMSTAT comstat;
char readBuffer[1024] = { 0 };
while (true)
{
Status = WaitCommEvent(serial, &dwGetEventMask, ov_r);
if (Status == FALSE && GetLastError() == ERROR_IO_PENDING)
{
Status = GetOverlappedResult(serial, ov_r, &TrByte, TRUE);
}
ClearCommError(serial, &CError, &comstat);
if (Status == TRUE && dwGetEventMask & EV_RXCHAR && comstat.cbInQue > 0)
{
DWORD readedSize = 0;
memset(readBuffer, 0, sizeof(readBuffer));
Status = ReadFile(serial, readBuffer, sizeof(readBuffer), &readedSize, ov_r);
if (Status)
{
std::cout << "Read: " << readBuffer << std::endl;
}
PurgeComm(serial, PURGE_RXABORT | PURGE_RXCLEAR);
}
}
}
然后在刚才SetCommMask(hSerial, EV_RXCHAR);的后面启动线程去执行这个函数
std::thread readThread(&readData, hSerial, &ov_read);
至此便开始了对串口的监听,如果要向串口调用WindowsAPI函数WriteFile即可:
WriteFile(hSerial, writeBuffer, sizeof(writeBuffer), &writedSize, &ov_write);
C++封装
最后为了方便以后的复用,我使用C++对其进行了封装形成一个类,使用方法也简单了很多,以下为一个简单的示例:
//新建应用程序
#include "JSerialPortTool.h"
void showReadContant(char*indata);//槽函数,串口接收到数据后调用此函数,并把接收到的内容传给indata
int main()
{
JSerialPortTool serialTool(_T("COM1")); //构造时传入串口名
serialTool.SetComConf(CBR_115200, 8, 1, NOPARITY);//设置波特率、字节长度、停止位和校验位
serialTool.SetTimeOut(MAXDWORD, 0, 0, 500, 5000);//设置超时参数
serialTool.connectReadSlot(showReadContant);//绑定槽函数,在串口接收到数据后调用绑定的方法
while (true)
{
char writebuff[1024] = { 0 };
std::cin >> writebuff;
serialTool.WriteData(writebuff);//调用WriteData成员函数向串口写数据
}
return 0;
}
void showReadContant(char*indata)
{
std::cout << "get contance:" << indata << std::endl;
}