windows 驱动实例分析系列-COM驱动的I/O处理
在文章 COM驱动讲解 中,我们讲解了virtualSerial驱动几乎所有代码,但是唯独没有讲解I/O操作,这是因为I/O操作往往关乎驱动的功能,由于那个驱动仅仅作为案例,故我们不打算在上面讲解太多,但实际开发中,serial一般都具有实际的功能,故大部分情况下我们都需要改造之前的umdfserial驱动,让它实际具有一定的功能。
在umdfserial中,写入数据是正确的,但是怎么读取数据是一个问题,实际上作为最经常使用的接口,serial的经典应用是它会源源不断主动上报数据,这个数据往往是调试信息、日志输出、传感器信息等。
所以我们先研究用户层是如何读取数据的,驱动层又是如何上报数据的。
数据上报方式
在windows api中,可以使用ReadFile、ReadFileEx、DeviceIoControl这样的消息来完成从串口读取数据的操作,但这需要解决的问题就是我们如何知道什么时候需要读取数据。
初学者最容易想到的就是使用一个线程持续的读取数据,代码看起来像下面一样:
...
ret = ReadFile(hFile,
lpBuffer,
nNumberOfBytesToRead,
lpNumberOfBytesRead,
lpOverlapped
);
...
注意,无论使用同步或者异步,结果都没什么变化,尤其是对于我们的 umdfserial 代码来说。
更机智的做法是深入了解UART的硬件机制,serial本身存在流控的说法,就是当数据到达的时候,它有某种方案通知上层,就是RTS和CTS信号,当然,对于我们来说,我们并没有实际上的信号线,但上层不知道这一点,所以我们只需要装作有RTS和CTS信号的样子就可以了。
不过在那之前,我们先来看一般的串口程序会怎么做,一般的串口读写工具(sscom之类的)往往会使用WaitCommEvent和ClearCommError函数,下面是代码:
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int main()
{
HANDLE hCom;
BOOL fSuccess;
DWORD dwEvtMask;
DWORD dwError;
COMSTAT ComState = { 0 };
CHAR buffer[256] = { 0 };
hCom = CreateFile(COM_NAME,
GENERIC_READ | GENERIC_WRITE,
0, // exclusive access
NULL, // default security attributes
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (hCom == INVALID_HANDLE_VALUE)
{
printf("CreateFile failed with error %d.\n", GetLastError());
return;
}
// 设置事件掩码
fSuccess = SetCommMask(hCom, EV_CTS | EV_DSR | EV_RXCHAR);
if (!fSuccess)
{
printf("SetCommMask failed with error %d.\n", GetLastError());
return;
}
// 等待事件 WaitCommEvent函数会在SetCommMask设定的监听时间发生前返回
if (WaitCommEvent(hCom, &dwEvtMask, NULL))
{
// 这个函数可以获取数据缓冲区的长度
if (ClearCommError(hCom, &dwError, &ComState))
{
DWORD Size = 0;
DWORD Length = 0;
while (ComState.cbOutQue != Length)
{
if (!ReadFile(hCom, buffer + Length, ComState.cbOutQue, &Size, NULL))
{
break;
}
Length