// ltr51api.cpp : Defines the entry point for the DLL application.
//

#include "ltr51api.h"
#include "ltrmodule.h"
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include "crc.h"

// Коды команд AVR
#define ALTERA_LOAD                                   (0x80C1)
#define SET_FS                                        (0x80C2)
#define SET_BASE                                      (0x80C3) 
#define SET_THRESHOLD                                 (0x80C4)
#define READ_THRESHOLD                                (0x80C5)
#define SET_EDGE_MODE                                 (0x80C6) 
#define START                                         (0x80C7)
#define STOP                                          (0x80C8)
#define READ_ID_RECORD                                (0x80C9)
#define WRITE_EEPROM                                  (0x80CA) 
#define READ_EEPROM                                   (0x80CB)



#define INIT                                           (0x80CF)
#define CHANNELS_ENA                                   (0x1A) 
#define PARITY_ERROR                                   (0x1B)        
#define TEST_REG_ERROR                                 (0x1C)  
#define TEST_REG_OK                                    (0x1E)
#define ALTERA_LOAD_OK                                 (0x1F)  


// Иные константы
#define STOP_TIMEOUT                                  (5000)
#define CMD_TIMEOUT                                   (2000)
#define BASE_1                                        (5000) 
#define BASE_2                                        (5000)
#define FS                                            (500000)

// Таймауты отправки и получения команд
#define TIMEOUT_CMD_SEND                              (4000)
#define TIMEOUT_CMD_RECIEVE                           (6000)

#define F_OSC  (15000000) // Тактовая частота микроконтроллера AVR 
#define F_REF  ((double)LTR51_REF_FREQ) // Частота дискретизации для формирования частоты выборки сэмплов


#define LTR51_TTF_MAX_STRING_BYTES    64
#define LTR51_TTF_MAX_CHAR_CNT        (LTR51_TTF_MAX_STRING_BYTES*4+10)
#define CONFIG_RECORD_LENGTH           42

/* коэффициент умножения в зависимости от диапазонов парогов */
#define LTR51_K_VALUE(range) (range==LTR51_THRESHOLD_RANGE_10V ? LTR51_K_RANGE_10 : LTR51_K_RANGE_1_2)

/* получение кода потенциометра по значению порога в Вольтах */
#define LTR51_VALUE_TO_CODE(range, val) (int) (((128*(val*LTR51_K_VALUE(range) + LTR51_UREF_VALUE))/LTR51_UREF_VALUE)+0.5)
/* получение значения в Вольтах порога по коду потенциометра */
#define LTR51_CODE_TO_VALUE(range, code)  ((LTR51_UREF_VALUE*(code-128))/(LTR51_K_VALUE(range)*128))


#define LTR51_LCH_GET_THRESHOLD_CODE_H(lch_wrd) ((lch_wrd>>24) & 0xFF)
#define LTR51_LCH_GET_THRESHOLD_CODE_L(lch_wrd) ((lch_wrd>>16) & 0xFF)
#define LTR51_LCH_GET_PHY_CH(lch_wrd) (lch_wrd & 0xF)
#define LTR51_LCH_GET_EDGE(lch_wrd) ((lch_wrd>>8) & 1)




#define LTR51_DATAWRD_GET_CNTR(wrd) ((wrd & 0x000000E0)>>5)

#ifdef _WIN32
/* хендл библиотеки для загрузки ресурсов */
static HINSTANCE hInstance;
#endif

static int f_read_config(PTLTR51 hnd, CHAR *version, CHAR *date, CHAR *name, CHAR *serial, CHAR *FPGA_version);

static INT f_check_resp(DWORD ack, DWORD cmd) {
    INT err = ltr_module_check_parity(ack);

    if (err==LTR_OK) {
        if(LTR_MODULE_CMD_GET_CMDCODE(ack)==PARITY_ERROR)
            err = LTR_ERROR_INVALID_CMD_PARITY;
        else if (LTR_MODULE_CMD_GET_CMDCODE(ack)!=LTR_MODULE_CMD_GET_CMDCODE(cmd))
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
    }
    return err;
}

static INT f_send_cmds_with_resp(PTLTR51 hnd, DWORD* cmds, DWORD* resps, DWORD cmd_cnt, DWORD ack_cnt) {
    INT err;
    err = ltr_module_send_cmd(&hnd->Channel, cmds, cmd_cnt);
    if (err==LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, resps, ack_cnt);
    }
    if (err==LTR_OK) {
        DWORD i;
        for (i=0; (i<ack_cnt) && (err==LTR_OK); i++) {
            err = f_check_resp(cmds[i < cmd_cnt ? i : cmd_cnt-1], resps[i]);
        }
    }
    return err;
}

static void f_info_init(TLTR51 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTR51");
}


/****************************************************************************************************************

Функция заполнения структуры описания модуля значениями по умолчанию и инициализации интерфейсного канала

****************************************************************************************************************/

// Выполняет инициализацию полей структуры описания модуля и канала связи с модулем   //
// ПАРАМЕТРЫ:
// hnd - указатель на структуру описания модуля                                      //
LTR51API_DllExport (INT) LTR51_Init(PTLTR51 hnd) {
    INT err;
    int i;
    double HighThreshold, LowThreshold;
    int ThresholdRange;

    err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (err==LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));
        hnd->size=sizeof(TLTR51);
        hnd->SetUserPars=0;
        hnd->Fs=500000;
        hnd->Base=70;
        hnd->LChQnt=16;

        /* Значения порогов по умолчанию */
        HighThreshold=0.8;
        LowThreshold=-0.8;
        ThresholdRange=1;

        /*По умолчанию создаем 16 логических каналов */

        for(i=0;i<16;i++)
            LTR51_CreateLChannel(i+1, &HighThreshold, &LowThreshold, ThresholdRange, 0);


        err=LTR_Init(&hnd->Channel);  // Инициализируем интерефейсный канал

        f_info_init(hnd);
    }

    return err;
}

/*******************************************************************************
   Функция создания и открытия канала связи с LTR51. Также RESET модуля

*******************************************************************************/

// Выполняет открытие канала связи с модулем, сброс микроконтроллера AVR модуля и загрузку ПЛИСа   //
//                                                                                                 //
// ПАРАМЕТРЫ:                                                                                       //
// hnd - указатель на структуру описания модуля                                                      //
// net_addr - сетевой адрес крейта                                                                 //
// net_port - номер порта                                                                          //
// *crate_sn - строка с серийным                                                                    //
// slot_num - номер посадочного места в крейте, где расположен данный модуль                       //
// *ttf_name - строка с именем файла, в котром расположена прошивка ПЛИСа                          //
//                                                                                                 //
// ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ - код ошибки                                                              //

LTR51API_DllExport (INT) LTR51_Open(PTLTR51 hnd, DWORD net_addr, WORD net_port,
                                    const CHAR *crate_sn, INT slot_num, const CHAR *ttf_name) {
    INT warning;
    DWORD flags=0;
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK) {
        err = ltr_module_open(&hnd->Channel, net_addr, net_port, crate_sn, slot_num,
                             LTR_MID_LTR51, &flags, NULL, &warning);
        if (err==LTR_OK) {
            f_info_init(hnd);
        }
    }

    if ((err==LTR_OK) && !(flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        DWORD cmd;
        FILE *f = NULL;

        /* ждем таймаут, пока завершится автокалибровка АЦП, так как
            в этот период все команды игнорируются*/
        LTRAPI_SLEEP_MS(500);
        // Посылаем первую команду INSTR INIT для инициализации
        cmd = ltr_module_fill_cmd_parity(INIT,0);
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);

        if (err==LTR_OK) {
#ifdef _WIN32
            const char* res_data = NULL;
            DWORD total_size = 0;
#endif

            /* при нулевом значении пути, берем файл из места по-умолчанию */
            if ((ttf_name == NULL) || (ttf_name[0]=='\0') || (ttf_name[0]=='\n') || (ttf_name[0]=='\r')) {
#ifndef _WIN32
                /* для ОС, отличных от Windows, используем файл из места, указанного
                    как путь установки при сборке */
                ttf_name = LTRMODULE_INSTALL_DATA_FILE;
#else
                /* Для Windows загружаем файл из ресурса библиотеки */
                HRSRC res_info = FindResource(hInstance, "LTR51_TTF", RT_RCDATA);
                HGLOBAL rhnd = NULL;
                if (res_info!=NULL) {
                    rhnd = LoadResource(hInstance, res_info);
                    if (rhnd!=NULL) {
                        res_data = LockResource(rhnd);
                        if (res_data)
                            total_size = SizeofResource(hInstance, res_info);
                    }
                }

                if (res_data==NULL)
                    err = LTR_ERROR_FIRM_FILE_OPEN;
#endif
            }
            /* если данные из файла, то открываем файл и определяем размер */
            if ((err == LTR_OK)
        #ifdef _WIN32
                    && (res_data==NULL)
        #endif
                    ) {
                f=fopen(ttf_name, "r");
                if(f==NULL) {
                    err=LTR_ERROR_FIRM_FILE_OPEN;
                }
            }

            if (err == LTR_OK) {
                int done = 0;
                char  *str  = malloc(LTR51_TTF_MAX_CHAR_CNT);
                DWORD *cmds = malloc(LTR51_TTF_MAX_STRING_BYTES*sizeof(cmds[0]));

                if ((str==NULL) || (cmds==NULL))
                    err = LTR_ERROR_MEMORY_ALLOC;


                while (!done && (err==LTR_OK))  {
                    BOOL proc_str;
#ifdef _WIN32
                    /* копирование одной строки при работе из ресурса */
                    if (res_data!=NULL) {
                        unsigned put_pos, out;


                        for (put_pos=0, out=0; (put_pos < (LTR51_TTF_MAX_CHAR_CNT-1)) && (total_size!=0) && !out;
                             total_size--, res_data++) {
                            if ((*res_data=='\n') || (*res_data=='\r')) {
                                if (put_pos!=0)
                                    out = 1;
                            } else {
                                str[put_pos++] = *res_data;
                            }
                        }
                        str[put_pos] = '\0';
                        proc_str = put_pos!=0;
                        done = total_size==0;
                    } else {
#endif
                        proc_str = fgets(str,LTR51_TTF_MAX_CHAR_CNT,f)!=NULL;
                        done =  feof(f);
#ifdef _WIN32
                    }
#endif
                    /* считываем ttf-файл построчно */
                    if (proc_str) {
                        char *curpos = str;
                        BYTE b1=0, b2=0;
                        int strend = 0, wrds_cnt=0;

                        /* строка - набор байтов, разделенных запятыми. Каждый байт
                         * всегда кодируется тремя символами */
                        while (!strend) {
                            /* считываем по 2 байта и формируем соответствующую
                             * команду для модуля */
                            b1 = atoi(curpos);
                            curpos+=3;
                            if (*curpos==',') {
                                b2 = atoi(++curpos);
                                curpos+=3;
                            }

                            if (*curpos!=',') {
                                strend = 1;
                            }
                            curpos++;

                            if ((*curpos=='\n') || (*curpos=='\r') || (*curpos=='\0'))
                                strend = 1;
                            cmds[wrds_cnt++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(ALTERA_LOAD, b1, b2);
                        }

                         if (err==LTR_OK) {
                             err = ltr_module_send_cmd(&hnd->Channel, cmds, wrds_cnt);
                         }
                    }
                }

                /* файл больше не нужен => закрываем его */
                if (f!=NULL)
                    fclose(f);

                free(str);
                free(cmds);
            }
        }

        if (err==LTR_OK) {
            /* принимаем ответ из 3-х слов */
            DWORD ack[3];
            err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));
            /* проверяем первый ответ - подтверждение загрузки */
            if (!err)
                err = f_check_resp(ack[0], ALTERA_LOAD);

            /* второй ответ - результат теста регистра */
            if(!err && ((ack[1]&0x0000001F)==TEST_REG_ERROR))
                err=LTR51_ERR_ALTERA_TEST_FAILED;

            if(!err && ((ack[1]&0x0000001F)!=TEST_REG_OK))
                err=LTR_ERROR_INVALID_CMD_RESPONSE;

            /* третий овет - маска резрешенных каналов */
            if(!err &&  ((ack[2]&0x0000001F)!=CHANNELS_ENA))
                err=LTR_ERROR_INVALID_CMD_RESPONSE;

            if (!err)
                hnd->ChannelsEna=ack[2]>>16;
        }

        if (!err) {
            err = f_read_config(hnd, hnd->ModuleInfo.FirmwareVersion,
                                     hnd->ModuleInfo.FirmwareDate, hnd->ModuleInfo.Name,
                                     hnd->ModuleInfo.Serial, hnd->ModuleInfo.FPGA_Version);
        }
    }
    return err==LTR_OK ? warning : err;
}


/*******************************************************************************
    Функция проверки: открыт ли модуль
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_IsOpened(PTLTR51 hnd) {
    return hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

/*******************************************************************************
    Функция окончания работы с модулем
*******************************************************************************/

LTR51API_DllExport (INT) LTR51_Close(PTLTR51 hnd) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK) {
        INT stop_err;
        //err = LTR43_StopSecondMark(hnd);
        stop_err=LTR_Close(&hnd->Channel);
        if (err==LTR_OK)
            err = stop_err;
    }
    return err;
}


/*******************************************************************************
   Функция создания логического канала
   Выполняет формирование слова для таблицы логических каналов

   ПАРАМЕТРЫ:
   PhysChannel - номер физического канала (1-16)
   HighThreshold - значение верхнего порога в вольтах
   LowThreshold - значение нижнего порога в вольтах
   EdgeMode - режим выделения активных перепадов
*******************************************************************************/
LTR51API_DllExport (DWORD) LTR51_CreateLChannel(INT PhysChannel, double *HighThreshold,
                                                double *LowThreshold, INT ThresholdRange,
                                                INT EdgeMode) {
    return LTR51_CreateLChannelEx(PhysChannel, HighThreshold, LowThreshold,
                                  ThresholdRange, EdgeMode, 0);
}


LTR51API_DllExport (DWORD) LTR51_CreateLChannelEx(INT PhysChannel, double *HighThreshold,
                                                  double *LowThreshold, INT ThresholdRange,
                                                  INT EdgeMode, INT FreqRange) {
    /* Формат логического канала
        24-31 - верхний порог потенциометра
        16-23 - нижний порог потенциометра
        14-15 - диапазон порогов (имеют значения только для LTR51M)
        12-13 - диапазон частот (имеют заначения только для LTR51M)
        8     - режим выделения активных перепадов для данного канала (0-по фронту, 1-по спаду)
        0-7   - номер физического канала с 0 */
    DWORD ChannelWord;
    int HighThresholdCode, LowThresholdCode;

    /* рассчитываем коды порогов по значению напряжения */
    HighThresholdCode=LTR51_VALUE_TO_CODE(ThresholdRange, *HighThreshold);
    LowThresholdCode =LTR51_VALUE_TO_CODE(ThresholdRange, *LowThreshold);

    /* корректируем выходы за диапазон */
    if((HighThresholdCode>255)||(HighThresholdCode<0))
        HighThresholdCode=0;

    if((LowThresholdCode>255)||(LowThresholdCode<0))
        LowThresholdCode=255;

    ChannelWord = ((HighThresholdCode&0xFF)<<24) |
                  ((LowThresholdCode&0xFF)<<16) |
                  ((EdgeMode&0x1)<<8) |
                  ((ThresholdRange&0x3) << 14) | ((FreqRange&0x3) << 12) |
                  ((PhysChannel-1) & 0xFF);


    *HighThreshold=LTR51_CODE_TO_VALUE(ThresholdRange, HighThresholdCode);
    *LowThreshold=LTR51_CODE_TO_VALUE(ThresholdRange, LowThresholdCode);

    return ChannelWord;
}

/*******************************************************************************
    Функция получения откорректированных значений напряжения порогов после
    их пересчета

   Выполняет передачу в модуль параметров сбора данных: частоту сбора Fs,
    значение счетчика Base

   ПАРАМЕТРЫ:
    hnd - указатель на структуру описания модуля
    PhysChannel - номер физического канала (0-16)
    HighThreshold - значение верхнего порога в вольтах
    LowThreshold - значение нижнего порога в вольтах
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_GetThresholdVals(PTLTR51 hnd, INT LChNumber,
                double *HighThreshold, double *LowThreshold, INT ThresholdRange) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK) {
        if (HighThreshold!=NULL)
            *HighThreshold=LTR51_CODE_TO_VALUE(ThresholdRange, LTR51_LCH_GET_THRESHOLD_CODE_H(hnd->LChTbl[LChNumber]));
        if (LowThreshold!=NULL)
            *LowThreshold=LTR51_CODE_TO_VALUE(ThresholdRange, LTR51_LCH_GET_THRESHOLD_CODE_L(hnd->LChTbl[LChNumber]));
    }
    return err;
}


/*******************************************************************************
   Функция конфигурирования и задания режима сбора данных

   Выполняет передачу в модуль параметров сбора данных: частоту выборки сэмплов
     Fs, значение счетчика Base     //

   ПАРАМЕТР:
    hnd - указатель на структуру описания модуля
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_Config(PTLTR51 hnd) {
    DWORD FsDiv;  // Делитель частоты дискретизации
    INT ch;
    INT err = LTR51_IsOpened(hnd);

    for(ch=0; !err && (ch<hnd->LChQnt);ch++) {
        /* Проверяем, что верхний порог выше чем нижний.
         * Так как множитель пересчета отрицательный, то меньший код соотвествует
         * большему напряжению */
        if (LTR51_LCH_GET_THRESHOLD_CODE_H(hnd->LChTbl[ch]) > LTR51_LCH_GET_THRESHOLD_CODE_L(hnd->LChTbl[ch]))
            err = LTR51_ERR_WRONG_THRESHOLD_VALUES;
    }

    if (err==LTR_OK) {
        if(!hnd->SetUserPars) {
            FsDiv=(DWORD) ((F_REF/FS)+0.5);

            hnd->Fs=FS;
            hnd->Base=BASE_1;
        } else {

            FsDiv=(DWORD) ((F_REF/hnd->Fs)+0.5);

            hnd->Fs=F_REF/FsDiv;

            if ((FsDiv < LTR51_FSDIV_MIN) || (FsDiv > LTR51_FSDIV_MAX))
                err = LTR51_ERR_WRONG_FS_SETTINGS;

            if((err==LTR_OK) && (hnd->Base < LTR51_BASE_VAL_MIN))
                err = LTR51_ERR_WRONG_BASE_SETTINGS;
        }
    }

    if (err == LTR_OK) {
        double tbase = (((double) hnd->AcqTime/1000.0)*F_REF)/(FsDiv*hnd->Base);
        hnd->TbaseQnt=(int)(tbase+0.5);
        if(hnd->TbaseQnt<2)
            hnd->TbaseQnt = 2;

        hnd->AcqTime=(int) (((double)hnd->TbaseQnt*FsDiv*hnd->Base/F_REF)*1000+0.5);
        hnd->F_Base=hnd->Fs/hnd->Base;
    }

    if (err==LTR_OK) {
        DWORD cmds[32];
        DWORD acks[32];
        WORD EdgeModeMask=0;

        int pos = 0;



        /* Важно!! Сперва нужно всегда отправлять 3 слова с настройками, после
         * чего дожитаться ответа. Затем можно передавать слова с настройками
         * логических каналов. Все сразу передавать нельзя - не работает для
         * 16 каналов */

        // Сначала отправляем делитель Fs
        cmds[pos++]=ltr_module_fill_cmd_parity(SET_FS, FsDiv-1);
        // Затем отправляем Base
        cmds[pos++]=ltr_module_fill_cmd_parity(SET_BASE, hnd->Base);

        for(ch=0;ch<hnd->LChQnt;ch++)
            EdgeModeMask|=LTR51_LCH_GET_EDGE(hnd->LChTbl[ch])<<(LTR51_LCH_GET_PHY_CH(hnd->LChTbl[ch]));

        // Затем устанавливаем режим выделения активных перепадов
        cmds[pos++]=ltr_module_fill_cmd_parity(SET_EDGE_MODE, EdgeModeMask);

        err = f_send_cmds_with_resp(hnd, cmds, acks, pos, pos);

        if (err == LTR_OK) {
            for(pos=ch=0; ch<hnd->LChQnt; ch++) {
                cmds[pos++]=LTR_MODULE_FILL_CMD_PARITY_BYTES(SET_THRESHOLD,
                                                       LTR51_LCH_GET_THRESHOLD_CODE_H(hnd->LChTbl[ch]),
                                                       (1<<4) | LTR51_LCH_GET_PHY_CH(hnd->LChTbl[ch]));
                cmds[pos++]=LTR_MODULE_FILL_CMD_PARITY_BYTES(SET_THRESHOLD,
                                                       LTR51_LCH_GET_THRESHOLD_CODE_L(hnd->LChTbl[ch]),
                                                       (0<<4) | LTR51_LCH_GET_PHY_CH(hnd->LChTbl[ch]));
            }
            err = f_send_cmds_with_resp(hnd, cmds, acks, pos, pos);
        }
    }

    return err;
}



/*******************************************************************************
    Функция старта сбора данных

    Выполняет старт сбора данных
    ПАРАМЕТР - hnd, указатель на структуру описания модуля
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_Start(PTLTR51 hnd) {
    INT err = LTR51_IsOpened(hnd);

    if (err==LTR_OK) {
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(START, 0);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    }
    return err;
}

/*******************************************************************************
    Функция останова сбора данных
    Выполняет останов сбора данных
    Параметр - hnd, указатель на структуру описания модуля
*******************************************************************************/

LTR51API_DllExport (INT) LTR51_Stop(PTLTR51 hnd) {
    INT err = LTR51_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmd;
        cmd = ltr_module_fill_cmd_parity(STOP, 0);
        err = ltr_module_stop(&hnd->Channel, &cmd, 1, cmd, LTR_MSTOP_FLAGS_CHECK_PARITY, 0, NULL);
    }
    return err;
}


/*******************************************************************************
    Функция получения массива входных данных при непрерывном вводе

ПАРАМЕТРЫ:
    hnd - указатель на структуру описания модуля
    *data - массив входных данных
    *tmark - массив с метками
    size - кол-во получаемых данных
    timeout - время ожидания получения данных в мс
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_Recv(PTLTR51 hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    int res = LTR51_IsOpened(hnd);
    if (res==LTR_OK)
        res=LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
    if ((res>=0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF))
        res=LTR_ERROR_RECV_OVERFLOW;
    return res;
} 



/*******************************************************************************
    Функция обработки массива входных данных и расчета средней частоты

Выполняет обработку исходного массива, считанного из КК и подсчет средней частоты за период измерения
ПАРАМЕТРЫ:
    hnd - указатель на структуру описания модуля
    *src - указатель на исходный массив
    *dest - указатель на выходной массив со значениями N и M
    *Frequency - указатель на массив с выходными значениями средней частоты за период измерения для всех лог. каналов
    *size - на входе функции - размер исходного массива src. На выходе - размер выходного dest.
********************************************************************************/
LTR51API_DllExport (INT) LTR51_ProcessData(PTLTR51 hnd, const DWORD *src, DWORD *dest,
                                           double *Frequency, DWORD *size) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                          (src==NULL) || (size==NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;
    INT dest_qnt = 0;
    DWORD src_size = 0;


    if (err==LTR_OK) {
        src_size = *size;
        if ((src_size%32) != 0)
            err = LTR51_ERR_WRONG_SRC_SIZE;
    }

    if (err==LTR_OK) {
        BYTE WordsCntr;
        DWORD i;

        /* проверка счетчика в принятом массиве */
        for(i=0, WordsCntr=LTR51_DATAWRD_GET_CNTR(src[0]);
            (i<src_size) && (err==LTR_OK);i++) {

            if(WordsCntr!=LTR51_DATAWRD_GET_CNTR(src[i]))
                err = LTR51_WRONG_DATA;

            if (err==LTR_OK) {
                WordsCntr++;
                if(WordsCntr>7)
                    WordsCntr=0;
            }
        }

        if(err == LTR_OK) {
            DWORD Tp_Qnt;

            dest_qnt=0;

            // Количество интервалов измерения равно size/32
            Tp_Qnt=(int) src_size/32;

            for(i=0; (i<(DWORD)hnd->LChQnt) && (err==LTR_OK);i++) {
                DWORD N=0;
                DWORD M1=0;
                DWORD Mk=0;
                DWORD M=0;
                DWORD Nm=0;
                DWORD Empty_Tp_Cntr=0;
                BOOL Empty_Tp_Flag=TRUE;
                DWORD PhysChannel=LTR51_LCH_GET_PHY_CH(hnd->LChTbl[i]);
                /* положение второго слова, соответствующего лог. каналу, в принятом кадре */
                INT ch_wrd_frame_pos=2*(15-PhysChannel)+1;
                DWORD Tp_Cntr;

                for(Tp_Cntr=0/*1*/; (Tp_Cntr<Tp_Qnt) && (err==LTR_OK); Tp_Cntr++) {
                    INT CurSampleIndex=32*Tp_Cntr+ch_wrd_frame_pos;

                    /* Сначала проверим правильность номера каналов */
                    if((src[CurSampleIndex]&0xF)!=(DWORD)PhysChannel) {
                        err = LTR51_WRONG_DATA;
                    } else if(((src[CurSampleIndex]&0x10)>>4)!=1) { /* Однобитный счтечик слов в паре */

                        err = LTR51_WRONG_DATA;
                    } else {
                        // Количество активных перепадов в текущем периоде измерения
                        Nm=((src[CurSampleIndex]&0xFFFF0000)>>16);
                        M=((src[CurSampleIndex-1]&0xFFFF0000)>>16);

                        if (dest!=NULL) {
                            /* Располагаем элементы в соответствии с номерами ЛОГИЧЕСКИХ каналов */
                            dest[hnd->LChQnt*Tp_Cntr+i] = (Nm<<16) | M;
                            dest_qnt++;
                        }

                        // Подсчитываем количество "пустых" периодов
                        if(Empty_Tp_Flag) {
                            if(Nm==0) {
                                Empty_Tp_Cntr++;
                            } else {
                                /* Как только встретился первый "непустой" период,
                                    запоминаем M1 и сбрасываем флаг отсутствия перепадов */
                                M1=M;
                                Empty_Tp_Flag=FALSE;
                                /* Мы не должны учитывать N первого периода изм, содержащего фронты! */
                            }
                        } else {
                            /* Если период "непустой", то просто подсчитываем N */
                            N+=Nm;
                        }

                        /* суммируем все M с последнего периода где был N не нулевым */
                        if (Nm==0) {
                            Mk+=M;
                        } else {
                            Mk = M;
                        }
                    }
                } // к for(Tp_Cntr...)

                /* расчет частоты по формуле F=Fref*Summa(Ni<i=2..k>)/(M1+BASE*(k-1)-Mk) с учетом частных случаев */
                if (Frequency!=NULL) {
                    /* В этом случае не было ни одного активного перепада */
                    if(Empty_Tp_Cntr==Tp_Qnt) {
                        Frequency[i]=0.0;
                    } else {
                        INT denominator=M1+ ((hnd->Base)*(Tp_Qnt-(Empty_Tp_Cntr+1))) - Mk;

                        if(denominator==0) {
                            Frequency[i]=0;
                        } else {
                            Frequency[i]=hnd->Fs*N/denominator;
                        }
                    }
                }
            }
        }   // к if dest!=NULL
    }

    if ((size!=NULL) && (dest!=NULL))
        *size=dest_qnt;

    return err;
}




/**********************************************************************************************
    Функция записи байта в указанную ячейку ППЗУ
************************************************************************************************/    
LTR51API_DllExport (INT) LTR51_WriteEEPROM(PTLTR51 hnd, INT Address, BYTE val)  {
    INT err = LTR51_IsOpened(hnd);
    if ((err==LTR_OK) && ((Address<0)||(Address)>=512))
        err =  LTR51_ERR_CANT_READ_EEPROM;
    if (err==LTR_OK) {
        DWORD cmds[2], ack;
        cmds[0] = ltr_module_fill_cmd_parity(WRITE_EEPROM, Address);
        cmds[1] = ltr_module_fill_cmd_parity(WRITE_EEPROM, val);
        err = f_send_cmds_with_resp(hnd, cmds, &ack, 2, 1);
    }
    return err;
}


/*******************************************************************************
    Функция чтения байта из указанной ячейки ППЗУ
*******************************************************************************/
LTR51API_DllExport (INT) LTR51_ReadEEPROM(PTLTR51 hnd, INT Address, BYTE *val)  {
    INT err = LTR51_IsOpened(hnd);
    if ((err==LTR_OK) && ((Address<0)||(Address)>=512))
        err =  LTR51_ERR_CANT_READ_EEPROM;
    if (err==LTR_OK) {
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(READ_EEPROM, Address);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
        if (err==LTR_OK)
            *val=(ack>>16)&0xFF;
    }
    return err;
}


/*******************************************************************************
    Функция расчета таймаута при получении данных
*******************************************************************************/
LTR51API_DllExport (DWORD) LTR51_CalcTimeOut(PTLTR51 hnd, int n) {
    double Fs;
    WORD Base;

    if(!hnd->SetUserPars) {
        Fs=FS;
        Base=BASE_1;
    } else {
        Fs=hnd->Fs;
        Base=hnd->Base;
    }

    return (DWORD) (1000.*n*Base/Fs)+2000; // Переводим в миллисекунды
}


/*******************************************************************************
    Функция чтения конфигурационной записи модуля
*******************************************************************************/
static int f_read_config(PTLTR51 hnd, CHAR *version, CHAR *date, CHAR *name,
                         CHAR *serial, CHAR *FPGA_version) {
    BYTE ver[2];
    BYTE conf_rec[CONFIG_RECORD_LENGTH];
    BYTE FPGA_ver;
    INT err = LTR51_IsOpened(hnd);

    if (err==LTR_OK) {
        DWORD command;
        command = ltr_module_fill_cmd_parity(READ_ID_RECORD, 0);
        err = ltr_module_send_cmd(&hnd->Channel, &command, 1);
        if (err==LTR_OK) {
            DWORD ack[CONFIG_RECORD_LENGTH + 2], i;
            WORD CR_CRC;      // Контр. сумма конф. записи, посчитанная тут
            WORD CR_CRC_RCV;  // Контр. сумма конф. записи, считанная из Flash

            err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[1]));

            for (i=0; (i < sizeof(ack)/sizeof(ack[0])) && (err==LTR_OK); i++) {
                err = f_check_resp(ack[i], command);
                if (i < CONFIG_RECORD_LENGTH)
                    conf_rec[i]=(BYTE)((ack[i]>>16)&0xFF);
            }

            /* сравниваем CRC */
            CR_CRC = eval_crc16(0, conf_rec, CONFIG_RECORD_LENGTH);
            CR_CRC_RCV=(ack[CONFIG_RECORD_LENGTH]>>16)&0xFFFF;
            if(CR_CRC!=CR_CRC_RCV)
                err = LTR51_ERR_WRONG_ID_REC;

            if (!err)
                FPGA_ver = (ack[CONFIG_RECORD_LENGTH+1]>>16)&0xFF;
        }
    }

    if (err==LTR_OK) {
        WORD i;

        if(conf_rec[0]!=(LTR_MID_LTR51&0xFF))
            err = LTR51_ERR_WRONG_ID_REC;

        for(i=0;i<2;i++)
            ver[i]=conf_rec[1+i]; // Версия БИОСа
        sprintf(version, "%d.%d",ver[0], ver[1]);

        memcpy(date, &conf_rec[3], 14); // Дата БИОСа
        memcpy(name, &conf_rec[17], 8); // Имя модуля
        memcpy(serial, &conf_rec[25], 17); // Серийный номер

        sprintf(FPGA_version, "%d",FPGA_ver); // Версия прошивки ПЛИСа
    }
    return err;
}




static const int f_min_range_id = -5000;
static const LPCSTR f_err_str[] = {
    "LTR51:Неверная структура описания модуля!",
    "LTR51:Невозможно открыть модуль!",
    "LTR51:Невозможно загрузить ПЛИС!",
    "LTR51:Неверный серийный номер крейта!",
    "LTR51:Неверный номер посадочного места!",
    "LTR51:Невозможно отправить команду!",
    "LTR51:Невозможно выполнить сброс модуля!",
    "LTR51:Модуль не отвечает!",
    "LTR51:Невозможно открыть модуль!",
    "LTR51:Ошибка четности при отправке команды в модуль!",
    "LTR51:Ошибка четности при получении ответной команды из модуля!",
    "LTR51:Тест ПЛИС завершился с ошибкой!",
    "LTR51:Невозможно запустить сбор данных!",
    "LTR51:Невозможно остановить сбор данных!",
    "LTR51:Невозможно установить делитель опорной частоты!",
    "LTR51:Невозможно установить значение счетчика BASE!",
    "LTR51:Невозможно установить режим выделения активных перепадов!",
    "LTR51:Невозможно установить порог напряжения!",
    "LTR51:Неверные данные от модуля! Нарушена целостность последовательности данных!",
    "LTR51:Неверное значение напряжения верхнего порога!",
    "LTR51:Неверное значение напряжения нижнего порога!",
    "LTR51:Невозможно открыть файл с прошивкой ПЛИС! Проверьте полный путь файла!",
    "LTR51:Невозможно считать конфигурационную запись!",
    "LTR51:Неверная конфигурационная запись!",
    "LTR51:Недопустимое значение делителя частоты выборки сэмплов Fs!",
    "LTR51:Недопустимое значение счетчика Base!",
    "LTR51:Невозможно выполнить запись в ППЗУ!",
    "LTR51:Невозможно выполнить чтение из ППЗУ!",
    "LTR51:Неверный адрес ППЗУ!",
    "LTR51:Напряжение верхнего порога должно быть выше напряжения нижнего!",
    "LTR51:Переполнение входного буфера крейт-контроллера!",
    "LTR51:Неверные установки интервала измерения частоты!",
    "LTR51:Интервал измерения должен содержать хотя бы два периода измерений!",
    "LTR51:Количество точек во входном массиве для расчета частоты должно быть кратно 32!"
};

/*****************************************************************************************************

Функция определения строки ошибки

******************************************************************************************************/

LTR51API_DllExport (LPCSTR) LTR51_GetErrorString(int Error_Code) {
    // Определяет строку ошибок по ее коду
    // ПАРАМЕТРЫ:
    // ErrorCode - код ошибки
    // ВЫХОДНОЕ ЗНАЧЕНИЕ - строка сообщения об ошибке

    LPCSTR ret_val = NULL;
    int ret_val_index;
    int error_range;

    error_range=-f_min_range_id + 1+(int)(sizeof f_err_str / sizeof f_err_str[0]);

    if ((-Error_Code < -f_min_range_id )|| (Error_Code < -error_range)) {
        ret_val = LTR_GetErrorString(Error_Code);
    } else {
        ret_val_index=-Error_Code + f_min_range_id-1;
        ret_val = f_err_str[ret_val_index];
    }

    return ret_val;
}

#ifdef _WIN32
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpvReserved) {
    if(fdwReason == DLL_PROCESS_ATTACH) {
        // сохраняем хендл библиотеки для загрузки ресурсов
        hInstance = hInst;
    }
    return TRUE;
}

int __stdcall DllEntryPoint (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    // Included for compatibility with Borland

    return 1;
}
#endif


LTR51API_DllExport(INT) LTR51_GetMezzaninesInfo(PTLTR51 hnd, TLTR51_MEZZANINE_INFO *descr) {
    INT err = descr == NULL ? LTR_ERROR_PARAMETERS : LTR51_IsOpened(hnd);
    if (err==LTR_OK) {
        unsigned i;
        memset(descr, 0, LTR51_MEZZANINE_CNT*sizeof(TLTR51_MEZZANINE_INFO));
        for (i=0; i < LTR51_MEZZANINE_CNT; i++) {
            if (hnd->ModuleInfo.Modification == LTR51_MOD_OLD) {
                if (((hnd->ChannelsEna >> 2*i) & 0x3) == 0x3) {
                    strcpy(descr[i].Name, "H-51FL/H-51FH");
                }
            } else {
                /** @todo */
            }
        }
    }
    return err;
}
