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

#include "ltr24api.h"

#include "crc.h"
#include "devices/flash_dev_at25df.h"
#include "devices/x25/flash_dev_at25sf.h"
#include "devices/x25/flash_dev_p25q40sh.h"
#include "flash.h"
#include "ltrmodule.h"
#ifdef LTRAPI_USE_KD_STORESLOTS
    #include "ltrslot.h"
#endif
#include "phasefilter.h"
#include "ports/ltr/flash_iface_ltr.h"


/*================================================================================================*/
#ifndef M_PI
#define M_PI (3.1415926535897932384626433832795)
#endif


/* Команды модуля и битовые маски полей. */
#define LTR24_CMD_ROMIO                 0x00008060
#define LTR24_CMD_ROM_RESP              0x00008060
#define LTR24_CMD_INSTR1_RESP           0x000080E0
#define LTR24_CMD_INSTR3_RESP           0x000080C0
#define LTR24_CMD_INSTR1                0x000080E0
#define LTR24_CMD_INSTR2                0x000080D0
#define LTR24_CMD_INSTR3                0x000080C0
#define LTR24_CMD_INTSR4                0x000080F0

/* Битовые маски полей данных модуля для 20-битного формата. */
#define LTR24_DATA_20_COMN_MASK         0x0000C080
#define LTR24_DATA_20_COUNT_MASK        0x00000040
#define LTR24_DATA_20_COUNT_POS         6
#define LTR24_DATA_20_CHAN_MASK         0x00000030
#define LTR24_DATA_20_CHAN_POS          4
#define LTR24_DATA_20_HIGH_DATA         0x0000000F
#define LTR24_DATA_20_LOW_DATA          0xFFFF0000
#define LTR24_DATA_20_LOW_DATA_POS      16
#define LTR24_DATA_20_SIGN_BIT          0x00000008
#define LTR24_DATA_20_MAX_CODE          0x00100000
#define LTR24_DATA_20_MIN_NEG_CODE      0x00080000

/* Битовые маски полей данных модуля для 24-битного формата. */
#define LTR24_DATA_24_COMN_MASK         0x0000C080
#define LTR24_DATA_24_COMN              0x00000080
#define LTR24_DATA_24_LOW_MASK          0x00000040
#define LTR24_DATA_24_HIGH_ZERO_MASK    0xFE000000
#define LTR24_DATA_24_CHAN_MASK         0x00000030
#define LTR24_DATA_24_CHAN_POS          4
#define LTR24_DATA_24_COUNT_MASK        0x0000000F
#define LTR24_DATA_24_OVLOAD_MASK       0x01000000
#define LTR24_DATA_24_LOW_DATA_MASK     0xFFFF0000
#define LTR24_DATA_24_LOW_DATA_POS      16
#define LTR24_DATA_24_HIGH_DATA_MASK    0x00FF0000
#define LTR24_DATA_24_HIGH_DATA_POS     16
#define LTR24_DATA_24_SIGN_BIT          0x00800000
#define LTR24_DATA_24_MAX_CODE          0x01000000
#define LTR24_DATA_24_MIN_NEG_CODE      0x00800000

/* Битовые поля INSTR1 */
#define INSTR1_CHEN0_POS    20
#define INSTR1_24BIT_POS    24
#define INSTR1_SYNC_POS     28
#define INSTR1_FREQ_POS     16
#define INSTR1_FREQ_MASK    0x000F0000UL
#define INSTR1_FREQ(f)      (((f) << INSTR1_FREQ_POS) & \
                            INSTR1_FREQ_MASK)
#define INSTR1_SYNC         (1 << INSTR1_SYNC_POS)
#define INSTR1_CHEN(n)      ((1 << INSTR1_CHEN0_POS) << (n))
#define INSTR1_24BIT        (1 << INSTR1_24BIT_POS)

/* Битовые поля RESET_RESP */
#define RESET_MID_POS       16
#define RESET_MID_MASK      0xFFFF0000UL
#define RESET_MID(d)        (((d) & RESET_MID_MASK) >> RESET_MID_POS)

#define ICP_TEST_DATA_MUL (3.0)


#define LTR24_INTERNAL_PARAMS(hnd) ((t_internal_params *)(hnd->Internal))

#define LTR24_FIR2_COEFS_CNT 4
#define LTR24_FIR2_XVALS_CNT (2*(LTR24_FIR2_COEFS_CNT - 1))


#define LTR24_PLD_ICP_NEW_RC(pldVer) (pldVer >= 5)

#define LTR24_R1_V1             25500
#define LTR24_R1_V2             110000
#define LTR24_R1(pldVer)        (LTR24_PLD_ICP_NEW_RC(pldVer) ? LTR24_R1_V2 : LTR24_R1_V1)
#define LTR24_R2_MUL            (1./2)
#define LTR24_DEFAULT_C         (6.8e-6)
#define LTR24_DEFAULT_C_OLD     (4.44128e-6) /* вариант емкости по умолчанию для старого режима работы с включенной отсечкой */
#define LTR24_PHASE_REF_FREQ    50

#define LTR24_ICP_MUL_COEF(r1)      ((r1 + 10.)/r1)

#define LTR24_ICP_R_IN(r1) (r1)



/* Адрес дескриптора в EEPROM. */
#define LTR24_DESCR_ADDR                0
/* Количество частот в 0-ой версии прошивки ПЛИС модуля */
#define LTR24_FREQ_NUM_V0               8
/* Признак нового формата информации во Flash-памяти */
#define LTR24_FLASH_INFO_SIGN           0xA55A1818

#define LTR24_FLASH_INFO_HDR_SIZE       16
#define LTR24_FLASH_INFO_FORMAT         1

#define LTR24_FLASH_INFO_MIN_SIZE       offsetof(t_ltr24_flash_info, ISrcVals)



/* Вспомогательные макросы для проверки принятых ответов на команды. */
#define LTR24_CHECK_INSTR1_RESP(r)      \
    (((r) & 0xFFFCC0FF) != LTR24_CMD_INSTR1_RESP)
#define LTR24_CHECK_INSTR3_RESP(r)      \
    (((r) & 0xFFFFC0FF) != LTR24_CMD_INSTR3_RESP)
#define LTR24_CHECK_MID(r)              \
    (((r) >> 16) != LTR24_MODULE_ID)

#define LTR24_SEND_CMD_WITH_RESP(hnd, cmd, check) do {\
        res = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);    \
        if (res==LTR_OK) \
            res = ltr_module_recv_cmd_resp(&hnd->Channel, &cmd, 1); \
        if ((res==LTR_OK) && (check(cmd))) \
            res = LTR_ERROR_INVALID_CMD_RESPONSE; \
    } while(0)

#define GET_CALIB_INFO(hnd, pinfo, max_freq) do {\
        unsigned ch, range,freq; \
        memcpy(hnd->ModuleInfo.Name, pinfo->Name, LTR24_NAME_SIZE); \
        memcpy(hnd->ModuleInfo.Serial, pinfo->Serial, LTR24_SERIAL_SIZE); \
        for (ch=0; ch < LTR24_CHANNEL_CNT; ch++) { \
            for (range=0; range < LTR24_RANGE_NUM; range++) { \
                for (freq=0; freq < LTR24_FREQ_NUM; freq++) { \
                    hnd->ModuleInfo.CalibCoef[ch][range][freq].Offset = \
                            pinfo->CalibCoef[ch][range][freq < max_freq ? freq : max_freq-1].Offset; \
                    hnd->ModuleInfo.CalibCoef[ch][range][freq].Scale = \
                            pinfo->CalibCoef[ch][range][freq < max_freq ? freq : max_freq-1].Scale; \
                } \
            } \
        } \
    } while(0)

#define ARRAY_LEN(a) (sizeof(a)/sizeof((a)[0]))


/*================================================================================================*/
#pragma pack(4)
struct LTR24Config {
    struct {
        CHAR name[LTR24_NAME_SIZE];
        CHAR serial[LTR24_SERIAL_SIZE];
        BYTE ver_pld;
        BOOL has_icp_support;
        double current_source_outs[LTR24_CHANNEL_CNT][LTR24_I_SRC_VALUE_NUM];
        struct Calibration {
            float offset;
            float gain;
        } calibr[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM][LTR24_FREQ_NUM];
        struct AFC {
            double freq;
            double fir_coeffs[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM];
            struct {
                double a0;
                double a1;
                double b0;
            } iir_coeffs;
        } afc;
    } card_info;
    struct {
        BOOL is_running;
        BYTE samplerate_id;
        BYTE resolution;
        BYTE current_source_out;
        BOOL ison_testmode;
        struct Calibration conv_coeffs[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM][LTR24_FREQ_NUM];
        struct AFC filter;
    } card_cfg;
    struct {
        BOOL is_enabled;
        BYTE band;
        BOOL ison_dc_cut;
        BOOL ison_icp;
    } channels_cfg[LTR24_CHANNEL_CNT];
};
#pragma pack()


typedef  struct {
    double b[LTR24_FIR2_COEFS_CNT];
}  t_fir2_coefs;

typedef  struct {
    int valid_vals_cnt;
    double prev_x[LTR24_FIR2_XVALS_CNT];
} t_fir2_state;




typedef struct {
    BOOLEAN use_k;
    double k;
    t_phasefilter_state phase_flt;
    t_fir2_state fir2_flt;
} t_internal_ch_params;


typedef struct {
    double r1;
    TLTR24_ICP_PHASE_SHIFT_COEFS ICPPhaseCoefs;
    t_fir2_coefs fir2_coefs;
    BOOL last_adc_freq_valid;
    double last_adc_freq;
    /* рассчитанный коэффициент для коррекции АЧХ */
    double afc_fir_k[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM];
    double afc_fir_last[LTR24_CHANNEL_CNT]; /* действительность последних значений */

    double afc_iir_last_x[LTR24_CHANNEL_CNT][2];
    double afc_iir_last_y[LTR24_CHANNEL_CNT][2];

    t_internal_ch_params Chs[LTR24_CHANNEL_CNT];

    BOOL afc_last_valid;
    /* последнее преобразование с коррекцией АЧХ было в вольтах или кодах */
    BOOL afc_last_volts;
} t_internal_params;




/* Информация о модуле (старая версия для 8 частот). */
#pragma pack(push, 4)
typedef struct {
    CHAR Name[LTR24_NAME_SIZE];             /* Название модуля.         */
    CHAR Serial[LTR24_SERIAL_SIZE];         /* Серийный номер модуля.   */
    BYTE VerPLD;                            /* Версия прошивки ПЛИС.    */
    /* Заводские поправочные коэффициенты. */
    struct {
        float Offset;
        float Scale;
    } CalibCoef[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM][LTR24_FREQ_NUM_V0];

} t_ltr24_flash_info_v0;
#pragma pack(pop)

/* Информация о модуле (старая версия для 16 частот). */
#pragma pack(push, 1)
typedef struct {
    CHAR Name[LTR24_NAME_SIZE];
    CHAR Serial[LTR24_SERIAL_SIZE];
    BYTE VerPLD;
    struct {
        float Offset;
        float Scale;
    } CalibCoef[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM][LTR24_FREQ_NUM];
} t_ltr24_flash_info_v1;
#pragma pack(pop)

/* Информация о модуле (16 частот, коэффициенты АЧХ и измеренные значения источников тока). */
typedef struct {
    DWORD sign;
    DWORD size;
    DWORD format;
    DWORD reserv;
    CHAR Name[LTR24_NAME_SIZE];
    CHAR Serial[LTR24_SERIAL_SIZE];
    struct {
        float Offset;
        float Scale;
    } CalibCoef[LTR24_CHANNEL_CNT][LTR24_RANGE_NUM][LTR24_FREQ_NUM];
    TLTR24_AFC_COEFS AfcCoefs;
    double ISrcVals[LTR24_CHANNEL_CNT][LTR24_I_SRC_VALUE_NUM];
    /* ранее эта структура хранилась без версии и reserved. так как в ней калибровка была
     * выполнена некорректно (без отключения отсчечки), то она не используется
     * в новой версии */
    TLTR24_ICP_PHASE_SHIFT_COEFS oldICPPhase;
    struct  {
        BYTE VerPLD; /* версия pld при которой была сделана калибровка
                        (т.к. отличаются параметры входной RC-цепи) */
        BYTE Reserved[7];
        TLTR24_ICP_PHASE_SHIFT_COEFS Coefs;
    } ICPPhase;
} t_ltr24_flash_info;

#define LTR24_FLASH_HAS_INFO(pinfo, field) ((pinfo)->size >= (offsetof(t_ltr24_flash_info, field)) + sizeof((pinfo)->field))

/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR24_ERR_INVAL_FREQ, "Задана некорректная частота дискретизации"},
    {LTR24_ERR_INVAL_FORMAT, "Задан некорректный формат данных"},    
    {LTR24_ERR_CFG_UNSUP_CH_CNT, "Для заданной частоты и 24-битного формата не поддерживается установленное количество каналов"},
    {LTR24_ERR_INVAL_RANGE, "Задан некорректный диапазон канала"},
    {LTR24_ERR_WRONG_CRC, "Неверная контрольная сумма EEPROM"},
    {LTR24_ERR_VERIFY_FAILED, "Ошибка верификации записи в EEPROM"},
    {LTR24_ERR_DATA_FORMAT, "Неверный формат данных в обработанных отсчетах"},
    {LTR24_ERR_UNALIGNED_DATA, "Невыровненные данные"},
    {LTR24_ERR_DISCONT_DATA, "Сбой счетчика данных в обработанных отсчетах"},
    {LTR24_ERR_CHANNELS_DISBL, "Ни один канал не был разрешен"},
    {LTR24_ERR_UNSUP_VERS, "Версия формата управляющей структуры модуля не поддерживается"},
    {LTR24_ERR_FRAME_NOT_FOUND, "Начало кадра не найдено"},
    {LTR24_ERR_UNSUP_FLASH_FMT, "Неподдерживаемый формат информации во Flash-памяти"},
    {LTR24_ERR_INVAL_I_SRC_VALUE, "Задано некорректное значение источника тока"},
    {LTR24_ERR_UNSUP_ICP_MODE, "Данная модификация модуля не поддерживает ICP-режим"}
};

static const double f_freqs[] = {
    117.1875e3, 78.125e3, 58.59375e3, 39.0625e3,
    29.296875e3, 19.53125e3, 14.6484375e3, 9.765625e3,
    7.32421875e3, 4.8828125e3, 3.662109375e3, 2.44140625e3,
    1.8310546875e3, 1.220703125e3, 0.91552734375e3, 0.6103515625e3
};

static const t_fir2_coefs f_def_fir2_coefs = {
    { -0.000200169/2,
      0.00050819/2,
      0.000276194/2,
      0.999415785
    }
};


/*================================================================================================*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr24_to_ltr24cfg(const void *hcard, void *cfg);
static void conv_ltr24cfg_to_hltr24(const void *cfg, void *hcard);
#endif
static void f_calc_afc_k(TLTR24 *hnd);
static INT f_check_params(TLTR24 *hnd);
static void f_copy_coefs(TLTR24 *hnd);
static DWORD f_fill_instr1(TLTR24 *ltr24, BYTE sync_mode);
static DWORD f_fill_instr3(TLTR24 *hnd);

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

/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr24_to_ltr24cfg(const void *hcard, void *cfg) {
    size_t ich;
    const TLTR24 *hltr24 = hcard;
    struct LTR24Config *pcfg = cfg;

    strncpy(pcfg->card_info.name, hltr24->ModuleInfo.Name, ARRAY_LEN(pcfg->card_info.name));
    strncpy(pcfg->card_info.serial, hltr24->ModuleInfo.Serial, ARRAY_LEN(pcfg->card_info.serial));
    pcfg->card_info.ver_pld = hltr24->ModuleInfo.VerPLD;
    pcfg->card_info.has_icp_support = hltr24->ModuleInfo.SupportICP;
    pcfg->card_info.afc.freq = hltr24->ModuleInfo.AfcCoef.AfcFreq;
    pcfg->card_info.afc.iir_coeffs.a0 = hltr24->ModuleInfo.AfcCoef.AfcIirCoef.a0;
    pcfg->card_info.afc.iir_coeffs.a1 = hltr24->ModuleInfo.AfcCoef.AfcIirCoef.a1;
    pcfg->card_info.afc.iir_coeffs.b0 = hltr24->ModuleInfo.AfcCoef.AfcIirCoef.b0;
    pcfg->card_cfg.filter.freq = hltr24->AfcCoef.AfcFreq;
    pcfg->card_cfg.filter.iir_coeffs.a0 = hltr24->AfcCoef.AfcIirCoef.a0;
    pcfg->card_cfg.filter.iir_coeffs.a1 = hltr24->AfcCoef.AfcIirCoef.a1;
    pcfg->card_cfg.filter.iir_coeffs.b0 = hltr24->AfcCoef.AfcIirCoef.b0;


    pcfg->card_cfg.is_running = hltr24->Run;
    pcfg->card_cfg.samplerate_id = hltr24->ADCFreqCode;
    pcfg->card_cfg.resolution = hltr24->DataFmt;
    pcfg->card_cfg.current_source_out = hltr24->ISrcValue;
    pcfg->card_cfg.ison_testmode = hltr24->TestMode;

    for (ich = 0; (ich < LTR24_CHANNEL_CNT); ich++) {
        size_t iis;
        size_t irng;
        pcfg->channels_cfg[ich].is_enabled = hltr24->ChannelMode[ich].Enable;
        pcfg->channels_cfg[ich].band = hltr24->ChannelMode[ich].Range;
        pcfg->channels_cfg[ich].ison_dc_cut = hltr24->ChannelMode[ich].AC;
        pcfg->channels_cfg[ich].ison_icp = hltr24->ChannelMode[ich].ICPMode;

        for (irng = 0; (irng < LTR24_RANGE_NUM); irng++) {
            size_t ifq;
            pcfg->card_info.afc.fir_coeffs[ich][irng] =
                hltr24->ModuleInfo.AfcCoef.FirCoef[ich][irng];
            pcfg->card_cfg.filter.fir_coeffs[ich][irng] = hltr24->AfcCoef.FirCoef[ich][irng];



            for (ifq = 0; (ifq < LTR24_FREQ_NUM); ifq++) {
                pcfg->card_cfg.conv_coeffs[ich][irng][ifq].offset =
                    hltr24->CalibCoef[ich][irng][ifq].Offset;
                pcfg->card_cfg.conv_coeffs[ich][irng][ifq].gain =
                    hltr24->CalibCoef[ich][irng][ifq].Scale;
                pcfg->card_info.calibr[ich][irng][ifq].offset =
                    hltr24->ModuleInfo.CalibCoef[ich][irng][ifq].Offset;
                pcfg->card_info.calibr[ich][irng][ifq].gain =
                    hltr24->ModuleInfo.CalibCoef[ich][irng][ifq].Scale;
            }
        }

        for (iis = 0; (iis < LTR24_I_SRC_VALUE_NUM); iis++) {
            pcfg->card_info.current_source_outs[ich][iis] = hltr24->ModuleInfo.ISrcVals[ich][iis];
        }
    }
}

/*------------------------------------------------------------------------------------------------*/
static void conv_ltr24cfg_to_hltr24(const void *cfg, void *hcard) {
    size_t ich;
    TLTR24 *hltr24 = hcard;
    const struct LTR24Config *pcfg = cfg;

    strncpy(hltr24->ModuleInfo.Name, pcfg->card_info.name, ARRAY_LEN(hltr24->ModuleInfo.Name));
    strncpy(hltr24->ModuleInfo.Serial, pcfg->card_info.serial,
        ARRAY_LEN(hltr24->ModuleInfo.Serial));
    hltr24->ModuleInfo.VerPLD = pcfg->card_info.ver_pld;
    hltr24->ModuleInfo.SupportICP = pcfg->card_info.has_icp_support;
    hltr24->ModuleInfo.AfcCoef.AfcFreq = pcfg->card_info.afc.freq;
    hltr24->ModuleInfo.AfcCoef.AfcIirCoef.a0 = pcfg->card_info.afc.iir_coeffs.a0;
    hltr24->ModuleInfo.AfcCoef.AfcIirCoef.a1 = pcfg->card_info.afc.iir_coeffs.a1;
    hltr24->ModuleInfo.AfcCoef.AfcIirCoef.b0 = pcfg->card_info.afc.iir_coeffs.b0;
    hltr24->AfcCoef.AfcFreq = pcfg->card_cfg.filter.freq;
    hltr24->AfcCoef.AfcIirCoef.a0 = pcfg->card_cfg.filter.iir_coeffs.a0;
    hltr24->AfcCoef.AfcIirCoef.a1 = pcfg->card_cfg.filter.iir_coeffs.a1;
    hltr24->AfcCoef.AfcIirCoef.b0 = pcfg->card_cfg.filter.iir_coeffs.b0;


    hltr24->Run = pcfg->card_cfg.is_running;
    hltr24->ADCFreqCode = pcfg->card_cfg.samplerate_id;
    hltr24->ADCFreq = f_freqs[hltr24->ADCFreqCode];
    hltr24->DataFmt = pcfg->card_cfg.resolution;
    hltr24->ISrcValue = pcfg->card_cfg.current_source_out;
    hltr24->TestMode = pcfg->card_cfg.ison_testmode;
    for (ich = 0; (ich < LTR24_CHANNEL_CNT); ich++) {
        size_t iis;
        size_t irng;
        hltr24->ChannelMode[ich].Enable = pcfg->channels_cfg[ich].is_enabled;
        hltr24->ChannelMode[ich].Range = pcfg->channels_cfg[ich].band;
        hltr24->ChannelMode[ich].AC = pcfg->channels_cfg[ich].ison_dc_cut;
        hltr24->ChannelMode[ich].ICPMode = pcfg->channels_cfg[ich].ison_icp;
        for (irng = 0; (irng < LTR24_RANGE_NUM); irng++) {
            size_t ifq;
            hltr24->ModuleInfo.AfcCoef.FirCoef[ich][irng] =
                pcfg->card_info.afc.fir_coeffs[ich][irng];
            hltr24->AfcCoef.FirCoef[ich][irng] = pcfg->card_cfg.filter.fir_coeffs[ich][irng];

            for (ifq = 0; (ifq < LTR24_FREQ_NUM); ifq++) {
                hltr24->CalibCoef[ich][irng][ifq].Offset =
                    pcfg->card_cfg.conv_coeffs[ich][irng][ifq].offset;
                hltr24->CalibCoef[ich][irng][ifq].Scale =
                    pcfg->card_cfg.conv_coeffs[ich][irng][ifq].gain;
                hltr24->ModuleInfo.CalibCoef[ich][irng][ifq].Offset =
                    pcfg->card_info.calibr[ich][irng][ifq].offset;
                hltr24->ModuleInfo.CalibCoef[ich][irng][ifq].Scale =
                    pcfg->card_info.calibr[ich][irng][ifq].gain;
            }
        }
        for (iis = 0; (iis < LTR24_I_SRC_VALUE_NUM); iis++) {
            hltr24->ModuleInfo.ISrcVals[ich][iis] = pcfg->card_info.current_source_outs[ich][iis];
        }
    }

    memset(hltr24->Internal, 0, sizeof(t_internal_params));
    f_calc_afc_k(hltr24);
}
#endif

/*------------------------------------------------------------------------------------------------*/
#ifdef _WIN32
BOOL WINAPI DllMain(HINSTANCE hmod, DWORD reason, LPVOID resvd) {
    switch (reason) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
#endif

#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, unsigned status) {
    unsigned cur_status = 0, orig_status, wr_status;
    t_flash_errs flash_err;
    INT err = LTR_OK;

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

            wr_status = orig_status;
            wr_status &= ~FLASH_X25_STATUS_PROT_MASK;
            wr_status |= (status & FLASH_X25_STATUS_PROT_MASK);
            flash_err = flash_set_status_ex(flash, wr_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 void f_calc_afc_k(TLTR24 *hnd) {
    /* рассчет коэффициентов фильтров */
    t_internal_params *par = LTR24_INTERNAL_PARAMS(hnd);
    double set_freq = hnd->ADCFreq;

    /* если уже были рассчитыны коэффициенты для этой частоты, то ничего делать не нужно,
     * иначе - выполняем перерассчет
     */
    if (!par->last_adc_freq_valid || (fabs(par->last_adc_freq-set_freq) > 0.1)) {
        unsigned ch;
        for (ch = 0; (ch < LTR24_CHANNEL_CNT); ch++) {
            unsigned range;
            const double fi = hnd->ADCFreq / 4;

            for (range = 0; (range < LTR24_RANGE_NUM); range++) {
                const double a = 1.0 / hnd->AfcCoef.FirCoef[ch][range];
                const double k1 = -0.5 +
                    sqrt(0.25 - (1 - a*a)/(2 - 2*cos(2*M_PI*hnd->AfcCoef.AfcFreq/f_freqs[0])));
                const double h =
                    sqrt((1+k1)*(1+k1) - 2*(1+k1)*k1*cos(2*M_PI*fi/f_freqs[0]) + k1*k1);

                par->afc_fir_k[ch][range] = -0.5 + sqrt(0.25 - 0.5*(1 - h*h));
            }
        }

        par->last_adc_freq_valid = 1;
        par->last_adc_freq = set_freq;
    }
}


static void f_fir2_reset(t_fir2_state *st) {
    st->valid_vals_cnt = 0;
}

static double f_fir2_apply(const t_fir2_coefs *coefs, t_fir2_state *st, double x) {
    double y;
    if (st->valid_vals_cnt == 0) {
        st->valid_vals_cnt++;
        st->prev_x[5] = x;
        st->prev_x[4] = x;
        st->prev_x[3] = x;
        st->prev_x[2] = x;
        st->prev_x[1] = x;
        st->prev_x[0] = x;
    }

    y = (coefs->b[0]*(x + st->prev_x[5]) +
            coefs->b[1] *(st->prev_x[0] + st->prev_x[4]) +
            coefs->b[2] * (st->prev_x[1] + st->prev_x[3]) +
            coefs->b[3] * st->prev_x[2]);

    st->prev_x[5] = st->prev_x[4];
    st->prev_x[4] = st->prev_x[3];
    st->prev_x[3] = st->prev_x[2];
    st->prev_x[2] = st->prev_x[1];
    st->prev_x[1] = st->prev_x[0];
    st->prev_x[0] = x;
    return y;
}


static void f_rst_filters(TLTR24 *hnd) {
    t_internal_params *params = LTR24_INTERNAL_PARAMS(hnd);
    int ch_num;

    for (ch_num = 0; (ch_num < LTR24_CHANNEL_CNT); ch_num++) {
        t_internal_ch_params *ch_params = &params->Chs[ch_num];
        phasefilter_reset(&ch_params->phase_flt);
        f_fir2_reset(&ch_params->fir2_flt);
    }

    params->afc_last_valid = FALSE;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_check_params(TLTR24 *hnd) {
    /* Проверяем корректность параметров управляющей структуры. */
    INT chan, i;
    int nchan_ok;

    if (hnd->ADCFreqCode > LTR24_FREQ_610)
        return LTR24_ERR_INVAL_FREQ;
    if ((hnd->DataFmt != LTR24_FORMAT_24) && (hnd->DataFmt != LTR24_FORMAT_20))
        return LTR24_ERR_INVAL_FORMAT;
    if ((hnd->ISrcValue != LTR24_I_SRC_VALUE_2_86) && (hnd->ISrcValue != LTR24_I_SRC_VALUE_10))
        return LTR24_ERR_INVAL_I_SRC_VALUE;


    for (i = 0, chan = 0; (i < LTR24_CHANNEL_CNT); i++) {
        const int range_ok = ((hnd->ChannelMode[i].Range == LTR24_RANGE_10) ||
            (hnd->ChannelMode[i].Range == LTR24_RANGE_2));
        if (!range_ok)
            return LTR24_ERR_INVAL_RANGE;

        if (!hnd->ModuleInfo.SupportICP && hnd->ChannelMode[i].ICPMode)
            return LTR24_ERR_UNSUP_ICP_MODE;

        if (hnd->ChannelMode[i].Enable)
            chan++;
    }

    nchan_ok = ((hnd->DataFmt == LTR24_FORMAT_20) ||
        !(((hnd->ADCFreqCode == LTR24_FREQ_117K) && (chan > 2)) ||
        ((hnd->ADCFreqCode == LTR24_FREQ_78K) && (chan > 3))));

    return nchan_ok ? LTR_OK : LTR24_ERR_CFG_UNSUP_CH_CNT;
}

/*------------------------------------------------------------------------------------------------*/
static void f_copy_coefs(TLTR24 *hnd) {
    /* копирование коэффициентов из ModuleInfo в используемые */
    memcpy(hnd->CalibCoef, hnd->ModuleInfo.CalibCoef, sizeof(hnd->ModuleInfo.CalibCoef));
    memcpy(&hnd->AfcCoef, &hnd->ModuleInfo.AfcCoef, sizeof(hnd->ModuleInfo.AfcCoef));
}

/*------------------------------------------------------------------------------------------------*/
static DWORD f_fill_instr1(TLTR24 *ltr24, BYTE sync_mode) {
    /* XXXSXXXF EEEEQQQQ 1000MMMM 1110XXXX */
    DWORD ret;
    int i;

    ret = LTR24_CMD_INSTR1 | INSTR1_FREQ(ltr24->ADCFreqCode);
    if (ltr24->DataFmt == LTR24_FORMAT_24)
        ret |= INSTR1_24BIT;
    if (sync_mode)
        ret |= INSTR1_SYNC;
    for (i = 0; (i < LTR24_CHANNEL_CNT); i++) {
        if (ltr24->ChannelMode[i].Enable)
            ret |= INSTR1_CHEN(i);
    }
    return ret;
}

/*------------------------------------------------------------------------------------------------*/
static DWORD f_fill_instr3(TLTR24 *hnd) {
    unsigned ch;
    WORD cmd = 0;                           /* XXXXXXXZ BBBBDDDD 1000MMMM 1100XXXX */
    for (ch = 0; (ch < LTR24_CHANNEL_CNT); ch++) {
        /* в режиме ICP или ICP-тест всегда отключаем отсечку */
        if (hnd->ChannelMode[ch].ICPMode || !hnd->ChannelMode[ch].AC)
            cmd |= 0x0001 << ch;
        if (hnd->ChannelMode[ch].Range == LTR24_RANGE_10)
            cmd |= 0x0010 << ch;
        if (hnd->ChannelMode[ch].ICPMode)
            cmd |= 0x0200 << ch;
    }
    if (!hnd->TestMode)
        cmd |= 0x0100;
    if (hnd->ISrcValue == LTR24_I_SRC_VALUE_10)
        cmd |= 0x2000;

    return LTR_MODULE_MAKE_CMD(LTR24_CMD_INSTR3, cmd);
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Close(TLTR24 *hnd) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (hnd->Internal != NULL) {
            free(hnd->Internal);
            hnd->Internal = NULL;
        }

        err = LTR_Close(&hnd->Channel);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_FindFrameStart(TLTR24 *hnd, const DWORD *data, INT size, INT *ind) {
    INT fm_size, i;
    DWORD patt, mask;

    if (hnd == NULL)
        return LTR_ERROR_INVALID_MODULE_DESCR;

    if ((data == NULL) || (size <= 0) || (ind == NULL))
        return LTR_ERROR_PARAMETERS;

    fm_size = 0;
    patt = 0xFF;
    for (i = 0; (i < LTR24_CHANNEL_CNT); i++) {
        if (hnd->ChannelMode[i].Enable) {
            fm_size++;
            if (patt == 0xFF)
                patt = (i << 4);
        }
    }
    if (hnd->DataFmt == LTR24_FORMAT_24) {
        fm_size *= 2;
        patt |= 0x80;
        mask = 0xF0;
    } else {
        mask = 0xB0;
    }

    for (i = 0; (i < size); i++, data++) {
        if ((*data & mask) == patt) {
            *ind = i;
            return LTR_OK;
        }
    }

    return LTR24_ERR_FRAME_NOT_FOUND;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_GetConfig(TLTR24 *hnd) {

    struct {
        DWORD sign;
        DWORD size;
        DWORD format;
    } hdr;
    t_flash_iface *hflash = NULL;
    INT res = LTR24_IsOpened(hnd);

    if ((res == LTR_OK) && hnd->Run)
        res = LTR_ERROR_MODULE_STARTED;

    if (res == LTR_OK) {
        if ((hflash = calloc(1, sizeof(t_flash_iface))) == NULL)
            res = LTR_ERROR_MEMORY_ALLOC;
    }

    if (res == LTR_OK) {
        t_internal_params *internal = LTR24_INTERNAL_PARAMS(hnd);
        /* установка указателя занового, т.к. между вызовами
         * структура может быть перемещена (в .Net вообще самим framework'ом)
         */
        t_flash_errs flash_res = flash_iface_ltr_init(hflash, &hnd->Channel);
        if (!flash_res) {
            flash_res = flash_set_from_list(hflash, f_supported_flash_devs,
                                            sizeof(f_supported_flash_devs)/sizeof(f_supported_flash_devs[0]), NULL);
        }

        if (!flash_res)
            flash_res = f_flash_protect(hflash);

        if (!flash_res)
            flash_res = flash_read(hflash, LTR24_DESCR_ADDR, (BYTE *)&hdr, sizeof(hdr));

        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);

        if (res == LTR_OK) {
            WORD crc=0;
            /* старая версия информации без полей о размере и формате */
            if (hdr.sign != LTR24_FLASH_INFO_SIGN) {
                BYTE old_info[sizeof(t_ltr24_flash_info_v1)];
                memcpy(old_info, &hdr, sizeof(hdr));

                flash_res = flash_read(hflash, LTR24_DESCR_ADDR+sizeof(hdr),
                    &old_info[sizeof(hdr)], sizeof(old_info)-sizeof(hdr));
                if (!flash_res) {
                    flash_res = flash_read(hflash,
                        LTR24_DESCR_ADDR + sizeof(t_ltr24_flash_info_v1), (unsigned char *)&crc, 2);
                }

                if (flash_res) {
                    res = flash_iface_ltr_conv_err(flash_res);
                } else  {
                    unsigned ecrc = eval_crc16(0, (unsigned char *)&old_info,
                        sizeof(t_ltr24_flash_info_v1));
                    if (ecrc == crc) {
                        t_ltr24_flash_info_v1 *pinfo = (t_ltr24_flash_info_v1*)old_info;
                        GET_CALIB_INFO(hnd, pinfo, LTR24_FREQ_NUM);
                    } else {
                        crc = old_info[sizeof(t_ltr24_flash_info_v0)] +
                                (WORD)((old_info[sizeof(t_ltr24_flash_info_v0)+1])<<8);
                        ecrc = eval_crc16(0, old_info, sizeof(t_ltr24_flash_info_v0));
                        if (ecrc == crc) {
                             t_ltr24_flash_info_v0 *pinfo = (t_ltr24_flash_info_v0*)old_info;
                             GET_CALIB_INFO(hnd, pinfo, LTR24_FREQ_NUM_V0);
                        } else {
                            res = LTR24_ERR_WRONG_CRC;
                        }
                    }
                }
            } else if ((hdr.format != LTR24_FLASH_INFO_FORMAT) ||
                       (hdr.size < LTR24_FLASH_INFO_MIN_SIZE)) {
                res =  LTR24_ERR_UNSUP_FLASH_FMT;
            } else {
                t_ltr24_flash_info *pinfo = malloc(hdr.size);
                if (pinfo == NULL) {
                    res = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    memcpy(pinfo, &hdr, sizeof(hdr));
                    flash_res = flash_read(hflash, LTR24_DESCR_ADDR+sizeof(hdr),
                        &((BYTE *)pinfo)[sizeof(hdr)], hdr.size-sizeof(hdr));
                    if (!flash_res) {
                        flash_res = flash_read(hflash, LTR24_DESCR_ADDR + hdr.size,
                                               (unsigned char *)&crc, 2);
                    }
                    if (flash_res) {
                        res = flash_iface_ltr_conv_err(flash_res);
                    } else  {
                        if (eval_crc16(0, (unsigned char *)pinfo, pinfo->size) == crc) {
                            GET_CALIB_INFO(hnd, pinfo, LTR24_FREQ_NUM);
                            hnd->ModuleInfo.AfcCoef = pinfo->AfcCoefs;
                            if (LTR24_FLASH_HAS_INFO(pinfo, ISrcVals)) {
                                memcpy(hnd->ModuleInfo.ISrcVals, pinfo->ISrcVals,
                                    sizeof(pinfo->ISrcVals));
                            }

                            if (LTR24_FLASH_HAS_INFO(pinfo, ICPPhase)) {
                                if ((LTR24_PLD_ICP_NEW_RC(hnd->ModuleInfo.VerPLD) ==
                                        LTR24_PLD_ICP_NEW_RC(pinfo->ICPPhase.VerPLD))
                                        && (pinfo->ICPPhase.Coefs.PhaseShiftRefFreq > 0)) {
                                    /* исползуем калибровку фазы только если она сделана
                                     * для тех же параметров RC-цепи */
                                    memcpy(&internal->ICPPhaseCoefs, &pinfo->ICPPhase.Coefs, sizeof(pinfo->ICPPhase.Coefs));
                                }
                            }

                        } else {
                            res = LTR24_ERR_WRONG_CRC;
                        }
                    }
                    free(pinfo);
                }
            }
        }
    } /*if (res == LTR_OK)*/

    /* по завершении работы с flash-памятью нужно подать команду STOP */
    if (res == LTR_OK) {
        const DWORD out = LTR010CMD_STOP;
        res = ltr_module_send_cmd(&hnd->Channel, &out, 1);
    }

    if (res == LTR_OK)
        f_copy_coefs(hnd);

    if (hflash != NULL)
        flash_iface_ltr_close(hflash);
    free(hflash);

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(LPCSTR) LTR24_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);
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(DWORD) LTR24_GetVersion(void) {
    return LTR24_VERSION_CODE;
}


static void f_info_init(TLTR24 *hnd) {
    unsigned ch;

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

    hnd->ModuleInfo.AfcCoef.AfcFreq = 31248;

    hnd->ModuleInfo.AfcCoef.AfcIirCoef.a0 = 0.0234;
    hnd->ModuleInfo.AfcCoef.AfcIirCoef.a1 = 0.4165;
    hnd->ModuleInfo.AfcCoef.AfcIirCoef.b0 = 0.99942;

    for (ch = 0; (ch < LTR24_CHANNEL_CNT); ++ch) {
        unsigned range;
        hnd->ModuleInfo.AfcCoef.FirCoef[ch][LTR24_RANGE_2] = 0.990744024;
        hnd->ModuleInfo.AfcCoef.FirCoef[ch][LTR24_RANGE_10] = 0.988447646;

        hnd->ModuleInfo.ISrcVals[ch][LTR24_I_SRC_VALUE_2_86] = 2.86;
        hnd->ModuleInfo.ISrcVals[ch][LTR24_I_SRC_VALUE_10] = 10.0;

        for (range = 0; (range < LTR24_RANGE_NUM); ++range) {
            unsigned freq;
            for (freq = 0; (freq < LTR24_FREQ_NUM); ++freq) {
                hnd->ModuleInfo.CalibCoef[ch][range][freq].Offset = 0.0;
                hnd->ModuleInfo.CalibCoef[ch][range][freq].Scale = 1.0;
            }
        }
    }

    hnd->Run = FALSE;
}


/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Init(TLTR24 *hnd) {
    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));
        hnd->Size = sizeof(*hnd);
        hnd->Internal = NULL;
        f_info_init(hnd);
        res = LTR_Init(&hnd->Channel);
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_IsOpened(TLTR24 *hnd) {
     return (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Open(TLTR24 *hnd, DWORD addr, WORD port, const CHAR *crate_sn, INT slot) {
    return LTR24_OpenEx(hnd, addr, port, crate_sn, slot, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_OpenEx(TLTR24 *hnd, DWORD net_addr, WORD net_port, const CHAR *crate_sn,
    INT slot, DWORD in_flags, DWORD *out_flags) {
    DWORD cmd;
    INT warning;
    DWORD open_flags = 0;
    DWORD out_flg = 0;
    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;


    if (res == LTR_OK) {
        if (LTR24_IsOpened(hnd) == LTR_OK)
            LTR24_Close(hnd);
    }

    if (res == LTR_OK) {
        if ((hnd->Internal = calloc(1, sizeof(t_internal_params))) == NULL)
            res = LTR_ERROR_MEMORY_ALLOC;
    }

    if ((res == LTR_OK) && (in_flags & LTR_OPENINFLG_REOPEN)) {
#ifdef LTRAPI_USE_KD_STORESLOTS
        res = ltrslot_restore_config(hnd, net_addr, net_port, crate_sn, slot,
            sizeof(struct LTR24Config), conv_ltr24cfg_to_hltr24, &out_flg);
        if ((res == LTR_OK) && (out_flg & LTR_OPENOUTFLG_REOPEN))
                open_flags |= LTR_MOPEN_INFLAGS_DONT_RESET;
#else
        res = LTR_ERROR_NOT_IMPLEMENTED;
#endif
    }

    if (res == LTR_OK) {
        res = ltr_module_open(&hnd->Channel, net_addr, net_port, crate_sn, slot, LTR_MID_LTR24,
            &open_flags, &cmd, &warning);
    }

#ifdef LTRAPI_USE_KD_STORESLOTS
    if ((res == LTR_OK) && !(in_flags & LTR_OPENOUTFLG_REOPEN))
        res = ltrslot_stop(&hnd->Channel);
#endif
    
    if ((res == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        unsigned ch;
        t_internal_params *internal = LTR24_INTERNAL_PARAMS(hnd);
        double def_phi = 0;

        /* Устанавливаем настройки по умолчанию. */
        f_info_init(hnd);


        hnd->ModuleInfo.VerPLD = cmd & 0x1F;
        hnd->ModuleInfo.SupportICP = (cmd & 0x20) ? TRUE : FALSE;

        if (hnd->ModuleInfo.SupportICP) {
            if (LTR24_PLD_ICP_NEW_RC(hnd->ModuleInfo.VerPLD))
                hnd->ModuleInfo.SupportedFeatures |= LTR24_FEATURE_ICP_EXT_BANDWIDTH_LF;
            internal->r1 = LTR24_R1(hnd->ModuleInfo.VerPLD);
            def_phi = phasefilter_calc_phi(internal->r1, internal->r1 * LTR24_R2_MUL,
                                       LTR24_DEFAULT_C,
                                       LTR24_PHASE_REF_FREQ, 1e6);
        }

        internal->ICPPhaseCoefs.PhaseShiftRefFreq = LTR24_PHASE_REF_FREQ;

        for (ch = 0; (ch < LTR24_CHANNEL_CNT); ch++) {
            internal->ICPPhaseCoefs.PhaseShift[ch] = def_phi;
        }

        internal->fir2_coefs = f_def_fir2_coefs;

        f_copy_coefs(hnd);

        /* Проверяем, находится ли модуль в состоянии после включения питания. */
        cmd = LTR24_CMD_INSTR1;
        LTR24_SEND_CMD_WITH_RESP(hnd, cmd, LTR24_CHECK_INSTR1_RESP);
        if ((res == LTR_OK) && (cmd & 0x10000)) {
            LTRAPI_SLEEP_MS(20);
        }
    }

    if (out_flags != NULL)
        *out_flags = out_flg;

    if (res != LTR_OK)
        LTR24_Close(hnd);

    return (res == LTR_OK) ? warning : res;
}



LTR24_EXPORT(INT) LTR24_GetICPPhaseCoefs(TLTR24 *hnd, TLTR24_ICP_PHASE_SHIFT_COEFS *PhaseCoefs) {
    INT err = LTR24_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params *internal = LTR24_INTERNAL_PARAMS(hnd);
        memcpy(PhaseCoefs, &internal->ICPPhaseCoefs, sizeof(TLTR24_ICP_PHASE_SHIFT_COEFS));
    }
    return err;
}



LTR24_EXPORT(INT) LTR24_SetICPPhaseCoefs(TLTR24 *hnd, const TLTR24_ICP_PHASE_SHIFT_COEFS *PhaseCoefs) {
    INT err = LTR24_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params *internal = LTR24_INTERNAL_PARAMS(hnd);
        memcpy(&internal->ICPPhaseCoefs, PhaseCoefs, sizeof(TLTR24_ICP_PHASE_SHIFT_COEFS));
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_ProcessData(TLTR24 *hnd, const DWORD *src, double *dest, INT *size,
    DWORD flags, BOOL *ovload) {
    /*  в случае приема данных не на границе, испорченные кадры отбрасываются.
     *  --- Считаем, что сюда передаются только данные, бит данных не проверяется.
     *  Если были отброшены какие-либо данные, возвращается ошибка
     *  LTR24_ERR_COUNTER - отброшены данные из-за сбоя счетчика
     *  LTR24_ERR_UNALIGNED - отброшены невыровнянные данные
     *  LTR24_ERR_FORMAT - ошибка формата данных
     *  LTR24_ERR_CHANNEL - пришли данные не от того канала
     */

    /* Порядок прихода данных
     * ======================
     *
     *
     * Форматы слов данных
     * ===================
     *
     * Обозначения:
     * D - данные
     * M - номер слота
     * C - счетчик данных
     * N - номер канала
     * O - признак перегрузки аналогового тракта
     *
     * LTR24_DATA_20:
     * DDDDDDDD DDDDDDDD 00XXMMMM 0СNNDDDD
     * <-------1------->           2  <-3>
     *
     * 1 - младшие 16 бит отсчета
     * 2 - счетчик данных (равен 1 в каждом 15-м слове)
     * 3 - старшие 4 бита отсчета
     *
     * LTR24_DATA_24_HIGH:
     * 0000000O DDDDDDDD 00XXMMMM 10NNCCCC
     *          <---1-->           2  <-3>
     *
     * LTR24_DATA_24_LOW:
     * DDDDDDDD DDDDDDDD 00XXMMMM 11NNCCCC
     * <-------4------->           2  <-3>
     *
     * 1 - старшие 8 бит отсчета
     * 2 - признак слова с младшей частью отсчета
     * 3 - счетчик данных (от 0 до 14, для двух частей
     *     одного отсчета значение одинаково)
     * 4 - младшие 16 бит отсчета
     */
    BYTE chan_list[LTR24_CHANNEL_CNT];      /* Список номеров каналов в кадре. */
    BYTE en_ch_cnt;                          /* Количество включенных каналов. */
    INT i, j;                               /* Счетчики циклов. */
    DWORD norm;                             /* Коэффициент для перевода в вольты. */
    double offs[LTR24_CHANNEL_CNT];         /* Смещения для всех каналов. */
    t_internal_params *params;
    DWORD range[LTR24_CHANNEL_CNT];         /* Значения диапазонов в вольтах. */
    double scale[LTR24_CHANNEL_CNT];        /* Масштаб для всех каналов. */
    /* Признак пропуска оставшейся части кадра до начала следующего кадра. */
    BOOL skip;
    DWORD start_patt;                       /* Шаблон первого слова данных кадра. */
    BYTE count = 0;                         /* Текущее значение счетчика. */
    BYTE exp_chan = 0;                      /* Ожидаемый номер канала в следующем слове данных. */
    /* Ожидаемое значение счетчика в следующем слове данных. */
    BYTE exp_count = 0;
    DWORD exp_patt = 0;                     /* Шаблон ожидаемого слова данных. */
    INT res = LTR_OK;                       /* Код возврата. */
    BOOL volts = (flags & LTR24_PROC_FLAG_VOLT) ? TRUE : FALSE;

    res = LTR24_IsOpened(hnd);
    if (res != LTR_OK)
        return res;

    /* Проверяем значения параметров. */
    if (!src || !dest || !size)
        return LTR_ERROR_PARAMETERS;

    /* Определяем включенные каналы. */

    params = LTR24_INTERNAL_PARAMS(hnd);
    en_ch_cnt = 0;
    for (i = 0; (i < LTR24_CHANNEL_CNT); i++) {
        if (hnd->ChannelMode[i].Enable)
            chan_list[en_ch_cnt++] = i;
    }
    if (en_ch_cnt == 0)
        return LTR24_ERR_CHANNELS_DISBL;

    if ((flags & LTR24_PROC_FLAG_NONCONT_DATA) || (volts != params->afc_last_volts)) {
        f_rst_filters(hnd);
    }

    params->afc_last_volts = volts;


    start_patt = chan_list[0] << 4;

    if (hnd->DataFmt == LTR24_FORMAT_24) {
        DWORD code = 0;                     /* Временное хранилище отсчета. */
        BOOL exp_high = 0;                  /* Признак ожидания старшего слова данных. */
        /* Флаги перегрузки для того, чтобы не потерять информацию о перегрузке
         * при пропуске отсчетов. */
        BOOL was_ovload[LTR24_CHANNEL_CNT] = {FALSE, FALSE, FALSE, FALSE};

        /* Проверяем формат входных данных. */
        for (i = 0; (i < *size); i++) {
            if ((src[i] & LTR24_DATA_24_COMN_MASK) != LTR24_DATA_24_COMN)
                return LTR24_ERR_DATA_FORMAT;
            if (!(src[i] & LTR24_DATA_24_LOW_MASK) && (src[i] & LTR24_DATA_24_HIGH_ZERO_MASK))
                return LTR24_ERR_DATA_FORMAT;
        }

        /* Ищем начало кадра. */
        exp_count = src[0] & LTR24_DATA_24_COUNT_MASK;
        for (i = 0; (i < *size); i++) {
            if ((src[i] & (LTR24_DATA_24_CHAN_MASK | LTR24_DATA_24_LOW_MASK)) == start_patt) {
                break;
            }

            /* Контроль счетчика. */
            count = src[i] & LTR24_DATA_24_COUNT_MASK;
            if (count != exp_count)
                res = LTR24_ERR_DISCONT_DATA;
            if (src[i] & LTR24_DATA_24_LOW_MASK) exp_count = (count + 1) % 15;
            else                                 exp_count = count;

            /* Контроль перегрузки. */
            if (!(src[i] & LTR24_DATA_24_LOW_MASK) && (src[i] & LTR24_DATA_24_OVLOAD_MASK)) {
                was_ovload[(src[i] & LTR24_DATA_24_CHAN_MASK) >> LTR24_DATA_24_CHAN_POS] = TRUE;
            }
        }
        if ((i != 0) && (res == LTR_OK))
            res = LTR24_ERR_UNALIGNED_DATA;

        /* Извлекаем данные АЦП. */
        skip = TRUE;
        j = 0;
        for (; (i < *size); i++) {
            if (skip) {
                if ((src[i] & (LTR24_DATA_24_CHAN_MASK | LTR24_DATA_24_LOW_MASK)) == start_patt) {
                    skip = FALSE;
                    exp_count = src[i] & LTR24_DATA_24_COUNT_MASK;
                    exp_chan = 0;
                    exp_high = TRUE;
                    exp_patt = chan_list[0] << LTR24_DATA_24_CHAN_POS;
                } else {
                    /* Контроль перегрузки. */
                    const int ovl = (!(src[i] & LTR24_DATA_24_LOW_MASK) &&
                        (src[i] & LTR24_DATA_24_OVLOAD_MASK));
                    if (ovl) {
                        was_ovload[(src[i] & LTR24_DATA_24_CHAN_MASK) >> LTR24_DATA_24_CHAN_POS] =
                            TRUE;
                    }
                    continue;
                }
            }

            /* Контроль счетчика. */
            count = src[i] & LTR24_DATA_24_COUNT_MASK;
            if (count != exp_count) {
                res = LTR24_ERR_DISCONT_DATA;
                skip = TRUE;
                /* Откатываем неполностью заполненный выходной кадр. */
                j -= j % en_ch_cnt;
                continue;
            }
            if (src[i] & LTR24_DATA_24_LOW_MASK) exp_count = (count + 1) % 15;
            else                                 exp_count = count;

            /* Контроль шаблона. */
            if ((src[i] & (LTR24_DATA_24_CHAN_MASK | LTR24_DATA_24_LOW_MASK)) != exp_patt)
                return LTR24_ERR_DATA_FORMAT;

            /* Извлекаем данные АЦП. */
            if (exp_high) {
                code = src[i] & LTR24_DATA_24_HIGH_DATA_MASK;
                if (ovload) {
                    ovload[j] =
                        ((src[i] & LTR24_DATA_24_OVLOAD_MASK) || was_ovload[chan_list[exp_chan]]) ?
                        TRUE : FALSE;
                    was_ovload[chan_list[exp_chan]] = FALSE;
                }
            } else {
                code |= src[i] >> LTR24_DATA_24_LOW_DATA_POS;
                dest[j] = code;
                if (code & LTR24_DATA_24_SIGN_BIT)
                    dest[j] -= LTR24_DATA_24_MAX_CODE;
                j++;
            }

            /* Обновляем ожидаемые значения. */
            exp_chan = exp_high ? exp_chan : (exp_chan + 1) % en_ch_cnt;
            exp_high = !exp_high;
            exp_patt = (chan_list[exp_chan] << LTR24_DATA_24_CHAN_POS) |
                (exp_high ? 0 : LTR24_DATA_24_LOW_MASK);
        } /*for (; (i < *size); i++)*/
    } else { /*if (hnd->DataFmt == LTR24_FORMAT_24)*/
        BOOL count_lock = FALSE;            /* Признак того, что была обнаружена 1 в бите счетчика и
                                             * ведется контроль его дальнейшего состояния. */

        /* Проверяем формат входных данных. */
        for (i = 0; (i < *size); i++) {
            if (src[i] & LTR24_DATA_20_COMN_MASK)
                return LTR24_ERR_DATA_FORMAT;
        }

        /* Ищем начало кадра. */
        for (i = 0; (i < *size); i++) {
            if ((src[i] & LTR24_DATA_20_CHAN_MASK) == start_patt)
                break;

            /* Контроль счетчика. */
            if (count_lock) {
                const int count_err = (((count == 14) && !(src[i] & LTR24_DATA_20_COUNT_MASK)) ||
                    ((count != 14) && (src[i] & LTR24_DATA_20_COUNT_MASK)));
                if (count_err) {
                    res = LTR24_ERR_DISCONT_DATA;
                    count_lock = FALSE;
                } else {
                    count = (count + 1) % 15;
                }
            } else if (src[i] & LTR24_DATA_20_COUNT_MASK) {
                count_lock = TRUE;
                count = 0;
            }
        }
        if ((i != 0) && (res == LTR_OK))
            res = LTR24_ERR_UNALIGNED_DATA;

        /* Извлекаем данные АЦП. */
        skip = TRUE;
        j = 0;
        for (; (i < *size); i++) {
            if (skip) {
                if ((src[i] & LTR24_DATA_20_CHAN_MASK) == start_patt) {
                    skip = FALSE;
                    exp_chan = 0;
                    exp_patt = chan_list[0] << LTR24_DATA_20_CHAN_POS;
                } else {
                    continue;
                }
            }

            /* Контроль счетчика. */
            if (count_lock) {
                const int count_err = (((count == 14) && !(src[i] & LTR24_DATA_20_COUNT_MASK)) ||
                    ((count != 14) && (src[i] & LTR24_DATA_20_COUNT_MASK)));
                if (count_err) {
                    res = LTR24_ERR_DISCONT_DATA;
                    count_lock = FALSE;
                    skip = TRUE;
                    j -= j % en_ch_cnt;
                    continue;
                }
                count = (count + 1) % 15;
            } else if (src[i] & LTR24_DATA_20_COUNT_MASK) {
                count_lock = TRUE;
                count = 0;
            }

            /* Контроль шаблона. */
            if ((src[i] & LTR24_DATA_20_CHAN_MASK) != exp_patt)
                return LTR24_ERR_DATA_FORMAT;

            /* Извлекаем данные АЦП. */
            dest[j] = ((src[i] & LTR24_DATA_20_HIGH_DATA) << 16) |
                      (src[i] >> LTR24_DATA_20_LOW_DATA_POS);
            if (src[i] & LTR24_DATA_20_SIGN_BIT)
                dest[j] -= LTR24_DATA_20_MAX_CODE;


            /* в 20-битном режиме не можем фиксировать перегрузку */
            if (ovload != NULL)
                ovload[j] = 0;

            j++;

            /* Обновляем ожидаемые значения. */
            exp_chan = (exp_chan + 1) % en_ch_cnt;
            exp_patt = chan_list[exp_chan] << LTR24_DATA_20_CHAN_POS;
        } /*for (; (i < *size); i++)*/
    } /*if (hnd->DataFmt == LTR24_FORMAT_24)...else*/

    /* Проверяем выровненность данных. */
    if (j % en_ch_cnt != 0) {
        if (res == LTR_OK)
            res = LTR24_ERR_UNALIGNED_DATA;
        j -= j % en_ch_cnt;
    }
    *size = j;

    /* Применяем калибровочные коэффициенты. */
    if (flags & LTR24_PROC_FLAG_CALIBR) {
        for (i = 0; (i < en_ch_cnt); i++) {
            exp_chan = chan_list[i];
            offs[i] = hnd->CalibCoef[exp_chan][hnd->ChannelMode[exp_chan].Range]
                [hnd->ADCFreqCode].Offset;
            if (hnd->DataFmt == LTR24_FORMAT_20)
                offs[i] /= 16;
            scale[i] = hnd->CalibCoef[exp_chan][hnd->ChannelMode[exp_chan].Range]
                [hnd->ADCFreqCode].Scale;
            /* компенсируем, что в ICP-режиме входы немного занижают показания
             * из-за делителя */
            if (hnd->ChannelMode[chan_list[i]].ICPMode) {
                scale[i] *= LTR24_ICP_MUL_COEF(params->r1);
            }
        }
    }

    norm = (hnd->DataFmt == LTR24_FORMAT_20) ?
        LTR24_DATA_20_MIN_NEG_CODE : LTR24_DATA_24_MIN_NEG_CODE;
    for (i = 0; (i < en_ch_cnt); i++) {
        range[i] = (hnd->ChannelMode[chan_list[i]].Range == LTR24_RANGE_2) ? 2 : 10;
    }

    for (i = 0; (i < *size); i++) {
        double val = dest[i];
        unsigned ch_num;
        t_internal_ch_params *ch_params;

        exp_chan = i % en_ch_cnt;
        ch_num = chan_list[exp_chan];
        ch_params = &params->Chs[ch_num];


        if (flags & LTR24_PROC_FLAG_CALIBR) {
            val  = (val + offs[exp_chan]) * scale[exp_chan];
        }

        if (flags & LTR24_PROC_FLAG_VOLT) {
            val = val / norm * range[exp_chan];
        }

        if (ch_params->use_k) {
            val = val * ch_params->k;
        }

        if (flags & (LTR24_PROC_FLAG_AFC_COR | LTR24_PROC_FLAG_AFC_COR_EX)) {

            /* применяем FIR-фильтр для коррекции наклона */
            double x_fir = val;

            if (i == en_ch_cnt)
                params->afc_last_valid = TRUE;

            if (params->afc_last_valid) {
                val = (val - params->afc_fir_last[ch_num]) *
                        params->afc_fir_k[ch_num][hnd->ChannelMode[chan_list[exp_chan]].Range] + x_fir;
            }
            params->afc_fir_last[ch_num] = x_fir;

            if (hnd->ADCFreqCode >= LTR24_FREQ_39K) {
                if (flags & LTR24_PROC_FLAG_AFC_COR_EX) {
                    val = f_fir2_apply(&params->fir2_coefs, &ch_params->fir2_flt, val);
                } else {
                    if (params->afc_last_valid) {
                        const double x_iir = val;
                        const double a0 = hnd->AfcCoef.AfcIirCoef.a0;
                        const double a1 = hnd->AfcCoef.AfcIirCoef.a1;
                        const double b0 = hnd->AfcCoef.AfcIirCoef.b0;

                        val = -a1 * params->afc_iir_last_y[ch_num][0] - a0 * params->afc_iir_last_y[ch_num][1] +
                            (1 + a0 - b0) * x_iir + a1 * params->afc_iir_last_x[ch_num][0] +
                            b0 * params->afc_iir_last_x[ch_num][1];

                        params->afc_iir_last_x[ch_num][1] = params->afc_iir_last_x[ch_num][0];
                        params->afc_iir_last_x[ch_num][0] = x_iir;
                        params->afc_iir_last_y[ch_num][1] = params->afc_iir_last_y[ch_num][0];
                        params->afc_iir_last_y[ch_num][0] = val;
                    } else {
                        params->afc_iir_last_x[ch_num][0] = params->afc_iir_last_x[ch_num][1] =
                            params->afc_iir_last_y[ch_num][0] = params->afc_iir_last_y[ch_num][1] = val;
                    }
                }
            }
        }

        if (hnd->ChannelMode[chan_list[exp_chan]].ICPMode) {
            if (flags & LTR24_PROC_FLAG_ICP_PHASE_COR) {
                val = phasefilter_process_point(&params->Chs[ch_num].phase_flt, val);
            }
            /* для режима ICP-тест учитываем учитываем падение напряжения на этом входе,
             * умножая на константу */
            if (hnd->TestMode)
                val *= ICP_TEST_DATA_MUL;
        }
        dest[i] = val;
    } /*for (i = 0; (i < *size); i++)*/

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Recv(TLTR24 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    /* Получаем данные АЦП. */
    INT res = LTR24_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;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_RecvEx(TLTR24 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout,
    LONGLONG *time_vals) {
    INT res = LTR24_IsOpened(hnd);
    if (res == LTR_OK)
        res = LTR_RecvEx(&hnd->Channel, data, tmark, size, timeout, time_vals);

    if ((res >= 0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF))
        res = LTR_ERROR_RECV_OVERFLOW;

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_SetACMode(TLTR24 *hnd, BYTE chan, BOOL ac_mode) {
    INT res = (chan >= LTR24_CHANNEL_CNT) ? LTR_ERROR_PARAMETERS : LTR24_IsOpened(hnd);
    if ((res == LTR_OK) && !hnd->Run)
        res = LTR_ERROR_MODULE_STOPPED;
    if (res == LTR_OK)
        res = f_check_params(hnd);
    if (res == LTR_OK) {
        DWORD cmd;
        BOOL old_val = hnd->ChannelMode[chan].AC;

        hnd->ChannelMode[chan].AC = ac_mode;
        cmd = f_fill_instr3(hnd);
        res = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        /* при неудаче, восстанавливаем старое значение */
        if (res != LTR_OK)
             hnd->ChannelMode[chan].AC = old_val;
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_SetADC(TLTR24 *hnd) {
    /* Настройка АЦП. Не влияет на фазирование, если только не меняется частота дискретизации. */
    INT res = LTR24_IsOpened(hnd);

    if ((res == LTR_OK) && hnd->Run)
        res = LTR_ERROR_MODULE_STARTED;
    if (res == LTR_OK)
        res =  f_check_params(hnd);
    if (res == LTR_OK) {
        DWORD cmd[2];
        /* Определяем реальную частоту сбора данных для информации. */
        hnd->ADCFreq = f_freqs[hnd->ADCFreqCode];

        cmd[0] = f_fill_instr1(hnd, 0);
        cmd[1] = f_fill_instr3(hnd);

        /* Выполняем передачу команд и проверку правильности ответов. */
        res = ltr_module_send_cmd(&hnd->Channel, cmd, 2);
        if (res == LTR_OK)
            res = ltr_module_recv_cmd_resp(&hnd->Channel, cmd, 2);
        if ((res == LTR_OK) && (LTR24_CHECK_INSTR1_RESP(cmd[0]) || LTR24_CHECK_INSTR3_RESP(cmd[1])))
            res = LTR_ERROR_INVALID_CMD_RESPONSE;
    }

    if (res == LTR_OK) {
        BYTE ch_num;
        t_internal_params *params = LTR24_INTERNAL_PARAMS(hnd);

        f_calc_afc_k(hnd);
        for (ch_num = 0; ch_num < LTR24_CHANNEL_CNT; ch_num++) {
            t_internal_ch_params *ch_params = &params->Chs[ch_num];
            TLTR24_CHANNEL_CONFIG *ch_cfg = &hnd->ChannelMode[ch_num];

            ch_params->use_k = (ch_cfg->SensorROut > 0) && ch_cfg->ICPMode;
            if (ch_params->use_k) {
                double r_in = LTR24_ICP_R_IN(params->r1);
                ch_params->k = (r_in + (double)ch_cfg->SensorROut)/r_in;
            }
        }

    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_SetZeroMode(TLTR24 *hnd, BOOL enable) {
    INT res = LTR24_IsOpened(hnd);
    if ((res == LTR_OK) && !hnd->Run)
        res = LTR_ERROR_MODULE_STOPPED;
    if (res == LTR_OK)
        res = f_check_params(hnd);

    if (res == LTR_OK) {
        DWORD cmd;
        BOOL old_value = hnd->TestMode;

        hnd->TestMode = enable;

        cmd = f_fill_instr3(hnd);

        /* Выполняем передачу команды. */
        res = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        /* при неудаче восстанавливаем старое значение */
        if (res != LTR_OK)
            hnd->TestMode = old_value;
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Start(TLTR24 *hnd) {
    /* Запуск АЦП. */
    INT res = LTR24_IsOpened(hnd);

    if ((res == LTR_OK) && hnd->Run)
    res = LTR_ERROR_MODULE_STARTED;
    if (res == LTR_OK) {
        /* Проверяем корректность параметров управляющей структуры. */
        res = f_check_params(hnd);
    }



    if (res == LTR_OK) {
        DWORD cmd;
        /* Формируем команду для установки ведущего устройства. */
        cmd = f_fill_instr1(hnd, 1);

        /* Выполняем передачу команды и проверку правильности ответов. */
        LTR24_SEND_CMD_WITH_RESP(hnd, cmd, LTR24_CHECK_INSTR1_RESP);
        if (res == LTR_OK) {
            /* Выполняем передачу команды запуска АЦП. */
            cmd = LTR24_CMD_INSTR2;
            res = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        }

        if (res == LTR_OK) {
            BYTE ch;
            t_internal_params *params = LTR24_INTERNAL_PARAMS(hnd);
            for (ch = 0; ch < LTR24_CHANNEL_CNT; ch++) {
                if (hnd->ChannelMode[ch].Enable) {
                    phasefilter_init(&params->Chs[ch].phase_flt, params->r1, params->r1 * LTR24_R2_MUL,
                                     params->ICPPhaseCoefs.PhaseShift[ch],
                                     params->ICPPhaseCoefs.PhaseShiftRefFreq,
                                     hnd->ADCFreq);
                }
            }
            f_rst_filters(hnd);
            hnd->Run = TRUE;
        }
    }

#ifdef LTRAPI_USE_KD_STORESLOTS
    if (res == LTR_OK) {        
        res = ltrslot_start_wconfig(hnd, &hnd->Channel, sizeof(struct LTR24Config), LTR_MID_LTR24,
            conv_hltr24_to_ltr24cfg);
        if (res == LTR_ERROR_CARDSCONFIG_UNSUPPORTED)
            res = LTR_OK;
    }
#endif

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_Stop(TLTR24 *hnd) {
    /* Останов АЦП. */
    INT res = LTR24_IsOpened(hnd);
    if (res == LTR_OK) {
        DWORD cmd = LTR24_CMD_INTSR4;
        res = ltr_module_stop(&hnd->Channel, &cmd, 1, cmd, 0, 0, NULL);
    }

#ifdef LTRAPI_USE_KD_STORESLOTS
    if (res == LTR_OK)
        res = ltrslot_stop(&hnd->Channel);
#endif

    if (res == LTR_OK)
        hnd->Run = FALSE;


    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_StoreConfig(TLTR24 *hnd, TLTR_CARD_START_MODE start_mode) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    INT res = LTR24_IsOpened(hnd);
    if (res == LTR_OK) {
        res = ltrslot_store_config(hnd, &hnd->Channel, sizeof(struct LTR24Config), LTR_MID_LTR24,
            start_mode, conv_hltr24_to_ltr24cfg);
    }
    return res;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTR24_EXPORT(INT) LTR24_WriteConfig(TLTR24 *hnd) {
    WORD crc = 0, crc_read = 0;
    t_ltr24_flash_info info, rd_info;
    t_flash_iface *hflash = NULL;

    INT res = LTR24_IsOpened(hnd);

    if ((res == LTR_OK) && hnd->Run)
        res = LTR_ERROR_MODULE_STARTED;

    if (res == LTR_OK) {
        t_internal_params *internal = LTR24_INTERNAL_PARAMS(hnd);
        unsigned ch, range, freq;
        memset(&info, 0, sizeof(info));
        info.sign = LTR24_FLASH_INFO_SIGN;
        info.format = LTR24_FLASH_INFO_FORMAT;
        info.size = sizeof(info);
        memcpy(info.Name, hnd->ModuleInfo.Name, LTR24_NAME_SIZE);
        memcpy(info.Serial, hnd->ModuleInfo.Serial, LTR24_SERIAL_SIZE);
        for (ch = 0; (ch < LTR24_CHANNEL_CNT); ch++) {
            for (range = 0; (range < LTR24_RANGE_NUM); range++) {
                for (freq = 0; (freq < LTR24_FREQ_NUM); freq++) {
                    info.CalibCoef[ch][range][freq].Offset =
                        hnd->ModuleInfo.CalibCoef[ch][range][freq].Offset;
                    info.CalibCoef[ch][range][freq].Scale =
                        hnd->ModuleInfo.CalibCoef[ch][range][freq].Scale;
                }
            }
        }
        info.AfcCoefs = hnd->ModuleInfo.AfcCoef;        
        memcpy(info.ISrcVals, hnd->ModuleInfo.ISrcVals, sizeof(info.ISrcVals));
        info.ICPPhase.VerPLD = hnd->ModuleInfo.VerPLD;
        memcpy(&info.ICPPhase.Coefs, &internal->ICPPhaseCoefs, sizeof(info.ICPPhase.Coefs));

        /* старую структуру заполняем значениями по умолчанию в режиме работы старой
         * библиотеки (так как старая всегда использует старые значения емкости и конденсатора,
         * то берем их для расчета) */
        info.oldICPPhase.PhaseShiftRefFreq = LTR24_PHASE_REF_FREQ;
        for (ch = 0; ch < LTR24_CHANNEL_CNT; ch++) {
            info.oldICPPhase.PhaseShift[ch] = phasefilter_calc_phi(LTR24_R1_V1, LTR24_R1_V1*LTR24_R2_MUL,
                                                                   LTR24_DEFAULT_C_OLD,
                                                                   info.oldICPPhase.PhaseShiftRefFreq,
                                                                   1e6);
        }


        crc = eval_crc16(0, (unsigned char *) &info, sizeof(info));
    }

    if (res == LTR_OK) {
        if ((hflash = calloc(1, sizeof(t_flash_iface))) == NULL)
            res = LTR_ERROR_MEMORY_ALLOC;
    }

    if (res == LTR_OK) {
        t_flash_errs dis_res;

        t_flash_errs flash_res = flash_iface_ltr_init(hflash, &hnd->Channel);
        if (flash_res == FLASH_ERR_OK) {
            flash_res = flash_set_from_list(hflash, f_supported_flash_devs,
                                            sizeof(f_supported_flash_devs)/sizeof(f_supported_flash_devs[0]), NULL);
        }


        if (flash_res == FLASH_ERR_OK)
            flash_res = f_flash_info_unprotect(hflash);

        if (flash_res == FLASH_ERR_OK)
            flash_res = flash_erase(hflash, LTR24_DESCR_ADDR, 4096);
        if (flash_res == FLASH_ERR_OK)  {
            flash_res = flash_write(hflash, LTR24_DESCR_ADDR, (unsigned char *)&info,
                sizeof(info), 0);
        }
        if (flash_res == FLASH_ERR_OK) {
            flash_res = flash_read(hflash, LTR24_DESCR_ADDR, (unsigned char *)&rd_info,
                sizeof(rd_info));
        }
        if (flash_res == FLASH_ERR_OK) {
            if (memcmp(&info, &rd_info, sizeof(info)))
                res = LTR24_ERR_VERIFY_FAILED;
        }

        if ((flash_res == FLASH_ERR_OK) && (res == LTR_OK)) {
            flash_res = flash_write(hflash, LTR24_DESCR_ADDR+sizeof(info),
                (unsigned char *)&crc, 2, 0);
        }
        if ((flash_res == FLASH_ERR_OK) && (res == LTR_OK)) {
            flash_res = flash_read(hflash, LTR24_DESCR_ADDR + sizeof(info),
                (unsigned char *)&crc_read, 2);
        }



        dis_res = f_flash_protect(hflash);
        if (flash_res == FLASH_ERR_OK)
            flash_res = dis_res;

        if ((res == LTR_OK) && (flash_res != FLASH_ERR_OK)) {
            res = flash_iface_ltr_conv_err(flash_res);
        }

        if ((res == LTR_OK) && (crc != crc_read))
            res = LTR24_ERR_VERIFY_FAILED;
    }

    if (res == LTR_OK) {
        DWORD out = LTR010CMD_STOP;
        res = ltr_module_send_cmd(&hnd->Channel, &out, 1);
    }

    if (hflash != NULL)
        flash_iface_ltr_close(hflash);
    free(hflash);

    return res;
}
