windows串口通信操作和代码实现


本文在撰写时参考了zwhxz的博客,博主的思路很清晰,可惜没有具体实现代码,所以本文在该博客的基础上进行了扩充并提供了实现代码。zwhxz的博客网址:http://www.cnblogs.com/zahxz/archive/2012/12/24/2830535.html


windows系统,windows处理串口和其他通信设备都是作为文件来处理的。串口的处理包括四个阶段:打开阶段、串口的初始化、从串口读取和写入数据以及串口的关闭。本文分析四个阶段串口完成的工作,并提供具体代码,将对串口的操作封装成一个单例类,声明如下:


#ifndef SERIALSINGLETON_H_

#defineSERIALSINGLETON_H_

#include<Windows.h>

classSerialSingleton

{

public:

   ~SerialSingleton();

   staticSerialSingleton& getInstance(); //获取串口对象,单例模式下,每个串口只有一个类对象

   bool openPort();   //打开串口

   bool initPort();   //串口初始化

  int readFromPort(void* buff, DWORD size,unsignedint timeout); //从串口读数据

   bool writeToPort(constvoid* buff,DWORD size,unsignedint timeout);//写数据到串口

   bool closePort();   //关闭串口

private:

   SerialSingleton(); //将构造函数声明为私有,单例模式时常用方式

   SerialSingleton(constSerialSingleton& ref); //只声明不定义,防止调用隐式复制构造函数

   SerialSingleton& operator = (constSerialSingleton& ref);//只声明不定义,防止调用隐式赋值函数

   HANDLE m_ucom;  //串口句柄

};

#endif


1)打开串口

   在使用串口前,需要打开串口,可以使用CreateFile函数打开串口,CreateFile有两种形式CreateFileACreateFileW,使用ASCII码时用CreateFileA,使用Unicode码时使用CreateFileW,系统是没有CreateFile函数的。以CreateFileA为例,CreateFileA返回一个HANDLE句柄,该句柄在随后的操作中会被使用到。CreateFileA函数原型如下:

CreateFileA(

   _In_LPCSTR lpFileName,

   _In_DWORD dwDesiredAccess,

   _In_DWORD dwShareMode,

   _In_opt_LPSECURITY_ATTRIBUTES lpSecurityAttributes,

   _In_DWORD dwCreationDisposition,

   _In_DWORD dwFlagsAndAttributes,

   _In_opt_HANDLE hTemplateFile

      );

    其中,lpFileName对应串口名,如“COM24”;dwDesiredAccess指定对串口的访问权限,GENERIC_READ表示读取权限,GENERIC_WRITE表示写入权限,GENERIC_READ | GENERIC_WRITE表示读写权限;dwShareMode表示共享模式,设为0表示不共享;lpSecurityAttributes指向一个SECURITY_ATTRIBUTES结构指针,定义了文件安全属性;dwCreationDisposition指定文件存在和不存在时如何操作,有五个值:CREATE_NEWCREATE_ALWAYSOPEN_EXISTINGOPEN_ALWAYSTRUNCATE_EXISTING,打开串口设备时,选择OPEN_EXISTING,表示打开时该串口必须存在,不然函数调用失败;dwFlagsAndAttributes指定文件属性和标志位;hTemplateFile为一个文件或设备句柄,表示按这个参数给出的句柄为模板创建文件。函数调用成功返回true,否则,返回false

打开串口设备的程序实现如下:

bool SerialSingleton::openPort()

{

   // open serial port

   if (m_ucom !=INVALID_HANDLE_VALUE)

   {

       returntrue;

   }

   std::string com_port ="\\\\.\\COM24";//打开窗口24

   m_ucom =CreateFileA(com_port.c_str(),GENERIC_READ |GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

   if (m_ucom ==INVALID_HANDLE_VALUE)

   {

       return false;

   }

   return true;

}


2串行口的初始化

打开串口后需要对串口的一些参数进行初始化,包括设置串口设备控制块DCB的参数、设置串口缓冲区大小、清除缓存区中数据。

A.获取串口当前参数

  一般先获取串口当前参数,然后在修改。获取串口当前设备控制块参数通过GetCommState函数,函数原型如下:

  GetCommState(

     _In_ HANDLE hFile,

     _Out_LPDCB lpDCB

   );

    其中,hFile为通过CreateFileA函数打开的设备句柄m_ucom,第二个参数指向设备控制块DCBDCB为一个结构体,用来设置串口的众多参数,比如串口波特率等。函数调用成功返回true,否则,返回false获取串口参数后,可以通过DCB变量来修改串口设备控制块的参数,比如波特率等了。

B.设置串口参数

修改后的串口设备控制块参数,可以通过SetCommState函数设到串口设备控制块,其函数原型如下:

   SetCommState(

       _In_HANDLE hFile,

     _In_LPDCB lpDCB

    );

    因此,可以通过GetCommState函数获得当前串口设备控制块DCB,然后修改DCB,再通过SetCommState来配置串口。函数调用成功返回true,否则,返回false。


C.设置串口接收和发送缓冲区大小

    当一个串口被打开时,可以为该串口分配一个发送缓冲区和一个接收缓冲区。串口发送缓冲区和接收缓冲区的配置可以由函数SetupComm实现。如果不调用SetupComm,系统会为该串口分配默认大小的发送缓冲区和接收缓冲区。如果对缓冲区的大小没有特别的需求,则不需要调用该函数进行设置。SetupComm函数原型如下

SetupComm(

   _In_HANDLE hFile,

   _In_DWORD dwInQueue,

   _In_DWORD dwOutQueue

   );


   其中hFile是由CreateFile函数返回指向已打开串口的句柄,本文为m_ucom。参数dwInQueuedwOutQueue分别指定应用程序推荐使用的接收缓冲区和发送缓冲区的大小。如果不设置则采用默认值,没有特别需求的情况下,不需要设置。函数调用成功返回true,否则,返回false。


D.清空发送和接收缓冲区

    在进行串口发送和接收数据操作之前,最好使用PurgeComm函数将串行口发送缓冲区和接收缓冲区中的数据清楚干净。PurgeComm函数原型如下:

PurgeComm(

   _In_HANDLE hFile,

   _In_DWORD dwFlags

   );

    参数hFile是由CreateFile函数返回指向已打开串行口的句柄,本文为m_ucom。参数dwFlags指明执行的动作。如果dwFlagsPURGE_TXCLEAR,则通知系统清空发送缓冲区;如果dwFlagsPURGE_RXCLEAR,则通知系统清空接收缓冲区;如果需要将发送缓冲区和接收缓冲区全部清空,可以把dwFlags设置为PURGE_TXCLEAR|PURGE_RXCLEAR。如果PurgeComm函数调用成功返回true,否则,返回false。初始化串口程序如下:

bool SerialSingleton::initPort()

{

   // initilize opend serial port

   if (!openPort())

   {

       printf("open serialfailed!/n");

       return false;

   }

   DCB dcb;

   memset(&dcb, 0,sizeof(dcb));

   GetCommState(m_ucom, &dcb);

   dcb.BaudRate = 460800;

   dcb.ByteSize = 8;

   dcb.StopBits =ONESTOPBIT;

   dcb.fParity =FALSE;

   dcb.fNull =FALSE;

   dcb.Parity =NOPARITY;

   // set serial port device parameter

   if (!SetCommState(m_ucom, &dcb))

   {

       printf("Set serialport error:%d!!/n", GetLastError());

       return false;

   }

   // set serial port receive buffer andsend buffer size

   /*if (!SetupComm(m_ucom, 1048576,1048576))

   {

       printf("Set serialreceive buffer and send buffer failed!, error %d/n", GetLastError());

       return 0;

   }*/


   // clear serial port receive bufferand send buffer

   if (!PurgeComm(m_ucom,PURGE_TXCLEAR |PURGE_RXCLEAR))

   {

       printf("clear receivebuffer and send buffer failed!, error %d/n", GetLastError());

       return 0;

   }

   return true;

}


3)从串口读取和写入数据

A.接收串口发送过来的数据

接收串口发送来的数据主要通过ReadFile函数,其原型如下:

ReadFile(

   _In_HANDLE hFile,

   _Out_writes_bytes_to_opt_(nNumberOfBytesToRead,*lpNumberOfBytesRead)__out_data_source(FILE)LPVOID lpBuffer,

   _In_DWORD nNumberOfBytesToRead,

   _Out_opt_LPDWORD lpNumberOfBytesRead,

   _Inout_opt_LPOVERLAPPED lpOverlapped

   );


参数hFile是由CreateFileA函数返回指向已打开串口的句柄,本文为m_ucom_Out_writes_bytes_to_opt_(nNumberOfBytesToRead,*lpNumberOfBytesRead)为可选参数。nNumberOfBytesToRead指定从串口中读取的字节数,lpNumberOfBytesRead表示实际读取到的字节数,lpOverlappedOVERLAPPED结构体指针,如果CreateFileA时没有指定文件的标志位为FILE_FLAG_OVERLAPPED来创建hFile时,一般置为NULL函数调用成功返回true,否则,返回false在用ReadFile读取串口数据时,如果一直没读到数据,程序会一直停留在这,因此需要设置一个超时,超过这个时间,程序跳出等待。COMMTIMEOUTS是用ReadFileWriteFile来读写串口时使用的参数,其原型如下:

typedefstruct_COMMTIMEOUTS {

   DWORD ReadIntervalTimeout;         /* Maximum time between read chars.*/

   DWORD ReadTotalTimeoutMultiplier;  /* Multiplier of characters.       */

   DWORD ReadTotalTimeoutConstant;    /* Constant in milliseconds.       */

   DWORD WriteTotalTimeoutMultiplier; /* Multiplier of characters.       */

   DWORD WriteTotalTimeoutConstant;   /* Constant in milliseconds.       */

}COMMTIMEOUTS,*LPCOMMTIMEOUTS;


COMMTIMEOUTS结构的成员都以毫秒为单位。读串口数据时,一般设置一个总的读时间常量ReadTotalTimeoutConstant就好,通过GetCommTimeOuts函数来获取当前COMMTIMEOUTS结构,修改结构成员的值后,通过SetCommTimeOuts,用修改后的参数设置超时。

读串口数据的函数实现如下:

int SerialSingleton::readFromPort(void*buff,DWORDsize,unsignedinttimeout)

{

   // read data from serial

   COMMTIMEOUTS readCommTimeOuts;

   memset(&readCommTimeOuts, 0,sizeof(readCommTimeOuts));

   readCommTimeOuts.ReadTotalTimeoutConstant=timeout;

   SetCommTimeouts(m_ucom,&readCommTimeOuts);

   DWORD dwReadBytes = 0;

   if (ReadFile(m_ucom,buff,size, &dwReadBytes,NULL))

   {

       return dwReadBytes;

   }

   else

   {

       printf("read data fromserial port failed, error %d/n", GetLastError());

       return -1;

   }

}


B.向串口写入数据

   向串口写入数据主要通过WriteFile函数,其原型如下:

WriteFile(

   _In_HANDLE hFile,

   _In_reads_bytes_opt_(nNumberOfBytesToWrite)LPCVOID lpBuffer,

   _In_DWORD nNumberOfBytesToWrite,

   _Out_opt_LPDWORD lpNumberOfBytesWritten,

   _Inout_opt_LPOVERLAPPED lpOverlapped

   );


各参数与ReadFile函数的参数类似,不同的是nNumberOfBytesToWrite指定向串口中写入的字节数,lpNumberOfBytesWritten为实际向串口中写入的字节数。函数调用成功返回true,否则,返回false。与读串口数据的实现类似,向串口写入数据时也设置一个超时,向串口写入数据的实现如下:

bool SerialSingleton::writeToPort(constvoid*buff,DWORDsize,unsignedinttimeout)

{

   // write data to port serial

   COMMTIMEOUTS writeCommTimeOuts;

   memset(&writeCommTimeOuts, 0,sizeof(writeCommTimeOuts));

   writeCommTimeOuts.WriteTotalTimeoutConstant=timeout;

   SetCommTimeouts(m_ucom,&writeCommTimeOuts);

   DWORD dwWriteBytes = 0;

   if (WriteFile(m_ucom,buff,size, &dwWriteBytes,NULL))

   {

       return true;

   }

   else

   {

       printf("write data toserial port failed, eror %d/n", GetLastError());

       return false;

   }

}


4)串行口的关闭

在使用完串口后,需要关闭该串口,不然串口会一直处于打开状态,导致其他程序不能使用。关闭串口使用closePort()函数,closePort()实现如下:

bool SerialSingleton::closePort()

{

  if (m_ucom != INVALID_HANDLE_VALUE)

     {

         return CloseHandle(m_ucom);

     }

  return true;

}


CloseHandle原型如下:

CloseHandle(

   _In_HANDLE hObject

);

hObject为打开串口的句柄,本文为m_ucom,函数调用成功返回true,否则,返回false

    最后给出构造函数、析构函数和SerialSingleton& SerialSingleton::getInstance()

函数的实现,如果需要使用SerialSingleton类中各函数,只需调用getInstance()来获得类对象,再调用各个函数,例如调用openPort函数:SerialSingleton::SerialSingleton().openPort()。


SerialSingleton::SerialSingleton()

   :m_ucom(INVALID_HANDLE_VALUE)

{

}

SerialSingleton::~SerialSingleton()

{

}

SerialSingleton&SerialSingleton::getInstance()

{

   staticSerialSingleton serial;

   return serial;

}



评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值