RomeoGolf

Пн 23 Апрель 2018

USB-polygon-16: Использование записи файла

Подготовка

В продолжение предыдущего выпуска: для использования операций записи в целях передачи информации от ПК в устройство предполагаю поступить следующим образом. Для выбора способа обработки передаваемой информации необходимо предварительно прочитать специально для этой цели созданный файл. Сам факт чтения файла должен переключить выбор обработчика данных.

Для записи предполагаю создать специальный файл. Дело в том, что Linux записывает измененный файл в начале свободной области. Если использовать для записи тот же файл, что используется для «командного» чтения, ОС может запомнить, что он уже в другом месте, и в следующий раз обращаться к нему по другому адресу. Не исключено, что это не так, но поступая предложенным образом, я ничем не рискую.

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

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

Сам способ обработки можно либо выбирать чем-то вроде оператора switch в функции process_data(), либо вызывать в ней по номеру файла специальную функцию, которую следует добавить в файловую таблицу по образцу функции чтения. Пожалуй, тоже логичнее будет первый вариант, поскольку странно привязывать функцию обработки записи в один файл к другому файлу в таблице.

Изменения в программе

Все изменения будут сделаны в файле Lib/fake_fs.c. Для начала добавлю константу, определяющую сектор, с которого начинается область файлов в адресном пространстве имитируемой файловой системы, для использования вместо прямого указания 0x57:

#define FILES_AREA      (ROOT_SECTOR + SECTORS_PER_CLUSTER)

Далее введу перечисление с разными вариантами обработки записываемых данных (которое при необходимости можно будет расширять) и соответствующую переменную:

enum WriteType {None, ToFile, ToLed};

static enum WriteType writeType = None;

Еще добавлю объявления функций для командных файлов, их нужно для демонстрации штуки три: команду выдавать данные в файл (хотя бы кусочек), команду выдавать данные на светодиодную линейку и команду прекратить обработку записываемых данных. А так как это все-таки файлы, пусть они при открытии выдают какие-то строки, тут же эти строки и определю. И длины они будут одинаковой, равной длине куска за один раз обрабатываемых данных:

#define SIZE_OF_COMMAND 16
static const char strToFile[] PROGMEM = "Write to file   ";
static const char strToLED[] PROGMEM = "Write to LED    ";
static const char strStop[] PROGMEM = "Stop writing    ";
uint8_t * readToFile(uint8_t *data_buf, uint32_t size, uint32_t offset);
uint8_t * readToLed(uint8_t *data_buf, uint32_t size, uint32_t offset);
uint8_t * readStop(uint8_t *data_buf, uint32_t size, uint32_t offset);

В конце программы надо написать определение функций, отвечающих за чтение файлов-команд:

uint8_t * readToFile(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    writeType = ToFile;
    memcpy_P(data_buf, (PGM_P)(&(strToFile[0]) + offset), 16);
    return data_buf;
}

uint8_t * readToLed(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    writeType = ToLed;
    memcpy_P(data_buf, (PGM_P)(&(strToLED[0]) + offset), 16);
    return data_buf;
}

uint8_t * readStop(uint8_t *data_buf, uint32_t size, uint32_t offset) {
    writeType = None;
    memcpy_P(data_buf, (PGM_P)(&(strStop[0]) + offset), 16);
    return data_buf;
}

Теперь можно добавить файлы-команды в файловую таблицу. После добавления она станет выглядеть так:

static const FileEntry fileTable[] PROGMEM =
{
    {"KEY_FILETXT", SIZE_OF_KEY, readKey},
    {"README  TXT", SIZE_OF_README, readMe},
    {"TESTFILETXT", SIZE_OF_TEST, readTest},
    {"USERDATATXT", SIZE_OF_USERDATA, readUserData},
    {"DATA    BIN", SIZE_OF_DATA, readData},
    {"TO_FILE TXT", SIZE_OF_COMMAND, readToFile},
    {"TO_LED  TXT", SIZE_OF_COMMAND, readToLed},
    {"STOP    TXT", SIZE_OF_COMMAND, readStop},
    {{ 0 }, 0, NULL}
};

Ну и, наконец, надо изменить функцию process_data() таким образом, чтобы записываемые данные пошли по нужному пути:

void process_data(uint8_t * data_buf, uint32_t BlockAddress, uint8_t BytesInBlockDiv16){
    static uint8_t ind = 0;

    switch (writeType){
    case ToFile:
        if (BlockAddress >= FILES_AREA) {
            if (ind < 128) {
                for (uint8_t i = 0; i < 16; i++) {
                    data[ind++] = data_buf[i];
                }
            }
        }
        break;
    case ToLed:
        if (BlockAddress >= FILES_AREA) {
            for (uint8_t i = 0; i < 16; i++) {
                data_PC = data_buf[i];
                PORTD = data_PC;
            }
        }
        break;
    default:
        ;
    }
}

Проверка записи

Компилирую, прошиваю, запускаю.

Устройство определяется в Windows в виде флэшки, и на ней появились дополнительные файлы. Трижды нажимаю одновременно две верхние кнопки — переключаюсь в режим выдачи переменной data_PC. Открываю файл TESTFILE.TXT блокнотом, сохраняю через меню. На светодиодах ничего, все погашены. Открываю DATA.BIN hex-редактором — в нем одни нули. Первый этап испытаний прошел с ожидаемым результатом.

Сбрасываю устройство кнопкой «Reset». Снова переключаюсь в режим выдачи data_PC на светодиоды. Снова открываю блокнотом TESTFILE.TXT. Открываю блокнотом же файл TO_FILE.TXT и сразу закрываю его. На светодиодах по-прежнему ничего, не горят. Открываю DATA.BIN и вижу следующее:

00000000: 30 30 30 30 30 30 20 20 30 30 30 30 30 31 20 20  000000  000001
00000010: 30 30 30 30 30 32 20 20 30 30 30 30 30 33 20 20  000002  000003
00000020: 30 30 30 30 30 34 20 20 30 30 30 30 30 35 20 20  000004  000005
00000030: 30 30 30 30 30 36 20 20 30 30 30 30 30 37 20 20  000006  000007
00000040: 30 30 30 30 30 38 20 20 30 30 30 30 30 39 20 20  000008  000009
00000050: 30 30 30 30 30 41 20 20 30 30 30 30 30 42 20 20  00000A  00000B
00000060: 30 30 30 30 30 43 20 20 30 30 30 30 30 44 20 20  00000C  00000D
00000070: 30 30 30 30 30 45 20 20 30 30 30 30 30 46 20 20  00000E  00000F

Затем открываю файл TO_LED.TXT и также закрываю его немедленно. Сохраняю через меню все еще открытый в блокноте файл TESTFILE.TXT. На светодиодах наблюдаю моргание, которое свидетельствует о том, что в порт выдаются постоянно обновляющиеся данные. В конце записи на светодиодной линейке фиксируется 0x20, что соответствует символу пробела — именно пробелом оканчивается файл, размер которого как раз кратен размеру сектора.

Последний пробел файла меняю в блокноте на «5» и снова выбираю в меню «Файл -> Сохранить». Снова наблюдаю мерцание светодиодной линейки, но в конце на ней фиксируется значение 0x35, что соответствует символу пятерки.

Открываю файл STOP.TXT и закрываю его. Еще раз сохраняю файл TESTFILE.TXT. На светодиодах изменений нет. Файл DATA.BIN смотреть обычными средствами бесполезно, он уже в кэше системы и будет взят оттуда. Но раз отключение последнего способа «записи» выполнено, можно и не проверять, я уверен.

В Linux провел эти же самые проверки, только вместо блокнота использовал gedit. Всё прошло примерно так же, только при сохранении файла было выдано сообщение о нехватке свободного места на флэшке, что естественно: если в Linux файл записывается не на старое место, а на свободное, то места может и не хватить. И еще с gedit не получилось поправить последний пробел на что-нибудь другое, потому что файл TESTFILE.TXT оказался для него слишком огромен, и концовка отображалась как-то зажевано, наслоениями.

Для устранения проблемы с нехваткой места я «расширил» память: все в том же файле Lib/fake_fs.c в функции read_mbr() заменил data_buf[11] = 0x10; на data_buf[11] = 0x5F;, и в функции read_boot_sect() в условии if (BytesInBlockDiv16 == 2) заменил data_buf[1] = 0x10; на data_buf[2] = 0x10;. Почему-то если в MBR поставить размер в 11 байте равным 0x60, то Windows 7 предлагает отформатировать диск. А так он получается 3,99 Мб. Здесь еще надо разобраться, но в контексте решения задачи это пока неважно.

Итог

Отладочную плату теперь можно использовать в качестве двунаправленного преобразователя информации: записывать данные в файл, инициируя передачу, скажем, по SPI, и проверять файл на наличие обновленных данных, принятых, скажем, по тому же SPI.

На следующем этапе мне хотелось бы воспользоваться этой возможностью для управления каким-нибудь устройством, работающем по SPI: экраном от телефона или SD-картой памяти.

Еще интересно разобраться с особенностями определения размера накопителя по записям в MBR и PBR, но это уже по остаточному принципу, насколько хватит времени и сил.


Теги: