/* Данный пример представляет из себя консольную программу на языке C,
   демонстрирующую работу с модулями L502 и E502 на примере синхронного
   ввода данных с АЦП и цифровых линий.

   Перед сбором в примере идет поиск модулей, подключенных по интерфейсам PCI-Express и USB,
   и предоставляется список для выбора модуля, с которым нужно работать.
   Для подключения по Ethernet нужно указать IP-адреса интересующих модулей
   в качестве аргументов командной строки при вызове примера, тогда эти адреса
   будут добавлены в конец списка выбора модуля.
   Например, если интересуют модули с адресами 192.168.1.5 и 192.168.1.6,
   то пример можно вызвать:
   x502_stream_read 192.168.1.5 192.168.1.6
   и две дополнительные строки с этими адресами появятся в списке выбора.

   Настройки частот, количества принимаемых данных и т.д. задаются с помощью макросов в
   начале программы.
   Настройки логических каналов - с помощью таблиц f_channels/f_ch_modes/f_ch_ranges.

   Пример выполняет прием блоков данных заданного размера.
   Сбор идет до нажатия любой клавиши на Windows или  CTRL+C на Linux.

   Пример также показывает как выполнять обработку данных и определять начало кадра,
   в случае если в X502_ProcessData() передается не целое число кадров.

   Данный пример содержит проект для Visual Studio 2008, а также может быть собран
   gcc в Linux или mingw в Windows через makefile или с помощью cmake (подробнее
   в комментариях в соответствующих файлах).

   Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
   (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
   -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
   на тот, где у вас лежат заголовочный файлы x502api.h, l502api.h и e502api.h и измените путь к библиотекам
   (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties) ->
   Компановщик (Linker) -> Общие (General) -> Дополнительные катологи библиотек (Additional Library Directories)).

   Внимание!!: Если Вы собираете проект под Visual Studio и взяли проект с сайта (а не из SDK),
   то для корректного отображения русских букв в программе нужно изменить кодировку
   или указать сохранение с сигнатурой кодировки для UTF-8:
   выберите Файл (File) -> Дополнительные параметры сохранения (Advanced Save Options)...
   и в поле Кодировка (Encoding) выберите Юникод (UTF8, с сигнатурой)/Unicode (UTF-8 with signature)
   и сохраните изменения в файле.

   */

#include "l502api.h"
#include "e502api.h"
#include <math.h>
#ifdef _WIN32
#include <locale.h>
#include <conio.h>
#else
#include <signal.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include "osspec.h"
#include "l502_bf_cmd_defs.h"

/* количество используемых логических каналов */
#define ADC_LCH_CNT  3

/* частота сбора АЦП в Гц*/
#define ADC_FREQ          2000000
/* частота кадров (на логический канал). При ADC_FREQ/ADC_LCH_CNT - межкадровой задержки нет */
#define ADC_FRAME_FREQ    (ADC_FREQ/ADC_LCH_CNT)
/* частота синхронного ввода в Гц*/
#define DIN_FREQ          2000000


#define TCP_CONNECTION_TOUT 5000


/* сколько отсчетов считываем за блок */
#define READ_BLOCK_SIZE   4096*200
/* таймаут на прием блока (мс) */
#define READ_TIMEOUT     2000


/* номера используемых физических каналов */
static uint32_t f_channels[ADC_LCH_CNT] = {0,4,6};
/* режимы измерения для каналов */
static uint32_t f_ch_modes[ADC_LCH_CNT] = {X502_LCH_MODE_DIFF, X502_LCH_MODE_DIFF, X502_LCH_MODE_DIFF};
/* диапазоны измерения для каналов */
static uint32_t f_ch_ranges[ADC_LCH_CNT] = {X502_ADC_RANGE_10, X502_ADC_RANGE_10, X502_ADC_RANGE_10};



/* признак необходимости завершить сбор данных */
static int f_out = 0;

#ifndef _WIN32
/* Обработчик сигнала завершения для Linux */
static void f_abort_handler(int sig) {
    f_out = 1;
}
#endif


/* Функция находит все подключенные модули по интерфейсам PCI-Express и USB и
 * сохраняет записи о этих устройствах в выделенный массив.
 * Также создаются записи по переданным IP-адресам модулей и добавляются в конец
 * массива.
 * Указатель на выделенный массив, который должен быть потом очищен, сохраняется
 * в pdevrec_list, а количество действительных элементов (память которых должна
 * быть в дальнейшем освобождена с помощью X502_FreeDevRecordList()) возвращается
 * как результат функции */
static uint32_t f_get_all_devrec(t_x502_devrec **pdevrec_list, uint32_t *ip_addr_list, unsigned ip_cnt) {
    int32_t fnd_devcnt = 0;
    uint32_t pci_devcnt = 0;
    uint32_t usb_devcnt = 0;

    t_x502_devrec *rec_list = NULL;

    /* получаем количество подключенных устройств по интерфейсам PCI и USB */
    L502_GetDevRecordsList(NULL, 0, 0, &pci_devcnt);
    E502_UsbGetDevRecordsList(NULL, 0, 0, &usb_devcnt);

    if ((pci_devcnt + usb_devcnt + ip_cnt) != 0) {
        /* выделяем память для массива для сохранения найденного количества записей */
        rec_list = malloc((pci_devcnt + usb_devcnt + ip_cnt) * sizeof(t_x502_devrec));

        if (rec_list != NULL) {
            unsigned i;
            /* получаем записи о модулях L502, но не больше pci_devcnt */
            if (pci_devcnt!=0) {
                int32_t res = L502_GetDevRecordsList(&rec_list[fnd_devcnt], pci_devcnt, 0, NULL);
                if (res >= 0) {
                    fnd_devcnt += res;
                }
            }
            /* добавляем записи о модулях E502, подключенных по USB, в конец массива */
            if (usb_devcnt!=0) {
                int32_t res = E502_UsbGetDevRecordsList(&rec_list[fnd_devcnt], usb_devcnt, 0, NULL);
                if (res >= 0) {
                    fnd_devcnt += res;
                }
            }

            /* создаем записи для переданного массива ip-адресов */
            for (i=0; i < ip_cnt; i++) {
                if (E502_MakeDevRecordByIpAddr(&rec_list[fnd_devcnt], ip_addr_list[i],0, TCP_CONNECTION_TOUT) == X502_ERR_OK) {
                    fnd_devcnt++;
                }
            }
        }
    }

    if (fnd_devcnt != 0) {
        /* если создана хотя бы одна запись, то сохраняем указатель на выделенный массив */
        *pdevrec_list = rec_list;
    } else {
        *pdevrec_list = NULL;
        free(rec_list);
    }

    return fnd_devcnt;
}


static t_x502_hnd f_dev_select_open(int argc, char** argv) {
    t_x502_hnd hnd = NULL;
    uint32_t fnd_devcnt,i, dev_ind;
    t_x502_devrec *devrec_list = NULL;
    uint32_t *ip_addr_list = NULL;
    uint32_t ip_cnt = 0;

    /* если есть аргументы командной строки, то предполагаем, что это могут быть
       ip-адреса интересующих устройств. */
    if (argc > 1) {
        ip_addr_list = malloc((argc-1) * sizeof(ip_addr_list[0]));
        if (ip_addr_list == NULL) {
            fprintf(stderr, "Ошибка выделения памяти!\n");
        } else {
            for (i=1; (int)i < argc; i++) {
                int a[4];
                if (sscanf(argv[i], "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3])==4) {
                    ip_addr_list[ip_cnt++] = ((a[0] & 0xFF) << 24) |
                                             ((a[1] & 0xFF) << 16) |
                                             ((a[2] & 0xFF) <<  8) |
                                             (a[3] & 0xFF);
                }
            }
        }
    }

    /* получаем список модулей для выбора */
    fnd_devcnt = f_get_all_devrec(&devrec_list, ip_addr_list, ip_cnt);

    if (fnd_devcnt == 0) {
        printf("Не найдено ни одного модуля\n");
    } else {
        /* выводим информацию по списку модулей */
        printf("Доступны следующие модули:\n");
        for (i=0; i < fnd_devcnt; i++) {
            printf("Модуль № %d: %s, %-9s", i, devrec_list[i].devname,
                   devrec_list[i].iface == X502_IFACE_PCI ? "PCI/PCIe" :
                   devrec_list[i].iface == X502_IFACE_USB ? "USB" :
                   devrec_list[i].iface == X502_IFACE_ETH ? "Ethernet" : "?");

            /* при подключении по сети по IP-адресу серийный номер можно узнать
               только после открытия соединения. При этом поле location
               содержит строку с описанием адреса устройства */
            if (devrec_list[i].iface != X502_IFACE_ETH) {
                printf("Сер. номер: %s\n", devrec_list[i].serial);
            } else {
                printf("Адрес: %s\n", devrec_list[i].location);
            }
        }

        /* выбираем нужный по введенному номеру модуля по порядку с клавиатуры */
        printf("Введите номер модуля, с которым хотите работать (от 0 до %d)\n", fnd_devcnt-1);
        fflush(stdout);
        scanf("%d", &dev_ind);

        if (dev_ind >= fnd_devcnt) {
            printf("Неверно указан номер модуля...\n");
        } else {
            /* если ввели номер правильно - создаем описатель */
            hnd = X502_Create();
            if (hnd==NULL) {
                fprintf(stderr, "Ошибка создания описателя модуля!");
            } else {
                /* устанавливаем связь с модулем по записи */
                int32_t err = X502_OpenByDevRecord(hnd, &devrec_list[dev_ind]);
                if (err != X502_ERR_OK) {
                    fprintf(stderr, "Ошибка установления связи с модулем: %s!", X502_GetErrorString(err));
                    X502_Free(hnd);
                    hnd = NULL;
                }
            }
        }

        /* освобождение ресурсов действительных записей из списка */
        X502_FreeDevRecordList(devrec_list, fnd_devcnt);
        /* очистка памяти самого массива */
        free(devrec_list);
    }

    /* освобождаем выделенный массив под IP-адреса (если был выделен) */
    free(ip_addr_list);

    return hnd;
}


/* настройка параметров модуля */
int32_t f_setup_params(t_x502_hnd hnd) {
    int32_t err = X502_ERR_OK, i;

    /* устанавливаем параметры логической таблицы АЦП */
    err = X502_SetLChannelCount(hnd, ADC_LCH_CNT);
    for (i=0; (i < ADC_LCH_CNT) && (err == X502_ERR_OK); i++)
        err = X502_SetLChannel(hnd, i, f_channels[i], f_ch_modes[i], f_ch_ranges[i], 0);

    /* устанавливаем частоты ввода для АЦП и цифровых входов */
    if (err == X502_ERR_OK) {
        double f_adc = ADC_FREQ, f_frame = ADC_FRAME_FREQ, f_din = DIN_FREQ;
        err = X502_SetAdcFreq(hnd, &f_adc, &f_frame);
        if (err == X502_ERR_OK)
            err = X502_SetDinFreq(hnd, &f_din);
        if (err == X502_ERR_OK) {
            /* выводим реально установленные значения - те что вернули функции */
            printf("Установлены частоты:\n    Частота сбора АЦП = %0.0f\n"
                "    Частота на лог. канал = %0.0f\n    Частота цифрового ввода = %0.0f\n",
                f_adc, f_frame, f_din);
        }
    }

    /* записываем настройки в модуль */
    if (err == X502_ERR_OK)
        err = X502_Configure(hnd, 0);

    /* разрешаем синхронные потоки */
    if (err == X502_ERR_OK) {
        err = X502_StreamsEnable(hnd, X502_STREAM_ADC | X502_STREAM_DIN);
    }

    return err;
}


#define OUT_BLOCK_SIZE (3*1024)
#define DAC1_ENABLE
#define DAC2_ENABLE
#define DOUT_ENABLE
#define SEND_TOUT 1000
#define RECV_TOUT 2000

#define IN_BLOCK_SIZE  (100*1024)

#if 0
static int f_send_block(t_x502_hnd hnd) {
    static double dac_data1[OUT_BLOCK_SIZE], dac_data2[OUT_BLOCK_SIZE];
    static uint32_t dout_data[OUT_BLOCK_SIZE];
    /* массив слов на запись в модуль - содержит смешенные подготовленные данные
       для всех каналов (максимум для 3-х - 2 ЦАП + DOUT) */
    static uint32_t sbuf[3*OUT_BLOCK_SIZE];
    uint32_t i;
    int32_t err = 0;
    size_t size = OUT_BLOCK_SIZE;
    int ch_cnt = 3;

    /* заполняем массив на вывод */
    for (i=0; i < OUT_BLOCK_SIZE; i++) {
        dac_data1[i] = 5.*sin(2.*M_PI*i/OUT_BLOCK_SIZE);
        dac_data1[i] = 5.*i/OUT_BLOCK_SIZE;
        dout_data[i] = i;
    }

    /* Если нужная функция определена, значит мы испоьлзуем этот канал, и
     * подаем на вход сформированный массив. Иначе - канал не используется
     * и передаем на вход NULL */
    err = X502_PrepareData(hnd, dac_data1, dac_data2, dout_data, size,
                           X502_DAC_FLAGS_VOLT | X502_DAC_FLAGS_CALIBR,
                           sbuf);
    if (err != X502_ERR_OK) {
        fprintf(stderr, "Ошибка подкотовки данных на передачу: %s\n",
                X502_GetErrorString(err));
    } else {
        /* посылаем данные */
        int32_t snd_cnt = X502_Send(hnd, sbuf, size*ch_cnt, SEND_TOUT);
        if (snd_cnt < 0) {
            err = snd_cnt;
            fprintf(stderr, "Ошибка передачи данных: %s\n", X502_GetErrorString(err));
        } else if ((uint32_t)snd_cnt != size*ch_cnt) {
            /* так как мы шлем всегда не больше чем готово, то должны
               всегда передать все */
            fprintf(stderr, "Переданно недостаточно данных: передавали = %d, передано = %d\n",
                    (int)size*ch_cnt, snd_cnt);
            err = X502_ERR_SEND_INSUFFICIENT_WORDS;
        }
    }
    return err;
}
#else
static int f_send_block(t_x502_hnd hnd) {
    static uint32_t sbuf[OUT_BLOCK_SIZE];
    uint32_t i;
    static uint32_t tx_cntr = 0;
    int32_t err = X502_ERR_OK;

    uint32_t cur_cntr = tx_cntr;

    for (i = 0; i < OUT_BLOCK_SIZE; i++) {
        sbuf[i] = cur_cntr++;
    }

    /* посылаем данные */
    int32_t snd_cnt = X502_Send(hnd, sbuf, OUT_BLOCK_SIZE, SEND_TOUT);
    if (snd_cnt < 0) {
        err = snd_cnt;
        fprintf(stderr, "Ошибка передачи данных: %s\n", X502_GetErrorString(err));
    } else  {
        tx_cntr += (uint32_t)snd_cnt;
    }

    return err;
}
#endif

typedef struct {
    t_x502_hnd hnd;
    int stop_req;
} t_wr_thread_ctx;


static OSSPEC_THREAD_FUNC_RET OSSPEC_THREAD_FUNC_CALL write_thread_func(void *data) {
    t_wr_thread_ctx *ctx = (t_wr_thread_ctx*)data;
    int err = 0;
    while (!ctx->stop_req && !err) {
        err = f_send_block(ctx->hnd);
    }
    fprintf(stderr, "end write with err %d\n", err);

    return 0;
}

#if 0
static int f_recv_block(t_x502_hnd hnd) {
    static uint32_t rbuf[IN_BLOCK_SIZE];
    static uint32_t rx_cntr = 0;
    static uint32_t total_pos = 0;
    int32_t err =  X502_ERR_OK;

    int32_t recvd_cnt = X502_Recv(hnd, rbuf, IN_BLOCK_SIZE, RECV_TOUT);
    fprintf(stderr, "recv cnt = %d\n", recvd_cnt);
    if (recvd_cnt < 0) {
        err = recvd_cnt;
        fprintf(stdout, "Ошибка приема данных: %s\n", X502_GetErrorString(err));
    } else  {
        int32_t i;
        for (i=0; i < recvd_cnt; i++) {
            //fprintf(stdout, "buf %4d: 0x%08X\n", total_pos++, rbuf[i]);
            if (rbuf[i] != rx_cntr) {
                fprintf(stderr, "Ошибка счетчика: ожидали 0x%08X, приняли 0x%08X\n", rx_cntr, rbuf[i]);
                rx_cntr = rbuf[i];
                err = -1;
                break;
            }

            ++rx_cntr;
        }
        fflush(stdout);
    }
    return err;

}
#else
static int f_recv_block(t_x502_hnd hnd) {
    static uint32_t rbuf[8*16];
    static uint32_t rx_cntr = 0;
    static uint32_t total_pos = 0;
    int32_t err =  X502_ERR_OK;

    int32_t recvd_cnt = X502_Recv(hnd, rbuf, sizeof(rbuf)/sizeof(rbuf[0]), RECV_TOUT);
    fprintf(stderr, "recv cnt = %d\n", recvd_cnt);
    if (recvd_cnt < 0) {
        err = recvd_cnt;
        fprintf(stdout, "Ошибка приема данных: %s\n", X502_GetErrorString(err));
    } else  {
        int32_t i;
        if ((recvd_cnt % 8) != 0) {
            printf("unalign((( %d\n", recvd_cnt);
            err = -1;
        } else {
            for (i=0; i < recvd_cnt; i+=8) {
                printf("addr 0x%08X, size 0x%08X, descr_rem 0x%08X, err_idx %d, id = %d, err_wrd_wr 0x%08X, err_exp_wrd 0x%08X\n",
                       rbuf[i], rbuf[i+1], rbuf[i+2], rbuf[i+3], rbuf[i+6],
                       rbuf[i+4], rbuf[i+5]);
                if ((int)rbuf[i+3] != -1)
                    err = -2;
            }
        }
        fflush(stdout);
    }
    return err;

}
#endif

int main(int argc, char** argv) {
    int32_t err = X502_ERR_OK;
    uint32_t ver;
    t_x502_hnd hnd = NULL;
#ifndef _WIN32
    struct sigaction sa;
    /* В ОС Linux устанавливаем свой обработчик на сигнал закрытия,
       чтобы завершить сбор корректно */
    sa.sa_handler = f_abort_handler;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
#endif
#ifdef _WIN32
    /* для вывода русских букв в консоль для ОС Windows в CP1251 без перевода в OEM */
    setlocale(LC_CTYPE, "");
#endif
    /* получаем версию библиотеки */
    ver = X502_GetLibraryVersion();
    printf("Версия библиотеки: %d.%d.%d\n", (ver >> 24)&0xFF, (ver>>16)&0xFF, (ver>>8)&0xFF);

    /********** Получение списка устройств и выбор, с каким будем работать ******************/
    //hnd = f_dev_select_open(argc, argv);
    hnd = X502_Create();
    //err = L502_Open(hnd, "");
    err = E502_OpenUsb(hnd, "");
    if (err) {
        X502_Free(hnd);
        hnd = NULL;
    }

    /********************************** Работа с модулем **************************/
    /* если успешно выбрали модуль и установили с ним связь - продолжаем работу */
    if (hnd != NULL) {
        /* получаем информацию */
        t_x502_info info;
        err = X502_GetDevInfo(hnd, &info);
        if (err != X502_ERR_OK) {
            fprintf(stderr, "Ошибка получения серийного информации о модуле: %s!", X502_GetErrorString(err));
        } else {
            /* выводим полученную информацию */
            printf("Установлена связь со следующим модулем:\n");
            printf(" Серийный номер          : %s\n", info.serial);
            printf(" Наличие ЦАП             : %s\n", info.devflags & X502_DEVFLAGS_DAC_PRESENT ? "Да" : "Нет");
            printf(" Наличие BlackFin        : %s\n", info.devflags & X502_DEVFLAGS_BF_PRESENT ? "Да" : "Нет");
            printf(" Наличие гальваноразвязки: %s\n", info.devflags & X502_DEVFLAGS_GAL_PRESENT ? "Да" : "Нет");
            printf(" Индустриальное исп.     : %s\n", info.devflags & X502_DEVFLAGS_INDUSTRIAL ? "Да" : "Нет");
            printf(" Наличие интерф. PCI/PCIe: %s\n", info.devflags & X502_DEVFLAGS_IFACE_SUPPORT_PCI ? "Да" : "Нет");
            printf(" Наличие интерф. USB     : %s\n", info.devflags & X502_DEVFLAGS_IFACE_SUPPORT_USB ? "Да" : "Нет");
            printf(" Наличие интерф. Ethernet: %s\n", info.devflags & X502_DEVFLAGS_IFACE_SUPPORT_ETH ? "Да" : "Нет");
            printf(" Версия ПЛИС             : %d.%d\n", (info.fpga_ver >> 8) & 0xFF, info.fpga_ver & 0xFF);
            printf(" Версия PLDA             : %d\n", info.plda_ver);
            if (info.mcu_firmware_ver != 0) {
                printf(" Версия прошивки ARM     : %d.%d.%d.%d\n",
                       (info.mcu_firmware_ver >> 24) & 0xFF,
                       (info.mcu_firmware_ver >> 16) & 0xFF,
                       (info.mcu_firmware_ver >>  8) & 0xFF,
                       info.mcu_firmware_ver & 0xFF);
            }
        }

        if (err == X502_ERR_OK) {
           // err = X502_StreamsStop(hnd);
        }

        if (err == X502_ERR_OK) {
            err = X502_BfLoadFirmware(hnd, "/home/user/PRJ/lpcie_sdk/firmware/l502-bf/build/release/bin/l502-bf.ldr");
        }

        if (err == X502_ERR_OK) {
            /* настраиваем параметры модуля */
            err = X502_Configure(hnd, 0);
            if (err != X502_ERR_OK)
                fprintf(stderr, "Ошибка настройки модуля: %s!", X502_GetErrorString(err));
        }

        if (err == X502_ERR_OK) {
            err = X502_StreamsEnable(hnd, X502_STREAM_ALL_IN);

            err = X502_StreamsEnable(hnd, X502_STREAM_ALL_OUT);

            err = X502_PreloadStart(hnd);
            //for (int i = 0; i < 100; i++)
            //    f_send_block(hnd);
        }

        /* запуск синхронного ввода-вывода */
        if (err == X502_ERR_OK) {
            err = X502_StreamsStart(hnd);
            if (err != X502_ERR_OK)
                fprintf(stderr, "Ошибка запуска сбора данных: %s!\n", X502_GetErrorString(err));
        }


        if (err == X502_ERR_OK) {
            int block;
            int32_t stop_err;

            printf("Сбор данных запущен. Для останова нажмите %s\n",
#ifdef _WIN32
                   "любую клавишу"
#else
                   "CTRL+C"
#endif
                   );
            fflush(stdout);


            t_wr_thread_ctx ctx;
            ctx.hnd = hnd;
            ctx.stop_req = 0;
            t_thread wr_thread = osspec_thread_create(write_thread_func, &ctx, 0);
            /*for (block = 0; block < 10; block++) {
                err = f_send_block(hnd);
            }*/


            for (block = 0; (err == X502_ERR_OK) && !f_out; block++) {
                //err = f_send_block(hnd);
                err = f_recv_block(hnd);                
                //err -= 1;

#ifdef _WIN32
                /* проверка нажатия клавиши для выхода */
                if (err == X502_ERR_OK) {
                    if (_kbhit())
                        f_out = 1;
                }
#endif
            }

            ctx.stop_req = 1;
            osspec_thread_wait(wr_thread, OSSPEC_TIMEOUT_INFINITY);

            /* останавливаем поток сбора данных (независимо от того, была ли ошибка) */
            stop_err = X502_StreamsStop(hnd);
            if (stop_err != X502_ERR_OK) {
                fprintf(stderr, "Ошибка останова сбора данных: %s\n", X502_GetErrorString(stop_err));
                if (err == X502_ERR_OK)
                    err = stop_err;
            } else {
                printf("Сбор данных остановлен успешно\n");
            }


            uint32_t recvd_block[L502_BF_CMD_DATA_SIZE_MAX];
            uint32_t recvd;
            err = X502_BfExecCmd(hnd, L502_BF_CMD_CODE_USER, 0, NULL, 0,
                                 recvd_block, L502_BF_CMD_DATA_SIZE_MAX, X502_BF_CMD_DEFAULT_TOUT,
                                 &recvd);
            printf("exec cmd res %d, recvd_size %d\n", err, recvd);
            for (uint32_t i = 0; i < recvd; i+= 8) {
                if (recvd_block[i] == 0)
                    break;
                printf("addr 0x%08X, size 0x%08X, descr_rem 0x%08X, err_idx %d, err_wrd_wr 0x%08X, err_exp_wrd 0x%08X\n",
                       recvd_block[i], recvd_block[i+1], recvd_block[i+2], recvd_block[i+3],
                        recvd_block[i+4], recvd_block[i+5]);

            }
        }

        /* закрываем связь с модулем */
        X502_Close(hnd);
        /* освобождаем описатель */
        X502_Free(hnd);
    }
    return err;
}
