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

文章中代码下载地址 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=1if (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+1rtn = 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关闭会立即返回FalseStatus = 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不会当成异步命令,而是立即读取并返回TrueStatus = 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, 否则返回Falseif (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;
}

感谢支持

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

  1. 串口 同步和异步 理解

    本文主要三大块:一,串口同步和异步在底层通信上的区别(这部分点到为止,不是主要探讨内容,有个基本理解即可). 二,串口同步和异步编程实例及详解(主要部分). 三,串口同步和异步的作用(着眼当下,理解为 ...

  2. Node中同步与异步的方式读取文件

    场景 Node.js最大的特点就是异步式I/O(或者非阻塞I/O)与事件紧密结合的编程模式.这种模式与传统的同步式I/O线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻 ...

  3. 同步和异步的区别和联系以及一般在什么情况下使用它们

    对于同步和异步而言,是所有程序员都应该的掌握的基本内容.由于同步和异步设计的知识面比较多,这里我仅仅进行一下简单的讲解. 其根本目的在于理解同步和异步的含义以及应用.至于更详细的信息可以上网查阅相关资 ...

  4. JS中同步与异步的理解

    你应该知道,javascript语言是一门"单线程"的语言,不像java语言,类继承Thread再来个thread.start就可以开辟一个线程,所以,javascript就像一条 ...

  5. Linux下的Ubuntu16.04系统配置并使用USB转串口(串口转USB),最终使用python的serial和pyserial包实现串口的打开并读取数据

    1. USB转串口的配置 1.1 首先使用 lsmod | grep usbserial 指令查看系统是否包含USB转串口驱动,如果没有信息输出不代表没有驱动,我这边就是没有信息输出,且看后面分析: ...

  6. 计算机的同步操作与异步操作的概念,运城计算机同步与异步的概念和应用方法...

    同步与异步是程序员在开发软件的时候会经常用到的一个编程方法,而金我们就通过案例分析来了解一下,同步与异步的概念和应用方法. 1.同步 一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后, ...

  7. MFC win32 API串口同步模式代码示范

    win32 API串口同步模式代码示范 源文件下载: vs2015打开 文件名: MFC_Win32API_同步串口.rar 在OnInitDialog()位置初始化串口: handleFile1 = ...

  8. Java中同步和异步的区别是什么?优点缺点以及概念理解

    同步:提交请求--->等待服务器处理--->服务器处理完毕返回的这个期间客户端浏览器不能干别的事 异步:请求通过事件触发--->服务器处理(这时客户端浏览器还可以做其他的事情)--- ...

  9. js同步和异步的区别

    js同步和异步的区别 同步任务 异步任务 同步和异步运行机制 首先,不同于其他后端语言,javascript语言是单线程机制.所谓单线程就是按次序执行,执行完一个任务再执行下一个.对于浏览器来说,也就 ...

最新文章

  1. 第十六届全国大学生智能车竞赛全国总决赛报名信息汇总
  2. HALCON基于形变的模板匹配实现
  3. [Go] Cookie 使用简介
  4. MAC地址与IP地址
  5. easypoi list中的map导出_如何优雅的导出 Excel
  6. 计算机部门 消防安全隐患,市计算机:消防隐患勿轻视,安全意识常在心
  7. 2016重庆大学计算机学院复试分数线,重庆大学2016考研复试分数线(已公布)
  8. 【detectron】FPN网络中RPN构建与相应的损失函数
  9. 三极管饱和状态的判断
  10. 创建用户要给session权限,报错:user lacks CREATE SESSION privilege
  11. docker容器获取宿主机IP
  12. 7岁儿童智力检测题_7岁-11岁儿童智商测试题
  13. 【Spring学习笔记 九】Spring声明式事务管理实现机制
  14. 简明图解冯·诺依曼计算机体系
  15. 微信小程序音乐播放器
  16. 什么是嵌入式开发?初学者必看嵌入式学习课程
  17. FEM基函数:从理论推导到matlab实现形式
  18. 本地游升温,银泰百货成新晋打卡热门地
  19. zeppelin使用中的问题汇总
  20. 科技青年 | 训练机器说话20年,他勇闯阿里巴巴宝库

热门文章

  1. CSRF---跨站请求伪造
  2. 微信清理内置浏览器缓存
  3. LeCun称梯度下降是最优雅的 ML 算法,Marcus:我不同意!
  4. 计算机常用键盘有几个键失灵,电脑键盘忽然有几个键失灵了
  5. 2022-04-13 工作记录--LayUI-动态渲染数据表格的表头参数
  6. GEE导出图像到本地结果全部为空
  7. 谈一谈SaaS产品的架构设计
  8. R语言数值取消科学计数法表示
  9. Django笔记总结
  10. 计算机丢失libjcc dll,libjcc.dll 64位