Как самому написать FreeView для GDS-820C

У цифровых осциллографов GW Instek GDS-8xx (GDS-810/GDS-820/GDS-840) есть USB порт к которому можно подключить PC с программой FreeView. Однако, оказалось, что у FreeView закрыт код и софт не работает под Vista, Windows 7, Linux.
FreeView занимается тем, что копирует содержимое экрана осциллографа на компьютер. Копирование экрана сделано примитивнейшим образом: содержимое экрана после запроса попиксельно посылается по UART-у на PC. Для того, чтобы этот процесс выглядел по-современнее, внутрь осциллографа поставлен чип конвертера USB2COM от www.ftdichip.com.

На компьютере устанавливается драйвер, который имитирует наличие COM-порта, к которому автоматически цепляется FreeView.

Но FreeView был написан для доисторического драйвера под XP, исходники никто не потрудился открыть, и, естественно, что на новых ОС и новых драйверах FreeView не работает, а под Linux подобный код вообще никто не потрудился написать.

Изучение работы FreeView показало, что ничего сложного в реализации нет. Копия экрана отсылается в бинарном виде после посылки запроса через виртуальный COM-порт.

Ниже приводится демонстрационный пример, который считывает копию экрана осциллографа и записывает в файл.

#include <windows.h>
#include <setupapi.h>
#include <stdio.h>

#define X_RESOLUTION    320
#define Y_RESOLUTION    240

void CloseCom(HANDLE* hCm)
{
    if (*hCm!=INVALID_HANDLE_VALUE)
    {
        CloseHandle(*hCm);
        *hCm=INVALID_HANDLE_VALUE;
    }
}

BOOL OpenCom(DWORD com, HANDLE* OUT hCm)
{
    BOOL            fSuccess;
    DCB             dcb;
    COMMTIMEOUTS    ct;
    DWORD           err;
    TCHAR           szComName[32];

    if (*hCm!=INVALID_HANDLE_VALUE) CloseCom(hCm);

    err=0;
    if (com==0) { err=1; goto err_exit; }
    wsprintf(szComName,L"\\\\.\\COM%u",com);
    *hCm=CreateFile(szComName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
    if (*hCm==INVALID_HANDLE_VALUE) { err=1; goto err_exit; }

    fSuccess=GetCommState(*hCm,&dcb);
    if (!fSuccess) { err=2; goto err_exit; }
    // изменение скорости на процесс чтения почему-то не влияет
    dcb.BaudRate=921600;//CBR_115200;        // set the baud rate
    dcb.fBinary=TRUE;
    dcb.fParity=FALSE;
    dcb.fOutxCtsFlow=FALSE;                  // CTS output flow control
    dcb.fOutxDsrFlow=FALSE;                  // DSR output flow control
    dcb.fDtrControl=DTR_CONTROL_DISABLE;     // DTR flow control type
    dcb.fDsrSensitivity=FALSE;               // DSR sensitivity
    dcb.fTXContinueOnXoff=TRUE;              // XOFF continues Tx
    dcb.fOutX=FALSE;                         // XON/XOFF out flow control
    dcb.fInX=FALSE;                          // XON/XOFF in flow control
    dcb.fErrorChar=FALSE;                    // enable error replacement
    dcb.fNull=FALSE;                         // enable null stripping
    dcb.fRtsControl=RTS_CONTROL_DISABLE;     // RTS flow control
    dcb.fAbortOnError=FALSE;                 // abort reads/writes on error
    dcb.wReserved=0;                         // not currently used
    dcb.XonLim=1;                            // transmit XON threshold
    dcb.XoffLim=8;                           // transmit XOFF threshold
    dcb.ByteSize=8;                          // number of bits/byte, 4-8
    dcb.Parity=NOPARITY;                     // no parity bit
    dcb.StopBits=ONESTOPBIT;                 // one stop bit
    dcb.StopBits=ONESTOPBIT;                 // 0,1,2 = 1, 1.5, 2
    dcb.XonChar=0x1B;                        // Tx and Rx XON character
    dcb.XoffChar=0x1C;                       // Tx and Rx XOFF character
    dcb.ErrorChar=0x00;                      // error replacement character
    dcb.EofChar=0x00;                        // end of input character
    dcb.EvtChar=(BYTE)0x80;                  // received event character
    fSuccess=SetCommState(*hCm,&dcb);
    if (!fSuccess) { err=3; goto err_exit; }

    ct.ReadIntervalTimeout=0;
    ct.ReadTotalTimeoutMultiplier=0;
    ct.ReadTotalTimeoutConstant=50;
    ct.WriteTotalTimeoutMultiplier=0;
    ct.WriteTotalTimeoutConstant=50;
    fSuccess=SetCommTimeouts(*hCm,&ct);
    if (!fSuccess) { err=4; goto err_exit; }
    fSuccess=SetupComm(*hCm,48,48);
    if (!fSuccess) { err=5; goto err_exit; }
    fSuccess=PurgeComm(*hCm,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
    if (!fSuccess) { err=6; goto err_exit; }
    fSuccess=EscapeCommFunction(*hCm,SETDTR);
    if (!fSuccess) { err=7; goto err_exit; }
    fSuccess=EscapeCommFunction(*hCm,SETRTS);
    if (!fSuccess) { err=8; goto err_exit; }
err_exit:
  if (err)
  {
      if (*hCm!=INVALID_HANDLE_VALUE) CloseHandle(*hCm);
      return FALSE;
  }
  return TRUE;
}

int main(void)
{
    // заголовок BITMAP (4 бита на цвет + палитра)
    BYTE        hdr[]={
0x42,0x4D,0x76,0xA0,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x00,0x00,0x00,0x28,0x00,
0x00,0x00,0x40,0x01,0x00,0x00,0xF0,0x00,0x00,0x00,0x01,0x00,0x04,0x00,0x00,0x00,
0x00,0x00,0x00,0xA0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,
0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0xFF,0xFF,0x00,0x77,0x77,0x77,0xFF,0xFF,0x00,0x00,0x66,0x44,0x22,0x00,0x66,0xFF,
0x66,0x66,0xFF,0xFF,0xFE,0xFF,0x88,0x88,0x87,0x88,0x22,0x22,0x88,0xFF,0x55,0x00,
0x00,0x00,0xBB,0xBB,0xBB,0xBB,0x33,0x66,0x99,0x99,0x88,0x88,0x88,0xFF,0x22,0x22,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF };

    HANDLE      hCom=INVALID_HANDLE_VALUE;
    DWORD       cnt,written,readen;
    BYTE        buf[]={ 0x57, 0x00, 0x00, 0x0A };
    DWORD       raw,col,ptr;
    BYTE        bt1,bt2;
    BYTE        ibuf[0xA000];
    BYTE        imgbuf[0xA000];

    printf("Open com... "); // открываем COM-порт который создал драйвер FTDI
    if (!OpenCom(4,&hCom))  // попытка открытия порта
    {
        printf("failed!\n");
        exit(1);
    }
    printf("ok.\n");        // открытие прошло успешно

    printf("Write...");     // посылка запроса чтения экрана
    if (!WriteFile(hCom,buf,4,&written,NULL))
    {
        printf("failed!\n");
        exit(1);
    }
    printf("ok.\nReading...\n");
    for(cnt=0;cnt<0xA000;)  // чтение ответа осциллографа
    {
        if (!ReadFile(hCom,&ibuf[cnt],0xA000-cnt,&readen,NULL))
        {
            printf("read failed...\n");
        }
        else
        {
            cnt+=readen;    // добавление считанных байтов к счётчику
        }
    }
    printf("Read complete.\n"); // чтение завершено
    printf("Close com...\n");   // закрытие COM-порта
    CloseCom(&hCom);

    // восстанавливаем из сырых данных картинку
    for(raw=0;raw<(X_RESOLUTION/2);raw++)
    {
        for(col=0,ptr=raw<<8;col<0xA000;col+=X_RESOLUTION,ptr++)
        {
            imgbuf[raw+col]=(ibuf[ptr]&0xF0)|(ibuf[ptr+128]>>4);
            imgbuf[raw+col+(X_RESOLUTION/2)]=(ibuf[ptr+128]&0x0F)|((ibuf[ptr]<<4)&0xF0);
        }
    }
    // поворачиваем картинку на 180
    for(raw=0;raw<Y_RESOLUTION;raw++)
    {
        for(col=0;col<(X_RESOLUTION/4);col++)
        {
            bt1=imgbuf[raw*(X_RESOLUTION/2)+col]; bt1=(bt1>>4)|(bt1<<4);
            bt2=imgbuf[((Y_RESOLUTION-1)-raw)*(X_RESOLUTION/2)+((X_RESOLUTION/2-1)-col)]; bt2=(bt2>>4)|(bt2<<4);
            imgbuf[raw*(X_RESOLUTION/2)+col]=bt2;
            imgbuf[((Y_RESOLUTION-1)-raw)*(X_RESOLUTION/2)+((X_RESOLUTION/2-1)-col)]=bt1;
        }
    }

    // запись bmp-файла
    hCom=CreateFile(L"temp.bmp",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
    WriteFile(hCom,hdr,0x76,&written,NULL);
    WriteFile(hCom,imgbuf,0x9600,&written,NULL);
    CloseHandle(hCom);

    return 1;
} 

Картинка, которая получается в результате работы программы.
Изображение, снятое с осциллографа GDS-820C