RomeoGolf

Вс 03 Сентябрь 2017

USB-polygon-13: чтение изменяющихся данных

Данные могут меняться

В предыдущем выпуске отладочную плату научили прикидываться флэшкой и получили возможность читать ее файлы (которых на самом деле нет). Однако данные в этих «файлах», в отличие от обычной флэшки, могут изменяться в то время, пока плата подключена к ПК. А ОС кэширует состояние ФС накопителя и однажды прочитанные файлы, в дальнейшем информация берется уже из кэша.

А между прочим, возможность изменения данных в устройстве — его самое интересное отличие от настоящей флэшки. Плата может получать данные извне, скажем, по UART или SPI, а ПК — получать принятую информацию, просто читая ее из файла, без использования драйверов (и тем более, без необходимости их написания). Что делать?

Борьба с кэшем

Поначалу я долго рыл просторы всяких интернетов в поисках возможности сбросить кэш ОС. Не нарыл совсем ничего. А жаль, потому что хотелось иметь возможность по-прежнему читать устройство, грубо говоря, мышью — с использованием стандартного файлового менеджера, стандартного просмотрщика и все такое. Просто периодически давал бы команду ОС забыть, что этот файл уже читали и требовал перечитать заново по шине.

Не получилось. Может, такой способ и есть, я не нашел. Поэтому придется воспользоваться другим, который в чем-то проще, в чем-то сложнее. Дело в том, что у функции CreateFile есть среди флагов, которые можно передать в параметрах, флаг FILE_FLAG_NO_BUFFERING, который заставит функцию чтения ReadFile игнорировать буфер и читать каждый раз с устройства.

Это само по себе несложно — вручную открывать и читать файл. Плохо только то, что для чтения файла, имеющего возможность изменения, придется писать собственную программку.

Может быть, ничего страшного в этой необходимости и нет. Ведь устройство получилось нестандартное, всего лишь имитирующее стандартную флэшку. Значит, данные, которые я буду из него получать, тоже будут нестандартные, какого-то определенного формата, который знаю только я. Следовательно, все равно придется писать какую-то программку для их правильной интерпретации. Но все равно немного обидно.

Однако напишу пример такой программы.

Пример программы

Не сильно заморачиваясь, написал на С++ несложный код:

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501

#include <iostream>
#include <windows.h>
#include <string.h>
#include <conio.h>

#define BUF_SIZE 512

int main() {

    OPENFILENAME ofn;       // common dialog box structure
    char szFile[260];       // buffer for file name
    HWND hwnd = GetConsoleWindow();              // owner window

    // Initialize OPENFILENAME
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = szFile;
    // Set lpstrFile[0] to '\0' so that GetOpenFileName does not 
    // use the contents of szFile to initialize itself.
    ofn.lpstrFile[0] = '\0';
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    if (GetOpenFileName(&ofn) == TRUE) {
        printf("Selected file %s\n", ofn.lpstrFile);
        HANDLE hf = CreateFile(ofn.lpstrFile,
            GENERIC_READ,
            0,
            (LPSECURITY_ATTRIBUTES) NULL,
            OPEN_EXISTING,
            FILE_FLAG_NO_BUFFERING | FILE_ATTRIBUTE_NORMAL,
            (HANDLE) NULL);

        if (hf != INVALID_HANDLE_VALUE) {
            DWORD dwBytesRead;
            char buf[BUF_SIZE];
            std::string s, old_s;
            bool fExit = false;

            std::cout << "Press \"Esc\" for exit." << std::endl;
            while (!fExit) {
                if (_kbhit() && getch() == 27) fExit = true;
                SetFilePointer(hf, 0, NULL, 0);
                if(ReadFile(hf, buf, BUF_SIZE, &dwBytesRead, NULL)) {
                    buf[dwBytesRead]='\0';
                    s = buf;
                    if (s != old_s) {
                        std::cout << s << std::endl;
                        old_s = s;
                    }
                } else {
                    std::cout << "Could not read from file \
                        (error GetLastError())" << std::endl;
                }
            }
            CloseHandle(hf);
        } else {
            std::cout << "Invalid handle." << std::endl;
        }
    } else {
        std::cout << "No file." << std::endl;
    }

    return 0;
}

Первые два define требуются для использования функции GetOpenFileName().

Включение conio нужно для функции _kbhit, для выхода из цикла по нажатию на клавишу, необходимость остальных include очевидна.

#define BUF_SIZE 512 требует особого внимания. Здесь определен размер буфера для чтения из файла на фейковом носителе. Он обязан быть кратным размеру сектора, иначе работать не будет. Это требование накладывает флаг FILE_FLAG_NO_BUFFERING. При установленном одном только флаге FILE_ATTRIBUTE_NORMAL размер буфера может быть любым, но нам нужно читать именно с носителя мимо буфера.

Остальное все достаточно просто. Сперва вызывается диалоговое окно выбора файла, в котором нужно выбрать USERDATA.TXT на носителе, имитируемом отладочной платой. Далее запускается цикл, выйти из которого можно, нажав кнопку «Escape». В этом цикле читается буфер, размером в сектор, и сравнивается с предыдущим результатом. При изменении содержимого файла это самое измененное содержимое выводится в консоль. Вот, собственно, и все.

Если подключить плату к ПК, она определится, как USB-накопитель. Запускаю программу, выбираю файл на носителе. При нажатии на одну из двух верхних кнопок в окне консоли будет появляться новая строчка с новым содержимым файла, например, «Counter = 03h».

Ах, да! Для компиляции кода (с учетом установленного на моем ПК MinGW) я использовал такой файл «cmpl.cmd»:

@PATH=c:\MinGW\bin;%PATH%
c:\MinGW\bin\g++.exe -std=c++0x -static-libgcc -static-libstdc++ -o test.exe test.cpp -I"C:\MinGW\include" -L"C:\MinGW\lib" -l"comdlg32"
@pause

Первая строчка нужна для того, чтобы при исполнении сценария компилятор мог найти необходимые для g++ DLL-библиотеки, лежащие в папке c:\MinGW\bin\, так как эта папка у меня не прописана в общей системной переменной PATH. А вставлена она в начало переменной, а не в конец, потому что некоторые библиотеки с аналогичными названиями (например, libiconv-2.dll) используют другие виндовские программы, портированные из линуксовой среды, например, тот же GIT, и не он один. При этом путь к этим программам прописан в PATH при их установке инсталлятором, версии библиотек могут быть старше и не иметь требуемых для g++ функций. Поэтому надо, чтобы приложения из MinGW при поиске своих DLL сперва находили свою же папку.

Во второй строчке подключаются статически пара библиотек, чтобы не требовать соответствующие DLL при запуске, а также указывается библиотека, где линкер сможет найти GetOpenFileName(). Порядок опций здесь важен: если поставить опции для линкера в начало этой строки, компиляция вывалится с ошибкой.

C отладкой чтения более-менее закончено. Можно эти наработки как-то использовать. Дальше хотелось бы разобраться с записью.


Теги: