/*
    При использовании заглушки соединяющей DOUT -> DIN есть возмость проверить корректность приема/передачи цифровых каналов в петле

    При использовании флага DCI_TEST_MODE по каналу АЦП должен приниматься 32-битный счетчик
*/

/*    
   Данный пример представляет из себя консольную программу на языке 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 "../../devs/e502/e502_fpga_regs.h"

#ifdef _WIN32
#include <locale.h>
#include <conio.h>

#else
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#endif

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

#include "x502api_private.h"

#define STREAM_IN_WRD_TYPE(wrd) wrd & 0x80000000 ? STREAM_IN_WRD_ADC : \
      (wrd & 0xFF000000) == 0x0 ? STREAM_IN_WRD_DIN : \
    ((wrd & 0xFF000000)>>24) == 0x01 ? STREAM_IN_WRD_MSG : STREAM_IN_WRD_USR
    
    
#ifdef _WIN32

#define CLOCK_MONOTONIC 1

int clock_gettime(int a, struct timespec *spec) {
    __int64 wintime; GetSystemTimeAsFileTime((FILETIME*)&wintime);
    wintime      -=116444736000000000i64;  //1jan1601 to 1jan1970
    spec->tv_sec  =wintime / 10000000i64;           //seconds
    spec->tv_nsec =wintime % 10000000i64 *100;      //nano-seconds
    return 0;
}
#endif    
    
/* признак необходимости завершить сбор данных */
static int f_out = 0;

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

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

// при ADC_FREQ==2000000 и DIN_FREQ==1000000 через Ethernet обязательно будет X502_STREAM_IN_MSG_OVERFLOW


#define TCP_CONNECTION_TOUT 5000


/* сколько отсчетов считываем за блок */
#define READ_BLOCK_SIZE   4096*66*3
//#define READ_BLOCK_SIZE   ((1024*2) + 3)
//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};


t_x502_hnd g_hnd = NULL;

uint32_t tstCntr = 0x00000001;
uint32_t tstCntr24 = 0x00000001;

int dac2_once = 1;
volatile int stream_ingibit_cntr = (2 * 1024 * 1024);
volatile int start_stream_ingibit = 1;

#define ENABLE_DOUT
#define ENABLE_DAC1
#define ENABLE_DAC2

#define ENABLE_ADC

#define NSEC_PER_SEC 1000000000

void timespec_diff(struct timespec *a, struct timespec *b,
    struct timespec *result) {
    result->tv_sec  = a->tv_sec  - b->tv_sec;
    result->tv_nsec = a->tv_nsec - b->tv_nsec;
    if (result->tv_nsec < 0) {
        --result->tv_sec;
        result->tv_nsec += NSEC_PER_SEC;
    }
}

double timespec_to_double(struct timespec *ts)
{
	return ((double)(ts->tv_sec) + ((double)(ts->tv_nsec) / NSEC_PER_SEC));
}

#ifdef _WIN32
DWORD WINAPI ThreadFunc(void* arg)
#else
void * threadFunc(void * arg)
#endif
{
    int32_t g_snd_cnt = 0;
    static uint32_t snd_buf[READ_BLOCK_SIZE];

    int i;
    struct timespec start_time;
    struct timespec cur_time;
    struct timespec spent_time;

    clock_gettime(CLOCK_MONOTONIC, &start_time);

    while(!f_out) {
#define DAC_SEND_TEST_CNTR
#ifdef DAC_SEND_TEST_CNTR
        for (i = 0; i < READ_BLOCK_SIZE; ) {
#ifdef ENABLE_DOUT
            //snd_buf[i] = ((tstCntr24 & 0xff) << 16) | ((tstCntr24 & 0xff00)) | ((tstCntr24 & 0xff0000) >> 16);
            snd_buf[i] = tstCntr24 & 0xffff;
            /*if (i & 8) {
                snd_buf[i] = 0x30000;
            } else {
                snd_buf[i] = 0x33412;
            }*/
            i++;
#endif
#ifdef ENABLE_DAC1
            snd_buf[i] = (tstCntr & 0xffff) | X502_STREAM_OUT_WORD_TYPE_DAC1;
            i++;
#endif
#ifdef ENABLE_DAC2
            snd_buf[i] = (tstCntr & 0xffff) | X502_STREAM_OUT_WORD_TYPE_DAC2;
            i++;
#endif
            tstCntr++;
            tstCntr24++;
        }
#else
        for (i = 0; i < READ_BLOCK_SIZE; i++) {
            snd_buf[i] = tstCntr;
            tstCntr++;
        }
#endif
        
        
        int32_t snd_cnt = 0;
        //printf("to send %x\n", snd_buf[0]);
        uint32_t to_send = READ_BLOCK_SIZE;
        uint32_t *to_send_ptr = snd_buf;

        while (to_send) {
            snd_cnt = X502_Send(g_hnd, to_send_ptr, to_send, READ_TIMEOUT);
            if (snd_cnt < 0 || f_out) {
                printf("thread exiting\n");
                return 0;
            }
            to_send -= snd_cnt;
            to_send_ptr += snd_cnt;
            stream_ingibit_cntr -=snd_cnt; 
            if(stream_ingibit_cntr <= 0) {
                start_stream_ingibit = 0;
            }
        }

        g_snd_cnt += READ_BLOCK_SIZE;
        /*g_snd_cnt += snd_cnt;
        if (snd_cnt != READ_BLOCK_SIZE) {
            printf("snd_cnt=%d %d expected!\n", snd_cnt, READ_BLOCK_SIZE);
        }
        */
        

        // infinite loop
        
        //printf("snd_cnt=%d\n", snd_cnt);
        //while(1);
        //while(!f_out);
        //return 0;
        
        //sleep(3);
        
        //tstCntr += snd_cnt;
        
        if(g_snd_cnt >= 128*3) {
        
            ///while (!f_out) {}
            //sleep(5);
            //g_snd_cnt = 0;
        }
        
        /*if(g_snd_cnt>=3) {
            printf("data sent ok\n");
            while(!f_out){}
        }*/



        clock_gettime(CLOCK_MONOTONIC, &cur_time);
        if (cur_time.tv_sec  - start_time.tv_sec >= 5) 
        {
            timespec_diff(&cur_time, &start_time, &spent_time);
            double spent_secs = timespec_to_double(&spent_time);
            printf("cnt=%d sec=%f snd speed=%f wrds/sec\n", g_snd_cnt, spent_secs, (g_snd_cnt) / (spent_secs));
            start_time = cur_time;
            g_snd_cnt = 0;
        }
    }
    return 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) {
    uint32_t fnd_devcnt,i, dev_ind;
    t_x502_devrec *devrec_list = NULL;
    uint32_t *ip_addr_list = NULL;
    uint32_t ip_cnt = 0;
    t_x502_hnd hnd = NULL;

    /* если есть аргументы командной строки, то предполагаем, что это могут быть
       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) {
            err = X502_SetOutFreqDivider(hnd, 8);
            if(err) {
                printf("X502_SetOutFreqDivider err=%d\n", err);
            }
        }
        if (err == X502_ERR_OK) {
            /* выводим реально установленные значения - те что вернули функции */
            printf("Установлены частоты:\n    Частота сбора АЦП = %0.0f\n"
                "    Частота на лог. канал = %0.0f\n    Частота цифрового ввода = %0.0f\n",
                f_adc, f_frame, f_din);
        }
    }
    
    double dout_freq = DOUT_FREQ;
    
    err = X502_SetOutFreq(hnd, &dout_freq);
    printf("X502_SetOutFreq err=%d dout_freq = %.1f\n", err, dout_freq);

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

    /* разрешаем синхронные потоки */
    if (err == X502_ERR_OK) {
        int streams = 0;
#if defined(ENABLE_ADC)
        streams |= X502_STREAM_ALL_IN;
#endif
#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
        streams |= X502_STREAM_ALL_OUT;
#endif
        err = X502_StreamsEnable(hnd, streams);        
    }
    return err;
}

#ifndef _WIN32
void update_firmware(void) {
    int32_t err = X502_ERR_OK;
    err = E502_SwitchToBootloader(g_hnd);
    if (err != X502_ERR_OK) {
        printf("E502_SwitchToBootloader err=%d\n", err);
    }
    X502_Close(g_hnd);
    /* освобождаем описатель */
    X502_Free(g_hnd);
    
    char *args[] = { "./lboot", "-v", "-d", "E502", "usb", "--hash", "--con-time=5000", "/home/ruslan/vbox_share/e502-riscv.bin", NULL };
    //-r" << "--recovery" << "--hash" << "--con-time=5000" << "--devname=E502
    int ret = execve(args[0], args, NULL);
    
    if (ret == -1) {
        perror("execve error");
    }
    exit(1);
}
#else
#define update_firmware() ((void*)0)
#endif

int main(int argc, char** argv) {
    int32_t err = X502_ERR_OK;
    uint32_t ver;
#ifndef _WIN32
    struct sigaction sa;
    memset(&sa, 0, sizeof(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);

    /********** Получение списка устройств и выбор, с каким будем работать ******************/
    g_hnd = f_dev_select_open(argc, argv);

    /********************************** Работа с модулем **************************/
    /* если успешно выбрали модуль и установили с ним связь - продолжаем работу */
    if (g_hnd != NULL) {
        /* получаем информацию */
        t_x502_info info;
        err = X502_GetDevInfo(g_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);
            }
        }
        
        {
            //update_firmware();
        }

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

//#define TEST_MODE DCI_TEST_MODE
#define TEST_MODE 0
#if 0
        
#ifdef ENABLE_DOUT
        X502_FpgaRegWrite(g_hnd, E502_REGS_ARM_BLOCK, TEST_MODE | E502_REGS_ARM_BLOCK_BUF_CLEAN | SYN_DAC1_EN | SYN_DAC2_EN | SYN_DIGOUT_EN);
#else
        X502_FpgaRegWrite(g_hnd, E502_REGS_ARM_BLOCK, TEST_MODE | E502_REGS_ARM_BLOCK_BUF_CLEAN | SYN_DAC1_EN | SYN_DAC2_EN);
#endif
#endif
        X502_FpgaRegWrite(g_hnd, E502_REGS_ARM_BLOCK, RING_MODE(1) | E502_REGBIT_ARM_DMA_DAC_BUF_CLR_Msk | E502_REGBIT_ARM_DMA_ADC_BUF_CLR_Msk);

        X502_FpgaRegWrite(g_hnd, E502_REGS_IOHARD_GO_SYNC_IO, 0);
        X502_FpgaRegWrite(g_hnd, E502_REGS_BF_CTL, X502_REGBIT_BF_CTL_BF_RESET_Msk);

#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
        
#ifdef _WIN32

        HANDLE thread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
        while(start_stream_ingibit);
        Sleep(2);
#else
        pthread_t threadId;
        // Create a thread that will function threadFunc()
        err = pthread_create(&threadId, NULL, &threadFunc, g_hnd);
        while(start_stream_ingibit);
        sleep(2);
#endif
#endif

        X502_SetSyncMode(g_hnd, X502_SYNC_INTERNAL);

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

#if 0
        X502_FpgaRegWrite(g_hnd, E502_REGS_IOHARD_GO_SYNC_IO, 0);
#ifdef ENABLE_DOUT
        X502_FpgaRegWrite(g_hnd, E502_REGS_ARM_BLOCK, DCI_TEST_MODE | E502_REGS_ARM_BLOCK_BUF_CLEAN | SYN_DAC1_EN | SYN_DAC2_EN | SYN_DIGOUT_EN);
#else
        X502_FpgaRegWrite(g_hnd, E502_REGS_ARM_BLOCK, DCI_TEST_MODE | E502_REGS_ARM_BLOCK_BUF_CLEAN | SYN_DAC1_EN | SYN_DAC2_EN);
#endif

        //X502_PreloadStart(g_hnd);

        X502_FpgaRegWrite(g_hnd, E502_REGS_IOHARD_GO_SYNC_IO, 1);
#endif

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

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

            struct timespec start_time;
            clock_gettime(CLOCK_MONOTONIC, &start_time);

            int32_t g_rcv_cnt = 0;
            int cntr = 0;
            int rcv_cntr = 1;//0x02000000;
            
            //threadFunc(NULL);
            //return;
            
            
#if !defined(ENABLE_ADC)
            if(1) {
                while(!f_out) {}
            } else
#endif
            for (block = 0; (err == X502_ERR_OK) && !f_out; block++) {
                int32_t rcv_size;
                uint32_t adc_size, din_size;

                /* массив для приема необработанных данных */
                static uint32_t rcv_buf[READ_BLOCK_SIZE];
                static uint32_t snd_buf[READ_BLOCK_SIZE];
                static double   adc_data[READ_BLOCK_SIZE];
                static uint32_t din_data[READ_BLOCK_SIZE];
                
#if 0
                int i;
                for(i=0;i<READ_BLOCK_SIZE;i++) {
                    snd_buf[i] = i + cntr;
                    snd_buf[i] &= ~0xff000000;
                    snd_buf[i] |= 0x02000000;
                }                
                
                int32_t snd_cnt = 0;
                snd_cnt = X502_Send(hnd, snd_buf, READ_BLOCK_SIZE, READ_TIMEOUT);
                cntr += snd_cnt;
                g_snd_cnt += snd_cnt;
                
#endif

                struct timespec cur_time;
                clock_gettime(CLOCK_MONOTONIC, &cur_time);
#if 0
                if (cur_time.tv_sec  - start_time.tv_sec != 0) {
                    printf("snd_cnt=%d speed=%d wrds/sec\n", snd_cnt, (g_snd_cnt) / (cur_time.tv_sec  - start_time.tv_sec));
                    printf("g_snd_cnt=%d secs=%d\n", g_snd_cnt, cur_time.tv_sec  - start_time.tv_sec);
                }
#endif
#if 0
                if (g_snd_cnt > READ_BLOCK_SIZE * 10) {
                    g_snd_cnt=0;
                    clock_gettime(CLOCK_MONOTONIC, &start_time);
                }
#endif
                //continue;
                
                /* принимаем данные (по таймауту) */
                rcv_size = X502_Recv(g_hnd, rcv_buf, READ_BLOCK_SIZE, READ_TIMEOUT);
                //printf("rcv_size=%d\n", rcv_size);
                g_rcv_cnt += rcv_size;
                int error = 0;
                
                if(1) {
                    uint32_t val;
                    static int dout_once = 1;
                    static int once = 1;
                    
                    if(dout_once) {
                        dout_once = 0;
                        X502_FpgaRegRead(g_hnd, E502_REGS_ARM_DAC_ERR, &val);
                        if (val & DIGOUT_ERROR) {
                            uint32_t err_val;
                            uint32_t exp_val;
                            X502_FpgaRegRead(g_hnd, 0x104, &err_val);
                            X502_FpgaRegRead(g_hnd, 0x105, &exp_val);
                            printf("dout error: %x %x expected\n", err_val, exp_val);
                        }
                    }
                    if(once) {
                        X502_FpgaRegRead(g_hnd, E502_REGS_ARM_DAC_ERR, &val);
                        if (val & DAC1_ERROR) {
                            once = 0;
                            uint32_t err_val;
                            uint32_t exp_val;
                            X502_FpgaRegRead(g_hnd, 0x106, &err_val);
                            X502_FpgaRegRead(g_hnd, 0x107, &exp_val);
                            printf("dac1 error: %x %x expected\n", err_val, exp_val);
                        }
                        if (val & DAC2_ERROR) {
                            once = 0;
                            uint32_t err_val;
                            uint32_t exp_val;
                            X502_FpgaRegRead(g_hnd, 0x108, &err_val);
                            X502_FpgaRegRead(g_hnd, 0x109, &exp_val);
                            printf("dac2 error: %x %x expected\n", err_val, exp_val);
                        }
                    }
                }

                if(0){
                    static int dout_once = 1;
                    static int dac1_once = 1;
                    static int dac2_once = 1;
                    uint32_t err_val;
                    uint32_t exp_val;
                    uint32_t next_val;
                    
                    if(dout_once) {
                        X502_FpgaRegRead(g_hnd, 0x120, &err_val);
                        if (err_val) {
                            dout_once = 0;
                            X502_FpgaRegRead(g_hnd, 0x121, &exp_val);
                            X502_FpgaRegRead(g_hnd, 0x122, &next_val);
                            printf("GD32 dout error: %x %x expected\n", err_val & 0xffffff, exp_val & 0xffffff);
                        }
                    }
                    if (dac1_once) {
                        X502_FpgaRegRead(g_hnd, 0x123, &err_val);
                        if (err_val) {
                            dac1_once = 0;
                            X502_FpgaRegRead(g_hnd, 0x124, &exp_val);
                            X502_FpgaRegRead(g_hnd, 0x125, &next_val);
                            printf("GD32 dac1 error: %x %x expected\n", err_val & 0xffff, exp_val & 0xffff);
                        }
                    }
                    if (dac2_once) {
                        X502_FpgaRegRead(g_hnd, 0x126, &err_val);
                        if (err_val) {
                            dac2_once = 0;
                            X502_FpgaRegRead(g_hnd, 0x127, &exp_val);
                            X502_FpgaRegRead(g_hnd, 0x128, &next_val);
                            printf("GD32 dac2 error: %x %x expected\n", err_val & 0xffff, exp_val & 0xffff);
                        }
                    }
                }
                clock_gettime(CLOCK_MONOTONIC, &cur_time);
#if 1
                if (cur_time.tv_sec  - start_time.tv_sec >= 5) {
                    printf("rcv speed=%lld wrds/sec\n", (g_rcv_cnt) / (cur_time.tv_sec  - start_time.tv_sec));
                    start_time.tv_sec = cur_time.tv_sec;
                    g_rcv_cnt = 0;
                }
#else
                if (cur_time.tv_sec  - start_time.tv_sec >= 5) {
                    printf("rcv speed=%lld wrds/sec cntr=%x\n", (g_rcv_cnt) / (cur_time.tv_sec  - start_time.tv_sec), rcv_cntr);
                    start_time.tv_sec = cur_time.tv_sec;
                    g_rcv_cnt = 0;
                }

                for (i = 0; i < rcv_size; i++) {
                    
#if 1
                    t_stream_in_wrd_type type = STREAM_IN_WRD_TYPE(rcv_buf[i]);
                    /* проверяем - это данные от АЦП или цифровых входов */
                    if (type != STREAM_IN_WRD_DIN) {
                        continue;
                    }
                    if ((rcv_buf[i] & 0xFFFF) != (rcv_cntr & 0xFFFF)) 
                    {
                        printf("%x[%i] %x expected\n", rcv_buf[i], i, rcv_cntr);
                        rcv_cntr = rcv_buf[i] & 0xFFFF;
                        error = 1;
                    }
                    rcv_cntr++;
#else

                    // проверка для DCI_TEST_MODE
                    if (rcv_buf[i] != rcv_cntr) {
                        printf("%x[%i] %x expected\n", rcv_buf[i], i, rcv_cntr);
                        rcv_cntr = rcv_buf[i];
                        error = 1;
                    }
                    rcv_cntr++;
                    //rcv_cntr &= ~0xff000000;
                    //rcv_cntr |= 0x02000000;
                    /*if (rcv_cntr >= 0x02000400) {
                        rcv_cntr = 0x02000000;
                    }*/
#endif
                }      
#endif

                continue;

                if(error) {                    
                    break;
                }
                continue;

                /* результат меньше нуля означает ошибку */
                if (rcv_size < 0) {
                    err = rcv_size;
                    fprintf(stderr, "Ошибка приема данных: %s\n", X502_GetErrorString(err));
                } else if (rcv_size > 0) {
                    uint32_t first_lch;
                    /* получаем номер логического канала, которому соответствует первый отсчет АЦП в массиве */
                    X502_GetNextExpectedLchNum(g_hnd, &first_lch);

                    adc_size = sizeof(adc_data)/sizeof(adc_data[0]);
                    din_size = sizeof(din_data)/sizeof(din_data[0]);

                    /* обрабатываем принятые данные, распределяя их на данные АЦП и цифровых входов */
                    err = X502_ProcessData(g_hnd, rcv_buf, rcv_size, X502_PROC_FLAGS_VOLT,
                                           adc_data, &adc_size, din_data, &din_size);
                    if (err != X502_ERR_OK) {
                        fprintf(stderr, "Ошибка обработки данных: %s\n", X502_GetErrorString(err));
                    } else {
                        uint32_t lch;

                        printf("Блок %3d. Обработано данных АЦП =%d, цифровых входов =%d\n",
                               block, adc_size, din_size);
                        /* если приняли цифровые данные - выводим первый отсчет */
                        if (din_size != 0)
                            printf("    din_data = 0x%05X\n", din_data[0]);

                        /* выводим по одному отсчету на канал. если обработанный блок
                           начинается не с начала кадра, то в данном примере для
                           вывода берем конец неполного кадра и начало следующего */
                        for (lch=0; lch < ADC_LCH_CNT; lch++) {
                            /* определяем позицию первого отсчета, соответствующего заданному логическому каналу:
                               либо с конца не полного кадра, либо из начала следующего */
                            uint32_t pos = lch >= first_lch ? lch - first_lch : ADC_LCH_CNT-first_lch + lch;
                            if (pos <= adc_size) {
                                printf("    lch[%d]=%6.4f\n", lch, adc_data[pos]);
                            } else {
                                printf("    lch[%d]= ---- \n", lch);
                            }
                        }
                        printf("\n");
                        fflush(stdout);
                    }
                }
#ifdef _WIN32
                /* проверка нажатия клавиши для выхода */
                if (err == X502_ERR_OK) {
                    if (_kbhit())
                        f_out = 1;
                }
#endif
            }
            
#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
        
#ifdef _WIN32
            //TODO: pthread_join
#else
            pthread_join(threadId, NULL);
#endif
#endif

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

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