[C#][串口]Win32串口API

Win32串口API

在工业控制中,工控机(一般都基于Windows平台)经常需要与智能仪表通过串口进行通信。串口通信方便易行,应用广泛。 

一般情况下,工控机和各智能仪表通过RS485总线进行通信。RS485的通信方式是半双工的,只能由作为主节点的工控PC机依次轮询网络上的各智能控制单元子节点。每次通信都是由PC机通过串口向智能控制单元发布命令,智能控制单元在接收到正确的命令后作出应答。 

在Win32下,可以使用两种编程方式实现串口通信,其一是使用ActiveX控件,这种方法程序简单,但欠灵活。其二是调用Windows的API函数,这种方法可以清楚地掌握串口通信的机制,并且自由灵活。本文我们只介绍API串口通信部分。 

串口的操作可以有两种操作方式:同步操作方式和重叠操作方式(又称为异步操作方式)。同步操作时,API函数会阻塞直到操作完成以后才能返回(在多线程方式中,虽然不会阻塞主线程,但是仍然会阻塞监听线程);而重叠操作方式,API函数会立即返回,操作在后台进行,避免线程的阻塞。

无论那种操作方式,一般都通过四个步骤来完成:

  1. 打开串口
  2. 配置串口
  3. 读写串口
  4. 关闭串口

(1) 打开串口

Win32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的原型为:

HANDLE CreateFile( LPCTSTR lpFileName,

                  DWORD dwDesiredAccess,

                  DWORD dwShareMode,

                  LPSECURITY_ATTRIBUTES lpSecurityAttributes,

                  DWORD dwCreationDistribution,

                  DWORD dwFlagsAndAttributes,

                  HANDLE hTemplateFile);

 

  1. lpFileName:将要打开的串口逻辑名,如“COM1”
  2. dwDesiredAccess:指定串口访问的类型,可以是读取、写入或二者并列;
  3. dwShareMode:指定共享属性,由于串口不能共享,该参数必须置为0;
  4. lpSecurityAttributes:引用安全性属性结构,缺省值为NULL;
  5. dwCreationDistribution:创建标志,对串口操作该参数必须置为OPEN_EXISTING;
  6. dwFlagsAndAttributes:属性描述,用于指定该串口是否进行异步操作,该值为FILE_FLAG_OVERLAPPED,表示使用异步的I/O;该值为0,表示同步I/O操作;
  7. hTemplateFile:对串口而言该参数必须置为NULL;

同步I/O方式打开串口的示例代码:

HANDLE hCom;  //全局变量,串口句柄hCom = CreateFile("COM1",//COM1                    GENERIC_READ|GENERIC_WRITE, //允许读和写      0, //独占方式      NULL,      OPEN_EXISTING, //打开而不是创建      0, //同步方式NULL);if(hCom==(HANDLE)-1)

{

 AfxMessageBox("打开COM失败!");

return FALSE;

}return TRUE;

重叠I/O打开串口的示例代码:

HANDLE hCom;  //全局变量,串口句柄hCom = CreateFile("COM1",  //COM1                    GENERIC_READ|GENERIC_WRITE, //允许读和写                    0,  //独占方式                    NULL,                  OPEN_EXISTING,  //打开而不是创建                    FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式NULL);if(hCom == INVALID_HANDLE_VALUE)

{

 AfxMessageBox("打开COM失败!");

return FALSE;

}return TRUE;

(2) 配置串口

在打开通讯设备句柄后,常常需要对串口进行一些初始化配置工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、数据位数、奇偶校验和停止位数等信息。在查询或配置串口的属性时,都要用DCB结构来作为缓冲区。 

一般用CreateFile打开串口后,可以调用GetCommState函数来获取串口的初始配置。要修改串口的配置,应该先修改DCB结构,然后再调用SetCommState函数设置串口。 

DCB结构包含了串口的各项参数设置,下面仅介绍几个该结构常用的变量:

typedef struct _DCB {

//………    //波特率,指定通信设备的传输速率。这个成员可以是实际波特率值或者下面的常量值之一:    DWORD BaudRate;     //CBR_110,CBR_300,CBR_600,CBR_1200,CBR_2400,CBR_4800,CBR_9600,CBR_19200, CBR_38400,     //CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000, CBR_14400    DWORD fParity; // 指定奇偶校验使能。若此成员为1,允许奇偶校验检查     //…    BYTE ByteSize; // 通信字节位数,4—8    BYTE Parity; //指定奇偶校验方法。此成员可以有下列值:    //EVENPARITY 偶校验     NOPARITY 无校验    //MARKPARITY 标记校验   ODDPARITY 奇校验    BYTE StopBits; //指定停止位的位数。此成员可以有下列值:    //ONESTOPBIT 1位停止位   TWOSTOPBITS 2位停止位    //ONE5STOPBITS   1.5位停止位    //………} DCB;

winbase.h文件中定义了以上用到的常量。如下:

#define NOPARITY            0

#define ODDPARITY           1

#define EVENPARITY          2

#define ONESTOPBIT          0

#define ONE5STOPBITS        1

#define TWOSTOPBITS         2

#define CBR_110             110

#define CBR_300             300

#define CBR_600             600

#define CBR_1200            1200

#define CBR_2400            2400

#define CBR_4800            4800

#define CBR_9600            9600

#define CBR_14400           14400

#define CBR_19200           19200

#define CBR_38400           38400

#define CBR_56000           56000

#define CBR_57600           57600

#define CBR_115200          115200

#define CBR_128000          128000

#define CBR_256000          256000

GetCommState函数可以获得COM口的设备控制块,从而获得相关参数:

BOOL GetCommState(

   HANDLE hFile, //标识通讯端口的句柄    LPDCB lpDCB //指向一个设备控制块(DCB结构)的指针);

SetCommState函数设置COM口的设备控制块:

BOOL SetCommState(

   HANDLE hFile,

   LPDCB lpDCB

);

除了在BCD中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串口输入和输出的数据。如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。

BOOL SetupComm(

    HANDLE hFile, // 通信设备的句柄      DWORD dwInQueue, // 输入缓冲区的大小(字节数)      DWORD dwOutQueue // 输出缓冲区的大小(字节数));

在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。超时的作用是在指定的时间内没有读入或发送指定数量的字符,ReadFile或WriteFile的操作仍然会结束。 

要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。 

读写串口的超时有两种:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延。总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读写操作的超时。 

COMMTIMEOUTS结构的定义为:

typedef struct _COMMTIMEOUTS {  

    DWORD ReadIntervalTimeout; //读间隔超时     DWORD ReadTotalTimeoutMultiplier; //读时间系数     DWORD ReadTotalTimeoutConstant; //读时间常量     DWORD WriteTotalTimeoutMultiplier; // 写时间系数     DWORD WriteTotalTimeoutConstant; //写时间常量} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是: 

总超时=时间系数×要求读/写的字符数+时间常量 

例如,要读入10个字符,那么读操作的总超时的计算公式为: 

读总超时=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant 

可以看出:间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。 

如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。 

在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。 

配置串口的示例代码:

SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024COMMTIMEOUTS TimeOuts;

GetCommState(hCom, &TimeOuts);//设定读超时TimeOuts.ReadIntervalTimeout = 1000; TimeOuts.ReadTotalTimeoutMultiplier = 500; TimeOuts.ReadTotalTimeoutConstant = 5000;//设定写超时 TimeOuts.WriteTotalTimeoutMultiplier = 500; TimeOuts.WriteTotalTimeoutConstant = 2000; SetCommTimeouts(hCom, &TimeOuts); //设置超时 DCB dcb; GetCommState(hCom, &dcb); dcb.BaudRate = 9600; //波特率为9600 dcb.ByteSize = 8; //每个字节有8位 dcb.Parity = NOPARITY; //无奇偶校验位 dcb.StopBits = TWOSTOPBITS; //两个停止位SetCommState(hCom, &dcb); PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);

在读写串口之前,还要用PurgeComm()函数清空缓冲区,该函数原型:

BOOL PurgeComm(

    HANDLE hFile, //串口句柄     DWORD dwFlags // 需要完成的操作);

参数dwFlags指定要完成的操作,可以是下列值的组合:

PURGE_TXABORT   中断所有写操作并立即返回,即使写操作还没有完成。

PURGE_RXABORT   中断所有读操作并立即返回,即使读操作还没有完成。

PURGE_TXCLEAR   清除输出缓冲区

PURGE_RXCLEAR   清除输入缓冲区

(3) 读写串口

我们使用ReadFile和WriteFile读写串口,下面是两个函数的声明:

BOOL ReadFile(

    HANDLE hFile, //串口的句柄     // 读入的数据存储的地址,     // 即读入的数据将存储在以该指针的值为首地址的一片内存区     LPVOID lpBuffer,     DWORD nNumberOfBytesToRead, // 要读入的数据的字节数          // 指向一个DWORD数值,该数值返回读操作实际读入的字节数     LPDWORD lpNumberOfBytesRead,           // 重叠操作时,该参数指向一个OVERLAPPED结构,同步操作时,该参数为NULL。     LPOVERLAPPED lpOverlapped   );  BOOL WriteFile(     HANDLE hFile, //串口的句柄          // 写入的数据存储的地址,     // 即以该指针的值为首地址的nNumberOfBytesToWrite     // 个字节的数据将要写入串口的发送数据缓冲区。     LPCVOID lpBuffer,           DWORD nNumberOfBytesToWrite, //要写入的数据的字节数          // 指向指向一个DWORD数值,该数值返回实际写入的字节数     LPDWORD lpNumberOfBytesWritten,           // 重叠操作时,该参数指向一个OVERLAPPED结构,     // 同步操作时,该参数为NULL。LPOVERLAPPED lpOverlapped   );

在用ReadFile和WriteFile读写串口时,既可以同步执行,也可以重叠执行。在同步执行时,函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,这两个函数也会立即返回,费时的I/O操作在后台进行。 

ReadFile和WriteFile函数是同步还是异步由CreateFile函数决定,如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的操作就应该是重叠的;如果未指定重叠标志,则读写操作应该是同步的。ReadFile和WriteFile函数的同步或者异步应该和CreateFile函数相一致。 

ReadFile函数只要在串口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲区,而且要等这些字符从串行口送出去后才算完成操作。 

如果操作成功,这两个函数都返回TRUE。需要注意的是,当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。这说明重叠操作还未完成。 

同步方式读写串口比较简单,下面先例举同步方式读写串口的代码:

//同步读串口char str[100];

DWORD wCount;//读取的字节数BOOL bReadStat; bReadStat = ReadFile(hCom,str,100,&wCount,NULL);if(!bReadStat)

{

 AfxMessageBox("读串口失败!");

return FALSE;

}return TRUE;

//同步写串口char lpOutBuffer[100];

DWORD dwBytesWrite=100;

COMSTAT ComStat;

DWORD dwErrorFlags;

BOOL bWriteStat;

ClearCommError(hCom,&dwErrorFlags,&ComStat);

bWriteStat = WriteFile(hCom, lpOutBuffer, dwBytesWrite, &dwBytesWrite, NULL);if(!bWriteStat)

{

 AfxMessageBox("写串口失败!");

}

PurgeComm(hCom, PURGE_TXABORT|

 PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

在重叠操作时,操作还未完成函数就返回。 

重叠I/O非常灵活,它也可以实现阻塞(例如我们可以设置一定要读取到一个数据才能进行到下一步操作)。有两种方法可以等待操作完成:一种方法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员;另一种方法是调用GetOverlappedResult函数等待,后面将演示说明。 

下面我们先简单说一下OVERLAPPED结构和GetOverlappedResult函数: 

OVERLAPPED结构 

OVERLAPPED结构包含了重叠I/O的一些信息,定义如下:

typedef struct _OVERLAPPED {

    DWORD  Internal;

    DWORD  InternalHigh;

    DWORD  Offset;

    DWORD  OffsetHigh;

    HANDLE hEvent;

} OVERLAPPED;

在使用ReadFile和WriteFile重叠操作时,线程需要创建OVERLAPPED结构以供这两个函数使用。线程通过OVERLAPPED结构获得当前的操作状态,该结构最重要的成员是hEvent。hEvent是读写事件。当串口使用异步通讯时,函数返回时操作可能还没有完成,程序可以通过检查该事件得知是否读写完毕。 

当调用ReadFile, WriteFile 函数的时候,该成员会自动被置为无信号状态;当重叠操作完成后,该成员变量会自动被置为有信号状态。

GetOverlappedResult函数

BOOL GetOverlappedResult(

    HANDLE hFile, // 串口的句柄            // 指向重叠操作开始时指定的OVERLAPPED结构     LPOVERLAPPED lpOverlapped,           // 指向一个32位变量,该变量的值返回实际读写操作传输的字节数。     LPDWORD lpNumberOfBytesTransferred,           // 该参数用于指定函数是否一直等到重叠操作结束。     // 如果该参数为TRUE,函数直到操作结束才返回。     // 如果该参数为FALSE,函数直接返回,这时如果操作没有完成,     // 通过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。BOOL bWait   );

该函数返回重叠操作的结果,用来判断异步操作是否完成,它是通过判断OVERLAPPED结构中的hEvent是否被置位来实现的。 

异步读串口的示例代码:

char lpInBuffer[1024];

DWORD dwBytesRead = 1024;

COMSTAT ComStat;

DWORD dwErrorFlags;

OVERLAPPED m_osRead;

memset(&m_osRead,0,sizeof(OVERLAPPED));

m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

ClearCommError(hCom,&dwErrorFlags,&ComStat);

dwBytesRead = min(dwBytesRead,(DWORD)ComStat.cbInQue);if(!dwBytesRead)    return FALSE;

BOOL bReadStatus;

bReadStatus = ReadFile(hCom, lpInBuffer, dwBytesRead, &dwBytesRead, &m_osRead);if(!bReadStatus) //如果ReadFile函数返回FALSEif (GetLastError() == ERROR_IO_PENDING)

//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行读操作   {   WaitForSingleObject(m_osRead.hEvent, 2000);   //使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟   //当串口读操作进行完毕后,m_osRead的hEvent事件会变为有信号   PurgeComm(hCom, PURGE_TXABORT|    PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);   return dwBytesRead;

 }

return 0;

}

PurgeComm(hCom, PURGE_TXABORT|

    PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);return dwBytesRead;

对以上代码再作简要说明:在使用ReadFile 函数进行读操作前,应先使用ClearCommError函数清除错误。ClearCommError函数的原型如下:

BOOL ClearCommError(

    HANDLE hFile, // 串口句柄     LPDWORD lpErrors, // 指向接收错误码的变量     LPCOMSTAT lpStat // 指向通讯状态缓冲区    );

该函数获得通信错误并报告串口的当前状态,同时,该函数清除串口的错误标志以便继续输入、输出操作。 

参数lpStat指向一个COMSTAT结构,该结构返回串口状态信息。 COMSTAT结构 COMSTAT结构包含串口的信息,结构定义如下:

typedef struct _COMSTAT { // cst       DWORD fCtsHold : 1;   // Tx waiting for CTS signal      DWORD fDsrHold : 1;   // Tx waiting for DSR signal      DWORD fRlsdHold : 1;  // Tx waiting for RLSD signal      DWORD fXoffHold : 1;  // Tx waiting, XOFF char rec''d      DWORD fXoffSent : 1;  // Tx waiting, XOFF char sent      DWORD fEof : 1;       // EOF character sent      DWORD fTxim : 1;      // character waiting for Tx      DWORD fReserved : 25; // reserved      DWORD cbInQue;        // bytes in input buffer      DWORD cbOutQue;       // bytes in output buffer  } COMSTAT, *LPCOMSTAT;

本文只用到了cbInQue成员变量,该成员变量的值代表输入缓冲区的字节数。 

最后用PurgeComm函数清空串口的输入输出缓冲区。

这段代码用WaitForSingleObject函数来等待OVERLAPPED结构的hEvent成员,下面我们再演示一段调用GetOverlappedResult函数等待的异步读串口示例代码:

char lpInBuffer[1024];

DWORD dwBytesRead = 1024;

BOOL bReadStatus;

DWORD dwErrorFlags;

COMSTAT ComStat;

OVERLAPPED m_osRead;

ClearCommError(hCom, &dwErrorFlags, &ComStat);if (!ComStat.cbInQue)

return 0;

dwBytesRead = min(dwBytesRead,(DWORD)ComStat.cbInQue);

bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead,&dwBytesRead,&m_osRead);if(!bReadStatus) //如果ReadFile函数返回FALSEif(GetLastError() == ERROR_IO_PENDING)

 {

  GetOverlappedResult(hCom, &m_osRead, &dwBytesRead, TRUE);

// GetOverlappedResult函数的最后一个参数设为TRUE,                 //函数会一直等待,直到读操作完成或由于错误而返回。   return dwBytesRead;

 }

return 0;

}return dwBytesRead;

异步写串口的示例代码:

char buffer[1024];

DWORD dwBytesWritten=1024;

DWORD dwErrorFlags;

COMSTAT ComStat;

OVERLAPPED m_osWrite;

BOOL bWriteStat;

bWriteStat = WriteFile(hCom,buffer, dwBytesWritten, &dwBytesWritten, &m_OsWrite);if (!bWriteStat)

{

if(GetLastError()==ERROR_IO_PENDING)

 {

  WaitForSingleObject(m_osWrite.hEvent,1000);

return dwBytesWritten;

 }

return 0;

}return dwBytesWritten;

(4) 关闭串口

利用API函数关闭串口非常简单,只需使用CreateFile函数返回的句柄作为参数调用CloseHandle即可:

BOOL CloseHandle(

    HANDLE hObject; //handle to object to close );

串口编程的一个实例

为了让您更好地理解串口编程,下面我们分别编写两个例程,这两个例程都实现了工控机与百特显示仪表通过RS485接口进行的串口通信。其中第一个例程采用同步串口操作,第二个例程采用异步串口操作。 

我们只介绍软件部分,RS485接口接线方法不作介绍,感兴趣的读者可以查阅相关资料。

例程1

打开VC++6.0,新建基于对话框的工程RS485Comm,在主对话框窗口IDD_RS485COMM_DIALOG上添加两个按钮,ID分别为IDC_SEND和IDC_RECEIVE,标题分别为“发送”和“接收”;添加一个静态文本框IDC_DISP,用于显示串口接收到的内容。 

在RS485CommDlg.cpp文件中添加全局变量:

HANDLE hCom;  //全局变量,串口句柄

在RS485CommDlg.cpp文件中的OnInitDialog()函数添加如下代码:

// TODO: Add extra initialization herehCom = CreateFile("COM1",//COM1   GENERIC_READ|GENERIC_WRITE, //允许读和写   0, //独占方式   NULL,   OPEN_EXISTING, //打开而不是创建   0, //同步方式NULL);if (hCom == INVALID_HANDLE_VALUE)

{

 AfxMessageBox("打开COM失败!");

return FALSE;

}

SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024COMMTIMEOUTS TimeOuts;//设定读超时TimeOuts.ReadIntervalTimeout = MAXDWORD; TimeOuts.ReadTotalTimeoutMultiplier = 0; TimeOuts.ReadTotalTimeoutConstant = 0;//在读一次输入缓冲区的内容后读操作就立即返回,//而不管是否读入了要求的字符。//设定写超时 TimeOuts.WriteTotalTimeoutMultiplier = 100; TimeOuts.WriteTotalTimeoutConstant = 500; SetCommTimeouts(hCom, &TimeOuts); //设置超时 DCB dcb; GetCommState(hCom, &dcb); dcb.BaudRate = 9600; //波特率为9600 dcb.ByteSize = 8; //每个字节有8位 dcb.Parity = NOPARITY; //无奇偶校验位 dcb.StopBits = TWOSTOPBITS; //两个停止位SetCommState(hCom, &dcb); PurgeComm(hCom, PURGE_TXCLEAR|PURGE_RXCLEAR);

分别双击IDC_SEND按钮和IDC_RECEIVE按钮,添加两个按钮的响应函数:

void CRS485CommDlg::OnSend()

{

// TODO: Add your control notification handler code here  // 在此需要简单介绍百特公司XMA5000的通讯协议:  //该仪表RS485通讯采用主机广播方式通讯。  //串行半双工,帧11位,1个起始位(0),8个数据位,2个停止位(1)  //如:读仪表显示的瞬时值,主机发送:DC1 AAA BB ETX  //其中:DC1是标准ASCII码的一个控制符号,码值为11H(十进制的17)  //在XMA5000的通讯协议中,DC1表示读瞬时值  //AAA是从机地址码,也就是XMA5000显示仪表的通讯地址  //BB为通道号,读瞬时值时该值为01  //ETX也是标准ASCII码的一个控制符号,码值为03H  //在XMA5000的通讯协议中,ETX表示主机结束符  char lpOutBuffer[7];

 memset(lpOutBuffer,'\0', 7); //前7个字节先清零  lpOutBuffer[0] = '\x11';  //发送缓冲区的第1个字节为DC1  lpOutBuffer[1] = '0';  //第2个字节为字符0(30H)  lpOutBuffer[2] = '0'; //第3个字节为字符0(30H)  lpOutBuffer[3] = '1'; // 第4个字节为字符1(31H)  lpOutBuffer[4] = '0'; //第5个字节为字符0(30H)  lpOutBuffer[5] = '1'; //第6个字节为字符1(31H)  lpOutBuffer[6] = '\x03'; //第7个字节为字符ETX  //从该段代码可以看出,仪表的通讯地址为001 DWORD dwBytesWrite = 7;  COMSTAT ComStat;  DWORD dwErrorFlags;  BOOL bWriteStat;  ClearCommError(hCom,&dwErrorFlags,&ComStat);

 bWriteStat = WriteFile(hCom, lpOutBuffer, dwBytesWrite, &dwBytesWrite, NULL);

if (!bWriteStat)

 {

  AfxMessageBox("写串口失败!");

 }

}

void CRS485CommDlg::OnReceive()

{

// TODO: Add your control notification handler code here  char str[100];

 memset(str, '\0', 100);

 DWORD wCount = 100;//读取的字节数  BOOL bReadStat;  bReadStat = ReadFile(hCom,str,wCount,&wCount,NULL);  if (!bReadStat)

  AfxMessageBox("读串口失败!");

 PurgeComm(hCom, PURGE_TXABORT|

  PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

 m_disp = str;

 UpdateData(FALSE);

}

您可以观察返回的字符串,其中有和仪表显示值相同的部分,您可以进行相应的字符串操作取出仪表的显示值。 

打开ClassWizard,为静态文本框IDC_DISP添加CString类型变量m_disp,同时添加WM_CLOSE的相应函数:

void CRS485CommDlg::OnClose()

{

// TODO: Add your message handler code here and/or call default                   CloseHandle(hCom); //程序退出时关闭串口  CDialog::OnClose(); }

程序的相应部分已经在代码内部作了详细介绍。连接好硬件部分,编译运行程序,细心体会串口同步操作部分。

例程2

打开VC++6.0,新建基于对话框的工程RS485Comm,在主对话框窗口IDD_RS485COMM_DIALOG上添加两个按钮,ID分别为IDC_SEND和IDC_RECEIVE,标题分别为“发送”和“接收”;添加一个静态文本框IDC_DISP,用于显示串口接收到的内容。在RS485CommDlg.cpp文件中添加全局变量:

HANDLE hCom; //全局变量

串口句柄在RS485CommDlg.cpp文件中的OnInitDialog()函数添加如下代码:

hCom=CreateFile("COM1",//COM1   GENERIC_READ|GENERIC_WRITE, //允许读和写   0, //独占方式   NULL,   OPEN_EXISTING, //打开而不是创建   FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重叠方式   NULL);  if (hCom == INVALID_HANDLE_VALUE)

 {

  AfxMessageBox("打开COM失败!");

return FALSE;

 }

 SetupComm(hCom, 1024, 1024); //输入缓冲区和输出缓冲区的大小都是100  COMMTIMEOUTS TimeOuts;  //设定读超时  TimeOuts.ReadIntervalTimeout = MAXDWORD;  TimeOuts.ReadTotalTimeoutMultiplier = 0;  TimeOuts.ReadTotalTimeoutConstant = 0;  //在读一次输入缓冲区的内容后读操作就立即返回,  //而不管是否读入了要求的字符。  //设定写超时  TimeOuts.WriteTotalTimeoutMultiplier = 100;  TimeOuts.WriteTotalTimeoutConstant = 500;  SetCommTimeouts(hCom, &TimeOuts); //设置超时  DCB dcb;  GetCommState(hCom, &dcb);  dcb.BaudRate = 9600; //波特率为9600  dcb.ByteSize = 8; //每个字节有8位  dcb.Parity = NOPARITY; //无奇偶校验位  dcb.StopBits = TWOSTOPBITS; //两个停止位SetCommState(hCom, &dcb);  PurgeComm(hCom, PURGE_TXCLEAR|PURGE_RXCLEAR);

分别双击IDC_SEND按钮和IDC_RECEIVE按钮,添加两个按钮的响应函数:

void CRS485CommDlg::OnSend()

{

// TODO: Add your control notification handler code hereOVERLAPPED m_osWrite;  memset(&m_osWrite,0,sizeof(OVERLAPPED));

 m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

char lpOutBuffer[7];

 memset(lpOutBuffer, '\0', 7);

 lpOutBuffer[0] = '\x11';

 lpOutBuffer[1] = '0';

 lpOutBuffer[2] = '0';

 lpOutBuffer[3] = '1';

 lpOutBuffer[4] = '0';

 lpOutBuffer[5] = '1';

 lpOutBuffer[6] = '\x03';

 DWORD dwBytesWrite = 7;

 COMSTAT ComStat;

 DWORD dwErrorFlags;

 BOOL bWriteStat;

 ClearCommError(hCom, &dwErrorFlags, &ComStat);

 bWriteStat = WriteFile(hCom,lpOutBuffer,

  dwBytesWrite,& dwBytesWrite,&m_osWrite);

if (!bWriteStat)

 {

if(GetLastError() == ERROR_IO_PENDING)

  {

   WaitForSingleObject(m_osWrite.hEvent, 1000);

  }

 }

}void CRS485CommDlg::OnReceive()

{

// TODO: Add your control notification handler code hereOVERLAPPED m_osRead;  memset(&m_osRead,0,sizeof(OVERLAPPED));

 m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

 COMSTAT ComStat;

 DWORD dwErrorFlags;

char str[100];

 memset(str,''\0'',100);

 DWORD dwBytesRead=100;//读取的字节数BOOL bReadStat;  ClearCommError(hCom,&dwErrorFlags,&ComStat);  dwBytesRead = min(dwBytesRead, (DWORD)ComStat.cbInQue);

 bReadStat = ReadFile(hCom,str,

  dwBytesRead,&dwBytesRead,&m_osRead);

if(!bReadStat)

 {

if(GetLastError()==ERROR_IO_PENDING)

//GetLastError()函数返回ERROR_IO_PENDING,表明串口正在进行读操作   {    WaitForSingleObject(m_osRead.hEvent, 2000);       //使用WaitForSingleObject函数等待,直到读操作完成或延时已达到2秒钟       //当串口读操作进行完毕后,m_osRead的hEvent事件会变为有信号}  }  PurgeComm(hCom, PURGE_TXABORT|   PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);  m_disp = str;  UpdateData(FALSE); }

打开ClassWizard,为静态文本框IDC_DISP添加CString类型变量m_disp,同时添加WM_CLOSE的相应函数:

void CRS485CommDlg::OnClose()

{

// TODO: Add your message handler code here and/or call default       CloseHandle(hCom); //程序退出时关闭串口CDialog::OnClose(); }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/421868.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

性能测试-jmeter的控制器(十六)

一、if控制器 需求:使用“用户自定义变量”定义name变量,值可以是“baidu”或“itcast”,使用变量值,控制是否访问对应网站。 1、步骤: 在测试计划中添加用户定义的变量name,取值可为baidu或itcast添加两个http请求&#xff1a…

misc音频隐写

一、MP3隐写 (1)题解:下载附件之后是一个mp3的音频文件;并且题目提示keysyclovergeek;所以直接使用MP3stego对音频文件进行解密;mp3stego工具是音频数据分析与隐写工具 (2)mp3stego工具的使用:…

CSS实现前端布局更巧妙的方案!在 flex 布局中通过使用 margin 实现水平垂直居中以及其他常见的前端布局

在前端开发中,实现水平垂直居中一直是个热门话题。随着 CSS Flexbox 布局的普及,开发者们开始更多地使用 justify-content 和 align-items 这两个属性来解决这个问题。 然而,还有一种更加简洁、灵活的方式——使用 margin: auto; 来实现居中以…

大数据之Flink(二)

4、部署模式 flink部署模式: 会话模式(Session Mode)单作业模式(Per-Job Mode)应用模式(Application Mode) 区别在于集群的生命周期以及资源的分配方式;以及应用的main方法到底在…

Vue3使用vue-qrcode-reader实现扫码绑定设备功能

需求描述 移动端进入网站后,登录网站进入设备管理界面。点击添加设备,可以选择直接添加或者扫一扫。点击扫一扫进行扫描二维码获取设备序列号自动填充到添加设备界面的序列号输入框中。然后点击完成进行设备绑定。 安装vue-qrcode-reader 这里使用的版…

2024.9.11 作业

绘制组件制作时钟 代码&#xff1a; /*******************************************/ 文件名&#xff1a;widget.h /*******************************************/ #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> #include &l…

MAX3483ESA+T具有±15kV ESD保护的+3.3V、低功耗收发器,适用于RS-485和RS-422通信

MAX3483ESAT具有15kV ESD保护的3.3V、低功耗收发器&#xff0c;适用于RS-485和RS-422通信。每个器件包含一个驱动器和一个接收器。MAX3483ESAT具有限摆率驱动器&#xff0c;可充分降低EMI并减少因电缆端接不当引起的反射&#xff0c;从而实现数据速率高达250kbps的无误差数据传…

【中间件】-容器编排平台Kubernetes简介

目录 什么是K8s 为什么需要K8s 什么是容器(Contianer) K8s能做什么&#xff1f; K8s的架构原理 控制平面(Control plane) kube-apiserver etcd kube-scheduler kube-controller-manager cloud-controller-manager 小结 节点组件(Node) container runtime Pod kubelet ku…

AnyChart 数据可视化框架

AnyChart 数据可视化框架 AnyChart 是一个灵活的 JavaScript&#xff08;HTML5、SVG、VML&#xff09;图表框架&#xff0c;适合任何需要数据可视化的解决方案。 目录 下载并安装开始插件将 AnyChart 与 TypeScript 结合使用将 AnyChart 与 ECMAScript 6 结合使用技术集成贡献…

Anolis OS 7.9(龙蜥操作系统)上Oracle12C Release 2 (12.2)打补丁

本文的oracle使用的是单实例环境 一、打补丁前环境准备 1、确保make, ar, ld,和 nm四个可执行命令在$PATH中 export PATH$PATH:/bin2、查看已装的Oracle的OPatch版本 #切换到oracle用户 su - oracle#进入到数据库的安装目录下的opatch目录 cd /ora01/app/oracle/product/12…

JS_函数声明

JS中的方法,多称为函数,函数的声明语法和JAVA中有较大区别 函数说明 函数没有权限控制符不用声明函数的返回值类型,需要返回在函数体中直接return即可,也无需void关键字参数列表中,无需数据类型调用函数时,实参和形参的个数可以不一致声明函数时需要用function关键字函数没有…

github actions CICD简单使用案例

参考&#xff1a; https://developer.aliyun.com/article/1540773 https://github.com/ViggoZ/producthunt-daily-hot/blob/main/.github/workflows/generate_markdown.yml 1、创建github项目 目录&#xff1a; .github/workflows/fetch-news.yml actions执行yaml&#xff08;…

C语言 | Leetcode C语言题解之第397题整数替换

题目&#xff1a; 题解&#xff1a; //第一种动态规划:超时 // class Solution { // public: // int integerReplacement(int n) { // vector<int>dp(n1,0); // dp[1]0; // for(int i2;i<n;i){ // if(i%20){ // …

Vue接入高德地图并实现基本的路线规划功能

目录 一、申请密钥 二、安装依赖 三、代码实现 四、运行截图 五、官方文档 一、申请密钥 登录高德开放平台&#xff0c;点击我的应用&#xff0c;先添加新应用&#xff0c;然后再添加Key。 如图所示填写对应的信息&#xff0c;系统就会自动生成。 二、安装依赖 npm i am…

学生签到系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;签到信息管理&#xff0c;学生签到管理&#xff0c;班课信息管理&#xff0c;加入班课管理&#xff0c;课程信息管理 微信端账号功能包括&#xff1a;系统首…

C++(三)----内存管理

1.C/C内存分布 看下面这个问题&#xff08;考考你们之前学的咋样&#xff09;&#xff1a; int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char* pCh…

JDK 安装及配置教程(Windows)【安装】

文章目录 一、 下载1. 官网下载2. 其它渠道 二、 安装三、 配置四、 验证五、 双 JDK 环境 软件 / 环境安装及配置目录 一、 下载 1. 官网下载 安装地址&#xff1a;https://www.oracle.com/ 打开浏览器输入网址 https://www.oracle.com/index.html&#xff0c;进入 Oracle …

Python——turtle库(海龟绘图)介绍与使用

一、概述 在 Python 中&#xff0c;海龟绘图提供了一个实体“海龟”形象&#xff08;带有画笔的小机器动物&#xff09;&#xff0c;假定它在地板上平铺的纸张上画线。 二、运行环境 本文运行环境&#xff1a;Windows11&#xff0c;Python3.11&#xff0c;Pycharm2023.1.4 使…

哈佛斯坦福大学团队联合发布病理基础模型CHIEF,全面提升癌症诊断的准确性|顶刊精析·24-09-12

小罗碎碎念 今日顶刊&#xff1a;Nature 今天精读的这篇文章于24-09-04发表于Nature&#xff0c;作者来自哈佛大学、斯坦福大学。 作者角色作者姓名单位名称&#xff08;英文&#xff09;单位名称&#xff08;中文&#xff09;第一作者Xiyue WangDepartment of Biomedical Info…

如何快速清理Docker中的停止容器?

如何快速清理Docker中的停止容器? 方法一:使用`docker container prune`方法二:结合`docker ps`和`docker rm`注意(这些命令慎用,确定容器不需要之后再执行)💖The Begin💖点点关注,收藏不迷路💖 Docker容器在停止后可能会占用不必要的磁盘空间。如何清理这些停止的…