RomeoGolf

Ср 25 Январь 2017

USB-polygon-7: Обмен по USB, подготовка устройства

Проект заново

Продолжаем разговор. Попробую завести какой-то обмен между платой и ПК. Подготовлю заготовку контроллерной программы, но не буду продолжать то, что сделано раньше, а начну сначала.

Создаю папку usb-polygon, копирую в нее demo-проект MassStorage из ClassDriver и саму библиотеку LUFA (второе, в принципе, не обязательно, но я так сделаю), и в MassStorage создаю git-репозиторий.

Далее правлю Makefile, оставляя в переменной LUFA_PATH только одну пару точек (библиотека теперь лежит одним уровнем выше) и закрываю комментариями переменные COMPILER_PATH и SHELL — они не нужны, если cygwin уже настроен, как описано в предыдущих выпусках цикла. В результате получаю первую успешную компиляцию проекта с получением hex-файла, который, однако, еще нельзя использовать.

Надо исправить в Makefile переменную MCU на at90usb162 и BOARD на NONE и начинать править код, потому что компилятор выдаст ошибки. Чтобы получить удачную компиляцию, в коде MassStorage.c закрываю комментариями функции (и блоки, их содержащие), с Dataflash в названии, а заодно и с LED в названии, потому что в моей плате хоть и есть светодиоды, но не такие и не там. Также закрываю Dataflash-функции в модуле Lib/SCSI.c и директивы #include с файлами LEDs.c, Dataflash.c и Platform.c в заголовочном файле MassStorage.h.

«Лампочки и кнопки»

Теперь надо добавить код, который будет выдавать что-то интересное на светодиоды и реагировать на кнопки.

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

unsigned char cnt = 0;          // просто счетчик

В главной функции после GlobalInterruptEnable(); добавляю инициализацию портов, объявление переменных и запуск таймеров:

    PORTD = 0x00;    // начальное значение - все нули
    DDRD = 0xFF;     // все линии порта на вывод
    PORTC = 0x00;    // без "подтяжки" (есть внешняя)
    DDRC = 0x00;     // все линии порта на ввод

    unsigned char cnt_bt = 0;     // счетчик нажатий на кнопки
    unsigned char mode_out = 0;   // режим вывода
    unsigned char bt_now = 0;     // состояние кнопок
    unsigned char bt_old = 0;     // состояние кнопок в прошлый раз

    /* запуск таймера 0 на период ~0.01 с */
    /* (защита от дребезга) */
    TCCR0B = 4;  /* 1 тик = 0.000032 с */
    TCNT0 = 0;   /* 256 раз ~ 0.008192 c  */

    /* запуск таймера 1 на период 0.5 с */
    /* (счетчик с полусекундной задержкой) */
    TCCR1B = 4;              /* 1 тик = 0.000032 с */
    TCNT1 = 65536 - 15625;
    /* разрешение прерываний таймера 1*/
    /*TIMSK1 = (1 << TOIE1);*/

В главном цикле for после USB_USBTask(); добавляю код:

        /* проверка срабатывания таймера без прерываний по флагу */
        if ((TIFR1 & 1) == 1) {
            TCNT1 = 65536 - 15625;  /* перезапуск таймера 1 */
            TIFR1 = 1;              /* сброс флага таймера 1 */
            cnt++;                  /* инкремент контрольного счетчика по таймеру */
        }

        /* обработка действий по срабатыванию таймера 0 */
        if ((TIFR0 & 1) == 1) {
            TCNT0 = 0;  /* перезапуск таймера 0 */
            TIFR0 = 1;  /* сброс флага срабатывания таймера 0 */

            /* cnt++;*/     // инкремент счетчика - чтобы что-то изменялось
            bt_now = PINC;                  // считывание порта с кнопками
            if (bt_now != bt_old) {         // если состояние порта изменилось
                if ((bt_now & 0x30) == 0) { // если нажаты сразу две верхние кнопки на разрядах 3 и 4
                    mode_out++;             // циклически изменить режим отображения,
                    mode_out = mode_out & 3;// которых всего 4 - 0, 1, 2 и 3 (2 разряда по маске)
                } else {                    // в противном случае
                    if ((bt_now & 0x10) == 0) {cnt_bt++;}  // верхняя кнопка увеличивает счет нажатий
                    if ((bt_now & 0x20) == 0) {cnt_bt--;}  // а вторая сверху - уменьшает
                }
                bt_old = bt_now;            // и сохраняем состояние порта для следующей проверки
            }

            switch (mode_out) {
                case 0 :
                    PORTD = cnt;    // просто счетчик
                    break;
                case 1 :
                     PORTD = bt_now;            // состояние кнопок
                    break;
                case 2 :
                    PORTD = cnt_bt;  // счетчик нажатий
                    break;
                default:
                    PORTD = 0x55;    // просто константа
            }
        }

После главного цикла добавлю на всякий случай незадействованный обработчик прерывания таймера 1:

/* обработчик прерывания таймера, если разрешено, для проверки */
ISR (TIMER1_OVF_vect)
{
    TCNT1 = 65536-15625;    // перезапуск таймера
    cnt++;                  // инкремент контрольного счетчика
}

Все это компилируется и работает на плате в автономном режиме: светодиоды по умолчанию демонстрируют счетчик, изменяющийся с полусекундным интервалом, одновременное нажатие на верхние две кнопки переключает режим отображения, позволяя показать состояние кнопок, «кнопочный» счетчик (увеличиваемый нажатием на верхнюю кнопку и уменьшаемый нажатием на вторую) или просто константу 0х55 (светодиоды горят через один). Плата при этом видна в системе как флэш-накопитель с файловой системой RAW и емкостью 0. Причем, Windows 7 дает ей букву очень не сразу и настойчиво предлагает отформатировать. Сохраняю очередной коммит в git и добавляю ему тег «v0.1».

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

Для начала хочу сделать так, чтобы плата посредством светодиодов показала, что видит обращение к себе со стороны компьютера.

Под объявлением счетчика cnt добавляю объявление еще одного:

unsigned char cnt = 0;
unsigned char cnt_usb = 0;

В переключателе switch (mode_out) вместо вывода константы делаю вывод этого счетчика:

                default:
                    PORTD = cnt_usb;

И mode_out при объявлении присваиваю значение 3, чтобы показывало, что там творится при подаче питания с порта. Ну и надо наконец найти подходящее место, в которое вставить инкремент этого счетчика.

Нахожу функцию void MassStorage_Task(void), в которой обрабатываются SCSI-команды, подаваемые устройству. Вот сразу после выяснения статуса команды проверяю этот статус и, если команда выполнена успешно, увеличиваю значение счетчика: после

        /* Decode the received SCSI command, set returned status code */
        CommandStatus.Status = SCSI_DecodeSCSICommand() ? MS_SCSI_COMMAND_Pass : MS_SCSI_COMMAND_Fail;

вставляю

        if (CommandStatus.Status == MS_SCSI_COMMAND_Pass){
            cnt_usb++;
        }

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

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

Кстати, если сравнивать статус не с MS_SCSI_COMMAND_Pass, а с MS_SCSI_COMMAND_Fail, счетчик ведет себя похоже, только не тикает, пока к устройству нет обращения. По-видимому, это связано с нереализованными в устройстве операциями записи и чтения. Точнее, с испорченными — там же были закрыты функции Dataflash.

Сделаю из этого в git отдельную ветку, скажем, test_usb.

Продолжу проверку связи. Условие с приращением счетчика cnt_usb в MassStorage.c я закрою, зато добавлю строчку cnt_usb++ в модуле SCSI.c в функции bool SCSI_DecodeSCSICommand(void), в переключателе CommandBlock.SCSICommandData[0] при выборе SCSI_CMD_INQUIRY'. Ну и добавлю объявление переменнойcnt_usbвSCSI.h, где-нибудь в самом конце перед#endif`:

        extern unsigned char cnt_usb;

Такая модификация позволяет узнать, когда и сколько раз система выдает устройству запрос INQUIRY. Оказывается, пять раз после подключения, а потом по разику после запуска программы на С++ из предыдущего выпуска, потому что там есть явная подача этой команды при помощи DeviceIoControl.

Аналогичным образом — переставляя инкремент счетчика в разные пункты данного switch — можно исследовать количество и интенсивность подачи разных SCSI-команд системой устройству при подключении или при программном обращении. Это тема забавная, но не очень интересная, потому что такую информацию можно найти не обязательно методами реверс-инжиниринга, достаточно почитать документацию. Но сохраню коммит с подсчетом INQUIRY в той же ветке и вернусь в ветку master к тегу «v0.1».

Пиши-читай (заготовка)

На следующем этапе пробую реализовать запись и чтение данных по USB. Понятно, что в прототипе функции чтения и записи реализованы так, чтобы читать и писать в микросхему флэш-памяти, то есть, сделано это все в модуле DataflashManager. Но у меня флэшки нет, и данный модуль мне не нужен. Однако убрать его совсем я пока не могу, потому что в нем есть важные макроопределения, без которых не компилируется проект. Значит, нужно эти самые #define перенести в нужный модуль (лучше, пожалуй, сделать для этого отдельный, но это попозже, пока для проверки — так) и переписать функции чтения-записи без участия флэш.

Для начала в заголовочном файле SCSI.h, где-нибудь в самом конце перед #endif, вставляю

/* ----- instead DataflashManager ----- */

/* ------------------------------------ */

для строк, которые должны заменить DataflashManager.h, и начинаю заполнять этот пробел. Сначала надо вставить то, что в DataflashManager.h отсутствует, но подключается через #include. Подключается хитро, с проверкой переменной BOARD, определенной в makefile, поэтому возьму этот кусок из LUFA\CodeTemplates\DriverStubs\Dataflash.h:

/* from ..\..\LUFA\CodeTemplates\DriverStubs\Dataflash.h */
    /* Public Interface - May be used in end-application: */
        /* Macros: */
            /** Constant indicating the total number of dataflash ICs mounted on the selected board. */
            #define DATAFLASH_TOTALCHIPS     1 // TODO: Replace with the number of Dataflashes on the board, max 2
            /** Mask for no dataflash chip selected. */
            #define DATAFLASH_NO_CHIP        0
            /** Mask for the first dataflash chip selected. */
            #define DATAFLASH_CHIP1          1 // TODO: Replace with mask with the pin attached to the first Dataflash /CS set
            /** Mask for the second dataflash chip selected. */
            #define DATAFLASH_CHIP2          2 // TODO: Replace with mask with the pin attached to the second Dataflash /CS set
            /** Internal main memory page size for the board's dataflash ICs. */
            #define DATAFLASH_PAGE_SIZE      1024 // TODO: Replace with the page size for the Dataflash ICs
            /** Total number of pages inside each of the board's dataflash ICs. */
            #define DATAFLASH_PAGES          8192 // TODO: Replace with the total number of pages inside one of the Dataflash ICs

Недостающие значения заполнил чем-то интуитивно похожим на правду, не разбираясь пока в тонкостях использования. Дальше переписываю уже из DataflashManager.h:

/* from DataflashManager.h */
    /* Preprocessor Checks: */
        #if (DATAFLASH_PAGE_SIZE % 16)
            #error Dataflash page size must be a multiple of 16 bytes.
        #endif

    /* Defines: */
        /** Total number of bytes of the storage medium, comprised of one or more Dataflash ICs. */
        #define VIRTUAL_MEMORY_BYTES         ((uint32_t)DATAFLASH_PAGES * DATAFLASH_PAGE_SIZE * DATAFLASH_TOTALCHIPS)
        /** Block size of the device. This is kept at 512 to remain compatible with the OS despite the underlying
         *  storage media (Dataflash) using a different native block size. Do not change this value.
         */
        #define VIRTUAL_MEMORY_BLOCK_SIZE    512
        /** Total number of blocks of the virtual memory for reporting to the host as the device's total capacity. Do not
         *  change this value; change VIRTUAL_MEMORY_BYTES instead to alter the media size.
         */
        #define VIRTUAL_MEMORY_BLOCKS        (VIRTUAL_MEMORY_BYTES / VIRTUAL_MEMORY_BLOCK_SIZE)
        /** Blocks in each LUN, calculated from the total capacity divided by the total number of Logical Units in the device. */
        #define LUN_MEDIA_BLOCKS             (VIRTUAL_MEMORY_BLOCKS / TOTAL_LUNS)

    /* Function Prototypes: */
        void WriteBlocks(const uint32_t BlockAddress,
                                          uint16_t TotalBlocks);
        void ReadBlocks(const uint32_t BlockAddress,
                                         uint16_t TotalBlocks);

При этом укорачиваю названия функций чтения и записи. В SCSI.c вставляю пока пустые определения объявленных в заголовочнике функций:

void WriteBlocks(const uint32_t BlockAddress,
                                          uint16_t TotalBlocks)
{

}

void ReadBlocks(const uint32_t BlockAddress,
                                         uint16_t TotalBlocks)
{

}

После этого в функции SCSI_Command_ReadWrite_10 раскомментирую блок if (IsDataRead == DATA_READ) и в нем тоже укорачиваю названия функций чтения и записи, чтобы соответствовали. Теперь можно закрыть пока что комментариями

//      #include "DataflashManager.h"

в заголовочниках SCSI.h и MassStorage.h. Теперь make clean, make и проверка работоспособности — прошивка и созерцание диодиков. Работает, как раньше, но без модуля DataflashManager. Надо добавить коммит git.

Правда, совсем стирать этот модуль рано, ведь функции чтения и записи еще пустые, а для их реализации надо опираться на имеющиеся.

Чтение и запись мимо flash-памяти

Из модуля DataflashManager беру тела функций DataflashManager_ReadBlocks и DataflashManager_WriteBlocks для ReadBlocks и WriteBlocks соответственно, стираю оттуда все, связанное с flash-памятью, добавляю по паре собственных переменных и модифицирую получение данных из буфера конечной точки. Получается следующее:

/** Writes blocks (OS blocks, not Dataflash pages) to the storage medium, the board Dataflash IC(s), from
 *  the pre-selected data OUT endpoint. This routine reads in OS sized blocks from the endpoint and writes
 *  them to the Dataflash in Dataflash page sized blocks.
 *
 *  \param[in] BlockAddress  Data block starting address for the write sequence
 *  \param[in] TotalBlocks   Number of blocks of data to write
 */
void WriteBlocks(const uint32_t BlockAddress,
                                  uint16_t TotalBlocks)
{
    uint16_t CurrDFPage          = ((BlockAddress * VIRTUAL_MEMORY_BLOCK_SIZE) / DATAFLASH_PAGE_SIZE);
    uint16_t CurrDFPageByte      = ((BlockAddress * VIRTUAL_MEMORY_BLOCK_SIZE) % DATAFLASH_PAGE_SIZE);
    uint8_t  CurrDFPageByteDiv16 = (CurrDFPageByte >> 4);
    uint8_t  data_8[16];

    bool first = true;

    /* Wait until endpoint is ready before continuing */
    if (Endpoint_WaitUntilReady())
      return;

    while (TotalBlocks)
    {
        uint8_t BytesInBlockDiv16 = 0;

        /* Write an endpoint packet sized data block to the Dataflash */
        while (BytesInBlockDiv16 < (VIRTUAL_MEMORY_BLOCK_SIZE >> 4))
        {
            /* Check if the endpoint is currently empty */
            if (!(Endpoint_IsReadWriteAllowed()))
            {
                /* Clear the current endpoint bank */
                Endpoint_ClearOUT();
                /* Wait until the host has sent another packet */
                if (Endpoint_WaitUntilReady())
                  return;
            }
            /* Check if end of Dataflash page reached */
            if (CurrDFPageByteDiv16 == (DATAFLASH_PAGE_SIZE >> 4))
            {
                /* Reset the Dataflash buffer counter, increment the page counter */
                CurrDFPageByteDiv16 = 0;
                CurrDFPage++;
            }

            /* Write one 16-byte chunk of data to the Dataflash */
            data_8[0] = Endpoint_Read_8();
            data_8[1] = Endpoint_Read_8();
            data_8[2] = Endpoint_Read_8();
            data_8[3] = Endpoint_Read_8();
            data_8[4] = Endpoint_Read_8();
            data_8[5] = Endpoint_Read_8();
            data_8[6] = Endpoint_Read_8();
            data_8[7] = Endpoint_Read_8();
            data_8[8] = Endpoint_Read_8();
            data_8[9] = Endpoint_Read_8();
            data_8[10] = Endpoint_Read_8();
            data_8[11] = Endpoint_Read_8();
            data_8[12] = Endpoint_Read_8();
            data_8[13] = Endpoint_Read_8();
            data_8[14] = Endpoint_Read_8();
            data_8[15] = Endpoint_Read_8();

            if (first) {
                first = false;
                data_PC = data_8[0];
            }

            /* Increment the Dataflash page 16 byte block counter */
            CurrDFPageByteDiv16++;

            /* Increment the block 16 byte block counter */
            BytesInBlockDiv16++;

            /* Check if the current command is being aborted by the host */
            if (IsMassStoreReset)
              return;
        }

        /* Decrement the blocks remaining counter */
        TotalBlocks--;
    }

    /* If the endpoint is empty, clear it ready for the next packet from the host */
    if (!(Endpoint_IsReadWriteAllowed()))
      Endpoint_ClearOUT();
}

/** Reads blocks (OS blocks, not Dataflash pages) from the storage medium, the board Dataflash IC(s), into
 *  the pre-selected data IN endpoint. This routine reads in Dataflash page sized blocks from the Dataflash
 *  and writes them in OS sized blocks to the endpoint.
 *
 *  \param[in] BlockAddress  Data block starting address for the read sequence
 *  \param[in] TotalBlocks   Number of blocks of data to read
 */
void ReadBlocks(const uint32_t BlockAddress,
                                 uint16_t TotalBlocks)
{
    uint16_t CurrDFPage          = ((BlockAddress * VIRTUAL_MEMORY_BLOCK_SIZE) / DATAFLASH_PAGE_SIZE);
    uint16_t CurrDFPageByte      = ((BlockAddress * VIRTUAL_MEMORY_BLOCK_SIZE) % DATAFLASH_PAGE_SIZE);
    uint8_t  CurrDFPageByteDiv16 = (CurrDFPageByte >> 4);
    uint8_t  data_8[16];
    uint8_t  *pdata = data_8;

    /* Wait until endpoint is ready before continuing */
    if (Endpoint_WaitUntilReady())
      return;

    while (TotalBlocks)
    {
        uint8_t BytesInBlockDiv16 = 0;

        /* Read an endpoint packet sized data block from the Dataflash */
        while (BytesInBlockDiv16 < (VIRTUAL_MEMORY_BLOCK_SIZE >> 4))
        {
            /* Check if the endpoint is currently full */
            if (!(Endpoint_IsReadWriteAllowed()))
            {
                /* Clear the endpoint bank to send its contents to the host */
                Endpoint_ClearIN();

                /* Wait until the endpoint is ready for more data */
                if (Endpoint_WaitUntilReady())
                  return;
            }

            /* Check if end of Dataflash page reached */
            if (CurrDFPageByteDiv16 == (DATAFLASH_PAGE_SIZE >> 4))
            {
                /* Reset the Dataflash buffer counter, increment the page counter */
                CurrDFPageByteDiv16 = 0;
                CurrDFPage++;
            }

            data_8[0] = data_device;

            /* Read one 16-byte chunk of data from the Dataflash */
            Endpoint_Write_8(pdata[0]);
            Endpoint_Write_8(pdata[1]);
            Endpoint_Write_8(pdata[2]);
            Endpoint_Write_8(pdata[3]);
            Endpoint_Write_8(pdata[4]);
            Endpoint_Write_8(pdata[5]);
            Endpoint_Write_8(pdata[6]);
            Endpoint_Write_8(pdata[7]);
            Endpoint_Write_8(pdata[8]);
            Endpoint_Write_8(pdata[9]);
            Endpoint_Write_8(pdata[10]);
            Endpoint_Write_8(pdata[11]);
            Endpoint_Write_8(pdata[12]);
            Endpoint_Write_8(pdata[13]);
            Endpoint_Write_8(pdata[14]);
            Endpoint_Write_8(pdata[15]);

            /* Increment the Dataflash page 16 byte block counter */
            CurrDFPageByteDiv16++;

            /* Increment the block 16 byte block counter */
            BytesInBlockDiv16++;

            /* Check if the current command is being aborted by the host */
            if (IsMassStoreReset)
              return;
        }

        /* Decrement the blocks remaining counter */
        TotalBlocks--;
    }

    /* If the endpoint is full, send its contents to the host */
    if (!(Endpoint_IsReadWriteAllowed()))
      Endpoint_ClearIN();
}

data_8 — это массив для обмена данными с буфером конечной точки, data_PC и data_device будут контрольными переменными для их отображения на светодиодах и в консоли ПК соответственно, а first нужна для того, чтобы в контрольную переменную попал первый байт передаваемого из ПК массива и не перезатерся при дальнейшей передаче 16-байтных кусков. Контрольные переменные надо не забыть объявить в SCSI.h:

    /* variables */
        extern uint8_t data_PC;
        extern uint8_t data_device;

А также их надо определить и использовать в MassStorage.c:

:cpp
uint8_t data_PC;
uint8_t data_device;

перед главной функцией, data_device = cnt_bt; перед switch (mode_out) { и PORTD = data_PC; вместо PORTD = 0x55.

Компиляция, прошивка, проверка. Теперь код для ПК, написанный несколько ранее на С++, работает ожидаемо: в консоли пишет data_2 = и число, соответствующее счетчику cnt_bt на устройстве (увеличивается нажатием верхней кнопки, уменьшается второй кнопкой, отображается на светодиодах в «режиме 2»), а на светодиодах в «режиме 3» вместо константы 0x55 отображается то, что указано в коде С++ в нулевом элементе массива q.

И буква устройству в Windows 7 стала выделяться гораздо быстрее — практически сразу после подключения.

Это изменения, достойные новой версии. Коммит с тегом «v0.2»

Еще немного о взаимодействии с ПК

Вернусь к коду на С++, написанному для ПК. В операциях чтения и записи, реализованных через DeviceIoControl, размер передаваемых данных указывается дважды: в поле myspti.t_spti.DataTransferLength и в 7–8 байтах myspti.t_spti.Cdb, причем, в первом случае — в байтах, а во втором — в логических блоках. Насколько я разобрался, размер логического блока соответствует VIRTUAL_MEMORY_BLOCK_SIZE на устройстве, то есть 512 байтов. Так вот, эти два размера в myspti должны соответствовать друг другу, иначе могут быть ошибки. Например, можно поставить q1 = 512 и myspti.t_spti.Cdb[8] = 0x01 и будет работать, а q1 = 1024 и myspti.t_spti.Cdb[8] = 0x01 вызовет ошибку.

А теперь я закрою эти две сложные конструкции чтения и записи с заполнением структур myspti и вызовом DeviceIoControl блочным комментарием, а вмето них, чуть выше их, напишу следующее:

                    result = WriteFile(hDevice, q, q1, &q2, NULL);
                    if (result==0) {
                        OutFormatMsg("ReadFile Error");
                    } else {
                        _tprintf("WriteFile done\n");
                        _tprintf("len = %lu\n", q2);
                    }


                    result = ReadFile(hDevice, q, q1, &q2, NULL);
                    if (result==0) {
                        OutFormatMsg("ReadFile Error");
                    } else {
                        _tprintf("ReadFile done\n");
                        _tprintf("data_2 = %x\n", q[0]);
                        _tprintf("len = %lu\n", q2);
                    }

То есть, воспользуюсь для чтения и записи специально предназначенными для этого функциями. Компилирую, прошиваю, проверяю.

Работает! Ура? Вроде, ура. Теперь уже можно использовать разработанное устройство для общения с ПК — передавать данные в обе стороны и как-то их использовать.

Причем, можно использовать не только данные, но и адрес. То есть, устройство, получив данные, может проанализировать адрес, по которому была команда их записать (или, получив команду чтения, проанализировать адрес, с которого данные затребованы) и выполнить определенные действия в зависимости от этого адреса. Сам адрес-то фиктивный, никакой памяти, к которой относится адресация, на плате нет. При использовании ReadFile/WriteFile можно управлять адресом c ПК при помощи предварительно выполненной функции SetFilePointer(hDevice, 0, NULL, FILE_BEGIN);, а в программе устройства анализировать BlockAddress.

Пожалуй, на этом надо заканчивать, и так слишком много получилось для одного выпуска. Продолжение следует…


Теги: