博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++串口同步和异步的读取与串口设备编程
阅读量:3975 次
发布时间:2019-05-24

本文共 9942 字,大约阅读时间需要 33 分钟。

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

 

文章中代码下载地址 

 

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 地址

 

 

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等待数据的读取方法

 

打开并配置串口

 
  1. HANDLE InitCOM(LPCTSTR Port)

  2. {

  3. HANDLE hCom = INVALID_HANDLE_VALUE;

  4. hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,

  5. 0/*同步方式打开串口*/, NULL);

  6. if (INVALID_HANDLE_VALUE == hCom)

  7. {

  8. return INVALID_HANDLE_VALUE;

  9. }

  10. SetupComm(hCom, 4096, 4096);//设置缓存

  11.  
  12. DCB dcb;

  13.  
  14. GetCommState(hCom, &dcb);//设置串口

  15. dcb.DCBlength = sizeof(dcb);

  16. dcb.BaudRate = CBR_9600;

  17. dcb.StopBits = ONESTOPBIT;

  18. SetCommState(hCom, &dcb);

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

  21.  
  22. COMMTIMEOUTS ct;

  23. //设置读取超时时间,及ReadFlie最长等待时间

  24. ct.ReadIntervalTimeout = 0;

  25. ct.ReadTotalTimeoutConstant = 5000;

  26. ct.ReadTotalTimeoutMultiplier = 500;

  27.  
  28. ct.WriteTotalTimeoutMultiplier = 500;

  29. ct.WriteTotalTimeoutConstant = 5000;

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

  32.  
  33. return hCom;

  34. }

 

 

数据读取

 
  1. bool ComRead(HANDLE hCom, LPBYTE buf, int &len)

  2. {

  3. DWORD ReadSize = 0;

  4. BOOL rtn = FALSE;

  5.  
  6. //设置读取1个字节数据,当缓存中有数据到达时则会立即返回,否则直到超时

  7. rtn = ReadFile(hCom, buf, 1, &ReadSize, NULL);

  8.  
  9. //如果是超时rtn=true但是ReadSize=0,如果有数据到达,会读取一个字节ReadSize=1

  10. if (rtn == TRUE && 1 == ReadSize)

  11. {

  12. DWORD Error;

  13. COMSTAT cs = {0};

  14. int ReadLen = 0;

  15. //查询剩余多少字节未读取,存储于cs.cbInQue中

  16. ClearCommError(hCom, &Error, &cs);

  17. ReadLen = (cs.cbInQue > len) ? len : cs.cbInQue;

  18. if (ReadLen > 0)

  19. {

  20. //由于之前等待时以读取一个字节,所欲buf+1

  21. rtn = ReadFile(hCom, buf+1, ReadLen, &ReadSize, NULL);

  22. len = 0;

  23. if (rtn)

  24. {

  25. len = ReadLen + 1;

  26. }

  27. }

  28. }

  29. PurgeComm(hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

  30. return rtn != FALSE;

  31. }

 

数据写入

 
  1. bool ComWrite(HANDLE hCom, LPBYTE buf, int &len)

  2. {

  3. PurgeComm(hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

  4. BOOL rtn = FALSE;

  5. DWORD WriteSize = 0;

  6. rtn = WriteFile(hCom, buf, len, &WriteSize, NULL);

  7.  
  8. len = WriteSize;

  9. return rtn != FALSE;

  10. }

 

 

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

 

4、COM口异步读写

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

 

WaitCommEvent等待数据读取的方法

 

异步读取类声明

 
  1. class ComAsy

  2. {

  3. public:

  4. ComAsy();

  5. ~ComAsy();

  6. bool InitCOM(LPCTSTR Port);//打开窗口

  7. void UninitCOM(); //关闭串口并清理

  8.  
  9. //写入数据

  10. bool ComWrite(LPBYTE buf, int &len);

  11.  
  12. //读取线程

  13. static unsigned int __stdcall OnRecv(void*);

  14.  
  15. private:

  16. HANDLE m_hCom;

  17. OVERLAPPED m_ovWrite;//用于写入数据

  18. OVERLAPPED m_ovRead;//用于读取数据

  19. OVERLAPPED m_ovWait;//用于等待数据

  20. volatile bool m_IsOpen;//串口是否打开

  21. HANDLE m_Thread;//读取线程句柄

  22. };

 
 
  1. ComAsy::ComAsy():

  2. m_hCom(INVALID_HANDLE_VALUE),

  3. m_IsOpen(false),

  4. m_Thread(NULL)

  5. {

  6. memset(&m_ovWait, 0, sizeof(m_ovWait));

  7. memset(&m_ovWrite, 0, sizeof(m_ovWrite));

  8. memset(&m_ovRead, 0, sizeof(m_ovRead));

  9. }

  10.  
  11. ComAsy::~ComAsy()

  12. {

  13. UninitCOM();

  14. }

 

 

初始化并配置串口

 
  1. bool ComAsy::InitCOM(LPCTSTR Port)

  2. {

  3. m_hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,

  4. FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,//设置异步标识

  5. NULL);

  6. if (INVALID_HANDLE_VALUE == m_hCom)

  7. {

  8. return false;

  9. }

  10. SetupComm(m_hCom, 4096, 4096);//设置发送接收缓存

  11.  
  12. DCB dcb;

  13. GetCommState(m_hCom, &dcb);

  14. dcb.DCBlength = sizeof(dcb);

  15. dcb.BaudRate = CBR_9600;

  16. dcb.StopBits = ONESTOPBIT;

  17. SetCommState(m_hCom, &dcb);//配置串口

  18.  
  19. PurgeComm(m_hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

  20.  
  21. COMMTIMEOUTS ct;

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

  23. ct.ReadTotalTimeoutConstant = 0; //

  24. ct.ReadTotalTimeoutMultiplier = 0;//

  25.  
  26. ct.WriteTotalTimeoutMultiplier = 500;

  27. ct.WriteTotalTimeoutConstant = 5000;

  28.  
  29. SetCommTimeouts(m_hCom, &ct);

  30.  
  31. //创建事件对象

  32. m_ovRead.hEvent = CreateEvent(NULL, false, false, NULL);

  33. m_ovWrite.hEvent = CreateEvent(NULL, false, false, NULL);

  34. m_ovWait.hEvent = CreateEvent(NULL, false, false, NULL);

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

  37.  
  38. //创建读取线程

  39. m_Thread = (HANDLE)_beginthreadex(NULL, 0, &ComAsy::OnRecv, this, 0, NULL);

  40. m_IsOpen = true;

  41. return true;

  42. }

写入数据,由于写入数据一般不会有太高的性能要求,所以异步写入时如果数据在后台写入,则会等待写入完成后再退出,此时相当于同步的写入。

 
  1. bool ComAsy::ComWrite(LPBYTE buf, int &len)

  2. {

  3. BOOL rtn = FALSE;

  4. DWORD WriteSize = 0;

  5.  
  6. PurgeComm(m_hCom, PURGE_TXCLEAR|PURGE_TXABORT);

  7. m_ovWait.Offset = 0;

  8. rtn = WriteFile(m_hCom, buf, len, &WriteSize, &m_ovWrite);

  9.  
  10. len = 0;

  11. if (FALSE == rtn && GetLastError() == ERROR_IO_PENDING)//后台读取

  12. {

  13. //等待数据写入完成

  14. if (FALSE == ::GetOverlappedResult(m_hCom, &m_ovWrite, &WriteSize, TRUE))

  15. {

  16. return false;

  17. }

  18. }

  19.  
  20. len = WriteSize;

  21. return rtn != FALSE;

  22. }

读取数据

 
  1. unsigned int __stdcall ComAsy::OnRecv( void* LPParam)

  2. {

  3. ComAsy *obj = static_cast<ComAsy*>(LPParam);

  4.  
  5. DWORD WaitEvent = 0, Bytes = 0;

  6. BOOL Status = FALSE;

  7. BYTE ReadBuf[4096];

  8. DWORD Error;

  9. COMSTAT cs = {0};

  10.  
  11. while(obj->m_IsOpen)

  12. {

  13. WaitEvent = 0;

  14. obj->m_ovWait.Offset = 0;

  15. Status = WaitCommEvent(obj->m_hCom,&WaitEvent, &obj->m_ovWait );

  16. //WaitCommEvent也是一个异步命令,所以需要等待

  17. if (FALSE == Status && GetLastError() == ERROR_IO_PENDING)//

  18. {

  19. //如果缓存中无数据线程会停在此,如果hCom关闭会立即返回False

  20. Status = GetOverlappedResult(obj->m_hCom, &obj->m_ovWait, &Bytes, TRUE);

  21. }

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

  23. if (TRUE == Status //等待事件成功

  24. && WaitEvent&EV_RXCHAR//缓存中有数据到达

  25. && cs.cbInQue > 0)//有数据

  26. {

  27. Bytes = 0;

  28. obj->m_ovRead.Offset = 0;

  29. memset(ReadBuf, 0, sizeof(ReadBuf));

  30. //数据已经到达缓存区,ReadFile不会当成异步命令,而是立即读取并返回True

  31. Status = ReadFile(obj->m_hCom, ReadBuf, sizeof(ReadBuf), &Bytes, &obj->m_ovRead);

  32. if (Status != FALSE)

  33. {

  34. cout<<"Read:"<<(LPCSTR)ReadBuf<<" Len:"<< Bytes<<endl;

  35. }

  36. PurgeComm(obj->m_hCom, PURGE_RXCLEAR|PURGE_RXABORT);

  37. }

  38.  
  39. }

  40. return 0;

  41. }

关闭串口
 
  1. void ComAsy::UninitCOM()

  2. {

  3. m_IsOpen = false;

  4. if (INVALID_HANDLE_VALUE != m_hCom)

  5. {

  6. CloseHandle(m_hCom);

  7. m_hCom = INVALID_HANDLE_VALUE;

  8. }

  9. if (NULL != m_ovRead.hEvent)

  10. {

  11. CloseHandle(m_ovRead.hEvent);

  12. m_ovRead.hEvent = NULL;

  13. }

  14. if (NULL != m_ovWrite.hEvent)

  15. {

  16. CloseHandle(m_ovWrite.hEvent);

  17. m_ovWrite.hEvent = NULL;

  18. }

  19. if (NULL != m_ovWait.hEvent)

  20. {

  21. CloseHandle(m_ovWait.hEvent);

  22. m_ovWait.hEvent = NULL;

  23. }

  24. if (NULL != m_Thread)

  25. {

  26. WaitForSingleObject(m_Thread, 5000);//等待线程结束

  27. CloseHandle(m_Thread);

  28. m_Thread = NULL;

  29. }

  30. }

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

 

5、串口设备开发

一般的串口设备都是上位机发送一个命令设备返回命令执行的结果,同步窗口读取非常适合这种模式。一般先WriteFile发送一个命令,然后ReadFile读取结果。将超时参数设为设备动作返回需要的最长时间,这样就在发送命令后知道命令的执行结果。

采用同步方式读取可以封装一个设备类,类的结构大致如下。

 
  1. class Device

  2. {

  3. public:

  4. Device();

  5. ~Device();

  6. bool Init(LPCTSTR Port, ...);

  7. void UnInit();

  8.  
  9. bool Option1(LPCTSTR Param1,...)

  10. {

  11. m_cs.Lock();//每一个操作前先锁定设备

  12. WriteFile(m_hCom, ...);//发送命令

  13.  
  14. ReadFile(m_hCom, ...);//获取命令结果

  15. m_cs.Unlock();

  16. return true;

  17. }

  18. bool Option2(LPCTSTR Param1,...);

  19. //...

  20.  
  21.  
  22. //查询状态线程,每隔一段时间查询一次状态,以便知道设备是否在线还是离线

  23. static unsigned int __stdcall QueryStatus(void*);

  24.  
  25.  
  26. private:

  27. HANDLE m_hCom;

  28. bool m_IsOpen;

  29. int m_DeviceStatus;

  30.  
  31. CCriticalSection m_cs;//

  32.  
  33. HANDLE m_ThreadStatus;

  34. };

 

 

 

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

 

 

 

 

 

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

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

 

 

串口初始化, 与之前的异步方法一致,只是在设置超时是将ReadIntervalTimeout设置为2ms, 这样这样ReadFile异步读取时,会返回FALSE,并且GetLastError() == ERROR_IO_PENDING,然后调用GetOverlappedResult等待数据到达,如果有数据到达且时间超过2ms则会返回,否则会一直等待。

串口未关闭的情况下,GetOverlappedResult返回需要两个条件,一是超时或者读取的字节以到达ReadFile中指定的字节数,二是有数据到达,只有同时两个条件满足才会返回。

 

代码如下:

初始化代码:

 
  1. bool ComAsy::InitCOM(LPCTSTR Port)

  2. {

  3. m_hCom = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,

  4. FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,//设置异步标识

  5. NULL);

  6. if (INVALID_HANDLE_VALUE == m_hCom)

  7. {

  8. return false;

  9. }

  10. SetupComm(m_hCom, 4096, 4096);//设置发送接收缓存

  11.  
  12. DCB dcb;

  13. GetCommState(m_hCom, &dcb);

  14. dcb.DCBlength = sizeof(dcb);

  15. dcb.BaudRate = CBR_9600;

  16. dcb.StopBits = ONESTOPBIT;

  17. SetCommState(m_hCom, &dcb);//配置串口

  18.  
  19. PurgeComm(m_hCom, PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR|PURGE_TXABORT);

  20.  
  21. COMMTIMEOUTS ct;

  22. //读取延时设为2ms,这样ReadFile异步读取时,会返回FALSE,并且GetLastError() == ERROR_IO_PENDING。

  23. //如果设为MAXDWORD,ReadFile异步读取时,会返回TRUE

  24. //然后调用GetOverlappedResult等待数据到达,如果有数据到达且时间超过2ms则会返回,否则会一直等待。

  25. ct.ReadIntervalTimeout = 2;

  26. ct.ReadTotalTimeoutConstant = 0; //

  27. ct.ReadTotalTimeoutMultiplier = 0;//

  28.  
  29. ct.WriteTotalTimeoutMultiplier = 500;

  30. ct.WriteTotalTimeoutConstant = 5000;

  31.  
  32. SetCommTimeouts(m_hCom, &ct);

  33.  
  34. //创建事件对象

  35. m_ovRead.hEvent = CreateEvent(NULL, false, false, NULL);

  36. m_ovWrite.hEvent = CreateEvent(NULL, false, false, NULL);

  37. m_ovWait.hEvent = CreateEvent(NULL, false, false, NULL);

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

  40.  
  41. //创建读取线程

  42. m_Thread = (HANDLE)_beginthreadex(NULL, 0, &ComAsy::OnRecv, this, 0, NULL);

  43. m_IsOpen = true;

  44. return true;

  45. }

 

 

读取线程代码:

 
  1. unsigned int __stdcall ComAsy::OnRecv( void* LPParam)

  2. {

  3. ComAsy *obj = static_cast<ComAsy*>(LPParam);

  4.  
  5. DWORD Bytes = 0;

  6. BOOL Status = FALSE;

  7. BYTE ReadBuf[4096];

  8. DWORD Error;

  9. COMSTAT cs = {0};

  10.  
  11. while(obj->m_IsOpen)

  12. {

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

  15.  
  16. Bytes = 0;

  17. obj->m_ovRead.Offset = 0;

  18. memset(ReadBuf, 0, sizeof(ReadBuf));

  19. Status = ReadFile(obj->m_hCom, ReadBuf, sizeof(ReadBuf), &Bytes, &obj->m_ovRead);

  20. //数据已经到达缓存区,读取会立即返回,并返回True, 否则返回False

  21. if (Status == FALSE && GetLastError() == ERROR_IO_PENDING)

  22. {

  23. //如果有数据到达 且 时间超过ReadIntervalTimeout则会返回,否则会一直等待

  24. Status = GetOverlappedResult(obj->m_hCom, &obj->m_ovRead, &Bytes, TRUE);

  25. }

  26. if (FALSE != Status && Bytes > 0)

  27. {

  28. cout<<"Read:"<<(LPCSTR)ReadBuf<<" Len:"<< Bytes<<endl;

  29. }

  30. PurgeComm(obj->m_hCom, PURGE_RXCLEAR|PURGE_RXABORT);

  31.  
  32. }

  33.  
  34. return 0;

  35. }

 

转载地址:http://zyeki.baihongyu.com/

你可能感兴趣的文章
js实时显示系统时间
查看>>
js隐藏导航菜单
查看>>
mysql里的varchar值转换为可排序的值(MySql varchar排序 CAST ,CONVERT)
查看>>
表单特效-点击左侧选项选入右侧选择框
查看>>
Javascript实现子窗口向父窗口传值(转)
查看>>
Apache的Order Allow Deny心得
查看>>
session写入错误解决
查看>>
整合freeBSD下nginx+php+mysql安装方案(ports安装)
查看>>
Ngnix安装方法和简单的配置共享
查看>>
Nginx前端代理Apache
查看>>
Nginx 413错误的排查:修改上传文件大小限制
查看>>
FreeBSD下nginx并支持php配置详解
查看>>
绝对好东西,让你省去几万块的大学学费
查看>>
Nginx中运行项目403 forbidden错误
查看>>
名企实习一年我学会15件事
查看>>
FreeBSD7.3 打开IPFW防火墙
查看>>
freebsd关闭ipfw防火墙
查看>>
sshguard-ipfw在freebsd 8下的安装及配置
查看>>
五险一金的用法~~
查看>>
背下这148句话,你可以提高一个档次了,不止在文学方面
查看>>