#include "ltr216api.h"
#include "ad7176.h"
#include <string.h>
#include "ltrmodule.h"
#include "flash.h"
#include "lbitfield.h"
#include "ports/ltr/flash_iface_ltr.h"
#include "devices/flash_dev_sst26.h"
#include "ltrmodule_flash_geninfo.h"
#include "ltimer.h"
#include <string.h>
#include <math.h>
#include "crc.h"

#if (LTR216_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_SIZE) || (LTR216_NAME_SIZE != LTR_FLASH_GENINFO_NAME_SIZE)
    #error module info len mismatch
#endif


#define LTR216_UREF_MEAS_RANGES_CNT 2

#define LTR216_PROC_FLAG_MEAS_MSK          (0xF << 16)

#define LTR216_MEASSTATE_IS_ERR(state) (state >= LTR216_MEASSTATUS_ADC_OVERRANGE)

#define CMD_ROM_WR_EN                   (LTR010CMD_PROGR | 0x10)
#define CMD_NOP                         (LTR010CMD_INSTR | 0x00)
#define CMD_CT_PRELOAD                  (LTR010CMD_INSTR | 0x01)
#define CMD_CT_CTL                      (LTR010CMD_INSTR | 0x02)
#define CMD_CT_IO                       (LTR010CMD_INSTR | 0x03)
#define CMD_CT_SIZE0                    (LTR010CMD_INSTR | 0x04)
#define CMD_CT_SIZE1                    (LTR010CMD_INSTR | 0x05)
#define CMD_F_DIV0                      (LTR010CMD_INSTR | 0x06)
#define CMD_F_DIV1                      (LTR010CMD_INSTR | 0x07)
#define CMD_GO                          (LTR010CMD_INSTR | 0x08)
#define CMD_CURR                        (LTR010CMD_INSTR | 0x09)
#define CMD_BG_STEP_SIZE                (LTR010CMD_INSTR | 0x0A)
#define CMD_FPGA_VER                    (LTR010CMD_INSTR | 0x0F)
#define CMD_ADC_IO_DATA                 (LTR010CMD_INSTR | 0x10)

#define CMD_ERROR                       (LTR010CMD_INSTR | 0x0E)


#define LCH_MSK_OFFSET   (0x3FFFUL << 0)
#define LCH_MSK_BOUT     (   0x1UL << 14)
#define LCH_MSK_SEC_TBL  (   0x1UL << 15)
#define LCH_MSK_GAIN     (   0x7UL << 16)
#define LCH_MSK_CHAN     (   0xFUL << 19)
#define LCH_MSK_SW       ( 0x7FFUL << 23)

#define LTABLE_CMD_BLOCK_SIZE 128

#define RESULT_ADC_SYNC_FREQ(div) ((double)LTR216_ADC_CLOCK/((div) + 1))


#define ADC_DATA_MSK_SEC_WRD            (0x1UL << 7)
#define ADC_DATA_MSK_CNTR               (0x7UL << 4)


#define ADC_DATA_FIRST_LOW_PTR          (0xFFUL << 24)
#define ADC_DATA_FIRST_BG_TABLE         (0x1UL  <<  2)
#define ADC_DATA_FIRST_HI_DATA          (0xFFUL << 16)
#define ADC_DATA_FIRST_HIGH_PTR         (0x3UL  <<  0)


#define ADC_DATA_SEC_FLAGS              (0xFUL << 0)
#define ADC_DATA_SEC_LOW_DATA           (0xFFFFUL << 16)

#define ADC_DATA_FLAG_OVERRANGE         (0x1UL << 0)
#define ADC_DATA_FLAG_REG_ERROR         (0x1UL << 1)
#define ADC_DATA_FLAG_LOAD_POW_EXC      (0x1UL << 2)

#define ADC_REGVAL_IFMODE(alt_sync)  (((alt_sync) ? AD7176_REGBIT_IFMODE_ALT_SYNC : 0) | AD7176_REGBIT_IFMODE_DATA_STAT)


#define FLASH_SIZE_MODULE_INFO          (SST26_BLOCKL_SIZE)
#define FLASH_SIZE_MODULE_TARE_INFO     (SST26_BLOCKL_SIZE)


/* Адрес дескриптора в EEPROM. */
#define FLASH_ADDR_MODULE_INFO        (SST26_FLASH_SIZE - FLASH_SIZE_MODULE_INFO)
#define FLASH_ADDR_MODULE_TARE_INFO   (SST26_FLASH_SIZE - FLASH_SIZE_MODULE_INFO - FLASH_SIZE_MODULE_TARE_INFO)
/* Признак нового формата информации во Flash-памяти */
#define FLASH_INFO_SIGN           LTR_FLASH_GENINFO_SIGN
#define FLASH_INFO_CRC_SIZE       LTR_FLASH_GENINFO_CRC_SIZE
#define FLASH_INFO_HDR_SIZE       LTR_FLASH_GENINFO_HDR_SIZE
#define FLASH_INFO_FORMAT         1
#define FLASH_INFO_MIN_SIZE       sizeof(t_ltr216_flash_info)



#define FLASH_TARE_INFO_SIGN           0xA55A1955
#define FLASH_TARE_INFO_CRC_SIZE       2
#define FLASH_TARE_INFO_HDR_SIZE       16
#define FLASH_TARE_INFO_FORMAT         1
#define FLASH_TARE_INFO_MIN_SIZE       sizeof(t_ltr216_flash_tare_info)


#define LTR216_UABS_INTERVAL_CNT        2
#define LTR216_UREF_OPEN_INTERVAL_CNT   2


#define DAC_OFFS_TARE_RANGES_CNT 5

#define  LTABLE_SW_WORD(sw1, sw2, sw3) ((((sw1) & 0xF) << 28) | \
    (((sw2) & 0xF) << 24) | \
    (((sw3) & 1) << 23))

#define LTABLE_GET_SW(word) (word & (0x1FFUL << 23))
#define LTABLE_GET_GAIN(word) (LBITFIELD_GET(word, LCH_MSK_GAIN))
#define LTABLE_GET_DAC(word) (LBITFIELD_GET(word, LCH_MSK_OFFSET))
#define LTABLE_GET_CH(word) (LBITFIELD_GET(word, LCH_MSK_CHAN))
#define LTABLE_GET_BOUT(word) (LBITFIELD_GET(word, LCH_MSK_BOUT))
#define LTABLE_GET_BG_TBL(word) (LBITFIELD_GET(word, LCH_MSK_SEC_TBL))

#define LTABLE_SW_MAIN          LTABLE_SW_WORD(1,0,1)
#define LTABLE_SW_OFFS          LTABLE_SW_WORD(1,1,0)
#define LTABLE_SW_UREF          LTABLE_SW_WORD(4,1,0)
#define LTABLE_SW_UREF_BOUT     LTABLE_SW_WORD(2,1,0)
#define LTABLE_SW_UREF_OFFS     LTABLE_SW_WORD(4,2,0)
#define LTABLE_SW_U             LTABLE_SW_WORD(4,0,1)
#define LTABLE_SW_U_BOUT        LTABLE_SW_WORD(2,0,1)
#define LTABLE_SW_VADJ          LTABLE_SW_WORD(2,4,0)
#define LTABLE_SW_UNEG          LTABLE_SW_WORD(2,2,0)





#define LTABLE_WORD(sw, ch, gain, offs, bout) (sw | \
    (((ch) & 0xF) << 19) | \
    (((gain) & 0x7) << 16) | \
    ((offs) & 0x3FFF) | ((bout ? 1 : 0) << 14))

#define LTABLE_WORD_BG    (1 << 15)

#define LTR216_RANGE_1200 2
#define UREF_RANGE  LTR216_RANGE_1200
#define UREF_MEAS_CH  15

#define SINGLE_CH_INIT_MEAS_FREQ   30

typedef enum {
    LTR216_SET_UPDATE_STATE         = 1 << 0,
    LTR216_SET_UPDATA_USER_TABLE    = 1 << 1,
    LTR216_SET_UPDATA_FREQ          = 1 << 2,

    LTR216_SET_ALL                  = 0xFFFFFFFF
} e_LTR216_SET_INT_FLAGS;




/* Информация о модуле (16 частот, коэффициенты АЧХ и измеренные значения источников тока). */
typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module module;
    TLTR216_FABRIC_CBR cbr;
} t_ltr216_flash_info;


typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    TLTR216_TARE_CH_COEFS Channels[LTR216_CHANNELS_CNT];
} t_ltr216_flash_tare_info;




#define AVG_PROC_MAX   10000

typedef struct {
    DWORD block_size;
    DWORD cur_size;
    DWORD cur_pos;
    double vals[AVG_PROC_MAX];
    double sum;
    double result;
} t_avg_ctx ;

typedef struct {
    e_LTR216_MEAS_STATUS base_state;
    BOOLEAN value_valid;
    BOOLEAN is_open;
    BOOLEAN is_short;
} t_internal_meas_state;


typedef struct {
    struct {
        t_avg_ctx avg;        
    } vals[LTR216_UREF_MEAS_RANGES_CNT];
    BOOLEAN ok_states[LTR216_UREF_MEAS_RANGES_CNT];
    t_internal_meas_state state;
    BOOLEAN open_states[LTR216_UREF_OPEN_INTERVAL_CNT];
} t_ref_meas_state;

typedef struct {
    WORD ch_mask;
    DWORD bg_meas_msk;

    struct {
        t_avg_ctx avg;
    } offs[LTR216_RANGES_CNT];
    


    struct {        
        t_ref_meas_state uref;
        t_ref_meas_state uref_r;

        t_avg_ctx offset;
        WORD  dac_codes[LTR216_UREF_MEAS_RANGES_CNT];
        WORD open_dac_codes[LTR216_UREF_OPEN_INTERVAL_CNT];
    } ref;

    struct {
        t_internal_meas_state state;
    } uneg;

    struct {
        struct {
            WORD dac_code;            
        } intervals[LTR216_UABS_INTERVAL_CNT];

        struct {
            BOOLEAN open_states[LTR216_UABS_INTERVAL_CNT];
            BOOLEAN ok_states[LTR216_UABS_INTERVAL_CNT];
            t_internal_meas_state state;
            int last_table_check_pos;
        } chs[LTR216_CHANNELS_CNT];
    } uabs;

    WORD dac_zero_code;
    double i_amp;

    TLTR216_MEASSTATE *balance_ref_state;

    TLTR216_DATA_STATUS status;
    TLTR216_DATA_CH_STATUS ch_status_list[LTR216_CHANNELS_CNT];
} t_internal_meas_ctx;

typedef struct {
    t_flash_iface flash;
    WORD bg_tbl_ptr;
    BYTE cur_cntr;
    BOOLEAN cntr_lost;
    BOOLEAN bg_tbl_ptr_lost;
    BOOLEAN isrc_en;
    double odr;
    t_internal_meas_ctx meas_ctx;
    struct {
        BOOLEAN single_ch;
        DWORD AdcOdrCode;
        DWORD FilterType;
        DWORD SyncFreqDiv;
    } main_cfg;
} t_internal_params;

static const double f_uref_dac_cbr_vals[LTR216_UREF_MEAS_RANGES_CNT] = {-2.4, -1.5};
static const struct {
    double min;
    double max;
} f_uref_intervals[LTR216_UREF_MEAS_RANGES_CNT] = {
    {2, 3.75},
    {0.25, 2}
};

static const struct  {
    double dac_val;
    double min;
    double max;
} f_uabs_intervals[LTR216_UABS_INTERVAL_CNT] = {
    {-2.5,  1.75,  3.75},
    {  -1, -0.25,  1.75}
};

static const double f_uref_open_dac_vals[LTR216_UREF_OPEN_INTERVAL_CNT] = {-2.4, -1};


static const struct {
    BYTE ku;
    double full_range;
    double user_range;
    DWORD range;
} f_gains[] = {
    {128,   9.77/1000,      9./1000,  -1},
    {64,   19.5/1000,      18./1000,  -1},
    {32,   39.0625/1000,   35./1000,  LTR216_RANGE_35},
    {16,   78.125/1000,    70./1000,  LTR216_RANGE_70},
    {8,   156./1000,      150./1000, -1},
    {4,   312./1000,      300./1000, -1},
    {2,   625./1000,      600./1000, -1},
    {1,  1250./1000,     1200./1000, LTR216_RANGE_1200}
};

static const struct {
    double sw_min_time;
    double amp_rest_time;
} f_range_times[LTR216_CBR_RANGES_CNT] = {
    {8, 300},
    {5, 250},
    {5, 233}
};

static const struct {
    unsigned code;
    double single_odr;
    double multi_odr;
    double notch;
} filt_sinc5_pars[] = {
    { 0, 250000,    50000,    250000},
    { 1, 125000,    41667,    125000},
    { 2,  62500,    31250,     62500},
    { 3,  50000,    27778,     50000},
    { 4,  31250,    20833,     31250},
    { 5,  25000,    17857,     25000},
    { 6,  15625,    12500,     15625},
    { 7,  10000,    10000,     11905},
    { 8,   5000,     5000,      5435},
    { 9,   2500,     2500,      2604},
    {10,   1000,     1000,      1016},
    {11,    500,      500,       504},
    {12,  397.5,    397.5,       400},
    {13,    200,      200,    200.64},
    {14,  100.2,    100.2,     100.4},
    {15,  59.98,    59.98,     59.98},
    {16,  49.96,    49.96,     50.00},
    {17,     20,       20,     20.01},
    {18,  16.66,    16.66,     16.66},
    {19,     10,       10,        10},
    {20,      5,        5,         5},
};


static const struct {
    unsigned code;
    double odr;
    double db;
} filt_enh50_pars[] = {
    { 2,  27.27,  47},
    { 3,     25,  62},
    { 5,     20,  85},
    { 6,  16.67,  90},
};

/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR216_ERR_ADC_ID_CHECK, "Не удалось обнаржить микросхему АЦП"},
    {LTR216_ERR_ADC_RECV_SYNC_OVERRATE, "Частота синхронизации АЦП превысила частоту преобразования"},
    {LTR216_ERR_ADC_RECV_INT_CYCLE_ERROR, "Ошибка внутреннего цикла чтения данных с АЦП"},
    {LTR216_ERR_ADC_REGS_INTEGRITY, "Нарушена целостность регистров АЦП"},
    {LTR216_ERR_INVALID_ADC_SWMODE, "Задано неверное значение режима опроса каналов АЦП"},
    {LTR216_ERR_INVALID_FILTER_TYPE, "Задано неверное значение типа фильтра АЦП"},
    {LTR216_ERR_INVALID_ADC_ODR_CODE, "Задано неверное значение кода, определяющего скорость преобразования АЦП"},
    {LTR216_ERR_INVALID_SYNC_FDIV, "Задано неверное значение делителя частоты синхронизации АЦП"},
    {LTR216_ERR_INVALID_LCH_CNT, "Задано неверное количество логических каналов"},
    {LTR216_ERR_INVALID_ISRC_CODE, "Задан неверный код, определяющий силу тока питания датчиков"},
    {LTR216_ERR_CH_NOT_FOUND_IN_LTABLE, "Указанный канал не был найден в логической таблице"},
    {LTR216_ERR_NO_CH_ENABLED, "Ни один канал не был разрешен"},
    {LTR216_ERR_TARE_CHANNELS, "Не удалось найти действительное значение при тарировке некоторых каналов"},
    {LTR216_ERR_TOO_MANY_LTABLE_CH, "Превышено максимальное число каналов в основном цикле опроса АЦП"},
    {LTR216_ERR_TOO_MANY_LTABLE_BG_CH, "Превышено максимальное число каналов в фоновом цикле опроса АЦП"},
    {LTR216_ERR_UNSUF_SW_TIME,  "Полученное время на коммутацию меньше заданного предела"},
    {LTR216_ERR_BAD_INIT_MEAS_STATUS,  "Измеренное значение начального параметра недействительно"},
    {LTR216_ERR_INVALID_CH_RANGE,      "Задан неверный код диапазона канала АЦП"},
    {LTR216_ERR_INVALID_CH_NUM,        "Задан неверный номер физического канала АЦП"},
    {LTR216_ERR_UREF_MEAS_REQ,         "Для выполнения операции требуется измерить действительное значение напряжения Uref"}
};

LTR216API_DllExport(INT) LTR216_ReadFlashInfo(TLTR216 *hnd);

static INT set_adc(TLTR216 *hnd, DWORD flags);
static INT fill_table_offs(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, TLTR216_RAWCONFIG *rawCfg);
static INT fill_bg_table(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, DWORD ch_mask, DWORD bg_meas_msk,
                           t_internal_meas_ctx *meas_ctx, TLTR216_RAWCONFIG *rawCfg);
static DWORD ltable_fill_user_wrd(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, const TLTR216_LCHANNEL *ch_cfg);
static INT load_tables(TLTR216 *hnd);
static INT restore_user_table(TLTR216 *hnd);
static INT add_lch(TLTR216_RAWCONFIG *rawCfg, DWORD word);
static void raw_tables_init(TLTR216_RAWCONFIG *rawCfg);
static void raw_tables_finish(TLTR216_RAWCONFIG *rawCfg);


static INT f_flash_wr_en(TLTR216 *hnd) {
    DWORD cmd = CMD_ROM_WR_EN;
    return ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
}

static INT f_flash_wr_dis(TLTR216 *hnd) {
    DWORD cmd = CMD_NOP;
    return ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
}


static double conv_volts(int gain_idx, double code) {
    return (code * f_gains[gain_idx].user_range)/LTR216_ADC_SCALE_CODE_MAX;
}

static double cbr_adc(const TLTR216_CBR_VALUE *cbr, double val) {
    return cbr->Scale * (val + cbr->Offset);
}

static double cbr_dac(const TLTR216_CBR_VALUE *cbr, double val) {
    return cbr->Scale * val + cbr->Offset;
}

static double cbr_dac_inv(const TLTR216_CBR_VALUE *cbr, double val) {
    return (val - cbr->Offset)/cbr->Scale;
}

static double fabric_cbr(const TLTR216_MODULE_INFO *minfo, int gain_idx, double val) {
    return cbr_adc(&minfo->FabricCbr.Adc[f_gains[gain_idx].range], val);
}

static BYTE get_gain(DWORD range) {
    BYTE gain = 0;
    unsigned i;
    for (i = 0; i < sizeof(f_gains)/sizeof(f_gains[0]); i++) {
        if (range == f_gains[i].range) {
            gain = i;
        }
    }
    return gain;
}

static WORD get_dac_code(const TLTR216_MODULE_INFO *minfo, DWORD range, double dac_val) {
    BYTE gain = get_gain(range);
    double dac_range = 2.5/f_gains[gain].ku;
    dac_val = cbr_dac(&minfo->FabricCbr.Dac[range], dac_val);

    /* Используем 16384, а не 16383, чтобы ноль был ровным кодом и не вносить доп. погрешности
     * при нулевом значении ЦАП */
    double dac_code = (dac_val + dac_range) * 16384 / (2. *dac_range);
    if (dac_code < 0)
        dac_code = 0;
    if (dac_code > 16383)
        dac_code = 16383;

    return (WORD)(dac_code + 0.5);
}

static double conv_dac_val(TLTR216 *hnd, int gain_idx, DWORD dac_code) {
    double dac_range = 2.5/f_gains[gain_idx].ku;
    double dac_val = 2. * dac_range * dac_code / 16384 - dac_range;
    return cbr_dac_inv(&hnd->ModuleInfo.FabricCbr.Dac[f_gains[gain_idx].range], dac_val);
}

static DWORD ltable_fill_word(const TLTR216_MODULE_INFO *minfo, DWORD sw, BYTE ch, DWORD range, double dac_val, BOOLEAN bout) {
    BYTE gain = get_gain(range);
    WORD dac_code = get_dac_code(minfo, range, dac_val);
    return LTABLE_WORD(sw, ch, gain, dac_code, bout);
}


static void avg_clear(t_avg_ctx *ctx) {
    ctx->block_size = 0;
    ctx->cur_pos = 0;
    ctx->cur_size = 0;
    ctx->result = ctx->sum = 0;
}


static void avg_init(t_avg_ctx *ctx, DWORD size) {
    avg_clear(ctx);
    if (size < 1)
        size = 1;
    if (size > AVG_PROC_MAX)
        size = AVG_PROC_MAX;
    ctx->block_size = size;
}

static void avg_add_val(t_avg_ctx *ctx, double val) {
    if (ctx->block_size != 0) {
        if (ctx->cur_size == ctx->block_size) {
            double old = ctx->vals[ctx->cur_pos];
            ctx->sum += (val - old);
        } else {
            ctx->sum += val;
            ctx->cur_size++;
        }
        ctx->vals[ctx->cur_pos] = val;
        if (++ctx->cur_pos == ctx->block_size) {
            ctx->cur_pos = 0;
        }
        ctx->result = ctx->sum / ctx->cur_size;
    }
}



#define ADC_PUT_CMD_DATA(cmd, pos, data) do {    \
        cmd[pos++] = LTR_MODULE_MAKE_CMD_BYTES(CMD_ADC_IO_DATA, 0, (data)); \
        cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP, 0); \
    } while(0)

#define ADC_PUT_CMD_START_RD(cmd, pos, addr)  ADC_PUT_CMD_DATA(cmd, pos,  \
    AD7176_REGBIT_COMMS_RW | ((addr) & AD7176_REGBIT_COMMS_RA))

#define ADC_PUT_CMD_START_WR(cmd, pos, addr)  ADC_PUT_CMD_DATA(cmd, pos,  \
    ((addr) & AD7176_REGBIT_COMMS_RA))


#define ADC_PUT_CMD_END(cmd, pos)  do { \
        cmd[pos++] = LTR_MODULE_MAKE_CMD_BYTES(CMD_ADC_IO_DATA, 1, 0); \
    } while(0)

static INT adc_check_transf_ack(const DWORD *ack, unsigned ack_cnt, BYTE *data) {
    INT err = LTR_OK;
    unsigned i;
    for (i = 0; (i < ack_cnt) && (err == LTR_OK); i++) {
        if (LTR_MODULE_CMD_GET_CMDCODE(ack[i]) != CMD_ADC_IO_DATA) {
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
        } else if ((data != NULL) && (i > 0) && (i != (ack_cnt -1))) {
            data[i-1] = LTR_MODULE_CMD_GET_DATA(ack[i]) & 0xFF;
        }
    }
    return err;
}

static INT adc_check_ctio_ack(const DWORD *ack, unsigned ack_cnt, DWORD *data) {
    INT err = LTR_OK;
    unsigned i;
    for (i = 0; (i < ack_cnt) && (err == LTR_OK); i++) {
        if (LTR_MODULE_CMD_GET_CMDCODE(ack[i]) != CMD_CT_IO) {
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
        } else if (data != NULL) {
            WORD wrd = LTR_MODULE_CMD_GET_DATA(ack[i]);
            if ((i & 1) == 0) {
                data[i/2] = wrd;
            } else {
                data[i/2] |= ((DWORD)wrd << 16);
            }
        }
    }
    return err;
}


static INT adc_read_reg16(TLTR216 *hnd, BYTE addr, WORD *data) {
    INT err;
    DWORD cmd[7], ack[4];
    BYTE  resp[2];
    unsigned pos = 0;

    ADC_PUT_CMD_START_RD(cmd, pos, addr);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_END(cmd, pos);

    err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
    if (err == LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));
    }
    if (err == LTR_OK) {
        err = adc_check_transf_ack(ack, sizeof(ack)/sizeof(ack[0]), resp);
    }
    if (err == LTR_OK) {
        if (data != NULL)
            *data = (resp[0] << 8) | resp[1];
    }
    return err;
}

static INT adc_read_reg32(TLTR216 *hnd, BYTE addr, DWORD *data) {
    INT err;
    DWORD cmd[11], ack[6];
    BYTE  resp[4];
    unsigned pos = 0;

    ADC_PUT_CMD_START_RD(cmd, pos, addr);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_END(cmd, pos);

    err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
    if (err == LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));
    }
    if (err == LTR_OK) {
        err = adc_check_transf_ack(ack, sizeof(ack)/sizeof(ack[0]), resp);
    }
    if (err == LTR_OK) {
        if (data != NULL)
            *data = (resp[0] << 24) | (resp[1] << 16) | (resp[2] << 8) | resp[3];
    }
    return err;
}

static INT adc_read_reg8(TLTR216 *hnd, BYTE addr, BYTE *data) {
    INT err;
    DWORD cmd[5], ack[3];
    BYTE  resp = 0;
    unsigned pos = 0;

    ADC_PUT_CMD_START_RD(cmd, pos, addr);
    ADC_PUT_CMD_DATA(cmd, pos, 0xFF);
    ADC_PUT_CMD_END(cmd, pos);

    err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
    if (err == LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));
    }
    if (err == LTR_OK) {
        err = adc_check_transf_ack(ack, sizeof(ack)/sizeof(ack[0]), &resp);
    }
    if (err == LTR_OK) {
        *data = resp;
    }
    return err;
}

static INT adc_write_reg16(TLTR216 *hnd, BYTE addr, WORD data) {
    INT err;
    DWORD cmd[7], ack[4];
    unsigned pos = 0;

    ADC_PUT_CMD_START_WR(cmd, pos, addr);
    ADC_PUT_CMD_DATA(cmd, pos, (data >> 8) & 0xFF);
    ADC_PUT_CMD_DATA(cmd, pos, data & 0xFF);
    ADC_PUT_CMD_END(cmd, pos);

    err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
    if (err == LTR_OK)
        err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, sizeof(ack)/sizeof(ack[0]));
    if (err == LTR_OK)
        err = adc_check_transf_ack(ack, sizeof(ack)/sizeof(ack[0]), NULL);

    return err;
}

static INT adc_clear_rdy(TLTR216 *hnd) {
    INT err = LTR_OK;


    if (hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_MULTICH_SYNC) {
        /* в режиме альтернативной синхронизации АЦП может выполнять преобразование
         * и его не сбросить через SYNC. переводим для сброса преобразования
         * в режим обычного SYNC, чтобы АЦП было в известном состоянии */
        err = adc_write_reg16(hnd, AD7176_REG_IFMODE, ADC_REGVAL_IFMODE(FALSE)
                              | AD7176_REGBIT_IFMODE_REG_CHECK);

        /* если RDY было установлено уже до этого, то сбрасываем его */
        if (err == LTR_OK) {
            err = adc_read_reg32(hnd, AD7176_REG_DATA, NULL);
        }

        /* Восстанавливаем режим альтернативной синхронизации */
        if (err == LTR_OK) {
            err = adc_write_reg16(hnd, AD7176_REG_IFMODE, ADC_REGVAL_IFMODE(TRUE)
                                  | AD7176_REGBIT_IFMODE_REG_CHECK);
        }


        /* При восстановлении альтернативной синхронизации АЦП само запускает
         * одно новое преобразование без SYNC.
         * Чтобы оно не помешало  - дожидаемся его завершения */
        if (err == LTR_OK) {
            BOOLEAN rdy = 0;
            t_ltimer tmr;
            t_internal_params *params = (t_internal_params *)hnd->Internal;

            ltimer_set_ms(&tmr, (unsigned)(1000./params->odr + 200));
            while ((err == LTR_OK ) && !rdy && !ltimer_expired(&tmr)) {
                BYTE status = 0;
                err = adc_read_reg8(hnd, AD7176_REG_STATUS, &status);
                if (err == LTR_OK)
                    rdy = (status & 0x80) == 0;
            }
        }
    }

    /* вычитываем значение АЦП, чтобы сбросить RDY, чтобы не помешал следующему
       старту */
    if (err == LTR_OK) {
        err = adc_read_reg32(hnd, AD7176_REG_DATA, NULL);
    }
    return err;
}


static INT ltable_load(TLTR216 *hnd, const DWORD *lch, DWORD lch_cnt, BOOLEAN sec_tbl) {
    INT err;
    DWORD ct_cmd, ct_ack;
    ct_cmd = LTR_MODULE_MAKE_CMD(sec_tbl ? CMD_CT_SIZE1 : CMD_CT_SIZE0, lch_cnt - 1);
    err = ltr_module_send_with_echo_resps(&hnd->Channel, &ct_cmd, 1, &ct_ack);
    if (err == LTR_OK) {
        ct_cmd = LTR_MODULE_MAKE_CMD(CMD_CT_CTL, ((sec_tbl ? 1 : 0) << 15) | (1 << 14));
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &ct_cmd, 1, &ct_ack);
    }
    if (err == LTR_OK) {
        DWORD i;
        DWORD rem_cnt = lch_cnt;
        while ((err == LTR_OK) && (rem_cnt != 0)) {
            DWORD cmd[LTABLE_CMD_BLOCK_SIZE], ack[LTABLE_CMD_BLOCK_SIZE/2];
            unsigned pos = 0;
            for (i = 0; (rem_cnt > 0) && (pos < sizeof(cmd)/sizeof(cmd[0])); i++) {
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_CT_IO, (*lch & 0xFFFF));
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,   0);
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_CT_IO, ((*lch >> 16) & 0xFFFF));
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,   0);
                lch++;
                rem_cnt--;
            }
            err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
            if (err == LTR_OK)
                err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, pos/2);
            if (err == LTR_OK)
                err = adc_check_ctio_ack(ack, pos/2, NULL);
        }
    }
    return err;
}

#if 0
static INT ltable_read(TLTR216 *hnd, DWORD *lch, DWORD lch_cnt, BOOLEAN sec_tbl) {
    INT err;
    DWORD ct_cmd, ct_ack;
    ct_cmd = LTR_MODULE_MAKE_CMD(CMD_CT_CTL, ((sec_tbl ? 1 : 0) << 15));
    err = ltr_module_send_with_echo_resps(&hnd->Channel, &ct_cmd, 1, &ct_ack);
    if (err == LTR_OK) {
        DWORD i;
        DWORD rem_cnt = lch_cnt;
        while ((err == LTR_OK) && (rem_cnt != 0)) {
            DWORD cmd[LTABLE_CMD_BLOCK_SIZE], ack[LTABLE_CMD_BLOCK_SIZE/2];
            unsigned pos = 0;
            for (i = 0; (i < rem_cnt) && (pos < sizeof(cmd)/sizeof(cmd[0])); i++) {
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_CT_IO, 0);
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,   0);
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_CT_IO, 0);
                cmd[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,   0);
                rem_cnt--;
            }
            err = ltr_module_send_cmd(&hnd->Channel, cmd, pos);
            if (err == LTR_OK)
                err = ltr_module_recv_cmd_resp(&hnd->Channel, ack, pos/2);
            if (err == LTR_OK) {
                err = adc_check_ctio_ack(ack, pos/2, lch);
                lch += pos/4;
            }
        }
    }
    return err;
}
#endif

static void fill_default_cbr(TLTR216_MODULE_INFO *info) {
    unsigned range_idx;
    unsigned ch_idx;

    info->FabricCbr.ISrc.Ref.Offset = 0;
    info->FabricCbr.ISrc.Ref.Scale = 1;

    for (ch_idx = 0; ch_idx < LTR216_CHANNELS_CNT; ch_idx++) {
        info->FabricCbr.ISrc.Ch[ch_idx].Offset = 0;
        info->FabricCbr.ISrc.Ch[ch_idx].Scale = 1;
    }

    for (range_idx = 0; range_idx < LTR216_CBR_RANGES_CNT; range_idx++) {
        info->FabricCbr.Adc[range_idx].Offset = 0;
        info->FabricCbr.Adc[range_idx].Scale = 1.;
        info->FabricCbr.Dac[range_idx].Offset = 0;
        info->FabricCbr.Dac[range_idx].Scale = 1.;
    }

    for (ch_idx = 0; ch_idx < LTR216_CHANNELS_CNT; ch_idx++) {
       info->Tare[ch_idx].DacValid = FALSE;
       info->Tare[ch_idx].DacValue = 0;
       info->Tare[ch_idx].OffsetValid = FALSE;
       info->Tare[ch_idx].Offset = 0;
       info->Tare[ch_idx].ScaleValid = FALSE;
       info->Tare[ch_idx].Scale = 1;

       info->Tare[ch_idx].Cfg.ISrcCode = 0;
       info->Tare[ch_idx].Cfg.Range = LTR216_RANGE_35;
    }
}



static void f_measstate_init(TLTR216_MEASSTATE *status, BOOLEAN used) {
    memset(status, 0, sizeof(TLTR216_MEASSTATE));
    status->Status = used ? LTR216_MEASSTATUS_NOT_INIT : LTR216_MEASSTATUS_NOT_USED;
}

static void f_int_measstate_init(t_internal_meas_state *state, BOOL used) {
    state->base_state = used ? LTR216_MEASSTATUS_NOT_INIT : LTR216_MEASSTATUS_NOT_USED;
    state->is_open = state->is_short = FALSE;
    state->value_valid = FALSE;
}

static void f_calc_res_state(TLTR216_MEASSTATE *res_status, const t_internal_meas_state *state, e_LTR216_MEAS_STATUS uneg_status) {
    if (uneg_status == LTR216_MEASSTATUS_OK) {
        if (state->is_open) {
            res_status->Status = LTR216_MEASSTATUS_OPEN;
            res_status->ValueValid = FALSE;
        } else if (state->is_short) {
            res_status->Status = LTR216_MEASSTATUS_SHORT;
            res_status->ValueValid = FALSE;
        } else {
            res_status->Status = state->base_state;
            res_status->ValueValid = state->value_valid;
        }
    } else {
        res_status->Status = LTR216_MEASSTATUS_CANT_CALC;
        res_status->ValueValid = FALSE;
    }
}

static void f_calc_ucm_state(INT bg_meas, const TLTR216_DATA_STATUS *status, TLTR216_DATA_CH_STATUS *chStatus) {
    BYTE ucm = LTR216_MEASSTATUS_NOT_USED;
    BOOLEAN ucm_val_valid = FALSE;

    if (bg_meas & LTR216_BG_MEAS_CH_CM) {
        INT uref = status->Uref.Status;
        INT ux = chStatus->Ux.Status;
        if ((uref == LTR216_MEASSTATUS_OK) && (ux == LTR216_MEASSTATUS_OK)) {
            ucm = LTR216_MEASSTATUS_OK;
        } else if ((uref == LTR216_MEASSTATUS_NOT_USED) || (ux == LTR216_MEASSTATUS_NOT_USED)) {
            ucm = LTR216_MEASSTATUS_NOT_USED;
        } else if ((uref == LTR216_MEASSTATUS_NOT_INIT) || (ux == LTR216_MEASSTATUS_NOT_INIT)) {
            ucm = LTR216_MEASSTATUS_NOT_INIT;
        } else {
            ucm = LTR216_MEASSTATUS_CANT_CALC;
        }
        ucm_val_valid = status->Uref.ValueValid && chStatus->Ux.ValueValid;
    }
    chStatus->Ucm.Status = ucm;
    chStatus->Ucm.ValueValid = ucm_val_valid;
}

static INT f_prestart_module_check(TLTR216 *hnd) {
    INT err = LTR216_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Configured)
        err = LTR_ERROR_MODULE_NOT_CONFIGURED;
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    return err;
}

LTR216API_DllExport(INT) LTR216_InitDefaultModuleInfo(TLTR216_MODULE_INFO *minfo) {
    memset(minfo, 0, sizeof(TLTR216_MODULE_INFO));
    strcpy(minfo->Name, "LTR216");
    fill_default_cbr(minfo);
    return LTR_OK;
}


static void f_info_init(TLTR216 *hnd) {
    LTR216_InitDefaultModuleInfo(&hnd->ModuleInfo);
    memset(&hnd->State, 0, sizeof(hnd->State));
}


LTR216API_DllExport(INT) LTR216_Init(TLTR216 *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;
        hnd->Cfg.ShortThresholdR = LTR216_DEFAULT_SHORT_THRESH_R;
        hnd->Cfg.CableCapacityPerUnit = LTR216_DEFAULT_CABLE_CAPACITY_PER_UNIT;
        hnd->Cfg.CableLength = LTR216_DEFAULT_CABLE_LENGTH;
        hnd->Cfg.AdcMinSwTimeUs = 10;
        f_info_init(hnd);

        LTR216_FindISrcCode(&hnd->ModuleInfo.FabricCbr.ISrc, 25, &hnd->Cfg.ISrcCode, NULL);
        if (res == LTR_OK)
            res = LTR_Init(&hnd->Channel);

    }
    return res;
}



LTR216API_DllExport(INT) LTR216_IsOpened(TLTR216 *hnd) {
    return (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

LTR216API_DllExport(INT) LTR216_Open(TLTR216 *hnd, DWORD ltrd_addr, WORD ltrd_port, const CHAR *csn, INT slot) {    
    INT warning;
    DWORD i;
    DWORD cmd, ack;
    DWORD open_flags = 0;
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (err == LTR_OK) {
        if (LTR216_IsOpened(hnd) == LTR_OK)
            LTR216_Close(hnd);
    }

    if (err == LTR_OK) {
        err = ltr_module_open(&hnd->Channel, ltrd_addr, ltrd_port, csn, slot, LTR_MID_LTR216,
            &open_flags, &ack, &warning);
        if (err == LTR_OK) {
            if ((hnd->Internal = calloc(1, sizeof(t_internal_params))) == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                t_internal_params *params = (t_internal_params *)hnd->Internal;
                params->isrc_en = TRUE;

                f_info_init(hnd);
            }
        }
    }

    if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        cmd = CMD_FPGA_VER;
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (err == LTR_OK) {
            hnd->ModuleInfo.VerFPGA = LTR_MODULE_CMD_GET_DATA(ack);
        }

        if (err == LTR_OK) {
            WORD id;
            err = adc_read_reg16(hnd, AD7176_REG_ID, &id);
            if ((err == LTR_OK) && ((id & AD7176_ID_MASK) != AD7176_ID_VAL)) {
                err = LTR216_ERR_ADC_ID_CHECK;
            }

            if (err == LTR_OK) {
                err = adc_write_reg16(hnd, AD7176_REG_ADCMODE, AD7176_REGBIT_ADCMODE_REFEN |
                                      LBITFIELD_SET(AD7176_REGBIT_ADCMODE_MODE, AD7176_MODE_CONTINUOUS) |
                                      LBITFIELD_SET(AD7176_REGBIT_ADCMODE_CLOCKSEL, AD7176_CLOCKSEL_EXT_CLOCK));
            }

            if (err == LTR_OK) {
                err = adc_write_reg16(hnd, AD7176_REG_GPIOCON, AD7176_REGBIT_GPIOCON_MUX_IO |
                                      AD7176_REGBIT_GPIOCON_SYNC_EN | AD7176_REGBIT_GPIOCON_OP_EN0);
            }
            for (i = 0; (i < 4) && (err == LTR_OK); i++) {
                err = adc_write_reg16(hnd, AD7176_REG_CHMAP(i), ((i < 2) ? AD7176_REGBIT_CHMAP_CH_EN : 0) |
                                      LBITFIELD_SET(AD7176_REGBIT_CHMAP_SETUP_SEL, 0) |
                                      LBITFIELD_SET(AD7176_REGBIT_CHMAP_AINPOS, AD7176_AIN_1) |
                                      LBITFIELD_SET(AD7176_REGBIT_CHMAP_AINNEG, AD7176_AIN_0));
            }

            if (err == LTR_OK) {
                err = adc_write_reg16(hnd, AD7176_REG_SETUPCON(0), AD7176_REGBIT_SETUPCON_BIPOLAR |
                                      LBITFIELD_SET(AD7176_REGBIT_SETUPCON_REF_SEL, AD7176_REFSEL_INT_REF));
            }
        }

        if (err == LTR_OK) {            
            t_internal_params *params = (t_internal_params *)hnd->Internal;
            INT flash_res = flash_iface_ltr_init(&params->flash, &hnd->Channel);



            if (flash_res == 0)
                flash_res = flash_sst26_set(&params->flash);
            if (flash_res == 0) {
                unsigned short status;
                flash_res = flash_sst26_get_status_ex(&params->flash, &status);


                t_sst26_bpr_value bpr;
                flash_res = flash_sst26_get_bpr(&params->flash, &bpr);
                if (!flash_res) {
                    /* Проверяем, что области памяти с калибровкой и тарировкой
                     * защищены для записи */

                    t_sst26_bpr_value change_msk =
                            flash_sst26_bpr_bpr_diff_msk(FLASH_ADDR_MODULE_INFO,
                                                         FLASH_SIZE_MODULE_INFO,
                                                         bpr, 1) |
                            flash_sst26_bpr_bpr_diff_msk(FLASH_ADDR_MODULE_TARE_INFO,
                                                         FLASH_SIZE_MODULE_TARE_INFO,
                                                         bpr, 1);
                    /* если нет, то необходимо защитить.
                     * Для смены защиты требуется посылка команды WR_EN */
                    if (change_msk != 0) {
                        INT dis_res;
                        flash_res = f_flash_wr_en(hnd);
                        if (!flash_res) {
                            flash_res = flash_sst26_set_bpr(&params->flash, bpr ^ change_msk);
                        }
                        dis_res = f_flash_wr_dis(hnd);
                        if (!flash_res)
                            flash_res = dis_res;
                    }
               }

               /* проверяем, что включена защита изменения регистра bpr пином.
                * если нет, то включаем */
               if (!(status & SST26_STATUS_WPEN)) {
                    if (!flash_res) {
                        status |= SST26_STATUS_WPEN;
                        flash_res = flash_sst26_set_status_ex(&params->flash, status);
                    }
               }

               if (!flash_res) {
                   flash_res = LTR216_ReadFlashInfo(hnd);

                   LTR216_ReadTareInfo(hnd, LTR216_CHANNEL_MASK_ALL);
               }

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

    if ((err != LTR_OK) && (err != LTR_ERROR_FLASH_INFO_NOT_PRESENT) &&
            (err != LTR_ERROR_FLASH_INFO_UNSUP_FORMAT) && (err != LTR_ERROR_FLASH_INFO_CRC)) {
        LTR216_Close(hnd);
    }

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

LTR216API_DllExport(INT) LTR216_Close(TLTR216 *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;
}

static INT svc_meas_exec(TLTR216 *hnd, DWORD frames_cnt, DWORD flags, double *vals,
                         TLTR216_DATA_STATUS *status, TLTR216_DATA_CH_STATUS *ch_status) {
    INT err = LTR_OK;
    INT    frame_samples = hnd->RawCfg.FrameSize;
    INT    recv_wrd_cnt = 2*frame_samples * frames_cnt;
    DWORD  *rbuf = (DWORD*)malloc(recv_wrd_cnt*sizeof(rbuf[0]));

    if (rbuf==NULL)
        err = LTR_ERROR_MEMORY_ALLOC;

    if (err==LTR_OK)
        err = LTR216_Start(hnd);

    if (err==LTR_OK) {
        INT recvd;
        /* В таймауте учитываем время сбора запрашиваемого числа отсчетов */
        DWORD tout = 1000 + (DWORD)(1000.*frames_cnt*frame_samples/
                                         hnd->State.AdcFreq + 1);
        recvd = LTR216_Recv(hnd, rbuf, NULL, recv_wrd_cnt, tout);
        if (recvd<0) {
            err = recvd;
        } else if (recvd!=recv_wrd_cnt) {
            err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
        } else {
            err = LTR216_ProcessData(hnd, rbuf, vals, &recvd, flags, status, ch_status);
        }

    } //while (!f_out && (err==LTR_OK))

    /* по завершению останавливаем сбор, если был запущен */
    if (hnd->State.Run) {
        INT stop_err = LTR216_Stop(hnd);
        if (err==LTR_OK)
            err = stop_err;
    }

    free(rbuf);
    return err;
}

static INT svc_cfg_start(TLTR216 *hnd)  {
    INT err = LTR_OK;
    t_internal_params *params = (t_internal_params *)hnd->Internal;

    params->main_cfg.single_ch = hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT;



    if (params->main_cfg.single_ch) {
        params->main_cfg.AdcOdrCode = hnd->Cfg.AdcOdrCode;
        params->main_cfg.FilterType = hnd->Cfg.FilterType;
        params->main_cfg.SyncFreqDiv = hnd->Cfg.SyncFreqDiv;
        hnd->Cfg.AdcSwMode = LTR216_ADC_SWMODE_MULTICH_SYNC;
        hnd->Cfg.FilterType = LTR216_FILTER_SINC5_1;
        hnd->Cfg.AdcOdrCode = 0;
        /* используем частоту 50 Гц, чтобы более менее гарантированно
         * не было влияния межканального прохождения */
        LTR216_FindSyncFreqDiv(50, &hnd->Cfg.SyncFreqDiv, NULL);
        err = set_adc(hnd, LTR216_SET_UPDATA_FREQ | LTR216_SET_UPDATE_STATE);
    }
    return err;
}

static INT svc_cfg_finish(TLTR216 *hnd)  {
    INT err = LTR_OK;
    t_internal_params *params = (t_internal_params *)hnd->Internal;

    if (params->main_cfg.single_ch) {
        INT rest_err;
        hnd->Cfg.AdcSwMode = LTR216_ADC_SWMODE_SIGNLECH_CONT;
        hnd->Cfg.FilterType = params->main_cfg.FilterType;
        hnd->Cfg.AdcOdrCode = params->main_cfg.AdcOdrCode;
        hnd->Cfg.SyncFreqDiv = params->main_cfg.SyncFreqDiv;
        rest_err =  set_adc(hnd, LTR216_SET_UPDATA_FREQ | LTR216_SET_UPDATA_USER_TABLE | LTR216_SET_UPDATE_STATE);
        if (err == LTR_OK)
            err = rest_err;
    } else {
        err = restore_user_table(hnd);
    }
    return err;
}


LTR216API_DllExport(INT) LTR216_DataStatusInit(TLTR216_DATA_STATUS *status) {
    memset(status, 0, sizeof(TLTR216_DATA_STATUS));
    return LTR_OK;
}
LTR216API_DllExport(INT) LTR216_DataChannelsStatusInit(TLTR216_DATA_CH_STATUS *ch_status_list, DWORD ch_cnt) {
    memset(ch_status_list, 0, ch_cnt * sizeof(TLTR216_DATA_CH_STATUS));
    return LTR_OK;
}



LTR216API_DllExport(INT) LTR216_InitMeasParams(TLTR216 *hnd, DWORD meas,
                                               DWORD bgMeas, TLTR216_DATA_STATUS *status) {
    INT err = f_prestart_module_check(hnd);
    if (err == LTR_OK) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;
        DWORD avg_size = (DWORD)(0.3 * hnd->State.AdcFreq/hnd->RawCfg.MainLChCnt);
        if (avg_size < 1)
            avg_size = 1;

        if (meas & LTR216_INIT_MEAS_OFFS) {
            /* измерение нуля может выполняться в том же режиме, что и основные настройки
             * и не требует переключения в сервисный режим измерений */
            BYTE range_idx;

            for (range_idx = 0; range_idx < LTR216_RANGES_CNT; range_idx++) {
                avg_init(&params->meas_ctx.offs[range_idx].avg, avg_size);
            }

            raw_tables_init(&hnd->RawCfg);
            fill_table_offs(&hnd->ModuleInfo, &hnd->Cfg, &hnd->RawCfg);
            raw_tables_finish(&hnd->RawCfg);

            err = load_tables(hnd);
            if (err == LTR_OK) {
                INT restore_err = LTR_OK;

                err = svc_meas_exec(hnd, params->meas_ctx.offs[0].avg.block_size, 0, NULL, NULL, NULL);

                restore_err = restore_user_table(hnd);
                if (err == LTR_OK)
                    err = restore_err;
            }
        }

        if (meas & LTR216_INIT_MEAS_UREF) {
            BYTE range_idx;
            INT restore_err = LTR_OK;

            /* измерение UREF требует работы фоновой таблицы и переключения режима,
             * если был настроен одноканальный */
            err = svc_cfg_start(hnd);
            avg_size = 1;

            avg_init(&params->meas_ctx.ref.offset, avg_size);
            f_measstate_init(&params->meas_ctx.status.Uref, TRUE);
            f_measstate_init(&params->meas_ctx.status.UrefR, hnd->Cfg.Ch16ForUref || (hnd->Cfg.RrefWireResistance > 0));

            f_measstate_init(&params->meas_ctx.status.Uneg, TRUE);
            f_int_measstate_init(&params->meas_ctx.uneg.state, TRUE);


            for (range_idx = 0; range_idx < LTR216_UREF_MEAS_RANGES_CNT; range_idx++) {
                avg_init(&params->meas_ctx.ref.uref.vals[range_idx].avg, avg_size);
                avg_init(&params->meas_ctx.ref.uref_r.vals[range_idx].avg, avg_size);
            }

            raw_tables_init(&hnd->RawCfg);



            err = fill_bg_table(&hnd->ModuleInfo, &hnd->Cfg, 0,
                          LTR216_BG_MEAS_UNEG |
                          LTR216_BG_MEAS_UREF | LTR216_BG_MEAS_UREF_OFFS |
                          (bgMeas & LTR216_BG_MEASGROUP_UREF_CHECK),
                          &params->meas_ctx, &hnd->RawCfg);

            raw_tables_finish(&hnd->RawCfg);

            if (err == LTR_OK)
                err = load_tables(hnd);
            if (err == LTR_OK) {
                err = svc_meas_exec(hnd, hnd->RawCfg.BgStepsCnt, 0, NULL, status, NULL);
            }

            restore_err = svc_cfg_finish(hnd);
            if (err == LTR_OK)
                err = restore_err;

            if (err == LTR_OK) {
                /* проверяем действительность полученного значения Uref */
                if (!params->meas_ctx.status.Uref.ValueValid ||
                        (hnd->Cfg.Ch16ForUref && !params->meas_ctx.status.UrefR.ValueValid)) {
                    err = LTR216_ERR_BAD_INIT_MEAS_STATUS;
                }
            }
        }
    }

    return err;
}


LTR216API_DllExport(INT) LTR216_CheckMeasStatus(TLTR216 *hnd, DWORD measList, TLTR216_DATA_STATUS *status, TLTR216_DATA_CH_STATUS *ch_status_list) {
    INT err = LTR216_IsOpened(hnd);
    TLTR216_CONFIG cfg = hnd->Cfg;
    BOOL measListChanged = (measList != hnd->Cfg.BgMeas) || (hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT);
    if (measListChanged) {
        hnd->Cfg.BgMeas = measList;
        err = LTR216_SetADC(hnd);
    }

    if (err == LTR_OK) {
        err = svc_meas_exec(hnd, hnd->RawCfg.BgStepsCnt, 0, NULL, status, ch_status_list);
    }

    if (measListChanged) {
        INT restore_err;
        hnd->Cfg = cfg;
        restore_err = LTR216_SetADC(hnd);
        if (err == LTR_OK)
            err = restore_err;
    }
    return err;
}


LTR216API_DllExport(INT) LTR216_TareOffset(TLTR216 *hnd, DWORD ch_mask, DWORD *ch_ok) {
    DWORD ok_ch_mask = 0;
    DWORD req_ch_mask = 0;
    INT err = f_prestart_module_check(hnd);

    if (err == LTR_OK) {
        INT restore_err = LTR_OK;
        err = svc_cfg_start(hnd);

        if (err == LTR_OK) {
            DWORD lch;
            BYTE ch;
            static const double dac_mul_ranges[DAC_OFFS_TARE_RANGES_CNT] = {-2, -1, 0, 1, 2};
            INT ch_nums[LTR216_CHANNELS_CNT];
            e_LTR216_RANGE ch_ranges[LTR216_CHANNELS_CNT];
            DWORD tare_ch_cnt = 0;
            BOOLEAN tare_en = hnd->Cfg.TareEnabled;

            /* запрещаем использование тарировки. Коэф. шаклы не должен влиять,
             * а смещение нуля вычисляется новое */
            hnd->Cfg.TareEnabled = FALSE;


            /* На первом этапе вычисляем подстроичные значения ЦАП */
            raw_tables_init(&hnd->RawCfg);

            /* находим разрешенные каналы */
            for (ch = 0; (ch < LTR216_CHANNELS_CNT) && (err == LTR_OK); ch++) {
                if ((1 << ch) & ch_mask) {
                    int fnd_lch = -1;
                    for (lch = 0; (fnd_lch < 0) && (lch < hnd->Cfg.LChCnt); lch++) {
                        if (hnd->Cfg.LChTbl[lch].PhyChannel == ch) {
                            fnd_lch = lch;
                        }
                    }

                    if (fnd_lch >= 0) {
                        /* сбрасываем действительность калибровки */
                        DWORD dac_mul_idx;
                        TLTR216_TARE_CH_COEFS *tare = &hnd->ModuleInfo.Tare[ch];

                        ch_ranges[tare_ch_cnt] = tare->Cfg.Range = hnd->Cfg.LChTbl[fnd_lch].Range;
                        ch_nums[tare_ch_cnt++] = ch;

                        req_ch_mask |= (1 << ch);

                        tare->OffsetValid =  tare->DacValid = FALSE;
                        tare->Cfg.ISrcCode = hnd->Cfg.ISrcCode;

                        /* на каждый канал создаем по DAC_OFFS_TARE_RANGES_CNT логических каналов
                         * со своим диапазоном ЦАП. Используем пять смещений, чтобы диапазоны
                         * пересекались и в любом случае поймать измерение не на самой границе диапазона*/
                        for (dac_mul_idx = 0; (dac_mul_idx < DAC_OFFS_TARE_RANGES_CNT) && (err == LTR_OK); dac_mul_idx++) {
                            err = add_lch(&hnd->RawCfg, ltable_fill_word(
                                        &hnd->ModuleInfo, LTABLE_SW_MAIN, hnd->Cfg.LChTbl[fnd_lch].PhyChannel,
                                        hnd->Cfg.LChTbl[fnd_lch].Range,
                                        dac_mul_ranges[dac_mul_idx]*f_gains[get_gain(hnd->Cfg.LChTbl[fnd_lch].Range)].full_range,
                                        FALSE));
                        }
                    }
                }
            }

            if (err == LTR_OK) {
                hnd->RawCfg.UserChCnt = hnd->RawCfg.ProcChCnt = hnd->RawCfg.MainLChCnt;
                raw_tables_finish(&hnd->RawCfg);
                if (hnd->RawCfg.UserChCnt == 0) {
                    err = LTR216_ERR_NO_CH_ENABLED;
                }
            }


            if (err == LTR_OK) {
                INT frames_cnt, recv_vals_cnt;
                double *rvals;
                TLTR216_DATA_CH_STATUS *ch_st;
                frames_cnt = (WORD)(1. * hnd->State.AdcFreq/hnd->RawCfg.MainLChCnt);
                if (frames_cnt < 1)
                    frames_cnt = 1;

                recv_vals_cnt = hnd->RawCfg.UserChCnt * frames_cnt;

                rvals = (double*)malloc(recv_vals_cnt*sizeof(rvals[0]));
                ch_st = (TLTR216_DATA_CH_STATUS *)calloc(1, hnd->RawCfg.UserChCnt * sizeof(ch_st[0]));
                if ((rvals == NULL) || (ch_st == NULL)) {
                    err = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    err = load_tables(hnd);
                    if (err == LTR_OK)
                        err = svc_meas_exec(hnd, frames_cnt, LTR216_PROC_FLAG_MEAS_UDIFF, rvals, NULL, ch_st);
                    if (err == LTR_OK) {
                        for (ch = 0; ch < tare_ch_cnt; ch++) {
                            /* рассчитываем среднее значение АЦП для каждого значения ЦАП */
                            double avg_vals[DAC_OFFS_TARE_RANGES_CNT] = {0,0,0,0,0};
                            DWORD dac_mul_idx;
                            INT frame_idx;
                            INT fnd_dacmul_idx = -1;
                            double fnd_val = 0;


                            for (frame_idx = 0; frame_idx < frames_cnt; frame_idx++) {
                                DWORD start_lch = (ch + frame_idx * tare_ch_cnt) * DAC_OFFS_TARE_RANGES_CNT;
                                for (dac_mul_idx = 0; dac_mul_idx < DAC_OFFS_TARE_RANGES_CNT; dac_mul_idx++)
                                     avg_vals[dac_mul_idx] += rvals[start_lch + dac_mul_idx];
                            }

                            /* находим самый близкий к значению диапазон ЦАП,
                             * т.е. где значение было ближе всего к нулю.
                             * При этом проверяем, что не было зашкала ЦАП.
                             * Если везде зашкал, то откалибровать смещение невозможно */
                            for (dac_mul_idx = 0; dac_mul_idx < DAC_OFFS_TARE_RANGES_CNT; dac_mul_idx++) {
                                avg_vals[dac_mul_idx]/=frames_cnt;
                                if (!(ch_st[ch*DAC_OFFS_TARE_RANGES_CNT + dac_mul_idx].StatusFlags &
                                        LTR216_CH_STATUS_FLAG_OVERRANGE)) {
                                    if ((fnd_dacmul_idx < 0) || (fabs(avg_vals[dac_mul_idx]) < fabs(fnd_val))) {
                                        fnd_dacmul_idx = dac_mul_idx;
                                        fnd_val = avg_vals[dac_mul_idx];
                                    }
                                }
                            }


                            if (fnd_dacmul_idx >= 0) {
                                TLTR216_TARE_CH_COEFS *tare = &hnd->ModuleInfo.Tare[ch_nums[ch]];
                                ok_ch_mask |= 1 << ch_nums[ch];
                                /* сохраняем нужное значение на ЦАП как выставленное
                                 * значение + значение для компенсации измеренного
                                 * напряжения с АЦП */
                                tare->DacValue = dac_mul_ranges[fnd_dacmul_idx]*
                                        f_gains[get_gain(ch_ranges[ch])].full_range - fnd_val;
                                tare->DacValid = TRUE;
                            } else {
                                ok_ch_mask &= ~(1 << ch_nums[ch]);
                            }
                        }
                    }


                    /* Второй этап - тонкая калибровка смещения коэффициентом */
                    if (err == LTR_OK) {
                        raw_tables_init(&hnd->RawCfg);

                        for (ch = 0; (ch < LTR216_CHANNELS_CNT) && (err == LTR_OK); ch++) {
                            if (ok_ch_mask & (1 << ch)) {
                                ch_nums[hnd->RawCfg.MainLChCnt]  = ch;
                                err = add_lch(&hnd->RawCfg, ltable_fill_word(
                                                 &hnd->ModuleInfo, LTABLE_SW_MAIN, ch,
                                                 hnd->ModuleInfo.Tare[ch].Cfg.Range,
                                                hnd->ModuleInfo.Tare[ch].DacValue,
                                                FALSE));
                            }
                        }
                        if (err == LTR_OK) {
                            hnd->RawCfg.UserChCnt = hnd->RawCfg.ProcChCnt = hnd->RawCfg.MainLChCnt;
                            raw_tables_finish(&hnd->RawCfg);
                            if (hnd->RawCfg.UserChCnt != 0) {
                                err = load_tables(hnd);
                                if (err == LTR_OK) {
                                    err = svc_meas_exec(hnd, frames_cnt, LTR216_PROC_FLAG_MEAS_UDIFF, rvals, NULL, ch_st);
                                }

                                if (err == LTR_OK) {
                                    for (ch = 0; ch < hnd->RawCfg.UserChCnt; ch++) {
                                        INT frame_idx;
                                        double offs = 0;
                                        for (frame_idx = 0; frame_idx < frames_cnt; frame_idx++) {
                                            offs += rvals[ch + frame_idx * hnd->RawCfg.UserChCnt];
                                        }
                                        offs /= frames_cnt;
                                        hnd->ModuleInfo.Tare[ch_nums[ch]].Offset = offs;
                                        hnd->ModuleInfo.Tare[ch_nums[ch]].OffsetValid = TRUE;
                                    }
                                }
                            }
                        }
                    }

                    free(rvals);
                    free(ch_st);
                }
            }

            hnd->Cfg.TareEnabled = tare_en;
        }

        restore_err = svc_cfg_finish(hnd);
        if (err == LTR_OK)
            err = restore_err;
    }

    if (ch_ok != NULL)
        *ch_ok = ok_ch_mask;

    if ((err == LTR_OK) && (ok_ch_mask != req_ch_mask)) {
        err = LTR216_ERR_TARE_CHANNELS;
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_TareScale(TLTR216 *hnd, DWORD ch_mask, DWORD *ch_ok) {
    DWORD ok_ch_mask = 0;
    DWORD req_ch_mask = 0;
    INT err = f_prestart_module_check(hnd);

    if (err == LTR_OK) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;
        if ((params->meas_ctx.balance_ref_state == NULL) ||
                !params->meas_ctx.balance_ref_state->ValueValid) {
            err = LTR216_ERR_UREF_MEAS_REQ;
        }
    }

    if (err == LTR_OK) {
        INT restore_err = LTR_OK;
        err = svc_cfg_start(hnd);

        if (err == LTR_OK) {
            DWORD ch, lch;
            INT ch_nums[LTR216_CHANNELS_CNT];
            DWORD tare_ch_cnt = 0;
            BOOLEAN tare_en = hnd->Cfg.TareEnabled;
            BOOLEAN ch_scale_valid[LTR216_CHANNELS_CNT];


            /* необходимо учитывать тарировку нуля, при этом тарирвка шкалы
             * применяться не должна */
            hnd->Cfg.TareEnabled = TRUE;
            for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++) {
                ch_scale_valid[ch] = hnd->ModuleInfo.Tare[ch].ScaleValid;
                hnd->ModuleInfo.Tare[ch].ScaleValid = FALSE;
            }




            raw_tables_init(&hnd->RawCfg);

            /* находим разрешенные каналы */
            for (ch = 0; (ch < LTR216_CHANNELS_CNT) && (err == LTR_OK); ch++) {
                if ((1 << ch) & ch_mask) {
                    int fnd_lch = -1;
                    for (lch = 0; (fnd_lch < 0) && (lch < hnd->Cfg.LChCnt); lch++) {
                        if (hnd->Cfg.LChTbl[lch].PhyChannel == ch) {
                            fnd_lch = lch;
                        }
                    }

                    if (fnd_lch >= 0) {
                        req_ch_mask |= (1 << ch);
                        ch_nums[tare_ch_cnt++] = ch;
                        err = add_lch(&hnd->RawCfg, ltable_fill_user_wrd(
                                    &hnd->ModuleInfo, &hnd->Cfg, &hnd->Cfg.LChTbl[fnd_lch]));
                    }
                }
            }

            if (err == LTR_OK) {
                hnd->RawCfg.UserChCnt = hnd->RawCfg.ProcChCnt = hnd->RawCfg.MainLChCnt;
                raw_tables_finish(&hnd->RawCfg);

                err = load_tables(hnd);
            }
            if (err == LTR_OK) {
                INT frames_cnt, recv_vals_cnt;
                double *rvals;
                TLTR216_DATA_CH_STATUS *ch_st;

                frames_cnt = (WORD)(1. * hnd->State.AdcFreq/hnd->RawCfg.MainLChCnt);
                if (frames_cnt < 1)
                    frames_cnt = 1;

                recv_vals_cnt = hnd->RawCfg.UserChCnt * frames_cnt;
                rvals = (double*)malloc(recv_vals_cnt*sizeof(rvals[0]));
                ch_st = (TLTR216_DATA_CH_STATUS *)malloc(hnd->RawCfg.UserChCnt * sizeof(ch_st[0]));
                if ((rvals == NULL) || (ch_st == NULL)) {
                    err = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    err = svc_meas_exec(hnd, frames_cnt, LTR216_PROC_FLAG_MEAS_UNBALANCE, rvals, NULL, ch_st);
                    if (err == LTR_OK) {
                        for (ch = 0; ch < hnd->RawCfg.UserChCnt; ch++) {
                            if (ch_st[ch].Ures.ValueValid) {
                                INT frame_idx;
                                double val = 0;
                                double range_val = f_gains[LTABLE_GET_GAIN(hnd->RawCfg.MainLChTbl[ch])].user_range;

                                for (frame_idx = 0; frame_idx < frames_cnt; frame_idx++) {
                                    val += rvals[ch + frame_idx * hnd->RawCfg.UserChCnt];
                                }
                                val /= frames_cnt;
                                if (fabs(val) > (range_val/0x800000)) {
                                    hnd->ModuleInfo.Tare[ch_nums[ch]].Scale = range_val/val;
                                    ch_scale_valid[ch_nums[ch]] = TRUE;
                                    ok_ch_mask |= (1 << ch_nums[ch]);
                                }
                            }
                        }
                    }
                }

                free(rvals);
                free(ch_st);
            }


            /* Восстанавливаем измененные параметры */
            hnd->Cfg.TareEnabled = tare_en;
            for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++) {
                hnd->ModuleInfo.Tare[ch].ScaleValid = ch_scale_valid[ch];
            }
        }

        restore_err = svc_cfg_finish(hnd);
        if (err == LTR_OK)
            err = restore_err;
    }

    if (ch_ok != NULL)
        *ch_ok = ok_ch_mask;
    if ((err == LTR_OK) && (ok_ch_mask != req_ch_mask)) {
        err = LTR216_ERR_TARE_CHANNELS;
    }
    return err;
}




static INT fill_table_offs(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, TLTR216_RAWCONFIG *rawCfg) {
    INT err = LTR_OK;
    DWORD lch_idx;
    BYTE range_idx;
    BOOLEAN range_en_mask[LTR216_RANGES_CNT] = {FALSE, FALSE};

    /* измерение собственного нуля выполняется в основной таблице, т.к. имеет одинаковую
     * синфазную составляющую с основными измерениями.
     * необходимо измерять ноль по каждому диапазону, который присутствует в пользовательской
     * теблице */
    for (lch_idx = 0; lch_idx < cfg->LChCnt; lch_idx++) {
        range_en_mask[cfg->LChTbl[lch_idx].Range] = TRUE;
    }

    for (range_idx = 0; range_idx < LTR216_RANGES_CNT; range_idx++) {
        if (range_en_mask[range_idx]) {
            err = add_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_OFFS, 0, range_idx, 0, FALSE));
        }
    }

    if (err == LTR_OK)
        rawCfg->MainLChCnt = rawCfg->ProcChCnt = rawCfg->MainLChCnt;

    return err;
}


static DWORD ltable_fill_user_wrd(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, const TLTR216_LCHANNEL *ch_cfg) {
    double dac_val = 0;
    if (cfg->TareEnabled && minfo->Tare[ch_cfg->PhyChannel].DacValid) {
        /** @todo check range */
        dac_val = minfo->Tare[ch_cfg->PhyChannel].DacValue;
    }

    return ltable_fill_word(minfo, LTABLE_SW_MAIN, ch_cfg->PhyChannel,
                            ch_cfg->Range, dac_val, FALSE);
}


static INT add_lch(TLTR216_RAWCONFIG *rawCfg, DWORD word) {
    INT err = LTR_OK;
    if (rawCfg->MainLChCnt == LTR216_MAX_LCHANNEL) {
        err = LTR216_ERR_TOO_MANY_LTABLE_CH;
    } else {
        rawCfg->MainLChTbl[rawCfg->MainLChCnt++] = word;
        if (LTABLE_GET_BG_TBL(word)) {
            rawCfg->BgFrameStepCnt++;
        }
    }
    return err;
}


static INT add_bg_lch(TLTR216_RAWCONFIG *rawCfg, DWORD word) {
    INT err = LTR_OK;
    if (rawCfg->BgLChCnt == LTR216_MAX_LCHANNEL) {
        err = LTR216_ERR_TOO_MANY_LTABLE_BG_CH;
    } else {
        rawCfg->BgLChTbl[rawCfg->BgLChCnt++] = word;
        if (LTABLE_GET_BOUT(word))
            rawCfg->BgBoutLChCnt++;
    }
    return err;
}

static INT add_uref_bg_lch(TLTR216_RAWCONFIG *rawCfg, const TLTR216_MODULE_INFO *minfo,
                           const TLTR216_CONFIG *cfg, int range_idx, BOOLEAN bout) {
    INT err = add_bg_lch(rawCfg, ltable_fill_word(
                            minfo, bout ? LTABLE_SW_UREF_BOUT : LTABLE_SW_UREF,
                            UREF_MEAS_CH, UREF_RANGE,
                            f_uref_dac_cbr_vals[range_idx],
                            bout));
    if ((err == LTR_OK) && cfg->Ch16ForUref) {
            err = add_bg_lch(rawCfg, ltable_fill_word(
                                minfo, bout ? LTABLE_SW_U_BOUT : LTABLE_SW_U,
                                UREF_MEAS_CH, UREF_RANGE,
                                f_uref_dac_cbr_vals[range_idx],
                                bout));
    }
    return err;
}


static void raw_tables_init(TLTR216_RAWCONFIG *rawCfg) {
    memset(rawCfg, 0, sizeof(TLTR216_RAWCONFIG));
}

static void raw_tables_finish(TLTR216_RAWCONFIG *rawCfg) {
    rawCfg->FrameSize = rawCfg->MainLChCnt;
    if (rawCfg->BgFrameStepCnt > 0)
        rawCfg->FrameSize += rawCfg->BgFrameStepCnt * (rawCfg->BgStepSize - 1);
}

static INT fill_bg_table(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, DWORD ch_mask, DWORD bg_meas_msk,
                           t_internal_meas_ctx *meas_ctx, TLTR216_RAWCONFIG *rawCfg) {
    BOOLEAN ux_meas;
    INT err = LTR_OK;
    rawCfg->BgStepSize = 1;


    if (bg_meas_msk & LTR216_BG_MEASGROUP_OPEN) {
        TLTR216_FILTER_OUT_PARAMS filtParams;
        double adcFreq, delta;
        double du = 4;
        double c = (cfg->CableLength * cfg->CableCapacityPerUnit + LTR216_CH_INTERNAL_CAPACITY) * 1e-12;
        double bout_t = du * c / LTR216_I_BURNOUT;


        LTR216_GetFilterOutParams(cfg->AdcSwMode, cfg->FilterType, cfg->AdcOdrCode, &filtParams);
        adcFreq = RESULT_ADC_SYNC_FREQ(cfg->SyncFreqDiv);
        delta =  (1./adcFreq - 1./filtParams.Odr);

        while (delta < bout_t) {
            rawCfg->BgStepSize++;
            delta += 1./adcFreq;
        }
    }

    if (meas_ctx != NULL) {
        meas_ctx->bg_meas_msk = bg_meas_msk;
    }



    rawCfg->BgLChCnt = rawCfg->BgBoutLChCnt = 0;

    /* Сперва выполняем запись таблицы с включенным bout. Все эти записи должны
     * быть вырванены на rawCfg->FrameBgChCnt и каждая повторяться rawCfg->FrameBgChCnt раз подряж */

    if (bg_meas_msk & LTR216_BG_MEAS_UNEG_OPEN) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_UNEG, 0, LTR216_RANGE_1200, 0, TRUE));
    }


    /* При проверке кз/обрыв линии Uref в режиме Ch16ForUref проверяем как 16 канал,
     * так и саму линию Uref, так как обе линии участвуют в измерении,
     * в противном случае 16 канал не используется */
    if (bg_meas_msk & LTR216_BG_MEAS_UREF_OPEN) {
        INT range_idx;
        for (range_idx = 0; (range_idx < LTR216_UREF_OPEN_INTERVAL_CNT) && (err == LTR_OK); range_idx++) {
             err = add_bg_lch(rawCfg, ltable_fill_word(
                                     minfo,  LTABLE_SW_UREF_BOUT,
                                     UREF_MEAS_CH, UREF_RANGE,
                                     f_uref_open_dac_vals[range_idx],
                                     TRUE));

            if ((meas_ctx != NULL) && (err == LTR_OK)) {
                meas_ctx->ref.open_dac_codes[range_idx] = LTABLE_GET_DAC(rawCfg->BgLChTbl[rawCfg->BgLChCnt-1]);
            }

            if (cfg->Ch16ForUref) {
                err = add_bg_lch(rawCfg, ltable_fill_word(
                                     minfo,  LTABLE_SW_U_BOUT,
                                     UREF_MEAS_CH, UREF_RANGE,
                                     f_uref_open_dac_vals[range_idx],
                                     TRUE));
            }
        }
    }

    if (bg_meas_msk & LTR216_BG_MEAS_CH_OPEN) {
        BYTE ch_idx;
        for (ch_idx = 0; (ch_idx < LTR216_CHANNELS_CNT) && (err == LTR_OK); ch_idx++) {
            if (ch_mask & (1 << ch_idx)) {
                INT interval_idx;
                /* для обрыва те же измерения, но с bout */

                for (interval_idx = 0; (interval_idx < LTR216_UABS_INTERVAL_CNT) && (err == LTR_OK); interval_idx++) {                    
                    err = add_bg_lch(rawCfg, ltable_fill_word(
                                         minfo, LTABLE_SW_U_BOUT, ch_idx,
                                         LTR216_RANGE_1200,
                                         f_uabs_intervals[interval_idx].dac_val, TRUE));

                    if ((meas_ctx != NULL) && (err == LTR_OK)) {
                        meas_ctx->uabs.intervals[interval_idx].dac_code =
                                LTABLE_GET_DAC(rawCfg->BgLChTbl[rawCfg->BgLChCnt-1]);
                    }
                }
            }

            if ((meas_ctx != NULL) && (err == LTR_OK)) {
                meas_ctx->uabs.chs[ch_idx].last_table_check_pos = rawCfg->BgLChCnt - 1;
            }
        }
    }




    if ((bg_meas_msk & LTR216_BG_MEAS_UNEG) && (err == LTR_OK)) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_UNEG, 0,
                                                  LTR216_RANGE_1200, 0, FALSE));
    }

    if ((bg_meas_msk & LTR216_BG_MEAS_UREF_OFFS) && (err == LTR_OK)) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_UREF_OFFS, 0,
                                                  UREF_RANGE, 0, FALSE));
    }


    if ((bg_meas_msk & LTR216_BG_MEAS_VADJ) && (err == LTR_OK)) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_VADJ, 0,
                                                  LTR216_RANGE_1200, -1, FALSE));
    }

    if ((bg_meas_msk & LTR216_BG_MEAS_UREF_SHORT) && (err == LTR_OK)) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_UREF, UREF_MEAS_CH,
                                                  UREF_RANGE, 0, FALSE));
        if (cfg->Ch16ForUref && (err == LTR_OK)) {
            err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_U, UREF_MEAS_CH,
                                                      UREF_RANGE, 0, FALSE));
        }

        if ((meas_ctx != NULL) && (err == LTR_OK))
            meas_ctx->dac_zero_code = LTABLE_GET_DAC(rawCfg->BgLChTbl[rawCfg->BgLChCnt-1]);
    }

    if ((bg_meas_msk & LTR216_BG_MEAS_UREF) && (err == LTR_OK)) {
        INT range_idx;
        for (range_idx = 0; (range_idx < LTR216_UREF_MEAS_RANGES_CNT) && (err == LTR_OK); range_idx++) {
            err = add_uref_bg_lch(rawCfg, minfo, cfg, range_idx, FALSE);

            if ((meas_ctx != NULL) && (err == LTR_OK)) {
                meas_ctx->ref.dac_codes[range_idx] =
                    LTABLE_GET_DAC(rawCfg->BgLChTbl[rawCfg->BgLChCnt-1]);
            }
        }
    }




    if (err == LTR_OK) {
        /* Для CM требуется измерить явно Ux, что также используется и для проверки КЗ */
        ux_meas = (bg_meas_msk & LTR216_BG_MEAS_CH_UX) || (bg_meas_msk & LTR216_BG_MEAS_CH_CM) ? TRUE : FALSE;

        if (ux_meas || (bg_meas_msk & LTR216_BG_MEAS_CH_SHORT)) {
            BYTE ch_idx;
            for (ch_idx = 0; (ch_idx < LTR216_CHANNELS_CNT) && (err == LTR_OK); ch_idx++) {
                if (ch_mask & (1 << ch_idx)) {
                    INT interval_idx;

                    if (bg_meas_msk & LTR216_BG_MEAS_CH_SHORT) {
                        err = add_bg_lch(rawCfg, ltable_fill_word(
                                             minfo, LTABLE_SW_U, ch_idx,
                                             LTR216_RANGE_1200, 0, FALSE));
                    }

                    if (ux_meas) {
                        for (interval_idx = 0; (interval_idx < LTR216_UABS_INTERVAL_CNT)
                             && (err == LTR_OK); interval_idx++) {
                            err = add_bg_lch(rawCfg, ltable_fill_word(
                                                 minfo, LTABLE_SW_U, ch_idx,
                                                 LTR216_RANGE_1200,
                                                 f_uabs_intervals[interval_idx].dac_val,
                                                 FALSE));
                            if ((meas_ctx != NULL) && (err == LTR_OK)) {
                                meas_ctx->uabs.intervals[interval_idx].dac_code =
                                    LTABLE_GET_DAC(rawCfg->BgLChTbl[rawCfg->BgLChCnt-1]);
                            }
                        }
                    }
                }

                if ((meas_ctx != NULL) && (err == LTR_OK)) {
                    meas_ctx->uabs.chs[ch_idx].last_table_check_pos = rawCfg->BgLChCnt - 1;
                }
            }
        }
    }

    /* В таблице количество не bout канав должно быть кратно rawCfg->BgStepChCnt,
     * чтобы выдерживалась длина одного шага фоновой таблицы */
    while ((((rawCfg->BgLChCnt - rawCfg->BgBoutLChCnt) % rawCfg->BgStepSize) != 0) && (err == LTR_OK)) {
        err = add_bg_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_UREF_OFFS, 0,
                                                  UREF_RANGE, 0, FALSE));
    }

    rawCfg->BgStepsCnt = rawCfg->BgBoutLChCnt + (rawCfg->BgLChCnt - rawCfg->BgBoutLChCnt) / rawCfg->BgStepSize;

    err = add_lch(rawCfg, LTABLE_WORD_BG);

    return err;
}


static INT fill_raw_tables(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, TLTR216_RAWCONFIG *rawCfg, t_internal_meas_ctx *meas_ctx) {
    INT err = LTR_OK;    
    BOOLEAN single_ch_mode = cfg->AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT;

    raw_tables_init(rawCfg);

    /* в одноканальном режиме не используется фоновая таблица или другие доп. измерения,
     * а только одно измерение пользовательского канала */
    if (single_ch_mode) {
        rawCfg->MainLChTbl[0] = ltable_fill_user_wrd(minfo, cfg, &cfg->LChTbl[0]);
        rawCfg->UserChCnt = rawCfg->ProcChCnt = rawCfg->MainLChCnt = 1;
    } else {
        DWORD lch_idx;
        DWORD ch_mask = 0;

        /* исходя из разрешенных диапазонов находим минимальное время на коммутацию
         * для определения необходимости добавлять холостые такты */

        double min_sw_time = f_range_times[cfg->LChTbl[0].Range].sw_min_time;
        double min_amp_time = f_range_times[cfg->LChTbl[0].Range].amp_rest_time;

        if (cfg->LChCnt > LTR216_MAX_LCHANNEL) {
            err = LTR216_ERR_TOO_MANY_LTABLE_CH;
        } else {
            for (lch_idx = 0; lch_idx < cfg->LChCnt; lch_idx++) {
                if (f_range_times[cfg->LChTbl[lch_idx].Range].sw_min_time > min_sw_time)
                    min_sw_time = f_range_times[cfg->LChTbl[lch_idx].Range].sw_min_time;
                if (f_range_times[cfg->LChTbl[lch_idx].Range].amp_rest_time > min_amp_time)
                    min_amp_time = f_range_times[cfg->LChTbl[lch_idx].Range].amp_rest_time;

                ch_mask |= 1 << cfg->LChTbl[lch_idx].PhyChannel;
            }



            rawCfg->UserChCnt = rawCfg->ProcChCnt = rawCfg->MainLChCnt = cfg->LChCnt;
            /* начало таблицы заполняем полезными измерениями разбаланса из пользовательских
             * настроек */
            for (lch_idx = 0; lch_idx < cfg->LChCnt; lch_idx++) {
                rawCfg->MainLChTbl[lch_idx] = ltable_fill_user_wrd(minfo, cfg, &cfg->LChTbl[lch_idx]);
            }
        }

        /* при автокоррекции нуля добавляем измерение нуля разбаланса в основную таблицу
         * для каждого разрешенного диапазона */
        if ((cfg->BgMeas & LTR216_BG_MEAS_OFFS) && (err == LTR_OK)) {
            err = fill_table_offs(minfo, cfg, rawCfg);
        }

        /* Если есть измерения из служебной таблице, то добавляем слово для фоновой таблицы */
        if (cfg->BgMeas & LTR216_BG_MEASGROUP_SECTBL) {
            TLTR216_FILTER_OUT_PARAMS filtParams;
            double adcFreq, delta;


            err = fill_bg_table(minfo, cfg, ch_mask, cfg->BgMeas, meas_ctx, rawCfg);
            if (err == LTR_OK) {
                /* после фоновой таблицы добавляем холостые такты, чтобы успел восстановится
                 * усилитель после смены синфазного напряжения. В фоновой таблице при этом
                 * могут быть только измерения с синфазной относительно U-, а все с синфазной
                 * относительно Uref - в основном цикле */
                LTR216_GetFilterOutParams(cfg->AdcSwMode, cfg->FilterType, cfg->AdcOdrCode, &filtParams);
                adcFreq = RESULT_ADC_SYNC_FREQ(cfg->SyncFreqDiv);
                delta =  (1./adcFreq - 1./filtParams.Odr) * 1000000;


                while ((delta < min_amp_time) && (err == LTR_OK)) {
                    err = add_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_OFFS, 0,
                                                           cfg->LChTbl[0].Range, 0, FALSE));
                    delta += 1000000./adcFreq;
                }
            }
        }


        if (cfg->AdcReqFrameFreq > 0) {
            /* если задана ожидаемая частота кадра и она ниже полученной, то расширяем
             * таблицу для приблежиния частоты к заданной */
            /** @todo сейчас расширяем только за счет увеличения главной. Возможно
             * расширение за счет фоновой, а также вставление доп. фоновых циклов.
             * Это позволит сильнее уменьшать частоту, но в первом случае требуется
             * подстраивать фоновую таблицу, т.к. там измерения без bout должны
             * быть кратны шагу, а во втором доп. рассчет + фоновые можно вставлять
             * только после первой фоновой до вставки холостых ходов.
             * Оба этих более сложных алгоритма оставлены на будущее, и могут
             * быть реализовоны при реальных запросах */
            raw_tables_finish(rawCfg);
            double adcFreq = RESULT_ADC_SYNC_FREQ(cfg->SyncFreqDiv);
            INT delta_frame_size = (INT)(adcFreq/cfg->AdcReqFrameFreq + 0.5) - rawCfg->FrameSize;
            while ((delta_frame_size > 0) && (rawCfg->MainLChCnt < LTR216_MAX_LCHANNEL)) {
                err = add_lch(rawCfg, ltable_fill_word(minfo, LTABLE_SW_OFFS, 0,
                                                       cfg->LChTbl[0].Range, 0, FALSE));
                delta_frame_size--;
            }
        }
    }



    raw_tables_finish(rawCfg);

    return err;
}

LTR216API_DllExport(INT) LTR216_FillRawTables(const TLTR216_MODULE_INFO *minfo, const TLTR216_CONFIG *cfg, TLTR216_RAWCONFIG *rawCfg) {
    return fill_raw_tables(minfo, cfg, rawCfg, 0);
}



static INT load_tables(TLTR216 *hnd) {
    INT err = ltable_load(hnd, hnd->RawCfg.MainLChTbl, hnd->RawCfg.MainLChCnt, FALSE);
    if (err == LTR_OK)
        err = ltable_load(hnd, hnd->RawCfg.BgLChTbl, hnd->RawCfg.BgLChCnt, TRUE);
    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = LTR_MODULE_MAKE_CMD(CMD_BG_STEP_SIZE, hnd->RawCfg.BgStepSize == 0 ? 0 : (hnd->RawCfg.BgStepSize - 1));
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
    }

    return err;
}

static INT restore_user_table(TLTR216 *hnd) {
    INT err = LTR_OK;

    if (!(hnd->Cfg.Flags & LTR216_CONFIG_FLAG_RAWTABLE))
        err = fill_raw_tables(&hnd->ModuleInfo, &hnd->Cfg, &hnd->RawCfg, &((t_internal_params*)hnd->Internal)->meas_ctx);

    if (err == LTR_OK) {
        err = load_tables(hnd);
    }
    return err;
}


static DWORD cmd_curr(DWORD ISrcCode, BOOLEAN en) {
    return LTR_MODULE_MAKE_CMD_BYTES(CMD_CURR,  ((ISrcCode  & 0xF) << 4) | (en ? 1 : 0),
                                       (ISrcCode >> 4));
}

static INT set_adc(TLTR216 *hnd, DWORD flags) {
    INT err = LTR_OK;
    TLTR216_FILTER_OUT_PARAMS filter_params;
    t_internal_params *params = (t_internal_params *)hnd->Internal;

    BOOLEAN single_ch_mode = hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT;
    err = LTR216_GetFilterOutParams(hnd->Cfg.AdcSwMode, hnd->Cfg.FilterType, hnd->Cfg.AdcOdrCode, &filter_params);
    if (err == LTR_OK)
        params->odr = filter_params.Odr;


    if ((err == LTR_OK) && !single_ch_mode && (hnd->Cfg.SyncFreqDiv > LTR216_SYNC_FDIV_MAX))
        err = LTR216_ERR_INVALID_SYNC_FDIV;    
    if ((err == LTR_OK) && (hnd->Cfg.ISrcCode >= LTR216_ISRC_CODE_MAX))
        err = LTR216_ERR_INVALID_ISRC_CODE;

    if ((err == LTR_OK) && !(hnd->Cfg.Flags & LTR216_CONFIG_FLAG_RAWTABLE)) {
        if (single_ch_mode && (hnd->Cfg.LChCnt != 1)) {
            err = LTR216_ERR_INVALID_LCH_CNT;
        } else if (hnd->Cfg.LChCnt > LTR216_MAX_LCHANNEL) {
            err = LTR216_ERR_INVALID_LCH_CNT;
        } else {
            DWORD lch_num;
            INT max_ch_num = hnd->Cfg.Ch16ForUref ? 15 : 16;
            for (lch_num = 0; (lch_num < hnd->Cfg.LChCnt) && (err == LTR_OK); lch_num++) {
                if ((hnd->Cfg.LChTbl[lch_num].Range != LTR216_RANGE_35) &&
                        (hnd->Cfg.LChTbl[lch_num].Range != LTR216_RANGE_70)) {
                    err = LTR216_ERR_INVALID_CH_RANGE;
                } else if (!(hnd->Cfg.LChTbl[lch_num].PhyChannel < max_ch_num)) {
                    err = LTR216_ERR_INVALID_CH_NUM;
                }
            }
        }
    }

    if ((err == LTR_OK) && (flags & LTR216_SET_UPDATA_FREQ)) {
        DWORD cmd[3], ack[3];
        DWORD FreqDiv = single_ch_mode ? 0xFFFFFF : hnd->Cfg.SyncFreqDiv;

        cmd[0] = cmd_curr(hnd->Cfg.ISrcCode, params->isrc_en);
        cmd[1] = LTR_MODULE_MAKE_CMD(CMD_F_DIV0, FreqDiv & 0xFFFF);
        cmd[2] = LTR_MODULE_MAKE_CMD(CMD_F_DIV1, (FreqDiv >> 16) & 0xFF);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, cmd, 3, ack);


        err = LTR216_CalcISrcValue(&hnd->ModuleInfo.FabricCbr.ISrc, hnd->Cfg.ISrcCode, &params->meas_ctx.i_amp);
        if (err == LTR_OK)
            params->meas_ctx.i_amp /= 1000;
    }

    if ((err == LTR_OK) && (flags & LTR216_SET_UPDATA_USER_TABLE))
        err = restore_user_table(hnd);

    /* сперва сбрасываем бит REG_CHECK, чтобы сбросить ошибку по изменению регистров */
    if (err == LTR_OK) {
        err = adc_write_reg16(hnd, AD7176_REG_IFMODE, ADC_REGVAL_IFMODE(FALSE));
    }

    if (err == LTR_OK) {
        WORD filtr_reg;
        if (hnd->Cfg.FilterType == LTR216_FILTER_SINC5_1) {
            filtr_reg = LBITFIELD_SET(AD7176_REGBIT_FILTCON_ODR, hnd->Cfg.AdcOdrCode);
        } else if (hnd->Cfg.FilterType == LTR216_FILTER_ENH_50HZ) {
            filtr_reg = AD7176_REGBIT_FILTCON_ENHFILTEN | LBITFIELD_SET(AD7176_REGBIT_FILTCON_ENHFILT, hnd->Cfg.AdcOdrCode);
        } else {
            filtr_reg = AD7176_REGBIT_FILTCON_SINC3_MAP | (hnd->Cfg.AdcOdrCode & 0x7FFF);
        }
        err = adc_write_reg16(hnd, AD7176_REG_FILTCON(0), filtr_reg);
    }

    /* в одноканальном режиме отключаем второй канал мультиплексора АЦП, чтобы
        не сбрасывал фильтр */
    if (err == LTR_OK) {
        err = adc_write_reg16(hnd, AD7176_REG_CHMAP(1), (single_ch_mode ? 0 : AD7176_REGBIT_CHMAP_CH_EN) |
                          LBITFIELD_SET(AD7176_REGBIT_CHMAP_SETUP_SEL, 0) |
                          LBITFIELD_SET(AD7176_REGBIT_CHMAP_AINPOS, AD7176_AIN_1) |
                          LBITFIELD_SET(AD7176_REGBIT_CHMAP_AINNEG, AD7176_AIN_0));
    }

    /* снова его устанавливаем, чтобы проверять целостность регистров во время сбора */
    if (err == LTR_OK) {
        err = adc_write_reg16(hnd, AD7176_REG_IFMODE, ADC_REGVAL_IFMODE(!single_ch_mode) | AD7176_REGBIT_IFMODE_REG_CHECK);
    }


    if (err == LTR_OK) {
        err = adc_clear_rdy(hnd);
    }

    if (err == LTR_OK) {
        params->meas_ctx.balance_ref_state = (hnd->Cfg.Ch16ForUref || (hnd->Cfg.RrefWireResistance > 0)) ?
                    &params->meas_ctx.status.UrefR :
                    &params->meas_ctx.status.Uref;
    }

    if ((err == LTR_OK) && (flags & LTR216_SET_UPDATE_STATE)) {
        hnd->State.AdcFreq = single_ch_mode ? filter_params.Odr : RESULT_ADC_SYNC_FREQ(hnd->Cfg.SyncFreqDiv);
        hnd->State.FrameFreq = hnd->State.AdcFreq/hnd->RawCfg.FrameSize;
        hnd->State.FrameWordsCount = 2 * hnd->RawCfg.FrameSize;
        LTR216_CalcISrcValue(&hnd->ModuleInfo.FabricCbr.ISrc, hnd->Cfg.ISrcCode, &hnd->State.ISrcValue);

        if (!single_ch_mode) {
            TLTR216_FILTER_OUT_PARAMS filterParams;
            LTR216_GetFilterOutParams(hnd->Cfg.AdcSwMode, hnd->Cfg.FilterType,
                                      hnd->Cfg.AdcOdrCode, &filterParams);
            double sw_time = (1./hnd->State.AdcFreq - 1./filterParams.Odr) * 1000000;
            if (sw_time < hnd->Cfg.AdcMinSwTimeUs) {
                err = LTR216_ERR_UNSUF_SW_TIME;
            }
        }
    }
    return err;
}


LTR216API_DllExport(INT) LTR216_SetADC(TLTR216 *hnd) {
    INT err = LTR216_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    if (err == LTR_OK) {
        err = set_adc(hnd, LTR216_SET_ALL);
    }
    if (err == LTR_OK) {
        hnd->State.Configured = TRUE;
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_SetISrcEnabled(TLTR216 *hnd, BOOLEAN enabled) {
    INT err = LTR216_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    if (err == LTR_OK) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;
        DWORD cmd, ack;

        params->isrc_en = enabled;
        cmd = cmd_curr(hnd->Cfg.ISrcCode, enabled);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_Start(TLTR216 *hnd) {
    INT err = f_prestart_module_check(hnd);
    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = LTR_MODULE_MAKE_CMD(CMD_CT_PRELOAD, hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT ? 1 : 0);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (err == LTR_OK) {
            cmd = LTR_MODULE_MAKE_CMD(CMD_GO, 1);
            err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        }
        if (err == LTR_OK) {
            INT idx;
            INT ch;
            BOOLEAN used;
            t_internal_params *params = (t_internal_params *)hnd->Internal;
            DWORD bg_meas = hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT ? 0 : hnd->Cfg.BgMeas;

            hnd->State.Run = TRUE;

            params->cur_cntr = 0;
            params->cntr_lost = TRUE;
            params->bg_tbl_ptr_lost = TRUE;
            params->bg_tbl_ptr = 0;

            /* Uref и Uneg сохраняется между запусками !!! */
            f_measstate_init(&params->meas_ctx.status.Vadj, bg_meas & LTR216_BG_MEAS_VADJ);



            for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++) {
                TLTR216_DATA_CH_STATUS *chStatus = &params->meas_ctx.ch_status_list[ch];
                chStatus->StatusFlags = 0;

                for (idx = 0; idx < LTR216_UABS_INTERVAL_CNT; idx++) {
                    params->meas_ctx.uabs.chs[idx].open_states[idx] = FALSE;
                    params->meas_ctx.uabs.chs[idx].ok_states[idx] = FALSE;
                }
                used = bg_meas & (LTR216_BG_MEAS_CH_UX | LTR216_BG_MEAS_CH_CM |
                        LTR216_BG_MEAS_CH_OPEN | LTR216_BG_MEAS_CH_SHORT) ? TRUE : FALSE;
                f_int_measstate_init(&params->meas_ctx.uabs.chs[ch].state, used);
                f_measstate_init(&chStatus->Ux, used);
                f_measstate_init(&chStatus->Ures, TRUE);
                f_measstate_init(&chStatus->Ucm, bg_meas & LTR216_BG_MEAS_CH_CM ? TRUE : FALSE);

                f_calc_ucm_state(bg_meas, &params->meas_ctx.status, chStatus);
            }

            params->meas_ctx.ref.uref.state.is_open =
                    params->meas_ctx.ref.uref_r.state.is_open = FALSE;

            for (idx = 0; idx < LTR216_UREF_OPEN_INTERVAL_CNT; idx++) {
                params->meas_ctx.ref.uref.open_states[idx] = FALSE;
                params->meas_ctx.ref.uref_r.open_states[idx] = FALSE;
            }

            for (idx = 0; idx < LTR216_UREF_MEAS_RANGES_CNT; idx++) {
                params->meas_ctx.ref.uref.ok_states[idx] = FALSE;
                params->meas_ctx.ref.uref_r.ok_states[idx] = FALSE;
            }
        }
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_Stop(TLTR216 *hnd) {
    INT err = LTR216_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_GO, 0);
        err = ltr_module_stop(&hnd->Channel, &cmd, 1, cmd, 0, 0, NULL);
        if (err == LTR_OK) {
            err = adc_clear_rdy(hnd);
            hnd->State.Run = FALSE;
        }
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_Recv(TLTR216 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK)
        res = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
    if (res >= 0) {
        INT i;
        for (i = 0; i < res; i++) {
            if ((data[i] & LTR_MODULE_CMD_CODE_MSK) == CMD_ERROR) {
                WORD flags = LTR_MODULE_CMD_GET_DATA(data[i]);
                if (flags & 2) {
                    res = LTR216_ERR_ADC_RECV_SYNC_OVERRATE;
                } else {
                    res = LTR216_ERR_ADC_RECV_INT_CYCLE_ERROR;
                }
            }
        }
    }

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

    return res;
}


#define CHECK_WRD_CNTR(wrd, cntr, cntr_lost, wrd_err) do { \
    BYTE wrd_cntr = LBITFIELD_GET(wrd, ADC_DATA_MSK_CNTR); \
    if (cntr_lost) { \
        (cntr) = wrd_cntr; \
        (cntr_lost) = FALSE; \
    } else { \
        if ((cntr) != wrd_cntr) { \
            (wrd_err) = LTR_ERROR_PROCDATA_CNTR; \
            (cntr_lost) = TRUE; \
        } \
    } \
    if (++(cntr) == 7) \
        (cntr) = 0; \
} while(0)




static void f_calc_open_intervals(BOOLEAN *open_states, INT states_cnt,
                                     INT cur_state_idx, INT meas_state,
                                     BOOLEAN *res) {
    open_states[cur_state_idx] = (meas_state == LTR216_MEASSTATUS_ADC_OVERRANGE);
    /* на последнем интервале проверяем, все ли вне диапазона
     * измерения. если да, то только тогда считаем обрыв */
    if (cur_state_idx == (states_cnt -1)) {
        BOOLEAN is_open = TRUE;
        INT tmp_idx;
        for (tmp_idx = 0; (tmp_idx < states_cnt) && is_open; tmp_idx++) {
            if (!open_states[tmp_idx]) {
                is_open = FALSE;
            }
        }
        *res = is_open;
    }
}

static BOOLEAN f_check_ok_intervals(BOOLEAN *ok_states, INT states_cnt) {
    BOOLEAN ok_state = FALSE;
    INT tmp_idx;
    for (tmp_idx = 0; (tmp_idx < states_cnt) && !ok_state; tmp_idx++) {
        if (ok_states[tmp_idx])
            ok_state = TRUE;
    }
    return ok_state;
}


LTR216API_DllExport(INT) LTR216_ProcessData(TLTR216 *hnd, const DWORD *src, double *dest,
                                            INT *size, DWORD flags,
                                            TLTR216_DATA_STATUS *status,
                                            TLTR216_DATA_CH_STATUS *ch_status_list) {
    INT err = LTR216_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;

        WORD table_pos = 0;
        WORD bg_step_pos = 0;
        BOOLEAN table_bg = hnd->RawCfg.MainLChTbl[table_pos] & LCH_MSK_SEC_TBL ? TRUE : FALSE;

        DWORD i;
        DWORD put_pos = 0;
        DWORD wrds_size = *size/2;
        BOOLEAN out = FALSE;
        DWORD meas_type = flags & LTR216_PROC_FLAG_MEAS_MSK;

        WORD bg_table_pos = params->bg_tbl_ptr;
        BOOLEAN bg_table_pos_lost = params->bg_tbl_ptr_lost;
        BOOLEAN cntr_lost = params->cntr_lost;
        BYTE cntr = params->cur_cntr;

        if (flags & LTR216_PROC_FLAG_NONCONT_DATA) {
            bg_table_pos_lost = TRUE;
            cntr_lost = TRUE;
        }

        for (i = 0; i < LTR216_CHANNELS_CNT; i++) {
            params->meas_ctx.ch_status_list[i].StatusFlags = 0;
            params->meas_ctx.ch_status_list[i].DetectedErrors = 0;
        }
        if (ch_status_list != NULL) {
            for (i = 0; i < hnd->RawCfg.UserChCnt; i++) {
                ch_status_list[i].StatusFlags = 0;
            }
        }


        params->meas_ctx.status.StatusFlags = 0;
        params->meas_ctx.status.DetectedErrors = status == NULL ? 0 : status->DetectedErrors;

        for (i = 0; (i < wrds_size) && !out; i++) {
            INT code = 0;
            BYTE  wrd_flags = 0;
            INT wrd_err = LTR_OK;
            DWORD wrd = *src++;
            WORD wrd_table_ptr = LBITFIELD_GET(wrd, ADC_DATA_FIRST_LOW_PTR) |
                    (LBITFIELD_GET(wrd, ADC_DATA_FIRST_HIGH_PTR) << 8);
            BOOLEAN wrd_table_bg = wrd & ADC_DATA_FIRST_BG_TABLE ? TRUE : FALSE;

            wrd_err = LTR_MODULE_WRD_CHECK_DATA(wrd);
            if ((wrd_err == LTR_OK) && (wrd & ADC_DATA_MSK_SEC_WRD))
                wrd_err = LTR_ERROR_PROCDATA_WORD_SEQ;
            if (wrd_err == LTR_OK) {
                CHECK_WRD_CNTR(wrd, cntr, cntr_lost, wrd_err);
            }

            if (wrd_err == LTR_OK) {
                code = LBITFIELD_GET(wrd, ADC_DATA_FIRST_HI_DATA) << 16;

                /* проверка второго слова на формат */
                wrd = *src++;
                wrd_err = LTR_MODULE_WRD_CHECK_DATA(wrd);
                if ((wrd_err == LTR_OK) && !(wrd & ADC_DATA_MSK_SEC_WRD)) {
                    wrd_err = LTR_ERROR_PROCDATA_WORD_SEQ;
                }
                if (wrd_err == LTR_OK) {
                    CHECK_WRD_CNTR(wrd, cntr, cntr_lost, wrd_err);
                }

                /* проверка правильности позиции в таблице */
                if ((wrd_err == LTR_OK) && (wrd_table_bg != table_bg)) {
                    wrd_err = LTR_ERROR_PROCDATA_CHNUM;
                } else {
                    if (wrd_table_bg) {
                        if (bg_table_pos_lost) {
                            bg_table_pos = wrd_table_ptr;
                        } else {
                            if (wrd_table_ptr != bg_table_pos) {
                                wrd_err = LTR_ERROR_PROCDATA_CHNUM;
                                bg_table_pos_lost = TRUE;
                            }
                        }
                    } else  if (wrd_table_ptr != table_pos) {
                        wrd_err = LTR_ERROR_PROCDATA_CHNUM;
                    }
                }

                if (wrd_err == LTR_OK) {
                    wrd_flags = LBITFIELD_GET(wrd, ADC_DATA_SEC_FLAGS);
                    code |= LBITFIELD_GET(wrd, ADC_DATA_SEC_LOW_DATA);
                }
            }

            if (wrd_err == LTR_OK) {


                if (wrd_flags & ADC_DATA_FLAG_REG_ERROR) {
                    err = LTR216_ERR_ADC_REGS_INTEGRITY;
                    out = TRUE;
                }

                /* перевод в знаковый формат */
                code -= 0x800000;

                if (!table_bg) {
                    DWORD table_wrd = hnd->RawCfg.MainLChTbl[table_pos];
                    BYTE gain_idx = LTABLE_GET_GAIN(table_wrd);
                    INT ch = LTABLE_GET_CH(table_wrd);

                    if (table_pos < hnd->RawCfg.UserChCnt) {
                        BOOL value_valid = TRUE;
                        BOOL overrange = FALSE;
                        TLTR216_DATA_CH_STATUS *chStatus = &params->meas_ctx.ch_status_list[ch];
                        double val;
                        DWORD sw_mode;


                        if (wrd_flags & ADC_DATA_FLAG_OVERRANGE) {                            
                            overrange = TRUE;
                            /* Признак Overrange относится к логическому, а не физическому каналу,
                             * поэтому его состояние обновляется напрямую в переменных
                             * ch_status_list, а не в chStatus */
                            if (ch_status_list != NULL) {
                                ch_status_list[table_pos].StatusFlags |= LTR216_CH_STATUS_FLAG_OVERRANGE;
                                ch_status_list[table_pos].DetectedErrors |= LTR216_CH_DETECT_ERR_OVERRANGE;
                            }
                        }
                        if (wrd_flags & ADC_DATA_FLAG_LOAD_POW_EXC) {
                            params->meas_ctx.status.StatusFlags |= LTR216_STATUS_FLAG_LOAD_POW_EXCEEDED;
                            params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_LOAD_POW_EXCEEDED;
                        }

                        val = code;
                        sw_mode = LTABLE_GET_SW(table_wrd);
                        if (sw_mode == LTABLE_SW_MAIN) {
                            val -= params->meas_ctx.offs[f_gains[gain_idx].range].avg.result;
                        } else if ((sw_mode == LTABLE_SW_UREF) || (sw_mode == LTABLE_SW_U)) {
                            val -= params->meas_ctx.ref.offset.result;
                        }

                        if (!(flags & LTR216_PROC_FLAG_NO_FABRIC_CBR))
                            val = fabric_cbr(&hnd->ModuleInfo, gain_idx, val);
                        if (meas_type != LTR216_PROC_FLAG_MEAS_UDIFF_CODE)
                            val = conv_volts(gain_idx, val);

                        if (hnd->Cfg.TareEnabled) {
                            const TLTR216_TARE_CH_COEFS *tare = &hnd->ModuleInfo.Tare[ch];
                            if (tare->OffsetValid)
                                val -= tare->Offset;
                            if (tare->ScaleValid)
                                val *=  tare->Scale;
                        }

                        if (meas_type == LTR216_PROC_FLAG_MEAS_UNBALANCE) {
                            if (params->meas_ctx.balance_ref_state->ValueValid) {
                                /* рассчет разбаланса, приведенного к 2.5 В */
                                val = val/params->meas_ctx.balance_ref_state->Value * 2.5;
                            } else {
                                val = 0;
                                value_valid  = FALSE;
                            }
                        }

                        dest[put_pos++] = val;

                        if (value_valid) {
                            /* при действительном Uref и включенном измерении Ux,
                             * состояние Ux опеределяет состояние результирующего
                             * значения */
                            chStatus->Ures.Value = val;
                            if ((params->meas_ctx.bg_meas_msk & LTR216_BG_MEAS_CH_UX) &&
                                    (chStatus->Ux.Status != LTR216_MEASSTATUS_NOT_INIT)) {
                                chStatus->Ures.Status = chStatus->Ux.Status;
                                value_valid = chStatus->Ux.ValueValid;
                            }

                            /* последним устанавливаем статус переполнения сетки АЦП,
                             * т.к. он может являться только следствием других проблем */
                            if (overrange && (chStatus->Ures.Status == LTR216_MEASSTATUS_OK))
                                chStatus->Ures.Status  = LTR216_MEASSTATUS_ADC_OVERRANGE;
                        } else {
                            chStatus->Ures.Status = LTR216_MEASSTATUS_CANT_CALC;
                        }

                        chStatus->Ures.ValueValid = value_valid;



                    } else if (table_pos < hnd->RawCfg.ProcChCnt) {
                        if (LTABLE_GET_SW(table_wrd) == LTABLE_SW_OFFS) {
                            DWORD range = f_gains[gain_idx].range;
                            avg_add_val(&params->meas_ctx.offs[range].avg, code);
                        }
                    }

                    if (++table_pos == hnd->RawCfg.MainLChCnt)
                        table_pos = 0;
                } else {
                    DWORD table_wrd = hnd->RawCfg.BgLChTbl[bg_table_pos];
                    BYTE gain_idx = LTABLE_GET_GAIN(table_wrd);
                    DWORD sw = LTABLE_GET_SW(table_wrd);
                    WORD dac_code = LTABLE_GET_DAC(table_wrd);
                    INT ch = LTABLE_GET_CH(table_wrd);
                    BOOLEAN bout = LTABLE_GET_BOUT(table_wrd) ? TRUE : FALSE;
                    /* для части измерений (при включенном bout) мы должны использовать
                     * только последнее измерение основного кадра (чтобы успела зарядится цепь).
                     * Сейчас фоновая таблица идет сразу за обрабатываемыми каналами, поэтому
                     * номер кадра можно определить как кол-во обрабатываемых каналов и кол-во
                     * лог. каналов измерения фоновой таблицы в основном кадре */
                    BOOLEAN last_bg_step_ch = bg_step_pos == hnd->RawCfg.BgStepSize - 1;

                    if (sw == LTABLE_SW_UREF_OFFS) {
                        avg_add_val(&params->meas_ctx.ref.offset, code);
                    } else {
                        double adc_val, abs_val;
                        e_LTR216_MEAS_STATUS meas_state = (wrd_flags & ADC_DATA_FLAG_OVERRANGE) ?
                                    LTR216_MEASSTATUS_ADC_OVERRANGE : LTR216_MEASSTATUS_OK;
                        adc_val= (double)code - params->meas_ctx.ref.offset.result;


                        adc_val = fabric_cbr(&hnd->ModuleInfo, gain_idx, adc_val);
                        adc_val = conv_volts(gain_idx, adc_val);

                        abs_val = adc_val - conv_dac_val(hnd, gain_idx, dac_code);

                        if ((hnd->Cfg.Ch16ForUref && ((sw == LTABLE_SW_U)  || (sw == LTABLE_SW_U_BOUT))
                             && (ch == UREF_MEAS_CH)) ||
                            (sw == LTABLE_SW_UREF) || (sw == LTABLE_SW_UREF_BOUT)) {
                            BOOL is_uref = (sw == LTABLE_SW_UREF) || (sw == LTABLE_SW_UREF_BOUT);
                            t_ref_meas_state *ref_st = is_uref ?
                                        &params->meas_ctx.ref.uref :
                                        &params->meas_ctx.ref.uref_r;
                            TLTR216_MEASSTATE *uref_res = is_uref ?
                                        &params->meas_ctx.status.Uref:
                                        &params->meas_ctx.status.UrefR;

                            if (!bout) {
                                if ((params->meas_ctx.bg_meas_msk & LTR216_BG_MEAS_UREF_SHORT) && !bout
                                        && (dac_code == params->meas_ctx.dac_zero_code)) {

                                    double r = adc_val /  params->meas_ctx.i_amp;
                                    ref_st->state.is_short = r < hnd->Cfg.ShortThresholdR;

                                    f_calc_res_state(uref_res, &ref_st->state,
                                                     params->meas_ctx.status.Uneg.Status);
                                    if (uref_res->Status == LTR216_MEASSTATUS_SHORT)
                                        params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_UREF_SHORT;
                                } else  {
                                    BYTE range_idx;
                                    if (params->meas_ctx.bg_meas_msk & (LTR216_BG_MEAS_UREF)) {
                                        /* какому диапазону соответствует измерение определяем по коду ЦАП */
                                        for (range_idx = 0; range_idx < LTR216_UREF_MEAS_RANGES_CNT; range_idx++) {
                                            if (dac_code == params->meas_ctx.ref.dac_codes[range_idx]) {
                                                ref_st->ok_states[range_idx] = (meas_state == LTR216_MEASSTATUS_OK);
                                                if (ref_st->ok_states[range_idx]) {
                                                    double res_val;

                                                    avg_add_val(&ref_st->vals[range_idx].avg, abs_val);

                                                    /* по полученному значению определяем, будем ли использовать
                                                     * нужный диапазон */
                                                    res_val = ref_st->vals[range_idx].avg.result;
                                                    if ((res_val > f_uref_intervals[range_idx].min) &&
                                                            (res_val < f_uref_intervals[range_idx].max)) {
                                                        uref_res->Value = res_val;
                                                        ref_st->state.base_state = LTR216_MEASSTATUS_OK;
                                                        ref_st->state.value_valid = TRUE;
                                                    }
                                                } else {
                                                    /* если при проходе по всем диапазонам не было действительного значение,
                                                     * то обновляем на состоняие последнего диапазона */
                                                   if (range_idx == (LTR216_UREF_MEAS_RANGES_CNT -1)) {
                                                       if (!f_check_ok_intervals(ref_st->ok_states,
                                                                                 LTR216_UREF_MEAS_RANGES_CNT)) {
                                                            ref_st->state.base_state = LTR216_MEASSTATUS_ADC_OVERRANGE;
                                                            params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_UREF_OVERRANGE;
                                                       }
                                                    }
                                                }
                                                f_calc_res_state(uref_res,  &ref_st->state,
                                                                 params->meas_ctx.status.Uneg.Status);

                                                /* Для двухпроводной схемы подключения, если задано сопротивление проводов,
                                                 * то вычисляем UrefR на основе Uref по заданному току и сопротивлению */
                                                if (is_uref && !hnd->Cfg.Ch16ForUref && (hnd->Cfg.RrefWireResistance > 0)) {
                                                    params->meas_ctx.status.UrefR = *uref_res;
                                                    if (params->meas_ctx.status.UrefR.ValueValid) {
                                                        params->meas_ctx.status.UrefR.Value -=
                                                                2. * hnd->Cfg.RrefWireResistance * hnd->State.ISrcValue / 1000;
                                                    }
                                                }

                                            }
                                        }
                                    }
                                }
                            } else {
                                if (params->meas_ctx.bg_meas_msk & LTR216_BG_MEAS_UREF_OPEN) {
                                    BYTE range_idx;
                                    for (range_idx = 0; range_idx < LTR216_UREF_OPEN_INTERVAL_CNT; range_idx++) {
                                        if (dac_code == params->meas_ctx.ref.open_dac_codes[range_idx]) {
                                            if (last_bg_step_ch) {
                                                f_calc_open_intervals(ref_st->open_states,
                                                                      LTR216_UREF_OPEN_INTERVAL_CNT,
                                                                      range_idx, meas_state,
                                                                      &ref_st->state.is_open);

                                                f_calc_res_state(uref_res, &ref_st->state,
                                                                 params->meas_ctx.status.Uneg.Status);
                                                if (params->meas_ctx.status.Uneg.Status == LTR216_MEASSTATUS_OPEN)
                                                    params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_UREF_OPEN;
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (sw == LTABLE_SW_VADJ) {
                            params->meas_ctx.status.Vadj.Value = abs_val / 0.10153;
                            params->meas_ctx.status.Vadj.ValueValid = TRUE;
                            params->meas_ctx.status.Vadj.Status = meas_state;
                            if ((meas_state == LTR216_MEASSTATUS_OK) &&
                                    (params->meas_ctx.status.Vadj.Value > LTR216_VADJ_THRESHOLD)) {
                                params->meas_ctx.status.Vadj.Status = LTR216_MEASSTATUS_BAD_VALUE_RANGE;
                            }

                            if (params->meas_ctx.status.Vadj.Status != LTR216_MEASSTATUS_OK) {
                                params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_VADJ_ERROR;
                            }
                        } else if (sw == LTABLE_SW_UNEG) {
                            if (!bout) {
                                params->meas_ctx.status.Uneg.Value = abs_val;
                                params->meas_ctx.uneg.state.base_state = meas_state;
                                params->meas_ctx.uneg.state.value_valid = TRUE;
                            } else {
                                if (last_bg_step_ch) {
                                    params->meas_ctx.uneg.state.is_open = meas_state == LTR216_MEASSTATUS_ADC_OVERRANGE;
                                }
                            }
                            f_calc_res_state(&params->meas_ctx.status.Uneg, &params->meas_ctx.uneg.state, LTR216_MEASSTATUS_OK);
                            if (LTR216_MEASSTATE_IS_ERR(params->meas_ctx.status.Uneg.Status))
                                params->meas_ctx.status.DetectedErrors |= LTR216_DETECT_ERR_UNEG_OPEN;
                        } else if ((sw == LTABLE_SW_U) || (sw == LTABLE_SW_U_BOUT)) {
                            t_internal_meas_state *chIntState = &params->meas_ctx.uabs.chs[ch].state;
                            TLTR216_DATA_CH_STATUS *chStatus = &params->meas_ctx.ch_status_list[ch];
                            if ((params->meas_ctx.bg_meas_msk & LTR216_BG_MEAS_CH_SHORT) &&
                                    (dac_code == params->meas_ctx.dac_zero_code) && !bout) {
                                double r = adc_val / params->meas_ctx.i_amp;
                                chIntState->is_short = r < hnd->Cfg.ShortThresholdR;
                                f_calc_res_state(&chStatus->Ux, chIntState, params->meas_ctx.status.Uneg.Status);

                                f_calc_ucm_state(params->meas_ctx.bg_meas_msk, &params->meas_ctx.status, chStatus);
                                if (chStatus->Ux.Status == LTR216_MEASSTATUS_SHORT)
                                    chStatus->DetectedErrors |= LTR216_CH_DETECT_ERR_UX_SHORT;
                            } else {
                                INT interval_idx;
                                INT fnd_interval = -1;
                                for (interval_idx = 0; (interval_idx < LTR216_UABS_INTERVAL_CNT) && (fnd_interval < 0); interval_idx++) {
                                    if (dac_code == params->meas_ctx.uabs.intervals[interval_idx].dac_code)
                                        fnd_interval = interval_idx;
                                }

                                if (fnd_interval >= 0) {
                                    if (bout) {
                                        if (last_bg_step_ch) {
                                            f_calc_open_intervals(params->meas_ctx.uabs.chs[ch].open_states,
                                                                  LTR216_UABS_INTERVAL_CNT,
                                                                  fnd_interval, meas_state,
                                                                  &chIntState->is_open);
                                            f_calc_res_state(&chStatus->Ux, chIntState, params->meas_ctx.status.Uneg.Status);
                                            f_calc_ucm_state(params->meas_ctx.bg_meas_msk, &params->meas_ctx.status, chStatus);
                                            if (chStatus->Ux.Status == LTR216_MEASSTATUS_OPEN)
                                                chStatus->DetectedErrors |= LTR216_CH_DETECT_ERR_UX_OPEN;
                                        }
                                    } else {
                                        params->meas_ctx.uabs.chs[ch].ok_states[fnd_interval] =
                                                (meas_state == LTR216_MEASSTATUS_OK) &&
                                                (abs_val > f_uabs_intervals[fnd_interval].min) &&
                                                (abs_val <= f_uabs_intervals[fnd_interval].max);
                                        if (params->meas_ctx.uabs.chs[ch].ok_states[fnd_interval]) {
                                            chIntState->base_state = meas_state;
                                            chIntState->value_valid = TRUE;
                                            chStatus->Ux.Value = abs_val;
                                            f_calc_res_state(&chStatus->Ux, chIntState, params->meas_ctx.status.Uneg.Status);

                                            if ((params->meas_ctx.status.Uref.Status == LTR216_MEASSTATUS_OK)
                                                    && params->meas_ctx.status.Uref.ValueValid
                                                && chStatus->Ux.ValueValid && (chStatus->Ux.Status == LTR216_MEASSTATUS_OK)) {

                                                chStatus->Ucm.Value = (params->meas_ctx.status.Uref.Value  + chStatus->Ux.Value)/2;
                                                chStatus->Ucm.ValueValid = TRUE;
                                                chStatus->Ucm.Status = LTR216_MEASSTATUS_OK;

                                                /* Для 15-канальной схемы Ucm расчитывается уже относительно AGND.
                                                   Для 16-канальной - в нем участвует U-, которое должно быть вычтено явно */
                                                if (hnd->Cfg.Ch16ForUref &&
                                                        ((params->meas_ctx.status.Uneg.Status == LTR216_MEASSTATUS_OK) &&
                                                        params->meas_ctx.status.Uneg.ValueValid)) {
                                                    chStatus->Ucm.Value -= params->meas_ctx.status.Uneg.Value;
                                                }
                                            } else {
                                                f_calc_ucm_state(params->meas_ctx.bg_meas_msk, &params->meas_ctx.status, chStatus);
                                            }
                                        }

                                        /* если за все интервалы не нашли действительное значение, то
                                         * устанавливаем статус ошибки данного измерения */
                                        if (fnd_interval == (LTR216_UABS_INTERVAL_CNT - 1)) {
                                           if (!f_check_ok_intervals(params->meas_ctx.uabs.chs[ch].ok_states,
                                                                     LTR216_UABS_INTERVAL_CNT)) {
                                                chIntState->base_state = LTR216_MEASSTATUS_ADC_OVERRANGE;
                                           }
                                           f_calc_res_state(&chStatus->Ux, chIntState, params->meas_ctx.status.Uneg.Status);
                                           f_calc_ucm_state(params->meas_ctx.bg_meas_msk, &params->meas_ctx.status, chStatus);
                                        }
                                    }
                                }
                            }

                            /* если прошили все измерения таблицы, а состояние NOT_INIT,
                             * то это может быть в случае, когда само значение не измереняется,
                             * а только признаки ошибок, а значит призноков ошибок нет и можно
                             * перевести состояние в OK */
                            if ((wrd_table_ptr == params->meas_ctx.uabs.chs[ch].last_table_check_pos)
                                && (chIntState->base_state == LTR216_MEASSTATUS_NOT_INIT)) {
                                chIntState->base_state = LTR216_MEASSTATUS_OK;
                                f_calc_res_state(&chStatus->Ux, chIntState, params->meas_ctx.status.Uneg.Status);
                                f_calc_ucm_state(params->meas_ctx.bg_meas_msk, &params->meas_ctx.status, chStatus);
                            }
                        }
                    }



                    if (!bout || last_bg_step_ch) {
                        if (++bg_table_pos  == hnd->RawCfg.BgLChCnt)
                            bg_table_pos = 0;
                    }
                    if (++bg_step_pos == hnd->RawCfg.BgStepSize) {
                        bg_step_pos = 0;
                        if (++table_pos == hnd->RawCfg.MainLChCnt)
                            table_pos = 0;
                    }
                }
                table_bg = hnd->RawCfg.MainLChTbl[table_pos] & LCH_MSK_SEC_TBL ? TRUE : FALSE;
            } else {
                /** @todo поиск начала таблицы */
                err = wrd_err;
                out = TRUE;
            }
        }

        *size = put_pos;

        if (!(flags & LTR216_PROC_FLAG_NONCONT_DATA)) {
            params->bg_tbl_ptr_lost = bg_table_pos_lost;
            params->bg_tbl_ptr = bg_table_pos;
            params->cntr_lost = cntr_lost;
            params->cur_cntr = cntr;
        }


        if (ch_status_list != NULL) {
            for (i = 0; i < hnd->RawCfg.UserChCnt; i++) {
                INT phy_ch = LTABLE_GET_CH(hnd->RawCfg.MainLChTbl[i]);
                INT gain = LTABLE_GET_GAIN(hnd->RawCfg.MainLChTbl[i]);

                TLTR216_DATA_CH_STATUS *baseChStatus = &params->meas_ctx.ch_status_list[phy_ch];
                TLTR216_DATA_CH_STATUS *lchStatus = &ch_status_list[i];



                /* в результате объединяем все состояние физического канала с флагами состояния и ошибок логического канала */
                DWORD detect_errs = baseChStatus->DetectedErrors | lchStatus->DetectedErrors;
                DWORD ch_status = baseChStatus->StatusFlags | lchStatus->StatusFlags;
                *lchStatus = *baseChStatus;
                lchStatus->DetectedErrors = detect_errs;
                lchStatus->StatusFlags = ch_status;

                /* границы Ucm могут быть свои для каждого логического канала в случае,
                 * если измеряется один физический канал на разных диапазонах,
                 * поэтому если значение Ucm общее для физического канала, то
                 * рассчет диапазона и проверку выхода за диапазон нужно
                 * осуществлять отдельно для каждого логического */
                if ((lchStatus->Ucm.Status == LTR216_MEASSTATUS_OK) && lchStatus->Ucm.ValueValid) {
                    double diff_m = fabs(params->meas_ctx.status.Uref.Value  - lchStatus->Ux.Value) *
                            f_gains[gain].ku;
                    ch_status_list[i].UcmMin = 0.5 * diff_m;
                    ch_status_list[i].UcmMax = 4.96 - 0.5 * diff_m;
                    if ((lchStatus->Ucm.Value < lchStatus->UcmMin) || (lchStatus->Ucm.Value > lchStatus->UcmMax)) {
                        lchStatus->DetectedErrors |= LTR216_CH_DETECT_ERR_UCM_OUT_OF_RANGE;
                        lchStatus->Ucm.Status = LTR216_MEASSTATUS_BAD_VALUE_RANGE;
                    }
                }
            }
        }

        if (status != NULL) {
            *status = params->meas_ctx.status;
        }
    }



    return err;
}

LTR216API_DllExport(INT) LTR216_CalcISrcValue(const TLTR216_ISRC_CBR *cbr, DWORD code, double *resultValue) {
    double val = 2048. * (code + 1) / (4096 * 33.2);
    val = cbr == NULL ? val : cbr_dac_inv(&cbr->Ref, val);
    if (resultValue  != NULL)
        *resultValue = val;
    return LTR_OK;
}

LTR216API_DllExport(INT) LTR216_FindISrcCode(const TLTR216_ISRC_CBR *cbr, double isrc,
                                             DWORD *code, double *result_value) {
    INT err = LTR_OK;
    DWORD res_code;
    double isrc_cbr =  cbr == NULL ? isrc : cbr_dac(&cbr->Ref, isrc);

    double dcode = isrc_cbr * (4096 * 33.2) / 2048 - 1;
    if (dcode > 4095)
        dcode = 4095;
    if (dcode < 0)
        dcode = 0;
    res_code = (DWORD)(dcode + 0.5);
    if (code != NULL)
        *code = res_code;
    if (result_value != NULL) {
        err = LTR216_CalcISrcValue(cbr, res_code, result_value);
    }
    return err;
}

LTR216API_DllExport(INT) LTR216_FillISrcCode(TLTR216 *hnd, double isrc, double *result_value) {
    return LTR216_FindISrcCode(&hnd->ModuleInfo.FabricCbr.ISrc, isrc, &hnd->Cfg.ISrcCode, result_value);
}



LTR216API_DllExport(INT) LTR216_FindSyncFreqDiv(double adcSyncFreq, DWORD *div, double *resultAdcSyncFreq) {
    DWORD fnd_div = (DWORD)(LTR216_ADC_CLOCK/adcSyncFreq + 0.5);
    if (fnd_div > 0)
        fnd_div--;
    if (fnd_div > LTR216_SYNC_FDIV_MAX)
        fnd_div = LTR216_SYNC_FDIV_MAX;
    if (div != NULL)
        *div = fnd_div;
    if (resultAdcSyncFreq != NULL)
        *resultAdcSyncFreq =RESULT_ADC_SYNC_FREQ(fnd_div);
    return LTR_OK;
}

LTR216API_DllExport(INT) LTR216_FillSyncFreqDiv(TLTR216 *hnd, double adcFreq, double *resultAdcFreq) {
    return LTR216_FindSyncFreqDiv(adcFreq, &hnd->Cfg.SyncFreqDiv, resultAdcFreq);
}


LTR216API_DllExport(INT) LTR216_FillFilterParams(TLTR216 *hnd, double adcFreq,
                                                TLTR216_FILTER_OUT_PARAMS *filterParams,
                                                double *swTimeRes) {
    TLTR216_FILTER_OUT_PARAMS tmpPar;
    if (hnd->Cfg.AdcSwMode == LTR216_ADC_SWMODE_MULTICH_SYNC)
        adcFreq = RESULT_ADC_SYNC_FREQ(hnd->Cfg.SyncFreqDiv);

    INT err = LTR216_FindFilterParams(hnd->Cfg.AdcSwMode, adcFreq, hnd->Cfg.AdcMinSwTimeUs,
                                      &tmpPar, swTimeRes);
    if (err == LTR_OK) {
        hnd->Cfg.FilterType = tmpPar.FilterType;
        hnd->Cfg.AdcOdrCode = tmpPar.AdcOdrCode;

        if (filterParams)
            *filterParams = tmpPar;
    }
    return err;
}


LTR216API_DllExport(INT) LTR216_FindFilterParams(DWORD adcSwMode, double adcFreq,
                                                 double swTimeUs,
                                                 TLTR216_FILTER_OUT_PARAMS *filterParams,
                                                 double *swTimeRes) {
    INT err = filterParams == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;    
    if (err == LTR_OK) {
        double outSwTime = 0;
        BOOL single_ch_mode = adcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT;
        double adc_per, odr, res_odr;

        adc_per = 1./adcFreq;
        if (single_ch_mode) {
            odr = adcFreq;
        } else {
            double acq_time;
            res_odr = 0;

            acq_time = adc_per - swTimeUs/1000000;
            if (acq_time < 1./filt_sinc5_pars[0].multi_odr)
                acq_time = 1./filt_sinc5_pars[0].multi_odr;
            odr = 1./acq_time;
        }


        if (odr <= filt_enh50_pars[0].odr) {
            unsigned idx;
            filterParams->FilterType = LTR216_FILTER_ENH_50HZ;
            for (idx = 0; idx < sizeof(filt_enh50_pars)/sizeof(filt_enh50_pars[0]); idx++) {
                if (filt_enh50_pars[idx].odr >= odr) {
                    filterParams->AdcOdrCode = filt_enh50_pars[idx].code;
                    res_odr = filt_enh50_pars[idx].odr;
                } else {
                    break;
                }
            }
        } else {
            unsigned idx;
            if (single_ch_mode) {
                /* Для частот 125 и 250 КГц используется SINC5, а в остальных SINC3,
                 * исходя из уровня шумов АЦП */
                if (odr < 125000) {
                    filterParams->FilterType = LTR216_FILTER_SINC3;
                    for (idx = 1; idx <= LTR216_SINC3_ODR_MAX_DIV; idx++) {
                        double fodr = 8000000./(32*idx);
                        if (fodr >= odr) {
                            filterParams->AdcOdrCode = idx;
                            res_odr = fodr;
                        }
                    }
                } else {
                    filterParams->FilterType = LTR216_FILTER_SINC5_1;
                    for (idx = 0; idx < sizeof(filt_sinc5_pars)/sizeof(filt_sinc5_pars[0]); idx++) {
                        if (filt_sinc5_pars[idx].single_odr >= odr) {
                            filterParams->AdcOdrCode = filt_sinc5_pars[idx].code;
                            res_odr = filt_sinc5_pars[idx].single_odr;
                        } else {
                            break;
                        }
                    }
                }
            } else {
                filterParams->FilterType = LTR216_FILTER_SINC5_1;
                for (idx = 0; idx < sizeof(filt_sinc5_pars)/sizeof(filt_sinc5_pars[0]); idx++) {
                    if (filt_sinc5_pars[idx].multi_odr >= odr) {
                        filterParams->AdcOdrCode = filt_sinc5_pars[idx].code;
                        res_odr = filt_sinc5_pars[idx].multi_odr;
                    } else {
                        break;
                    }
                }
            }
        }



        if (err == LTR_OK) {
            err = LTR216_GetFilterOutParams(adcSwMode, filterParams->FilterType,
                                            filterParams->AdcOdrCode, filterParams);
        }

        if (!single_ch_mode) {
            outSwTime = (adc_per - 1./res_odr) * 1000000;
            if (outSwTime < swTimeUs) {
                err = LTR216_ERR_UNSUF_SW_TIME;
            }
        }


        if (swTimeRes != NULL) {
            *swTimeRes = outSwTime;
        }
    }
    return err;
}



LTR216API_DllExport(INT) LTR216_GetFilterOutParams(DWORD adcSwMode, DWORD filterType, DWORD AdcODRCode, TLTR216_FILTER_OUT_PARAMS *filterParams) {
    INT err = filterParams == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    BOOL single_ch_mode = adcSwMode == LTR216_ADC_SWMODE_SIGNLECH_CONT;

    if (err == LTR_OK) {
        memset(filterParams, 0, sizeof(TLTR216_FILTER_OUT_PARAMS));
        filterParams->FilterType = filterType;
        filterParams->AdcOdrCode = AdcODRCode;

        if ((adcSwMode != LTR216_ADC_SWMODE_MULTICH_SYNC) && (adcSwMode != LTR216_ADC_SWMODE_SIGNLECH_CONT)) {
            err = LTR216_ERR_INVALID_ADC_SWMODE;
        }
    }

    if (err == LTR_OK) {
        if (filterType == LTR216_FILTER_SINC5_1) {
            BOOL fnd = FALSE;
            unsigned i;
            for (i = 0; i < (sizeof(filt_sinc5_pars)/sizeof(filt_sinc5_pars[0])) && !fnd; i++) {
                if (AdcODRCode == filt_sinc5_pars[i].code) {
                   fnd = TRUE;
                   filterParams->Odr =  single_ch_mode ? filt_sinc5_pars[i].single_odr : filt_sinc5_pars[i].multi_odr;
                   filterParams->NotchFreq = filt_sinc5_pars[i].notch;
                }
            }

            if (!fnd) {
                err = LTR216_ERR_INVALID_ADC_ODR_CODE;
            }
        } else if (filterType == LTR216_FILTER_ENH_50HZ) {
            unsigned i;
            BOOL fnd = FALSE;
            for (i = 0; i < (sizeof(filt_enh50_pars)/sizeof(filt_enh50_pars[0])) && !fnd; i++) {
                if (AdcODRCode == filt_enh50_pars[i].code) {
                   fnd = TRUE;
                   filterParams->Odr = filt_enh50_pars[i].odr;
                   filterParams->NotchFreq = 50;
                   filterParams->NotchDB = filt_enh50_pars[i].db;
                }
            }

            if (!fnd) {
                err = LTR216_ERR_INVALID_ADC_ODR_CODE;
            }
        } else if (filterType == LTR216_FILTER_SINC3) {
            if ((AdcODRCode == 0) || (AdcODRCode > LTR216_SINC3_ODR_MAX_DIV)) {
                err = LTR216_ERR_INVALID_ADC_ODR_CODE;
            } else {
                double freq = 8000000./(32*AdcODRCode);
                filterParams->Odr = freq / (single_ch_mode ? 1 : 3);
                filterParams->NotchFreq = freq;
            }
        } else {
            err = LTR216_ERR_INVALID_FILTER_TYPE;
        }
    }
    return err;
}


LTR216API_DllExport(INT) LTR216_ReadFlashInfo(TLTR216 *hnd) {
    INT err = LTR216_IsOpened(hnd);

    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, FLASH_ADDR_MODULE_INFO,
                (unsigned char *)&hdr, sizeof(hdr)));
        }

        if ((err == LTR_OK) && (hdr.sign != FLASH_INFO_SIGN))
            err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
        if ((err == LTR_OK) && (hdr.format != FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && (hdr.size < FLASH_INFO_MIN_SIZE))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltr216_flash_info *pinfo = malloc(hdr.size+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,
                            FLASH_ADDR_MODULE_INFO+sizeof(hdr), ((BYTE *)pinfo) + sizeof(hdr),
                            hdr.size + 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 = LTR_ERROR_FLASH_INFO_CRC;
            }

            if (err == LTR_OK) {
                hnd->ModuleInfo.FlashInfoFlags = pinfo->hdr.flags;
                memcpy(hnd->ModuleInfo.Name, pinfo->module.name, LTR216_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTR216_SERIAL_SIZE);
                memcpy(&hnd->ModuleInfo.FabricCbr, &pinfo->cbr, sizeof(TLTR216_FABRIC_CBR));
            }
            free(pinfo);
        }
    } /*if (err == LTR_OK)*/
    return err;
}

LTR216API_DllExport(INT) LTR216_WriteInfo(TLTR216 *hnd) {
    INT err = LTR216_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_flash_wr_en(hnd);
        if (err == LTR_OK) {
            t_ltr216_flash_info info, info_ver;
            WORD crc, crc_ver;
            t_internal_params *params = (t_internal_params *)hnd->Internal;
            t_flash_errs flash_res, dis_res;

            info.hdr.sign = FLASH_INFO_SIGN;
            info.hdr.size = sizeof(info);
            info.hdr.format = FLASH_INFO_FORMAT;
            info.hdr.flags =  hnd->ModuleInfo.FlashInfoFlags;
            memcpy(info.module.name, hnd->ModuleInfo.Name, LTR216_NAME_SIZE);
            memcpy(info.module.serial, hnd->ModuleInfo.Serial, LTR216_SERIAL_SIZE);
            memcpy(&info.cbr, &hnd->ModuleInfo.FabricCbr, sizeof(TLTR216_FABRIC_CBR));
            crc = eval_crc16(0, (BYTE *)&info, sizeof(info));

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

            dis_res = flash_lock(&params->flash, FLASH_ADDR_MODULE_INFO,
                                 FLASH_SIZE_MODULE_INFO);
            if (!flash_res) {
                flash_res = dis_res;
            }
            dis_res = f_flash_wr_dis(hnd);
            if (!flash_res)
                flash_res = dis_res;

            if (!flash_res) {
                flash_res = flash_read(&params->flash, FLASH_ADDR_MODULE_INFO,
                    (unsigned char *)&info_ver, sizeof(info));
            }
            if (!flash_res) {
                flash_res = flash_read(&params->flash,
                    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 = LTR_ERROR_FLASH_VERIFY;
            }


        } /*if (err == LTR_OK)*/
    } /*if (err == LTR_OK)*/

    return err;
}




static INT read_tare_info(TLTR216 *hnd, TLTR216_TARE_CH_COEFS *tare_info, DWORD ch_mask) {
    INT err = LTR216_IsOpened(hnd);

    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, FLASH_ADDR_MODULE_TARE_INFO,
                (unsigned char *)&hdr, sizeof(hdr)));
        }

        if ((err == LTR_OK) && (hdr.sign != FLASH_TARE_INFO_SIGN))
            err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
        if ((err == LTR_OK) && (hdr.format != FLASH_TARE_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && (hdr.size < FLASH_TARE_INFO_MIN_SIZE))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltr216_flash_tare_info *pinfo = malloc(hdr.size+FLASH_TARE_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,
                            FLASH_ADDR_MODULE_TARE_INFO+sizeof(hdr), ((BYTE *)pinfo) + sizeof(hdr),
                            hdr.size + FLASH_TARE_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 = LTR_ERROR_FLASH_INFO_CRC;
            }

            if (err == LTR_OK) {
                INT ch;
                for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++) {
                    if (ch_mask & (1 << ch)) {
                        tare_info[ch] = pinfo->Channels[ch];
                    }
                }
            }
            free(pinfo);
        }
        flash_iface_flush(&params->flash, err);
    } /*if (err == LTR_OK)*/
    return err;
}

LTR216API_DllExport(INT) LTR216_ReadTareInfo(TLTR216 *hnd, DWORD ch_mask) {
    return read_tare_info(hnd, hnd->ModuleInfo.Tare, ch_mask);
}


LTR216API_DllExport(INT) LTR216_WriteTareInfo(TLTR216 *hnd, DWORD ch_mask) {
    INT err = LTR216_IsOpened(hnd);
    if (err == LTR_OK) {
        t_ltr216_flash_tare_info info;

        read_tare_info(hnd, info.Channels, LTR216_CHANNEL_MASK_ALL);

        err = f_flash_wr_en(hnd);
        if (err == LTR_OK) {
            t_ltr216_flash_tare_info info_ver;
            WORD crc, crc_ver;
            INT ch;
            t_internal_params *params = (t_internal_params *)hnd->Internal;
            t_flash_errs flash_res, dis_res;

            info.hdr.sign = FLASH_TARE_INFO_SIGN;
            info.hdr.size = sizeof(info);
            info.hdr.format = FLASH_TARE_INFO_FORMAT;
            info.hdr.flags = 0;

            for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++) {
                if (ch_mask & (1 << ch)) {
                    info.Channels[ch] = hnd->ModuleInfo.Tare[ch];
                }
            }

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

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

            dis_res = flash_lock(&params->flash, FLASH_ADDR_MODULE_TARE_INFO,
                                      FLASH_SIZE_MODULE_TARE_INFO);
            if (!flash_res) {
                flash_res = dis_res;
            }

            dis_res = f_flash_wr_dis(hnd);
            if (!flash_res) {
                flash_res = dis_res;
            }

            if (!flash_res) {
                flash_res = flash_read(&params->flash, FLASH_ADDR_MODULE_TARE_INFO,
                    (unsigned char *)&info_ver, sizeof(info));
            }
            if (!flash_res) {
                flash_res = flash_read(&params->flash,
                    FLASH_ADDR_MODULE_TARE_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 = LTR_ERROR_FLASH_VERIFY;
            }
        } /*if (err == LTR_OK)*/
    } /*if (err == LTR_OK)*/

    return err;
}


LTR216API_DllExport(INT) LTR216_FlashWriteEnable(TLTR216 *hnd) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_unlock(&pars->flash, LTR216_FLASH_USERDATA_ADDR, LTR216_FLASH_USERDATA_SIZE);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR216API_DllExport(INT) LTR216_FlashWriteDisable(TLTR216 *hnd) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_lock(&pars->flash, LTR216_FLASH_USERDATA_ADDR, LTR216_FLASH_USERDATA_SIZE);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}



LTR216API_DllExport(INT) LTR216_FlashRead(TLTR216 *hnd, DWORD addr, BYTE *data, DWORD size) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_read(&pars->flash, addr, data, size);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR216API_DllExport(INT) LTR216_FlashWrite(TLTR216 *hnd, DWORD addr, const BYTE *data, DWORD size) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params *)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_write(&pars->flash, addr, data, size, 0);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR216API_DllExport(INT) LTR216_FlashErase(TLTR216 *hnd, DWORD addr, DWORD size) {
    INT res = LTR216_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params *)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_erase(&pars->flash, addr, size);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR216API_DllExport(LPCSTR) LTR216_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);
}



