9.1 设定逾时参数(time-out parameters

应用程序每次打开一个通信端口时,必须使用COMMTIMEOUTS结构设置通信超时。如果这个结构未被配置,端口使用由驱动程序提供的默认时间超时或是以前通信连接的超时时间。如果实际使用的超时设置不同,而采用相同的超时处理,应用程序会出现读写操作永远不能结束或是频繁结束的现象。

当读写操作超时,操作完成,而且ReadFileWriteFile函数没有返回错误值。要确定是否一个操作已超时,验证实际传输的字节数是否少于请求的字节数。例如,如果ReadFile函数返回TRUE,但传输的字节数比请求的字节数要少,说明操作已超时。

COMMTIMEOUTS结构是用来在SetCommTimeoutsGetCommTimeouts函数中对通信设备的超时参数进行设置和查询的功能。这些参数确定了设备上ReadFileWriteFile函数的行为。

COMMTIMEOUTS结构体的定义如下:

typedef struct _COMMTIMEOUTS { 

DWORD ReadIntervalTimeout; 

DWORD ReadTotalTimeoutMultiplier; 

DWORD ReadTotalTimeoutConstant; 

DWORD WriteTotalTimeoutMultiplier; 

DWORD WriteTotalTimeoutConstant; 

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

成员ReadIntervalTimeout指定了通信线路上的两个字符到达的可接受的最大时间,以毫秒为单位。在执行ReadFile操作时,从收到的第一个字符时开始计时。如果任何两个字符的到来时间间隔超过这一数额,ReadFile操作完成,而且任何缓冲的数据返回。零值表示超时间隔不使用。

成员ReadTotalTimeoutMultiplier指定了用于计算读操作的总超时的乘数因子,以毫秒为单位。对于每次读操作,这个值乘以要读取的字节数。

成员ReadTotalTimeoutConstant指定用于计算读操作的总超时的常数因子,以毫秒为单位。对于每次读操作,该值被加上ReadTotalTimeoutMultiplier成员和所请求的字节数的乘积。如果ReadTotalTimeoutMultiplierReadTotalTimeoutConstant成员均为零值,表示超时设置不用于读操作。

成员WriteTotalTimeoutMultiplierWriteTotalTimeoutConstant的意义分别与成员ReadTotalTimeoutMultiplierReadTotalTimeoutConstant类似。

设定串口的逾时参数的步骤如下:

1. 通过调用GetCommTimeouts函数或手动设置初始化COMMITEMEOUTS结构。 

2. 设置ReadIntervalTimeout成员,指定传输的两个字符之间在不超时的情况下能间隔的最大毫秒数。

3. 指定读超时乘数因子ReadTotalTimeoutMultiplier成员。对于每次读操作,用这个数乘以预计将收到的字节数。 

4. 指定读超时常数因子ReadTotalTimeoutConstant成员。这个成员加上总字节数乘以ReadTotalTimeoutMultiplier毫秒数,结果是一个读操作发生超时之前必须经过的毫秒数。

5. 指定写超时乘数因子WriteTotalTimeoutMultiplier成员

6. 指定读超时常数因子WriteTotalTimeoutConstant成员。

7. 调用SetCommTimeouts函数激活端口超时设置。 

下面的例子给出了如何设定逾时参数:

// Retrieve the time-out parameters for all read and write operations

// on the port. 

COMMTIMEOUTS CommTimeouts;

GetCommTimeouts (hPort, &CommTimeouts);

// Change the COMMTIMEOUTS structure settings.

CommTimeouts.ReadIntervalTimeout = MAXDWORD;  

CommTimeouts.ReadTotalTimeoutMultiplier = 0;  

CommTimeouts.ReadTotalTimeoutConstant = 0;    

CommTimeouts.WriteTotalTimeoutMultiplier = 10;  

CommTimeouts.WriteTotalTimeoutConstant = 1000;    

// Set the time-out parameters for all read and write operations

// on the port. 

if (!SetCommTimeouts (hPort, &CommTimeouts))

{

  // Could not set the time-out parameters.

  MessageBox (hMainWnd, TEXT("Unable to set the time-out parameters"), 

              TEXT("Error"), MB_OK);

  dwError = GetLastError ();

  return FALSE;

}

9.2 写入通信端口

串口通过WriteFile函数连接到另一台设备进行数据传输。调用此函数之前,必须打开一个应用程序和配置串行端口。

函数WriteFile将数据写入到一个文件。WriteFile从文件指针指示的位置向文件中写入数据。写操作已经完成后,文件指针根据实际写入的字节数进行调整。

函数WriteFile的原型如下:

BOOL WriteFile( 

HANDLE hFile, 

LPCVOID lpBuffer, 

DWORD nNumberOfBytesToWrite, 

LPDWORD lpNumberOfBytesWritten, 

LPOVERLAPPED lpOverlapped);

参数hFile是要写入的文件句柄。创建该文件句柄时,必须设置对文件的GENERIC_WRITE访问。

参数lpBuffer指向要写入文件数据的缓冲区。

参数nNumberOfBytesToWrite是写入文件的字节数。零值指定一个空的写操作。一个空写操作不向文件写入任何字节,但改变时间戳。 WriteFile函数不截断也不扩展文件。如果需要截断或扩展文件,使用SetEndOfFile函数。网络上命名管道的写操作长度的上限是65,535个字节。

参数lpNumberOfBytesWritten指向真正写入到文件的字节数。在做任何工作或错误检查之前,WriteFile函数将此值设置为零。

参数lpOverlapped不被支持,值为NULL

函数WriteFile返回非零表示成功,零表示失败。

因为Windows CE不支持重叠I / O时,主线程或任何线程不应该尝试向串行端口写入大量数据。应用程序可以创建多个线程处理读写操作来模拟重叠I/O。为了协调多个线程,应用程序需要调用WaitCommEvent函数来阻止线程,直到特定的通信事件发生。

写入通信端口的步骤如下:

1. 将端口的句柄作为hFile参数传入到WriteFile函数。这个句柄是由CreateFile函数打开端口时返回的。

2. 向参数lpBuffer指定一个指针,指向要写入的数据缓冲区。通常,这个数据是二进制数据或字符数组。

3. 向参数nNumberOfBytesToWrite中指定要写入的字符数。基于Windows CE的设备,应用程序可以将Unicode字符转换为ASCII字符,从而支持串口设备间的文本传输。整个缓冲区都可以传递到驱动程序。

4. 往参数lpNumberOfBytesWritten指针一个指针,用于返回实际写入的字节数。 WriteFile函数将填充这个变量,这样应用程序可以判断是否正确完成数据的传输。

5. 设置lpOverlappedNULL

下面的例子给出了如何写入串口:

DWORD dwError, dwNumBytesWritten;

WriteFile (hPort,              // Port handle

           &Byte,              // Pointer to the data to write 

           1,                  // Number of bytes to write

           &dwNumBytesWritten, // Pointer to the number of bytes written

           NULL                // Must be NULL for Windows CE

);

9.3 使用通信事件(communication event

通讯事件是在发生重大事故时由Windows CE发送给应用程序的通知。使用WaitCommEvent函数,应用程序可以阻止一个线程直到一个特定的事件发生。调用SetCommMask函数可以设置应用程序继续处理前必须要等待的事件。当指定多个事件时,任何一个指定事件的发生都会造成WaitCommEvent函数返回。

例如,这种机制使应用程序能够监控数据到达串口的时间。通过等待一个表示数据是否到达的通信事件,应用程序避免了一直循环调用ReadFile函数等待数据到达的串行端口。 ReadFile函数只有当有数据读取时才被调用。

函数SetCommMask的功能是指定通讯设备需要监视的事件。该函数的原型如下:

BOOL SetCommMask(

HANDLE hFile, 

DWORD dwEvtMask);

参数hFile要写入的文件句柄。

函数dwEvtMask指定要启用的事件。零值表示禁用所有事件。这个参数可以是下列值的组合:如表9-7

描述

EV_BREAK

检测到输入有中断发生

EV_CTS

CTS信号发生状态转变

EV_DSR

DSR信号发生状态转变

EV_ERR

线状态发生错误。线状态错误有CE_FRAMECE_OVERRUN,以及CE_RXPARITY

EV_RING

检测到振铃指示

EV_RLSD

RLSD信号发生状态转变

EV_RXCHAR

接收到字符,并存放在输入缓冲区

EV_RXFLAG

接收到事件字符,并存放在输入缓冲区。事件字符在设备的DCB结构中指定,并使用SetCommState函数进行设置

EV_TXEMPTY

输出缓冲区中的最后一个字符被发送

9-7 事件返回值说明

函数SetCommMask返回非零表示成功,零表示失败。

函数WaitCommEvent等待指定通信设备上事件的发生。 WaitCommEvent监视的事件是包含在与设备句柄相关联的事件掩码中。函数的原型如下:

BOOL WaitCommEvent(

HANDLE hFile, 

LPDWORD lpEvtMask, 

LPOVERLAPPED lpOverlapped);

参数hFile要写入的文件句柄。

参数lpEvtMask是一个指向32位变量的长指针。这个变量接收表示发生的事件的掩码。如果出现错误,该值是零;否则,它是一个或多个下列值:如表9-8所示

描述

EV_BREAK

检测到输入有中断发生

EV_CTS

CTS信号发生状态转变

EV_DSR

DSR信号发生状态转变

EV_ERR

线状态发生错误。线状态错误有CE_FRAMECE_OVERRUN,以及CE_RXPARITY

EV_POWER

电源事件,这是在器件上电时产生的

EV_RING

检测到振铃指示

EV_RLSD

RLSD信号发生状态转变

EV_RXCHAR

接收到字符,并存放在输入缓冲区

EV_RXFLAG

接收到事件字符,并存放在输入缓冲区。事件字符在设备的DCB结构中指定,并使用SetCommState函数进行设置

EV_TXEMPTY

输出缓冲区中的最后一个字符被发送

9-8 程序状态值说明

参数lpOverlapped被忽略,设置为NULL

函数WaitCommEvent返回非零表示成功,零表示失败。

使用通信事件的步骤如下:

1. 调用SetCommMask函数设置需要监控的事件。

2. 调用WaitCommEvent函数。当一个应用程序指定一个以上的事件,lpEvtMask参数指向一个变量。该变量用来标识导致WaitCommEvent函数返回的事件。

3. 如果WaitCommEvent函数返回有数据需要读取的事件。使用一个循环调用ReadFile函数直到所有接收到的数据已全部被读取。

4. 再次调用SetCommMask函数设置需要监控的事件。

下面的例子展示了如何使用通信时间:

BYTE Byte;

DWORD dwBytesTransferred;

// Specify a set of events to be monitored for the port.

SetCommMask (hPort, EV_RXCHAR | EV_CTS | EV_DSR | EV_RLSD | EV_RING);

while (hPort != INVALID_HANDLE_VALUE) 

{

  // Wait for an event to occur for the port.

  WaitCommEvent (hPort, &dwCommModemStatus, 0);

  // Re-specify the set of events to be monitored for the port.

  SetCommMask (hPort, EV_RXCHAR | EV_CTS | EV_DSR | EV_RING);

  if (dwCommModemStatus & EV_RXCHAR) 

  {

    // Loop for waiting for the data.

    do 

    {

      // Read the data from the serial port.

      ReadFile (hPort, &Byte, 1, &dwBytesTransferred, 0);

      // Display the data read.

      if (dwBytesTransferred == 1)

        ProcessChar (Byte);

    } while (dwBytesTransferred == 1);

  }

9.4 关闭序列通信端口

调用CloseHandle函数来关闭串行端口,此时应用程序时完成对串口的交互。函数CloseHandle有一个参数,它是由CreateFile函数调用打开的端口返回的句柄。函数CloseHandle的原型如下:

BOOL CloseHandle( 

HANDLE hObject);

函数CloseHandle返回非零表示成功,零表示失败。

9.5 小结

本章主要介绍了串口通信的流程,包括了序列通信的基础,如何设置并监控序列通信端口。