应用程序每次打开一个通讯端口时,必须使用COMMTIMEOUTS结构设置通讯超时。若是这个结构未被配置,端口使用由驱动程序提供的默认时间超时或是之前通讯链接的超时时间。若是实际使用的超时设置不一样,而采用相同的超时处理,应用程序会出现读写操做永远不能结束或是频繁结束的现象。数组
当读写操做超时,操做完成,并且ReadFile和WriteFile函数没有返回错误值。要肯定是否一个操做已超时,验证明际传输的字节数是否少于请求的字节数。例如,若是ReadFile函数返回TRUE,但传输的字节数比请求的字节数要少,说明操做已超时。网络
COMMTIMEOUTS结构是用来在SetCommTimeouts和GetCommTimeouts函数中对通讯设备的超时参数进行设置和查询的功能。这些参数肯定了设备上ReadFile和WriteFile函数的行为。app
COMMTIMEOUTS结构体的定义以下:ide
typedef struct _COMMTIMEOUTS { 函数
DWORD ReadIntervalTimeout; oop
DWORD ReadTotalTimeoutMultiplier; spa
DWORD ReadTotalTimeoutConstant; 线程
DWORD WriteTotalTimeoutMultiplier; 指针
DWORD WriteTotalTimeoutConstant; code
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
成员ReadIntervalTimeout指定了通讯线路上的两个字符到达的可接受的最大时间,以毫秒为单位。在执行ReadFile操做时,从收到的第一个字符时开始计时。若是任何两个字符的到来时间间隔超过这一数额,ReadFile操做完成,并且任何缓冲的数据返回。零值表示超时间隔不使用。
成员ReadTotalTimeoutMultiplier指定了用于计算读操做的总超时的乘数因子,以毫秒为单位。对于每次读操做,这个值乘以要读取的字节数。
成员ReadTotalTimeoutConstant指定用于计算读操做的总超时的常数因子,以毫秒为单位。对于每次读操做,该值被加上ReadTotalTimeoutMultiplier成员和所请求的字节数的乘积。若是ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant成员均为零值,表示超时设置不用于读操做。
成员WriteTotalTimeoutMultiplier和WriteTotalTimeoutConstant的意义分别与成员ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant相似。
设定串口的逾时参数的步骤以下:
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;
}
串口经过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. 设置lpOverlapped为NULL。
下面的例子给出了如何写入串口:
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
);
通信事件是在发生重大事故时由Windows CE发送给应用程序的通知。使用WaitCommEvent函数,应用程序能够阻止一个线程直到一个特定的事件发生。调用SetCommMask函数能够设置应用程序继续处理前必需要等待的事件。当指定多个事件时,任何一个指定事件的发生都会形成WaitCommEvent函数返回。
例如,这种机制使应用程序可以监控数据到达串口的时间。经过等待一个表示数据是否到达的通讯事件,应用程序避免了一直循环调用ReadFile函数等待数据到达的串行端口。 ReadFile函数只有当有数据读取时才被调用。
函数SetCommMask的功能是指定通信设备须要监视的事件。该函数的原型以下:
BOOL SetCommMask(
HANDLE hFile,
DWORD dwEvtMask);
参数hFile是要写入的文件句柄。
函数dwEvtMask指定要启用的事件。零值表示禁用全部事件。这个参数能够是下列值的组合:如表9-7
值 |
描述 |
EV_BREAK |
检测到输入有中断发生 |
EV_CTS |
|
EV_DSR |
DSR信号发生状态转变 |
EV_ERR |
线状态发生错误。线状态错误有CE_FRAME,CE_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_FRAME,CE_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);
}
调用CloseHandle函数来关闭串行端口,此时应用程序时完成对串口的交互。函数CloseHandle有一个参数,它是由CreateFile函数调用打开的端口返回的句柄。函数CloseHandle的原型以下:
BOOL CloseHandle(
HANDLE hObject);
函数CloseHandle返回非零表示成功,零表示失败。
本章主要介绍了串口通讯的流程,包括了序列通讯的基础,如何设置并监控序列通讯端口。