#include "ltr210api.h"
#include "ltrmodule.h"
#include "ltrmodule_cyclone.h"
#include "ltimer.h"
#include "flash.h"
#include "devices/flash_dev_at25df.h"
#include "devices/x25/flash_dev_at25sf.h"
#include "devices/x25/flash_dev_p25q40sh.h"
#include "ports/ltr/flash_iface_ltr.h"

#include "crc.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>


/* коды команд ПЛИС */
#define CMD_SIZE_L          (LTR010CMD_INSTR | 0x24)
#define CMD_SIZE_H          (LTR010CMD_INSTR | 0x25)
#define CMD_SIZE_HIST_L     (LTR010CMD_INSTR | 0x26)
#define CMD_SIZE_HIST_H     (LTR010CMD_INSTR | 0x27)
#define CMD_RD_MODE         (LTR010CMD_INSTR | 0x28)
#define CMD_SYNC_LEVEL      (LTR010CMD_INSTR | 0x29)
#define CMD_WR_CONTROL      (LTR010CMD_INSTR | 0x2A)
#define CMD_ADC_MODE        (LTR010CMD_INSTR | 0x2B)
#define CMD_ANALOG_CTL      (LTR010CMD_INSTR | 0x2C)
#define CMD_CBR_MEMORY      (LTR010CMD_INSTR | 0x2D)
#define CMD_PROGRAM_SYNC    (LTR010CMD_INSTR | 0x2F)
#define CMD_FPS0            (LTR010CMD_INSTR | 0x30)
#define CMD_FPS1            (LTR010CMD_INSTR | 0x31)
#define CMD_FPGA_VER        (LTR010CMD_INSTR | 0x32)
#define CMD_SET_STATUS_MODE (LTR010CMD_INSTR | 0x33)
#define CMD_DIG_BIT_MODE    (LTR010CMD_INSTR | 0x34)


#define CMD_RESP_FPGA       (LTR010CMD_INSTR | 0x32)
#define CMD_RESP_KEEPALIVE  (LTR010CMD_INSTR | 0x33)

#define CMD_RESP_FRAME_STATUS (LTR010CMD | 0)


#define CMDBIT_MODE_EDGE    (1UL<<7)
//#define CMDBIT_MODE_FAST    (1UL<<15)
#define CMDBIT_DATA_FIRST   (1UL<<4)
#define CMDBIT_TRANSF_RATE_POS  13

#define CMDBIT_WRCTL_AUTOSUSP  (1UL << 2)

#define LTR210_DATA_CNTR_MOD 7

#define CHECK_PLL_LOCK_FIRST(resp) (((resp>>16)&1) ? LTR_OK : LTR210_ERR_PLL_NOT_LOCKED)
#define CHECK_PLL_LOCK(resp)  (((resp>>16)&3) == 3 ? LTR_OK : LTR210_ERR_PLL_NOT_LOCKED)

#define LTR210_IS_CONTINUOUS(cfg) ((cfg.SyncMode==LTR210_SYNC_MODE_CONTINUOUS) && (cfg.GroupMode!=LTR210_GROUP_MODE_SLAVE))

#define CMD_CODE_MSK 0xF0FF

#define ADC_FREQ(div, dcm) (((double)LTR210_ADC_FREQ_HZ)/((div+1)*(dcm+1)))

#ifndef M_PI
    #define M_PI 3.1415926535897932384626433832795
#endif

#define _MSK_CH(ch)  (1 << ch)
#define _MSK_CH_ALL  (_MSK_CH(0) | _MSK_CH(1))


typedef struct {
    t_flash_iface  flash;
    TLTR210_CONFIG Cfg; /* настройки, которые в действительности записаны
                            в модуль. могут отличаться при изменении на лету */
    DWORD last_word;     /* последнее принятое слово */
    DWORD last_tmark;    /* метка времени, соответствующая последнему принятому слову */
    BYTE last_word_valid;     /* признак, что в last_word сохранено последнее слово */
    BYTE fpga_loaded;    /* признак, загружен ли ПЛИС */
    BYTE slow_crate;     /* признак, что используется медленный крейт (LTR-U-1),
                            и скорость передачи нужно ограничить */
    BYTE cntr_valid;

    double afc_k[LTR210_CHANNEL_CNT][LTR210_RANGE_CNT]; /* рассчитанный коэффициент для коррекции АЧХ */
    double afc_iir_k[LTR210_CHANNEL_CNT][LTR210_AFC_IIR_COR_RANGE_CNT][3]; /* коэффициенты для нелинейной коррекции */

    double afc_iir_last_x[LTR210_CHANNEL_CNT];
    double afc_iir_last_y[LTR210_CHANNEL_CNT];
    double afc_fir_last[LTR210_CHANNEL_CNT]; /* действительность последних значений */
    BOOLEAN afc_last_valid[LTR210_CHANNEL_CNT];
    BOOLEAN afc_last_volts; /* последнее преобразование с коррекцией АЧХ было в вольтах или кодах */

    double last_adc_freq;
    BOOLEAN last_adc_freq_valid;


    BYTE   last_range[LTR210_CHANNEL_CNT]; /* диапазон, на котором було выполнено преобразование */

    WORD cntr; /* значение счетчика, соответствующего последнему слову */
    DWORD  frame_rem_size; /* количество слов, которое осталось принять до конца кадра */
    t_lclock_ticks last_clock; /* значение счетчика клоков при приеме последнего слова */
} t_internal_params;



static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR210_ERR_INVALID_SYNC_MODE, "Задан неверный код условия сбора кадра"},
    {LTR210_ERR_INVALID_GROUP_MODE, "Задан неверный код режима работы модуля в составе группы"},
    {LTR210_ERR_INVALID_ADC_FREQ_DIV, "Задано неверное значение делителя частоты АЦП"},
    {LTR210_ERR_INVALID_CH_RANGE, "Задан неверный код диапазона канала АЦП"},
    {LTR210_ERR_INVALID_CH_MODE, "Задан неверный режим измерения канала"},
    {LTR210_ERR_SYNC_LEVEL_EXCEED_RANGE , "Установленный уровень аналоговой синхронизации выходит за границы установленного диапазона"},
    {LTR210_ERR_NO_ENABLED_CHANNEL, "Ни один канал АЦП не был разрешен"},
    {LTR210_ERR_PLL_NOT_LOCKED, "Ошибка захвата PLL"},
    {LTR210_ERR_INVALID_RECV_DATA_CNTR, "Неверное значение счетчика в принятых данных"},
    {LTR210_ERR_RECV_UNEXPECTED_CMD, "Прием неподдерживаемой команды в потоке данных"},
    {LTR210_ERR_FLASH_INFO_SIGN, "Неверный признак информации о модуле во Flash-памяти"},
    {LTR210_ERR_FLASH_INFO_SIZE, "Неверный размер прочитанной из Flash-памяти информации о модуле"},
    {LTR210_ERR_FLASH_INFO_UNSUP_FORMAT, "Неподдерживаемый формат информации о модуле из Flash-памяти"},
    {LTR210_ERR_FLASH_INFO_CRC, "Ошибка проверки CRC информации о модуле из Flash-памяти"},
    {LTR210_ERR_FLASH_INFO_VERIFY, "Ошибка проверки записи информации о модуле во Flash-память"},
    {LTR210_ERR_CHANGE_PAR_ON_THE_FLY, "Часть измененных параметров нельзя изменять на лету"},
    {LTR210_ERR_INVALID_ADC_DCM_CNT, "Задан неверный коэффициент прореживания данных АЦП"},
    {LTR210_ERR_MODE_UNSUP_ADC_FREQ, "Установленный режим не поддерживает заданную частоту АЦП"},
    {LTR210_ERR_INVALID_FRAME_SIZE, "Неверно задан размер кадра"},
    {LTR210_ERR_INVALID_HIST_SIZE, "Неверно задан размер предыстории"},
    {LTR210_ERR_INVALID_INTF_TRANSF_RATE, "Неверно задано значение скорости выдачи данных в интерфейс"},
    {LTR210_ERR_INVALID_DIG_BIT_MODE, "Неверно задан режим работы дополнительного бита"},
    {LTR210_ERR_SYNC_LEVEL_LOW_EXCEED_HIGH, "Нижний порог аналоговой синхронизации превышает верхний"},
    {LTR210_ERR_KEEPALIVE_TOUT_EXCEEDED, "Не пришло ни одного статуса от модуля за заданный интервал"},
    {LTR210_ERR_WAIT_FRAME_TIMEOUT,   "Не удалось дождаться прихода кадра за заданное время"},
    {LTR210_ERR_FRAME_STATUS, "Слово статуса в принятом кадре указывает на ошибку данных"}
};


#define LTR210_FLASH_INFO_SIGN          0xA55AC210
#define LTR210_FLASH_INFO_FORMAT        0x1
#define LTR210_FLASH_ADDR_MODULE_INFO   0x0
#define LTR210_FLASH_INFO_CRC_SIZE      2

/* информация в том формате, в котором она хранится во Flash-памяти модуля */
typedef struct {
    DWORD sign;
    DWORD size;
    DWORD format;
    DWORD flags;
    char  name[LTR210_NAME_SIZE];
    char  serial[LTR210_SERIAL_SIZE];
    TLTR210_CBR_COEF cbr[LTR210_CHANNEL_CNT][8];
    double AfcCoefFreq;
    double AfcCoef[LTR210_CHANNEL_CNT][8];
    TLTR210_AFC_IIR_COEF AfcIirParam[LTR210_CHANNEL_CNT][8];
} t_ltr210_flash_info;



typedef struct {
    int param;
    WORD code;
} t_code_tbl;


/* соответствие кодов режима запуска и значения, записываемого в регистр */
static t_code_tbl f_sync_codes[] = {
    {LTR210_SYNC_MODE_INTERNAL, 0},
    {LTR210_SYNC_MODE_CH1_RISE, 1},
    {LTR210_SYNC_MODE_CH1_FALL, CMDBIT_MODE_EDGE | 1},
    {LTR210_SYNC_MODE_CH2_RISE, 2},
    {LTR210_SYNC_MODE_CH2_FALL, CMDBIT_MODE_EDGE | 2},
    {LTR210_SYNC_MODE_SYNC_IN_RISE, 3},
    {LTR210_SYNC_MODE_SYNC_IN_FALL, CMDBIT_MODE_EDGE | 3},
    {LTR210_SYNC_MODE_PERIODIC, 4},
    {LTR210_SYNC_MODE_CONTINUOUS, 5}
};

static t_code_tbl  f_range_codes[] = {
    {LTR210_ADC_RANGE_10, 0},
    {LTR210_ADC_RANGE_5, 1},
    {LTR210_ADC_RANGE_2, 2},
    {LTR210_ADC_RANGE_1, 3},
    {LTR210_ADC_RANGE_0_5, 4}
};

static const t_flash_info *f_supported_flash_devs[] = {
    &flash_info_at25df41a,
    &flash_info_at25df41b,
    &flash_info_at25sf,
    &flash_info_p25q40sh
};

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


static double f_range_vals[] = {10.,5.,2.,1.,0.5};
static int f_transf_rates[] = {500000, 200000, 100000, 50000, 25000, 10000};

static t_code_tbl  f_ch_mode_codes[] =
{
    {LTR210_CH_MODE_ZERO, 0},
    {LTR210_CH_MODE_ACDC, 1},
    {LTR210_CH_MODE_AC,   2}
};


LTR210API_DllExport(INT)  LTR210_GetConfig(TLTR210 *hnd);


#define GET_CODE(_tbl, _param, _fnd, _code) do { \
        unsigned _i; \
        for (_i=0, _fnd=0; !_fnd && (_i < sizeof(_tbl)/sizeof(_tbl[0])); _i++) { \
            if (_tbl[_i].param == (_param)) { \
                _fnd = 1; \
                _code = _tbl[_i].code; \
            } \
        } \
    } \
    while (0);


#define GET_VALUE(_tbl, _code, _fnd, _param) do { \
        unsigned _i; \
        for (_i=0, _fnd=0; !_fnd && (_i < sizeof(_tbl)/sizeof(_tbl[0])); _i++) { \
            if (_tbl[_i].code == (_code)) { \
                _fnd = 1; \
                _param = _tbl[_i].param; \
            } \
        } \
    } \
    while (0);




static INT f_get_sync_lvl(TLTR210 *hnd, int ch, WORD *sync_lvl_l, WORD *sync_lvl_h) {
    int err = LTR_OK;
    if ((fabs(hnd->Cfg.Ch[ch].SyncLevelL) > f_range_vals[hnd->Cfg.Ch[ch].Range])
            || (fabs(hnd->Cfg.Ch[ch].SyncLevelH) > f_range_vals[hnd->Cfg.Ch[ch].Range])) {
        err = LTR210_ERR_SYNC_LEVEL_EXCEED_RANGE;
    } else if (hnd->Cfg.Ch[ch].SyncLevelL > hnd->Cfg.Ch[ch].SyncLevelH) {
        err = LTR210_ERR_SYNC_LEVEL_LOW_EXCEED_HIGH;
    } else {
        INT val = (INT)(LTR210_ADC_SCALE_CODE_MAX*hnd->Cfg.Ch[ch].SyncLevelL/f_range_vals[hnd->Cfg.Ch[ch].Range]);
        *sync_lvl_l = (val/2)&0x3FFF;

        val = (INT)(LTR210_ADC_SCALE_CODE_MAX*hnd->Cfg.Ch[ch].SyncLevelH/f_range_vals[hnd->Cfg.Ch[ch].Range]);
        *sync_lvl_h = (val/2)&0x3FFF;
    }
    return err;
}


#define FLASH_X25_STATUS_PROT_MASK      (FLASH_X25_STATUS_SEC | FLASH_X25_STATUS_TB |  FLASH_X25_STATUS_CMP | \
                                         FLASH_X25_STATUS_BP0 | FLASH_X25_STATUS_BP1 | FLASH_X25_STATUS_BP2)
#define FLASH_X25_STATUS_PROT_DEFAULT   (FLASH_X25_STATUS_BP2)
#define FLASH_X25_STATUS_PROT_WR_INFO   (FLASH_X25_STATUS_CMP | FLASH_X25_STATUS_SEC | FLASH_X25_STATUS_TB | FLASH_X25_STATUS_BP0)

static INT f_at25sf_set_prot(t_flash_iface *flash, short status) {
    unsigned cur_status;
    t_flash_errs flash_err;
    INT err = LTR_OK;

    flash_err = flash_get_status_ex(flash, &cur_status);
    if (!flash_err) {
        if ((cur_status & FLASH_X25_STATUS_PROT_MASK) !=
                (status & FLASH_X25_STATUS_PROT_MASK)) {

            cur_status &= ~FLASH_X25_STATUS_PROT_MASK;
            cur_status |= (status & FLASH_X25_STATUS_PROT_MASK);
            flash_err = flash_set_status_ex(flash, cur_status, 0);

            if (!flash_err) {
                flash_err = flash_get_status_ex(flash, &cur_status);
            }

            if (!flash_err && ((cur_status & FLASH_X25_STATUS_PROT_MASK) !=
                               (status & FLASH_X25_STATUS_PROT_MASK))) {
                err = LTR_ERROR_FLASH_SET_PROTECTION;
            }
        }
    }
    return err == LTR_OK ? flash_iface_ltr_conv_err(flash_err) : err;
}

static INT f_flash_protect(t_flash_iface *flash) {
    return  ((flash->flash_info == &flash_info_at25df41a) || (flash->flash_info == &flash_info_at25df41b))  ?
               flash_at25df_global_protect(flash) : f_at25sf_set_prot(flash, FLASH_X25_STATUS_PROT_DEFAULT);
}

static INT f_flash_info_unprotect(t_flash_iface *flash) {
    return  ((flash->flash_info == &flash_info_at25df41a) || (flash->flash_info == &flash_info_at25df41b))  ?
               flash_at25df_global_unprotect(flash) : f_at25sf_set_prot(flash, FLASH_X25_STATUS_PROT_WR_INFO);
}




static INT f_fpga_afterload(TLTR210 *hnd) {
    INT err = ltr_module_fpga_enable(&hnd->Channel, 1);
    if (err == LTR_OK) {
        DWORD resp = CMD_RESP_FPGA;
        DWORD cmd = CMD_FPGA_VER;

        /* получаем версию ПЛИС */
        err = ltr_module_send_with_single_resp(&hnd->Channel, &cmd, 1, &resp);
        if (err == LTR_OK) {
            WORD data = (resp>>16)&0xFFFF;
            hnd->ModuleInfo.VerFPGA = (data >> 8) & 0xFF;
            ((t_internal_params*)hnd->Internal)->cntr = 0;
            err = CHECK_PLL_LOCK_FIRST(resp);            
        }

        if (err == LTR_OK) {
            ((t_internal_params*)(hnd->Internal))->fpga_loaded = 1;
        }
    }
    return err;
}

/* рассчет передаточной характеристики IIR-фильтра */
static double f_calc_iir_h(double r2, double c, double f, double f_adc) {
    double q2 = pow(0.5 * sin(2*M_PI*f/f_adc)/(1-cos(2*M_PI*f/f_adc)),2);
    double r1 = 1.;

    double h = sqrt(pow(0.5*r1*c + r1*r2*c*c + (0.5+r2*c)*(0.5+r2*c) + q2, 2) +
                    pow(r1*c,2)*q2)/(pow(0.5 + r2*c,2)+q2);
    return h;
}


/* подбор коэффициентов c и r для IIR фильтра АЧХ при переходе на другую
 * частоту дискретизации. Просто перебираем r и c так, чтобы отклонение
 * полученной характеристики было минимальным от оригинальной.
 * При этом, так как c уменьшается с уменьшением частоты АЦП, то
 * уменьшаем шаг и предел для него, чтобы улучшить точность и уменьшить
 * время подбора */
static void f_calc_iir_param(double f_adc, double r_orig, double c_orig, double f_orig, double *r_res, double *c_res) {
    unsigned f;
    double r, c, fnd_r=0, fnd_c=0, fnd_sko = 1000000;
    static const double freqs[] = {1000, 2000, 4000, 6000, 8000, 10000,
                                   15000, 20000, 30000, 40000, 50000, 60000,
                                   70000, 80000, 90000, 100000 };
    double kf = f_adc/f_orig;


    for (r= 0.1*r_orig; r < 1.2*r_orig; r+=0.01*r_orig) {
        for (c=0.02*kf; c < 0.02*kf + 1.2*c_orig*kf; c+=0.02*kf) {
            double sko = 0;
            /* подбор нежной характеристики */
            for (f=0; f < sizeof(freqs)/sizeof(freqs[0]); f++) {
                double freq = freqs[f]*f_adc/10000000.;
                double h_orig = f_calc_iir_h(r_orig, c_orig, freq, 10000000.);
                double h = f_calc_iir_h(r, c, freq, f_adc);
                sko += pow(h_orig-h,2);
            }

            if (sko < fnd_sko) {
                fnd_c = c;
                fnd_r = r;
                fnd_sko = sko;
            }
        }
    }

    *r_res = fnd_r;
    *c_res = fnd_c;
}

/* рассчет коэффициентов фильтров */
static void f_calc_afc_k(TLTR210 *hnd) {
    unsigned ch, range;
    t_internal_params *par = (t_internal_params *)hnd->Internal;

    /* если уже были рассчитыны коэффициенты для этой частоты, то ничего делать
     * не нужно, иначе - выполняем перерассчет */
    if (!par->last_adc_freq_valid || (fabs(par->last_adc_freq-hnd->State.AdcFreq) > 0.1)) {
        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
            for (range = 0; range < LTR210_RANGE_CNT; range++) {
                double fi = hnd->State.AdcFreq/4;
                double a = 1./hnd->ModuleInfo.AfcCoef[ch][range];
                double k1 = -0.5 + sqrt(0.25 - (1-a*a)/(2-2*cos(2*M_PI*hnd->ModuleInfo.AfcCoefFreq/10000000.)));
                double h = sqrt((1+k1)*(1+k1) - 2*(1+k1)*k1*cos(2*M_PI*fi/10000000.) + k1*k1);
                par->afc_k[ch][range] = -0.5 + sqrt(0.25 - 0.5*(1 - h*h));
            }


            for (range = 0; range < LTR210_AFC_IIR_COR_RANGE_CNT; range++) {
                double r1 = 1.;
                double r2 = hnd->ModuleInfo.AfcIirParam[ch][range].R;
                double c = hnd->ModuleInfo.AfcIirParam[ch][range].C;

                /*  Во Flash сохранены параметры только для максимальной частоты.
                 *  для остальных надо подобрать нужные параметры для аналогичной
                 *  характеристики */
                if ((par->Cfg.AdcDcmCnt != 0) || (par->Cfg.AdcFreqDiv != 0)) {
                    f_calc_iir_param(hnd->State.AdcFreq, r2, c, 10000000, &r2, &c);
                }

                /* На основании параметров схемы фильтра рассчитываем его коэффициенты */
                par->afc_iir_k[ch][range][0] = (r2/r1)/((r2/r1) + 1/(r1*c));
                par->afc_iir_k[ch][range][1] = (1+r2/r1+1/(r1*c))/((r2/r1) + 1/(r1*c));
                par->afc_iir_k[ch][range][2] = -(1+r2/r1)/((r2/r1) + 1/(r1*c));
            }
        }
    }

    par->last_adc_freq_valid = 1;
    par->last_adc_freq = hnd->State.AdcFreq;
}

static void f_clear_afc_filter(TLTR210 *hnd, DWORD ch_mask) {
    unsigned ch;
    t_internal_params *par = (t_internal_params *)hnd->Internal;
    for (ch = 0; ch < LTR210_CHANNEL_CNT; ch++) {
        if (ch_mask & (1 << ch)) {
            par->afc_fir_last[ch] = 0;
            par->afc_iir_last_x[ch] = 0;
            par->afc_iir_last_y[ch] = 0;
            par->afc_last_valid[ch] = 0;
        }
    }
}

/* Загрузка коэффициентов в регистры ПЛИС */
static INT f_load_coefs(TLTR210 *hnd) {
    DWORD cmds[32], resp[32];
    INT err=LTR_OK;
    DWORD ch, range;

    for (ch = 0; ch < LTR210_CHANNEL_CNT; ch++) {
        for (range = 0; range < 8; range++) {
            DWORD offs_code=0, scale_code=0;
            if (range < LTR210_RANGE_CNT) {
                offs_code = (DWORD)(hnd->ModuleInfo.CbrCoef[ch][range].Offset >= 0 ?
                                        hnd->ModuleInfo.CbrCoef[ch][range].Offset+0.5 :
                                        hnd->ModuleInfo.CbrCoef[ch][range].Offset-0.5);
                scale_code = (DWORD)(hnd->ModuleInfo.CbrCoef[ch][range].Scale*0x4000);
            }
            cmds[(ch*8+range)*2]   = LTR_MODULE_MAKE_CMD(CMD_CBR_MEMORY, offs_code);
            cmds[(ch*8+range)*2+1] = LTR_MODULE_MAKE_CMD(CMD_CBR_MEMORY, scale_code);
        }
    }

    err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, sizeof(cmds)/sizeof(cmds[0]), resp);

    return err;
}

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


static void f_info_init(TLTR210 *hnd) {
    unsigned range, ch;

    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTR210");

    /* устанавливаем коэффициенты по-умолчанию (k=1, offs=0) */
    for (ch = 0; ch < LTR210_CHANNEL_CNT; ++ch) {
        for (range = 0; range < sizeof(hnd->ModuleInfo.CbrCoef[0])/sizeof(hnd->ModuleInfo.CbrCoef[0][0]); range++) {
            hnd->ModuleInfo.CbrCoef[ch][range].Offset = 0;
            hnd->ModuleInfo.CbrCoef[ch][range].Scale = 1.;
        }
    }

    for (ch = 0; ch < LTR210_CHANNEL_CNT; ++ch) {
        for (range = 0; range < sizeof(hnd->ModuleInfo.AfcCoef[0])/sizeof(hnd->ModuleInfo.AfcCoef[0][0]); range++) {
            hnd->ModuleInfo.AfcCoef[ch][range] = 1.;
        }
    }

    for (ch = 0; ch < LTR210_CHANNEL_CNT; ++ch) {
        for (range = 0; range < sizeof(hnd->ModuleInfo.AfcIirParam[0])/sizeof(hnd->ModuleInfo.AfcIirParam[0][0]); range++) {
            hnd->ModuleInfo. AfcIirParam[ch][range].R = 0.;
            hnd->ModuleInfo.AfcIirParam[ch][range].C = 1.;
        }
    }

    hnd->ModuleInfo.AfcCoefFreq  = 2000000.;

    hnd->State.Run = FALSE;
}

LTR210API_DllExport(INT) LTR210_Init(TLTR210 *hnd) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        memset(hnd, 0, sizeof(TLTR210));
        hnd->Size = sizeof(TLTR210);
        err = LTR_Init(&hnd->Channel);
        if (err == LTR_OK) {
            f_info_init(hnd);

            hnd->Cfg.FrameFreqDiv = 1000000;
            hnd->Cfg.FrameSize = 4096;
        }
    }
    return err;
}

LTR210API_DllExport(INT) LTR210_Close(TLTR210 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (hnd->Internal != NULL) {
            t_internal_params *params = (t_internal_params*)hnd->Internal;
            flash_iface_ltr_close(&params->flash);
            free(hnd->Internal);
            hnd->Internal = NULL;
        }
        err=LTR_Close(&hnd->Channel);
    }
    return err;
}


LTR210API_DllExport (INT)  LTR210_Open(TLTR210 *hnd, DWORD ltrd_addr,
                                       WORD ltrd_port, const CHAR *crate_sn,
                                       WORD slot_num) {
    DWORD open_flags = 0;
    INT warning, err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD ack;

        if (LTR210_IsOpened(hnd) == LTR_OK) {
            LTR210_Close(hnd);
        }

        err = ltr_module_open(&hnd->Channel, ltrd_addr, ltrd_port, crate_sn, slot_num,
                              LTR_MID_LTR210, &open_flags, &ack, &warning);
        if (err == LTR_OK) {
            f_info_init(hnd);
            hnd->ModuleInfo.VerPLD = ack & 0x3F;

        }
    }

    /* выделяем структуру под внутренний приватный буфер */
    if (err == LTR_OK) {
        t_internal_params *par = hnd->Internal = calloc(1, sizeof(t_internal_params));
        if (hnd->Internal == NULL) {
            err = LTR_ERROR_MEMORY_ALLOC;
        } else {
            memset(par, 0, sizeof(*par));
            par->Cfg = hnd->Cfg;
        }

        /* Проверяем тип крейта. 500 КСлов/c можно устанавливать
         * только в случае, если крейт не LTR-U-1 (LTR021) */
        if (err == LTR_OK) {
            TLTR hcrate;
            LTR_Init(&hcrate);            
            err = LTR_OpenCrate(&hcrate, ltrd_addr, ltrd_port, LTR_CRATE_IFACE_UNKNOWN, crate_sn);
            if (err == LTR_OK) {
                TLTR_CRATE_INFO info;
                err = LTR_GetCrateInfo(&hcrate, &info);
                if (err == LTR_OK) {
                    if (info.CrateType == LTR_CRATE_TYPE_LTR021) {
                        par->slow_crate = 1;
                    }
                }
                LTR_Close(&hcrate);
            }
        }
    }


    if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_init(&params->flash, &hnd->Channel);
        if (!flash_res) {
            flash_res = flash_set_from_list(&params->flash, f_supported_flash_devs,
                                            sizeof(f_supported_flash_devs)/sizeof(f_supported_flash_devs[0]), NULL);
        }

        if (!flash_res) {
            err = f_flash_protect(&params->flash);

            flash_iface_flush(&params->flash, flash_res);

            if (err == LTR_OK)
                err = LTR210_GetConfig(hnd);
        } else {
            flash_iface_flush(&params->flash, flash_res);
            err = flash_iface_ltr_conv_err(flash_res);
        }


    }

    /* закрываем соединение с модулем при ошибке, за исключением случая, когда
     * во Flash-памяти лежит неверная информация (т.к. иначе мы не сможем его
     * открыть, чтобы изменить ее) */
    if ((err != LTR_OK) && (err != LTR210_ERR_FLASH_INFO_CRC) &&
            (err != LTR210_ERR_FLASH_INFO_SIGN) &&
            (err != LTR210_ERR_FLASH_INFO_SIZE) &&
            (err != LTR210_ERR_FLASH_INFO_UNSUP_FORMAT)) {
        LTR210_Close(hnd);
    }

    return err == LTR_OK ? warning : err;
}


LTR210API_DllExport(INT) LTR210_IsOpened(TLTR210 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}


LTR210API_DllExport (LPCSTR) LTR210_GetErrorString(INT err) {
    size_t i;
    for (i = 0; i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0]); i++) {
        if (f_err_tbl[i].code == err)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(err);
}


LTR210API_DllExport(INT) LTR210_FPGAIsLoaded(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        if (!((t_internal_params*)hnd->Internal)->fpga_loaded)
            err = ltr_module_fpga_is_loaded(&hnd->Channel);
    }
    return err;
}

LTR210API_DllExport(INT) LTR210_LoadFPGA(TLTR210 *hnd, const char *filename,
                                         TLTR210_LOAD_PROGR_CB progr_cb, void *cb_data) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        err  = ltr_module_fpga_load(&hnd->Channel, filename,
                            #ifdef _WIN32
                                    hInstance, "LTR210_FPGA",
                            #else
                                    NULL, LTRMODULE_INSTALL_DATA_FILE,
                            #endif
                                    (t_ltr_fpga_load_cb)progr_cb, cb_data, hnd);
    }
    if (err == LTR_OK)
        err = f_fpga_afterload(hnd);
    if (err == LTR_OK)
        err = f_load_coefs(hnd);
    return err;
}


LTR210API_DllExport(INT) LTR210_LoadCbrCoef(TLTR210 *hnd) {
    INT err = LTR210_FPGAIsLoaded(hnd);
    if (err == LTR_OK) {
        err = f_load_coefs(hnd);
    }
    return err;
}

/* получение команд для установки параметров, которые можно менять на лету */
static INT f_get_onfly_params(TLTR210 *hnd, WORD *actl, WORD *sync_lvls, WORD *ch_cnt) {
    unsigned ch;
    INT err=LTR_OK;
    WORD analog_ctl=0, cnt=0;
    DWORD fnd;


    for (ch = 0; (err == LTR_OK) && (ch < LTR210_CHANNEL_CNT); ch++) {
        WORD code;
        GET_CODE(f_range_codes, hnd->Cfg.Ch[ch].Range, fnd, code);
        if (!fnd) {
            err = LTR210_ERR_INVALID_CH_RANGE;
        } else {
            analog_ctl |= (code<<(8+4*ch));
        }

        if (err == LTR_OK) {
            GET_CODE(f_ch_mode_codes, hnd->Cfg.Ch[ch].Mode, fnd, code);

            if (!fnd) {
                err = LTR210_ERR_INVALID_CH_MODE;
            } else {
                analog_ctl |= (code<<(4*ch));
            }
        }

        if (err == LTR_OK) {
            err = f_get_sync_lvl(hnd, ch, &sync_lvls[2*ch], &sync_lvls[2*ch+1]);
        }

        if (err == LTR_OK) {
            if (hnd->Cfg.Ch[ch].Enabled)
                cnt++;
        }
    }


    if (err == LTR_OK) {
        *actl = analog_ctl;
        *ch_cnt = cnt;
    }

    return err;
}


static INT f_set_adc(TLTR210 *hnd, int no_coef_recalc) {
    INT err = LTR_OK;
    t_internal_params *par = (t_internal_params *)hnd->Internal;
    DWORD set_frame_div, min_frame_div;

    /* частота следования кадров должна быть как минимум быть в 2 раза
     * меньше, чем частота сбора АЦП */
    set_frame_div = hnd->Cfg.FrameFreqDiv;
    min_frame_div = 8*(hnd->Cfg.AdcDcmCnt+1)*(hnd->Cfg.AdcFreqDiv+1);

    if (set_frame_div < min_frame_div)
        set_frame_div = min_frame_div;


    if (!hnd->State.Run) {
        int intf_freq=0;
        DWORD cmds[15];
        DWORD fnd;
        DWORD data_bit_mode=0;
        DWORD ch=0;
        INT pos = 0;
        WORD sync_code=0, analog_ctl=0, adc_mode, ch_cnt=0;
        WORD sync_lvls[4]= {0,0,0,0};
        double adc_freq = ADC_FREQ(hnd->Cfg.AdcFreqDiv, hnd->Cfg.AdcDcmCnt);


        /* Ищем по режиму запуска соответствующий код из таблицы, который
           нужно записать в ПЛИС. Если не нашли => режим задан неверно */
        GET_CODE(f_sync_codes, hnd->Cfg.SyncMode, fnd, sync_code);

        if (!fnd)
            err = LTR210_ERR_INVALID_SYNC_MODE;



        if ((err == LTR_OK) && hnd->Cfg.GroupMode!=LTR210_GROUP_MODE_INDIVIDUAL &&
                     (hnd->Cfg.GroupMode!=LTR210_GROUP_MODE_MASTER) &&
                     (hnd->Cfg.GroupMode!=LTR210_GROUP_MODE_SLAVE)) {
            err = LTR210_ERR_INVALID_GROUP_MODE;
        }

        if ((err == LTR_OK) && (hnd->Cfg.AdcFreqDiv>=LTR210_ADC_FREQ_DIV_MAX)) {
            err = LTR210_ERR_INVALID_ADC_FREQ_DIV;
        }

        if ((err == LTR_OK) && (hnd->Cfg.AdcDcmCnt>=LTR210_ADC_DCM_CNT_MAX)) {
            err = LTR210_ERR_INVALID_ADC_DCM_CNT;
        }

        if (err == LTR_OK) {
            err = f_get_onfly_params(hnd, &analog_ctl, sync_lvls, &ch_cnt);
        }

        if ((err == LTR_OK) && (ch_cnt==0))
            err = LTR210_ERR_NO_ENABLED_CHANNEL;

        if (!LTR210_IS_CONTINUOUS(hnd->Cfg)){
            if ((err == LTR_OK) && ((hnd->Cfg.FrameSize > (DWORD)LTR210_FRAME_SIZE_MAX/ch_cnt)
                                  || (hnd->Cfg.FrameSize==0))) {
                err = LTR210_ERR_INVALID_FRAME_SIZE;
            }

            if ((err == LTR_OK) && (hnd->Cfg.HistSize > hnd->Cfg.FrameSize)) {
                err = LTR210_ERR_INVALID_HIST_SIZE;
            }
        }

        for (ch=0; (ch < LTR210_CHANNEL_CNT) && (err == LTR_OK); ch++) {
            if (hnd->Cfg.Ch[ch].DigBitMode > LTR210_DIG_BIT_MODE_INTERNAL_SYNC) {
                err = LTR210_ERR_INVALID_DIG_BIT_MODE;
            } else {
                data_bit_mode |= (hnd->Cfg.Ch[ch].DigBitMode << (ch*8));
            }
        }

        if (err == LTR_OK) {
            DWORD transfRate = hnd->Cfg.IntfTransfRate;
            if (par->slow_crate && (transfRate==LTR210_INTF_TRANSF_RATE_500K))
                transfRate = LTR210_INTF_TRANSF_RATE_200K;

            if (transfRate >= sizeof(f_transf_rates)/sizeof(f_transf_rates[0])) {
                err = LTR210_ERR_INVALID_INTF_TRANSF_RATE;
            } else {
                sync_code |= transfRate << CMDBIT_TRANSF_RATE_POS;
                intf_freq = f_transf_rates[transfRate];
            }
        }


        /* Проверяем предел скорости для непрерывного режима */
        if ((err == LTR_OK) && LTR210_IS_CONTINUOUS(hnd->Cfg)) {
            if (adc_freq*ch_cnt > intf_freq)
                err = LTR210_ERR_MODE_UNSUP_ADC_FREQ;
        }


        if (err == LTR_OK) {
            adc_mode = hnd->Cfg.AdcFreqDiv & 0xF;
            if (hnd->Cfg.Flags & LTR210_CFG_FLAGS_TEST_CNTR_MODE)
                adc_mode |= (1<<7);

            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_WR_CONTROL, (hnd->Cfg.AdcDcmCnt&0xFF)<<8);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SIZE_L, (ch_cnt*hnd->Cfg.FrameSize)&0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SIZE_H, ((ch_cnt*hnd->Cfg.FrameSize)>>16) & 0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SIZE_HIST_L, (ch_cnt*hnd->Cfg.HistSize)&0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SIZE_HIST_H, ((ch_cnt*hnd->Cfg.HistSize)>>16) & 0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_RD_MODE, sync_code | ((WORD)hnd->Cfg.GroupMode << 8));
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_ANALOG_CTL, analog_ctl);

            for (ch=0; ch < sizeof(sync_lvls)/sizeof(sync_lvls[0]); ch++) {
                cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SYNC_LEVEL, (ch<<14) | sync_lvls[ch]);
            }

            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_ADC_MODE, adc_mode);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_FPS0, set_frame_div&0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_FPS1, (set_frame_div>>16)&0xFFFF);
            cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_DIG_BIT_MODE, data_bit_mode);

            err = ltr_module_send_cmd(&hnd->Channel, cmds, pos);
        }

        if (err == LTR_OK) {
            par->Cfg = hnd->Cfg;
            par->Cfg.FrameFreqDiv = set_frame_div;
            hnd->State.RecvFrameSize = LTR210_IS_CONTINUOUS(hnd->Cfg) ?
                        ch_cnt*hnd->Cfg.FrameSize : ch_cnt*hnd->Cfg.FrameSize+1;

            hnd->State.AdcFreq = adc_freq;


            if ((hnd->Cfg.SyncMode==LTR210_SYNC_MODE_PERIODIC) &&
                    (hnd->Cfg.GroupMode!=LTR210_GROUP_MODE_SLAVE)) {
                hnd->State.FrameFreq = 1000000./(set_frame_div+1);
            } else {
                hnd->State.FrameFreq = 0;
            }

            if (!no_coef_recalc) {
                f_calc_afc_k(hnd);
            }
            f_clear_afc_filter(hnd, _MSK_CH_ALL);
        }
    } else {
        /* проверяем, что изменились только параметры, которые можно менять на лету */
        if ((par->Cfg.AdcDcmCnt!=hnd->Cfg.AdcDcmCnt) ||
                (par->Cfg.AdcFreqDiv!=hnd->Cfg.AdcFreqDiv) ||
                (par->Cfg.FrameSize!=hnd->Cfg.FrameSize) ||
                (par->Cfg.HistSize!=hnd->Cfg.HistSize) ||
                (par->Cfg.SyncMode!=hnd->Cfg.SyncMode) ||
                (par->Cfg.GroupMode!=hnd->Cfg.GroupMode) ||
                (par->Cfg.Ch[0].Enabled != hnd->Cfg.Ch[0].Enabled) ||
                (par->Cfg.Ch[1].Enabled != hnd->Cfg.Ch[1].Enabled)) {
            err = LTR210_ERR_CHANGE_PAR_ON_THE_FLY;
        } else {
            WORD analog_ctl=0, ch_cnt;
            WORD sync_lvls[4]= {0,0,0,0};

            err = f_get_onfly_params(hnd, &analog_ctl, sync_lvls, &ch_cnt);
            if ((err == LTR_OK) && (ch_cnt==0))
                err = LTR210_ERR_NO_ENABLED_CHANNEL;
            if (err == LTR_OK) {
                DWORD cmds[7];
                DWORD ch;
                INT pos = 0;
                cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_ANALOG_CTL, analog_ctl);
                for (ch=0; ch < sizeof(sync_lvls)/sizeof(sync_lvls[0]); ch++) {
                    cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SYNC_LEVEL, (ch<<14) | sync_lvls[ch]);
                }
                cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_FPS0, set_frame_div&0xFFFF);
                cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_FPS1, (set_frame_div>>16)&0xFFFF);

                err = ltr_module_send_cmd(&hnd->Channel, cmds, pos);
            }

            if (err == LTR_OK) {
                par->Cfg = hnd->Cfg;
                par->Cfg.FrameFreqDiv = set_frame_div;
                if ((hnd->Cfg.SyncMode==LTR210_SYNC_MODE_PERIODIC) &&
                        (hnd->Cfg.GroupMode!=LTR210_GROUP_MODE_SLAVE)) {
                    hnd->State.FrameFreq = 1000000./(set_frame_div+1);
                } else {
                    hnd->State.FrameFreq = 0;
                }
            }
        }
    }
    return err;
}




LTR210API_DllExport(INT) LTR210_SetADC(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_set_adc(hnd, 0);
    }
    return err;
}



LTR210API_DllExport(INT) LTR210_Start(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    else if (err == LTR_OK) {
        t_internal_params* par = (t_internal_params*)hnd->Internal;
        if (!par->Cfg.Ch[0].Enabled && !par->Cfg.Ch[1].Enabled)
            err = LTR210_ERR_NO_ENABLED_CHANNEL;

        if (err == LTR_OK) {
            WORD wr_val=(WORD)par->Cfg.AdcDcmCnt<<8;
            DWORD cmd[2], ch;

            for (ch = 0; ch < LTR210_CHANNEL_CNT; ch++) {
                if (par->Cfg.Ch[ch].Enabled)
                    wr_val |= (1<<ch);
            }

            if (par->Cfg.Flags & LTR210_CFG_FLAGS_WRITE_AUTO_SUSP)
                wr_val |= CMDBIT_WRCTL_AUTOSUSP;

            cmd[0] = LTR_MODULE_MAKE_CMD(CMD_WR_CONTROL, wr_val);
            cmd[1] = LTR_MODULE_MAKE_CMD(CMD_SET_STATUS_MODE, (par->Cfg.Flags & LTR210_CFG_FLAGS_KEEPALIVE_EN) &&
                                         !LTR210_IS_CONTINUOUS(par->Cfg) ? 1 : 0);
            err = ltr_module_send_cmd(&hnd->Channel, cmd, sizeof(cmd)/sizeof(cmd[0]));

            if (err == LTR_OK) {
                par->cntr = 0;
                par->cntr_valid = 0;
                hnd->State.Run = TRUE;
                par->last_clock = lclock_get_ticks();

                par->frame_rem_size = hnd->State.RecvFrameSize;
            }
        }
    }
    return err;
}


LTR210API_DllExport(INT) LTR210_Stop(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Run)
        err = LTR_ERROR_MODULE_STOPPED;
    if (err == LTR_OK) {
        DWORD cmd[2], ack;
        cmd[0] = LTR_MODULE_MAKE_CMD(CMD_SET_STATUS_MODE, 0);
        cmd[1] = LTR_MODULE_MAKE_CMD(CMD_WR_CONTROL, (WORD)((t_internal_params *)hnd->Internal)->Cfg.AdcDcmCnt<<8);
        ack = LTR_MODULE_MAKE_CMD(CMD_RESP_FRAME_STATUS, 0x8000);
        err = ltr_module_stop(&hnd->Channel, cmd, sizeof(cmd)/sizeof(cmd[0]), ack, LTR_MSTOP_FLAGS_SPEC_MASK,
                0x80C0C0FF, NULL);
        if (err == LTR_OK) {
            hnd->State.Run = FALSE;
        }
    }
    return err;
}

LTR210API_DllExport(INT) LTR210_FrameStart(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Run)
        err = LTR_ERROR_MODULE_STOPPED;

    /* проверяем, что установленны необходимые режимы */
    if (err == LTR_OK) {
        t_internal_params* par = (t_internal_params*)hnd->Internal;
        if (par->Cfg.SyncMode != LTR210_SYNC_MODE_INTERNAL) {
            err = LTR210_ERR_INVALID_SYNC_MODE;
        } else if (par->Cfg.GroupMode == LTR210_GROUP_MODE_SLAVE) {
            err = LTR210_ERR_INVALID_GROUP_MODE;
        }
    }

    if (err == LTR_OK) {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_PROGRAM_SYNC, 0);
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
    }

    return err;
}


LTR210API_DllExport(INT) LTR210_WaitEvent(TLTR210 *hnd, DWORD *event,
                                          DWORD *status, DWORD tout) {
    INT err = event==NULL ? LTR_ERROR_PARAMETERS : LTR210_IsOpened(hnd);
    INT fnd_evt = 0;
    t_internal_params *par;

    if ((err == LTR_OK) && !hnd->State.Run)
        err = LTR_ERROR_MODULE_STOPPED;

    if (err == LTR_OK) {
        /* Для непрерывного сбора всегда возвращаем начало кадра */
        par = (t_internal_params *)hnd->Internal;
        if (LTR210_IS_CONTINUOUS(par->Cfg)) {
            *event = LTR210_RECV_EVENT_SOF;
        } else {
            t_ltimer tmr;

            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));

            while (!fnd_evt && (err == LTR_OK)) {
                DWORD wrd, tmark;
                INT rcv_res;

                rcv_res = LTR_Recv(&hnd->Channel, &wrd, &tmark, 1,
                                   LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));


                if (rcv_res<0) {
                    err = rcv_res;
                } else if (rcv_res>1) {
                    err = LTR_ERROR_RECV;
                } else if (rcv_res==1) {
                    par->last_clock = lclock_get_ticks();

                    if (wrd & LTR010CMD) {
                        if ((wrd & CMD_CODE_MSK) == CMD_RESP_KEEPALIVE) {
                            *event = LTR210_RECV_EVENT_KEEPALIVE;
                            fnd_evt = 1;
                            /* в периодическом статусе высылается и признак
                             * захвата PLL, который нужно проверить */
                            err = CHECK_PLL_LOCK(wrd);
                            if (status)
                                *status = (wrd >> 16) & 0xFFFF;

                        }
                    } else if (wrd & CMDBIT_DATA_FIRST) {
                        /* в данных анализируем первый бит */
                        fnd_evt = 1;
                        par->last_word_valid=1;
                        par->last_word = wrd;
                        par->last_tmark = tmark;
                        par->frame_rem_size = hnd->State.RecvFrameSize;
                        f_clear_afc_filter(hnd, _MSK_CH_ALL);
                        *event = LTR210_RECV_EVENT_SOF;
                    }
                }

                if ((err == LTR_OK) && !fnd_evt && ltimer_expired(&tmr)) {
                    /* выходим, если за таймаут ничего не нашли */
                    fnd_evt = 1;
                    *event = LTR210_RECV_EVENT_TIMEOUT;
                }
            }
        }
    }

    return err;
}


LTR210API_DllExport(INT) LTR210_Recv(TLTR210 *hnd, DWORD *data, DWORD *tmark,
                                     DWORD size, DWORD timeout) {
    INT res = (data==NULL) || (size==0) ? LTR_ERROR_PARAMETERS : LTR210_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *par = (t_internal_params *)hnd->Internal;
        /* В покадровом режиме принимаем данные до границы кадра, даже если
         * запрашивалось больше */
        if (!LTR210_IS_CONTINUOUS(par->Cfg)) {
            INT ret_size = 0, recv_size;
            if (par->last_word_valid) {
                *data++ = par->last_word;
                if (tmark!=NULL)
                    *tmark++ = par->last_tmark;
                par->last_word_valid = 0;
                ret_size++;
                size--;
                par->frame_rem_size--;
            }

            if (size > par->frame_rem_size) {
                size = par->frame_rem_size;
            }

            recv_size = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
            if (recv_size < 0) {
                res = recv_size;
            }
            else if (hnd->Channel.flags & LTR_FLAG_RBUF_OVF) {
                res = LTR_ERROR_RECV_OVERFLOW;
            } else {
                par->frame_rem_size-=recv_size;
                if (par->frame_rem_size==0)
                    par->frame_rem_size = hnd->State.RecvFrameSize;
                res = recv_size + ret_size;
            }
        } else {
            res = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
        }

        if (res > 0)
            par->last_clock = lclock_get_ticks();
    }
    return res;
}





LTR210API_DllExport(INT)  LTR210_ProcessData(TLTR210 *hnd, const DWORD *src,
                                             double *dest, INT *size, DWORD flags,
                                             TLTR210_FRAME_STATUS *frame_status,
                                             TLTR210_DATA_INFO *data_info) {
    INT err = (hnd==NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : (src==NULL) || (size==NULL) ?
                            LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        BOOLEAN volts = flags & LTR210_PROC_FLAG_VOLT ? TRUE : FALSE;
        t_internal_params *par = (t_internal_params *)hnd->Internal;
        INT rem_size;
        INT put_size = 0;
        TLTR210_FRAME_STATUS fstatus;
        unsigned proc_cntr=0;
        int first = 1;

        fstatus.Result = LTR210_FRAME_RESULT_PENDING;
        fstatus.Reserved = 0;
        fstatus.Flags =0;


        if (flags & LTR210_PROC_FLAG_AFC_COR) {
            if ((flags & LTR210_PROC_FLAG_NONCONT_DATA)
                    || (volts != par->afc_last_volts)) {
                f_clear_afc_filter(hnd, _MSK_CH_ALL);
            }
            par->afc_last_volts = volts;
        }


        /* обрабатываем данные либо до конца массива, либо пока не найдем конец кадра */
        for (rem_size = *size; (rem_size>0) && (fstatus.Result==LTR210_FRAME_RESULT_PENDING); rem_size--, src++) {
            INT wrd_err = LTR_OK;

            /* единственная команда, которая может попасться при обработке кадра -
             * признак конца кадра */
            if (*src & LTR010CMD) {
                DWORD ctl_wrd = *src;

                if ((ctl_wrd & CMD_CODE_MSK)== CMD_RESP_FRAME_STATUS) {
                    INT pll_err = CHECK_PLL_LOCK(ctl_wrd);
                    if (pll_err!=LTR_OK)
                        err = pll_err;
                    fstatus.Flags = ctl_wrd>>=16;
                    if (ctl_wrd & (LTR210_STATUS_FLAG_OVERLAP | LTR210_STATUS_FLAG_INVALID_HIST)) {
                        fstatus.Result = LTR210_FRAME_RESULT_ERROR;
                    } else {
                        fstatus.Result = LTR210_FRAME_RESULT_OK;
                    }

                    f_clear_afc_filter(hnd, _MSK_CH_ALL);
                } else {
                    err = LTR210_ERR_RECV_UNEXPECTED_CMD;
                }
            } else {
                DWORD src_wrd = *src;
                unsigned cntr = (src_wrd >> 5) & 0x7;
                TLTR210_DATA_INFO info;
                double val;
                info.Range = LTR210_ADC_RANGE_10;
                info.Reserved = 0;

                /* Установка начального значения счетчика для проверки непрерывности
                 * данных.
                 * Если данные не идут подряд, то используем локальный счетчик
                 * проверки целостности только внутри блока.
                 * Иначе берем сохраненное значение, если оно имеется, чтобы
                 * проверить целостность и между блоками */
                if (first) {
                    first = 0;
                    if (flags & LTR210_PROC_FLAG_NONCONT_DATA) {
                        proc_cntr = cntr;
                    } else if (!par->cntr_valid) {
                        par->cntr_valid = 1;
                        proc_cntr = cntr;
                    } else {
                        proc_cntr = par->cntr;
                    }
                }

                /* проверка счетчика */
                if (cntr != proc_cntr) {
                    wrd_err = LTR210_ERR_INVALID_RECV_DATA_CNTR;
                    proc_cntr = cntr;
                }

                if (!(par->Cfg.Flags & LTR210_CFG_FLAGS_TEST_CNTR_MODE)) {
                    int fnd;
                    /* извлекаем информацию из битов пришедшего слова */
                    info.DigBitState = (src_wrd>>16) & 1;
                    info.Ch = src_wrd & 1;
                    GET_VALUE(f_range_codes, (src_wrd >> 1) & 0x7, fnd, info.Range);


                    val =  (double)(((SHORT)((WORD)((src_wrd>>16) & 0xFFFF))) >> 1);

                    /* коррекция смещения нуля */
                    if (flags & LTR210_PROC_FLAG_ZERO_OFFS_COR) {
                        val -= hnd->State.AdcZeroOffset[info.Ch];
                    }

                    /* перевод в вольты */
                    if (flags & LTR210_PROC_FLAG_VOLT) {
                        val = val * f_range_vals[info.Range] / LTR210_ADC_SCALE_CODE_MAX;
                    }


                    /* корректировка АЧХ */
                    if (flags & LTR210_PROC_FLAG_AFC_COR) {
                        double prev_x;

                        if (info.Range != par->last_range[info.Ch]) {
                            f_clear_afc_filter(hnd, _MSK_CH(info.Ch));
                        }

                        /* сперва применяем IIR фильтр, если он неообходим */
                        if (info.Range < LTR210_AFC_IIR_COR_RANGE_CNT) {
                            prev_x = val;

                            if (par->afc_last_valid[info.Ch]) {
                                val = par->afc_iir_k[info.Ch][info.Range][0] * par->afc_iir_last_y[info.Ch]
                                        + par->afc_iir_k[info.Ch][info.Range][1] * val
                                        +par->afc_iir_k[info.Ch][info.Range][2] * par->afc_iir_last_x[info.Ch];
                            }

                            par->afc_iir_last_y[info.Ch] = val;
                            par->afc_iir_last_x[info.Ch] = prev_x;
                        }

                        /* применяем FIR-фильтр для коррекции наклона */
                        prev_x = val;
                        if (par->afc_last_valid[info.Ch]) {
                            val = (val - par->afc_fir_last[info.Ch])*par->afc_k[info.Ch][info.Range] + val;
                        }
                        par->afc_fir_last[info.Ch] = prev_x;

                        /* сохраняем значение диапазона, чтобы сбросить фильтр,
                         * если будет изменение диапазона на лету */
                        par->last_range[info.Ch] = info.Range;
                        par->afc_last_valid[info.Ch] = 1;
                    }

                    if (dest!=NULL)
                        *dest++ = val;
                    if (data_info!=NULL)
                        *data_info++ = info;
                } else {
                    /* для тестового режима не выполняем никаких спец. преобразований
                     * и всегда считаем что данные принадлежат одному каналу */
                    info.DigBitState = 0;
                    info.Ch = 0;
                    info.Range = LTR210_ADC_RANGE_10;
                    val = (SHORT)((src_wrd>>16) & 0xFFFF);
                    if (dest!=NULL)
                        *dest++ = val*5/LTR210_ADC_SCALE_CODE_MAX;
                    if (data_info!=NULL)
                        *data_info++ = info;
                }

                if (++proc_cntr==LTR210_DATA_CNTR_MOD)
                    proc_cntr = 0;

                put_size++;
            }

            if (err == LTR_OK)
                err = wrd_err;
        }


        *size = put_size;
        if (frame_status!=NULL)
            *frame_status = fstatus;

        /* при непрерывной обработке сохраняем значение счетчика, чтобы проверить
         * при следующем вызове ProcessData() целостность данных между блоками */
        if (!(flags & LTR210_PROC_FLAG_NONCONT_DATA) && !first) {
            par->cntr = proc_cntr;
        }
    }
    return err;
}

LTR210API_DllExport(INT)  LTR210_AfcCoefInvalidate(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params *par = (t_internal_params *)hnd->Internal;
        par->last_adc_freq_valid = 0;
    }
    return err;
}


LTR210API_DllExport(INT)  LTR210_GetConfig(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        err = ltr_module_fpga_enable(&hnd->Channel, 0);
    }

    if (err == LTR_OK) {
        struct {
            DWORD sign;
            DWORD size;
            DWORD format;
        } hdr;
        t_internal_params * params = (t_internal_params*)hnd->Internal;

        err = flash_iface_ltr_conv_err(flash_iface_ltr_set_channel(&params->flash, &hnd->Channel));

        if (err == LTR_OK) {
            /* вначале читаем только минимальный заголовок, чтобы определить
                сразу признак информации и узнать полный размер */
            err = flash_iface_ltr_conv_err(flash_read(&params->flash,
                                          LTR210_FLASH_ADDR_MODULE_INFO, (unsigned char*)&hdr, sizeof(hdr)));
        }
        if ((err == LTR_OK) && (hdr.sign!=LTR210_FLASH_INFO_SIGN))
            err = LTR210_ERR_FLASH_INFO_SIGN;
        if ((err == LTR_OK) && (hdr.format!=LTR210_FLASH_INFO_FORMAT))
            err = LTR210_ERR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && (hdr.size < sizeof(t_ltr210_flash_info)))
            err = LTR210_ERR_FLASH_INFO_SIZE;
        if (err == LTR_OK) {
            t_ltr210_flash_info *pinfo = malloc(hdr.size + LTR210_FLASH_INFO_CRC_SIZE);
            if (pinfo==NULL)
                err = LTR_ERROR_MEMORY_ALLOC;
            /* читаем оставшуюся информацию */
            if (err == LTR_OK) {
                memcpy(pinfo, &hdr, sizeof(hdr));
                err = flash_iface_ltr_conv_err(flash_read(&params->flash,
                                        LTR210_FLASH_ADDR_MODULE_INFO+sizeof(hdr),
                                        ((BYTE*)pinfo) + sizeof(hdr), hdr.size +
                                        LTR210_FLASH_INFO_CRC_SIZE - sizeof(hdr)));
            }

            /* рассчитываем CRC и сверяем со считанной из памяти */
            if (err == LTR_OK) {
                WORD crc, crc_ver;
                crc_ver = eval_crc16(0, (unsigned char *) pinfo, hdr.size);
                crc = ((BYTE*)pinfo)[hdr.size] |
                        (((WORD)((BYTE*)pinfo)[hdr.size+1]) << 8);
                if (crc!=crc_ver)
                    err = LTR210_ERR_FLASH_INFO_CRC;
            }

            if (err == LTR_OK) {
                unsigned ch, range;
                memcpy(hnd->ModuleInfo.Name, pinfo->name, LTR210_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->serial, LTR210_SERIAL_SIZE);

                for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                    for (range=0; range < 8; range++) {
                        hnd->ModuleInfo.CbrCoef[ch][range] = pinfo->cbr[ch][range];
                    }
                }

                hnd->ModuleInfo.AfcCoefFreq = pinfo->AfcCoefFreq;

                for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                    for (range=0; range < 8; range++) {
                        hnd->ModuleInfo.AfcCoef[ch][range] = pinfo->AfcCoef[ch][range];
                    }
                }


                for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                    for (range=0; range < LTR210_AFC_IIR_COR_RANGE_CNT; range++) {
                        hnd->ModuleInfo.AfcIirParam[ch][range] = pinfo->AfcIirParam[ch][range];
                    }
                }
            }

            free(pinfo);
        }

        flash_iface_flush(&params->flash, err);

        if (LTR210_FPGAIsLoaded(hnd)==LTR_OK) {
            INT stop_err = f_fpga_afterload(hnd);
            if (err == LTR_OK)
                err = stop_err;
        }
    }
    return err;
}

LTR210API_DllExport(INT)  LTR210_SaveConfig(TLTR210 *hnd) {
    INT err = LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        err = ltr_module_fpga_enable(&hnd->Channel, 0);
    }

    if (err == LTR_OK) {
        t_ltr210_flash_info info, info_ver;
        unsigned ch, range;
        WORD crc, crc_ver;
        t_internal_params * params = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res, dis_res;

        info.sign = LTR210_FLASH_INFO_SIGN;
        info.size = sizeof(info);
        info.format = LTR210_FLASH_INFO_FORMAT;
        info.flags = 0;
        memcpy(info.name, hnd->ModuleInfo.Name, LTR210_NAME_SIZE);
        memcpy(info.serial, hnd->ModuleInfo.Serial, LTR210_SERIAL_SIZE);

        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
            for (range=0; range < 8; range++) {
                info.cbr[ch][range] = hnd->ModuleInfo.CbrCoef[ch][range];
            }
        }

        info.AfcCoefFreq = hnd->ModuleInfo.AfcCoefFreq;

        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
            for (range=0; range < 8; range++) {
                info.AfcCoef[ch][range] = hnd->ModuleInfo.AfcCoef[ch][range];
            }
        }

        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
            for (range=0; range < LTR210_AFC_IIR_COR_RANGE_CNT; range++) {
                info.AfcIirParam[ch][range] = hnd->ModuleInfo.AfcIirParam[ch][range];
            }
        }

        crc = eval_crc16(0, (BYTE*)&info, sizeof(info));

        flash_res = flash_iface_ltr_set_channel(&params->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = f_flash_info_unprotect(&params->flash);
        if (!flash_res)
            flash_res = flash_erase(&params->flash, LTR210_FLASH_ADDR_MODULE_INFO, 4096);
        if (!flash_res) {
            flash_res = flash_write(&params->flash, LTR210_FLASH_ADDR_MODULE_INFO,
                                           (unsigned char*)&info, sizeof(info), 0);
        }
        if (!flash_res) {
            flash_res = flash_write(&params->flash, LTR210_FLASH_ADDR_MODULE_INFO + sizeof(info),
                                           (unsigned char*)&crc, sizeof(crc), 0);
        }

        dis_res = f_flash_protect(&params->flash);
        if (!flash_res) {
            flash_res=dis_res;
        }

        if (!flash_res) {
            flash_res = flash_read(&params->flash, LTR210_FLASH_ADDR_MODULE_INFO,
                                          (unsigned char*)&info_ver, sizeof(info));
        }
        if (!flash_res) {
            flash_res = flash_read(&params->flash, LTR210_FLASH_ADDR_MODULE_INFO + sizeof(info),
                                        (unsigned char*)&crc_ver, sizeof(crc_ver));
        }

        flash_iface_flush(&params->flash, flash_res);
        err = flash_iface_ltr_conv_err(flash_res);

        if (err == LTR_OK) {
            if ((crc!=crc_ver) || memcmp(&info, &info_ver, sizeof(info)))
                err = LTR210_ERR_FLASH_INFO_VERIFY;
        }


        if (LTR210_FPGAIsLoaded(hnd)==LTR_OK) {
            INT stop_err = f_fpga_afterload(hnd);
            if (err == LTR_OK)
                err = stop_err;
        }
    }

    return err;
}


LTR210API_DllExport(INT) LTR210_GetLastWordInterval(TLTR210 *hnd, DWORD *interval) {
    INT err = interval == NULL ? LTR_ERROR_PARAMETERS : LTR210_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params* par = (t_internal_params*)hnd->Internal;
        *interval = LTIMER_CLOCK_TICKS_TO_MS(lclock_get_ticks() - par->last_clock);
    }
    return err;
}


LTR210API_DllExport(INT) LTR210_FillAdcFreq(TLTR210_CONFIG *cfg, double freq, DWORD flags, double *set_freq) {
    WORD fnd_div=0, cur_div;
    DWORD fnd_dcm=0;
    double fnd_df = fabs(freq-ADC_FREQ(fnd_div, fnd_dcm));

    for (cur_div = LTR210_ADC_FREQ_DIV_MAX; cur_div != 0 ; cur_div--) {
        DWORD cur_dcm = (DWORD)(LTR210_ADC_FREQ_HZ/(cur_div*freq) + 0.5);
        double df;

        if (cur_dcm > LTR210_ADC_DCM_CNT_MAX)
            cur_dcm = LTR210_ADC_DCM_CNT_MAX;
        if (cur_dcm==0)
            cur_dcm = 1;
        df = fabs(freq-ADC_FREQ(cur_div-1, cur_dcm-1));
        if (df < fnd_df) {
            fnd_df = df;
            fnd_div=cur_div-1;
            fnd_dcm = cur_dcm-1;
        }
    }

    if (cfg != NULL) {
        cfg->AdcDcmCnt = fnd_dcm;
        cfg->AdcFreqDiv = fnd_div;
    }

    if (set_freq) {
        *set_freq = ADC_FREQ(fnd_div, fnd_dcm);
    }

    return LTR_OK;
}

LTR210API_DllExport(INT) LTR210_FillFrameFreq(TLTR210_CONFIG *cfg, double freq,
                                              double *set_freq) {
    DWORD freq_div = (DWORD)((double)LTR210_FRAME_FREQ_HZ/freq + 0.5)-1;
    if (cfg != NULL) {
        cfg->FrameFreqDiv = freq_div;
    }
    if (set_freq != NULL) {
        *set_freq = (double)LTR210_FRAME_FREQ_HZ/(freq_div + 1);
    }
    return LTR_OK;
}


#define ZERO_MEAS_DEFAULT_SIZE  4000


LTR210API_DllExport(INT) LTR210_MeasAdcZeroOffset(TLTR210 *hnd, DWORD flags) {
    INT err = LTR210_IsOpened(hnd);    
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;

    if (err == LTR_OK) {
        unsigned ch;
        TLTR210_CONFIG oldCfg = hnd->Cfg;
        INT stop_err = LTR_OK;
        double avg[LTR210_CHANNEL_CNT] = {0,0};

        /* устанавливаем необходимую конфигурацию для измерения нуля */
        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
            hnd->Cfg.Ch[ch].Enabled = 1;
            hnd->Cfg.Ch[ch].Mode = LTR210_CH_MODE_ZERO;
        }
        hnd->Cfg.SyncMode = LTR210_SYNC_MODE_INTERNAL;
        hnd->Cfg.GroupMode = LTR210_GROUP_MODE_INDIVIDUAL;
        hnd->Cfg.AdcDcmCnt = 0;
        hnd->Cfg.AdcFreqDiv = 0;
        hnd->Cfg.FrameSize = ZERO_MEAS_DEFAULT_SIZE;
        hnd->Cfg.HistSize = 0;
        hnd->Cfg.Flags = 0;
        err = f_set_adc(hnd, 1);
        if (err == LTR_OK) {
            /* запускаем запись */
            err = LTR210_Start(hnd);
            if (err == LTR_OK) {
                DWORD evt;
                DWORD *wrds = malloc(sizeof(wrds[0])*hnd->State.RecvFrameSize);
                double *data = malloc(sizeof(data[0])*hnd->State.RecvFrameSize);
                if ((wrds==NULL) || (data==NULL)) {
                    err = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    /* вызываем программное синхрособытие для приема одного кадра */
                    err = LTR210_FrameStart(hnd);
                }

                if (err == LTR_OK) {
                    /* ждем начало кадра */
                    err = LTR210_WaitEvent(hnd, &evt, NULL, LTR_MODULE_CMD_RECV_TIMEOUT);
                    if ((err == LTR_OK) && (evt==LTR210_RECV_EVENT_TIMEOUT))
                        err = LTR210_ERR_WAIT_FRAME_TIMEOUT;
                }

                if (err == LTR_OK) {
                    INT recv_res = LTR210_Recv(hnd, wrds, NULL, hnd->State.RecvFrameSize,
                                               LTR_MODULE_CMD_RECV_TIMEOUT);
                    if (recv_res < 0) {
                        err = recv_res;
                    } else if (recv_res != (INT)hnd->State.RecvFrameSize) {
                        err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                    } else {
                        TLTR210_FRAME_STATUS status;
                        err = LTR210_ProcessData(hnd, wrds, data, &recv_res,
                                                 0, &status, NULL);
                        if ((err == LTR_OK) && (status.Result != LTR210_FRAME_RESULT_OK))
                            err = LTR210_ERR_FRAME_STATUS;
                    }

                    if (err == LTR_OK) {
                        INT i;
                        /* рассчитываем среднее по каждому каналу */

                        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                            avg[ch] = 0;
                        }
                        recv_res/=LTR210_CHANNEL_CNT;

                        for (i=0; i < recv_res; i++) {
                            for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                                avg[ch] += data[LTR210_CHANNEL_CNT*i + ch];
                            }
                        }

                        for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                            avg[ch] /= recv_res;
                        }
                    }
                }

                stop_err = LTR210_Stop(hnd);
                if (err == LTR_OK)
                    err = stop_err;

                free(wrds);
                free(data);
            }
        }

        /* восстанавливаем конфигурацию, которая была установлена до вызова функции */
        hnd->Cfg = oldCfg;
        stop_err = f_set_adc(hnd, 1);
        if (err == LTR_OK)
            err = stop_err;

        /* если все выполнено успешно, сохраняем результаты измерения */
        if (err == LTR_OK) {
            for (ch=0; ch < LTR210_CHANNEL_CNT; ch++) {
                hnd->State.AdcZeroOffset[ch] = avg[ch];
            }
        }
    }

    return err;
}
