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

#include "ltr25api.h"

#include "adau1978.h"
#include "crc.h"
#include "devices/flash_dev_sst25.h"
#include "devices/flash_dev_sst26.h"
#include "flash.h"
#include "ltrmodule.h"
#include "ltrmodule_fpga_autoload.h"
#include "ports/ltr/flash_iface_ltr.h"
#include "ltimer.h"
#include "lbitfield.h"
#include "phasefilter.h"
#include "ltrmodule_flash_geninfo.h"
#include <string.h>

#ifdef LTRAPI_USE_KD_STORESLOTS
#include "ltrslot.h"
#endif


#if (LTR25_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_SIZE) || (LTR25_NAME_SIZE != LTR_FLASH_GENINFO_NAME_SIZE)
    #error module info len mismatch
#endif

#define LTR25_PLD_ICP_NEW_RC(pldVer)    (pldVer >= 2)
#define LTR25_R1_V1                     31600
#define LTR25_R1_V2                     110000
#define LTR25_R1(pldVer)                (LTR25_PLD_ICP_NEW_RC(pldVer) ? LTR25_R1_V2 : LTR25_R1_V1)
#define LTR25_R2_MUL                    (1./2)
#define LTR25_DEFAULT_C                 (6.8e-6)
#define LTR25_PHASE_REF_FREQ            50

#define LTR25_ICP_R_IN(r1)              (r1)


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

#define ADC_PLL_LOCK_TOUT  2000
#define ADC_WAIT_STABLE_TIME  850

#define CMD_ROM_WR_EN                   (LTR010CMD_PROGR | 0x10)
#define CMD_STATUS_FPGA                 (LTR010CMD_INSTR | 0x23)
#define CMD_CH_CONTROL                  (LTR010CMD_INSTR | 0x00)
#define CMD_ADC_FREQ                    (LTR010CMD_INSTR | 0x01)
#define CMD_GO                          (LTR010CMD_INSTR | 0x02)
#define CMD_ADC                         (LTR010CMD_INSTR | 0x04)
#define CMD_K_ADDR                      (LTR010CMD_INSTR | 0x07)
#define CMD_K_DATA1                     (LTR010CMD_INSTR | 0x08)
#define CMD_K_DATA2                     (LTR010CMD_INSTR | 0x09)
#define CMD_POW_CTL                     (LTR010CMD_INSTR | 0x0A)
#define CMD_TEDS_ACC                    (LTR010CMD_INSTR | 0x0B)

#define CMD_RESP_ADC                    (LTR010CMD_INSTR | 0x04)
#define CMD_RESP_ADC_ERR                (LTR010CMD_INSTR | 0x05)

#define FORMAT20_CNTR_MOD               15
#define FORMAT32_CNTR_MOD               13


#define LTR25_COEF_ADDR_CBR                0
#define LTR25_COEF_ADDR_FILTER1            0x40
#define LTR25_COEF_ADDR_FILTER2            0x80
#define LTR25_COEF_ADDR_FILTER3            0xC0

#define CMD_BIT_K_ADDR_WR                  (1UL << 15)


#define CMD_BIT_TEDS_OK                    (0x1 << 15)
#define CMD_BIT_TEDS_CH                    (0x7 << 12)
#define CMD_BIT_TEDS_OP                    (0x7 << 8)
#define CMD_BIT_TEDS_DATA                  (0xFF << 0)

#define TEDS_OP_RESET                       1
#define TEDS_OP_WRITE                       2
#define TEDS_OP_READ                        4

#define TEDS_OP_STATUS_OK                 0
#define TEDS_OP_STATUS_NO_PRESENSE_PULSE  1
#define TEDS_OP_STATUS_NO_IDLE            2
#define TEDS_OP_STATUS_BAD_OPCODE         3
#define TEDS_OP_STATUS_PREV_ERR           4

#define TEDS_CMD_BUF_SIZE                   512


#define MAKE_TEDS_RST_CMD(ch)            LTR_MODULE_MAKE_CMD(CMD_TEDS_ACC, \
    LBITFIELD_SET(CMD_BIT_TEDS_CH, ch) | LBITFIELD_SET(CMD_BIT_TEDS_OP, TEDS_OP_RESET))
#define MAKE_TEDS_RD_CMD(ch)            LTR_MODULE_MAKE_CMD(CMD_TEDS_ACC, \
    LBITFIELD_SET(CMD_BIT_TEDS_CH, ch) | LBITFIELD_SET(CMD_BIT_TEDS_OP, TEDS_OP_READ))
#define MAKE_TEDS_WR_CMD(ch, data)      LTR_MODULE_MAKE_CMD(CMD_TEDS_ACC, \
    LBITFIELD_SET(CMD_BIT_TEDS_CH, ch) | LBITFIELD_SET(CMD_BIT_TEDS_OP, TEDS_OP_WRITE) \
    | LBITFIELD_SET(CMD_BIT_TEDS_DATA, data))

/* Стандартные команды адресации устройств на шине 1-Wire */
#define ONEWIRE_CMD_READ_ROM        0x33
#define ONEWIRE_CMD_MATCH_ROM       0x55
#define ONEWIRE_CMD_SEARCH_ROM      0xF0
#define ONEWIRE_CMD_SKIP_ROM        0xCC
#define ONEWIRE_CMD_RESUME          0xA5
#define ONEWIRE_CMD_OV_SKIP_ROM     0x3C
#define ONEWIRE_CMD_OV_MATCH_ROM    0x69

/* Команды работы с памятью DS2431 */
#define DS2431_CMD_WRITE_SCRATCHPAD   0x0F
#define DS2431_CMD_READ_SCRATCHPAD    0xAA
#define DS2431_CMD_COPY_SCRATCHPAD    0x55
#define DS2431_CMD_READ_MEMORY        0xF0
#define DS2431_TIME_PROG_MS           10

#define DS2430A_CMD_READ_MEMORY       0xF0
#define DS2430A_CMD_READ_APP_REG      0xC3


#define TEDS_READ_BLOCK_SIZE        (4*32)
#define TEDS_CHECKSUM_BLOCK_SIZE    32


typedef enum {
    FLASH_PROT_DEFAULT,
    FLASH_PROT_FPGA_UPDATE,
    FLASH_PROT_INFO_UPDATE
} t_flash_prot_lvl;



/* количество успешных проверок захвата PLL перед принятием решения, что все ок */
#define PLL_LOCK_CHECK_CNT   3

/* Адрес дескриптора в EEPROM. */
#define LTR25_FLASH_ADDR_MODULE_INFO    0x1F0000
#define LTR25_FLASH_SIZE_MODULE_INFO    0x010000
#define LTR25_FLASH_ADDR_FPGA_FIRM      0x100000
#define LTR25_FLASH_SIZE_FPGA_FIRM      0x0F0000


/* Признак нового формата информации во Flash-памяти */
#define LTR25_FLASH_INFO_SIGN           LTR_FLASH_GENINFO_SIGN
#define LTR25_FLASH_INFO_CRC_SIZE       LTR_FLASH_GENINFO_CRC_SIZE
#define LTR25_FLASH_INFO_HDR_SIZE       LTR_FLASH_GENINFO_HDR_SIZE
#define LTR25_FLASH_INFO_FORMAT         1
#define LTR25_FLASH_INFO_MIN_SIZE       sizeof(t_ltr25_flash_info_v0)
#define LTR25_FLASH_INFO_MAX_SIZE       0x10000

#define LTR25_FLASH_HAS_INFO(pinfo, field) ((pinfo)->hdr.size >= (offsetof(t_ltr25_flash_info, field)) + sizeof((pinfo)->field))

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

/** Размер блока памяти TEDS датчика. Запись может быть только кратной этому размеру */
#define LTR25_TEDS_MEMORY_BLOCK_SIZE 8



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

#define LTR25_MAKE_ADC_CMD(adc_msk, reg_addr, wr, data) LTR_MODULE_MAKE_CMD(CMD_ADC, \
        (((wr & 0x01) << 15) | ((adc_msk & 0x03) << 13) | ((reg_addr & 0x1F) << 8) | (data & 0xFF)))

#define LTR25_MAKE_ADC_FREQ_CMD(hnd, clk_en)  LTR_MODULE_MAKE_CMD(CMD_ADC_FREQ, \
    f_freq_params[hnd->Cfg.FreqCode].FreqCode | (clk_en ? (1<<8) : 0))

#define f_adau1978_reg_rd(hnd, adc_msk, reg, pval) f_adau1978_reg_op(hnd, adc_msk, reg, 0, 0, pval)
#define f_adau1978_reg_wr(hnd, adc_msk, reg, val) f_adau1978_reg_op(hnd, adc_msk, reg, 1, val, NULL)


/*================================================================================================*/

typedef struct {
    BYTE devid;
    DWORD mem_size;
    DWORD teds_data_size;
    INT (*teds_mem_read)(TLTR25 *hnd, INT ch,   DWORD addr, BYTE *data, DWORD size, DWORD flags);
    INT (*teds_data_read)(TLTR25 *hnd, INT ch,  BYTE *data, DWORD size, DWORD *read_size);
    INT (*teds_data_write)(TLTR25 *hnd, INT ch,  const BYTE *data, DWORD size);
} t_ltr25_teds_nodedev_type;

typedef  struct {
    BYTE valid;
    BYTE devid;
    const t_ltr25_teds_nodedev_type *devtype;
}  t_ltr25_teds_dev_state;

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

typedef struct {
    t_flash_iface flash;
    double r1;
    BOOLEAN cntr_lost;
    BYTE cntr_val;
    INT cur_freq_cfg;

    BOOL afc_cor_enabled;
    BOOL last_adc_freq_valid;
    double last_adc_freq;
    BOOL afc_last_valid[LTR25_CHANNEL_CNT];
    double afc_fir_k[LTR25_CHANNEL_CNT];    /* рассчитанный коэффициент для коррекции АЧХ */
    double afc_fir_last[LTR25_CHANNEL_CNT]; /* действительность последних значений */
    t_ltr25_teds_dev_state teds_node[LTR25_CHANNEL_CNT];

    t_internal_ch_params Chs[LTR25_CHANNEL_CNT];
} t_internal_params;

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

typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module module;
    TLTR25_CBR_COEF cbr[LTR25_CHANNEL_CNT][LTR25_FREQ_CNT];
    TLTR25_PHASE_SHIFT_COEFS ICPPhaseCoefs;
    BYTE VerPLD; /* версия pld при которой была сделана калибровка
                   (т.к. отличаются параметры входной RC-цепи) */
    BYTE Pad[3];
} t_ltr25_flash_info;

#pragma pack(4)
struct LTR25Config {
    struct {
        CHAR name[LTR25_NAME_SIZE];
        CHAR serial[LTR25_SERIAL_SIZE];
        WORD ver_fpga;
        BYTE ver_pld;
        BYTE rev_board;
        BOOL is_industrial;
        struct Calibration {
            float offset;
            float gain;
        } calibr[LTR25_CHANNEL_CNT][LTR25_CBR_FREQ_CNT];
        struct AFC {
            double freq;
            double fir_coeffs[LTR25_CHANNEL_CNT];
        } afc;
    } info;
    struct {
        struct {
            BOOL is_enabled;
        } channels[LTR25_CHANNEL_CNT];
        BYTE sample_rate;
        BYTE resolution;
        BYTE current_source_out;
    } cfg;
    struct {
        BYTE fpga;
        BOOL is_running;
        BOOL ison_low_power;
    } state;
};
#pragma pack()


/*================================================================================================*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr25_to_ltr25cfg(const void *hcard, void *cfg);
static void conv_ltr25cfg_to_hltr25(const void *cfg, void *hcard);
#endif
static INT f_adau1978_reg_op(TLTR25 *hnd, BYTE adc_msk, BYTE reg, BYTE wr, BYTE wr_data,
    BYTE *rd_val);
static void f_calc_afc_k(TLTR25 *hnd);
static INT f_adc_check_config(TLTR25 *hnd, BYTE adc_msk);
static INT f_adc_set_reset(TLTR25 *hnd, BYTE adc_msk);
static INT f_adc_configure(TLTR25 *hnd, BYTE adc_msk);
static INT f_adc_wait_pll(TLTR25 *hnd, BYTE adc_msk);
static INT f_fpga_status_cmd(TLTR25 *hnd, WORD data);
static INT f_load_coef(TLTR25 *hnd);
static INT f_set_flash_protection(TLTR25 *hnd, t_flash_prot_lvl protect);

/*================================================================================================*/
/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR25_ERR_FPGA_FIRM_TEMP_RANGE,
        "Загружена прошивка ПЛИС для неверного температурного диапазона"},
    {LTR25_ERR_I2C_ACK_STATUS, "Ошибка обмена при обращении к регистрам АЦП по интерфейсу I2C"},
    {LTR25_ERR_I2C_INVALID_RESP,
        "Неверный ответ на команду при обращении к регистрам АЦП по интерфейсу I2C"},
    {LTR25_ERR_INVALID_FREQ_CODE, "Неверно задан код частоты АЦП"},
    {LTR25_ERR_INVALID_DATA_FORMAT, "Неверно задан формат данных АЦП"},
    {LTR25_ERR_INVALID_I_SRC_VALUE, "Неверно задано значение источника тока"},
    {LTR25_ERR_CFG_UNSUP_CH_CNT,
        "Для заданной частоты и формата не поддерживается заданное количество каналов АЦП"},
    {LTR25_ERR_NO_ENABLED_CH, "Не был разрешен ни один канал АЦП"},
    {LTR25_ERR_ADC_PLL_NOT_LOCKED, "Ошибка захвата PLL АЦП"},
    {LTR25_ERR_ADC_REG_CHECK, "Ошибка проверки значения записанных регистров АЦП"},
    {LTR25_ERR_LOW_POW_MODE_NOT_CHANGED,
        "Не удалось перевести АЦП из/в низкопотребляющее состояние"},
    {LTR25_ERR_LOW_POW_MODE, "Модуль находится в низкопотребляющем режиме"},
    {LTR25_ERR_INVALID_SENSOR_POWER_MODE, "Неверное значение режима питания датчиков"},
    {LTR25_ERR_CHANGE_SENSOR_POWER_MODE, "Не удалось изменить режим питания датчиков"},
    {LTR25_ERR_INVALID_CHANNEL_NUMBER, "Указан неверный номер канала модуля"},
    {LTR25_ERR_ICP_MODE_REQUIRED, "Модуль не переведен в ICP-режим питания датчиков, необходимый для данной операции"},
    {LTR25_ERR_TEDS_MODE_REQUIRED, "Модуль не переведен в TEDS режим питания датчиков, необходимый для данной операции"},
    {LTR25_ERR_TEDS_UNSUP_NODE_FAMILY, "Данное семейство устройств TEDS узла не поддерживается библиотекой"},
    {LTR25_ERR_TEDS_UNSUP_NODE_OP,  "Данная операция не поддерживается библиотекой для обнаруженого устройства TEDS узла"},    
    {LTR25_ERR_TEDS_DATA_CRC, "Неверное значение контрольной суммы в прочитанных данных TEDS"},
    {LTR25_ERR_TEDS_1W_NO_PRESENSE_PULSE, "Не обнаружено сигнала присутствия TEDS узла на однопроводной шине"},
    {LTR25_ERR_TEDS_1W_NOT_IDLE, "Однопроводная шина не была в незанятом состоянии на момент начала обмена"},
    {LTR25_ERR_TEDS_1W_UNKNOWN_ERR, "Неизвестная ошибка при обмене по однопроводной шине с узлом TEDS"},
    {LTR25_ERR_TEDS_MEM_STATUS, "Неверное состояние памяти TEDS узла"},
    {LTR25_ERR_TEDS_NODE_URN_CRC, "Неверное значение контрольной суммы в URN узла TEDS"},
    {LTR25_ERR_TEDS_1W_BAD_OPCODE, "Неверный код операции однопроводной шины"},
    {LTR25_ERR_TEDS_1W_PREV_CYCLE_ERR, "Команда откинута из-за ошибки в предыдущем цикле обмена по однопроводной шине"}
};

static const struct {
    double AdcFreq;
    BYTE FreqCode;
    BYTE AdcFs;                             /* Значение поля FS регистра АЦП */
    BYTE MaxCh20;
    BYTE MAXCh24;
    BYTE CoefIdx;
} f_freq_params[] = {
    {78.125e3,       1,  ADAU1987_SAI_CTRL0_FS_64_96, 6, 3, 0}, /**< 78.125 кГц */
    {39.0625e3,      3,  ADAU1987_SAI_CTRL0_FS_32_48, 8, 6, 1}, /**< 39.0625 кГц */
    {19.53125e3,     5,  ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}, /**< 19.53125 кГц */
    {9.765625e3,     7,  ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}, /**< 9.765625 кГц */
    {4.8828125e3,    9,  ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}, /**< 4.8828125 кГц */
    {2.44140625e3,   11, ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}, /**< 2.44140625 кГц */
    {1.220703125e3,  13, ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}, /**< 1.220703125 кГц */
    {0.6103515625e3, 15, ADAU1987_SAI_CTRL0_FS_32_48, 8, 8, 1}  /**< 610.3515625 Гц */
};

static const struct {
    BYTE addr;
    BYTE val;
} f_adc_regs[] = {
    {ADAU1978_REG_MPOWER, ADAU1978_REGBIT_MPOWER_PWUP},
    {ADAU1978_REG_BPOWER_SAI, ADAU1978_REGBIT_BPOWER_SAI_ADC_EN1 |
        ADAU1978_REGBIT_BPOWER_SAI_ADC_EN2 | ADAU1978_REGBIT_BPOWER_SAI_ADC_EN3 |
        ADAU1978_REGBIT_BPOWER_SAI_ADC_EN4 | ADAU1978_REGBIT_BPOWER_SAI_VREF_EN |
        ADAU1978_REGBIT_BPOWER_SAI_LDO_EN},
    {ADAU1978_REG_SAI_CTRL0, ADAU1987_SAI_CTRL0_SAI_TDM4 | ADAU1987_SAI_CTRL0_SDATA_FMT_LJ},
    {ADAU1978_REG_SAI_CTRL1, 0},
    {ADAU1978_REG_SAI_CMAP12, 0x10},
    {ADAU1978_REG_SAI_CMAP34, 0x32},
    {ADAU1978_REG_SAI_OVERTEMP, ADAU1978_REGBIT_SAI_OVERTEMP_DRV_C1 |
        ADAU1978_REGBIT_SAI_OVERTEMP_DRV_C2 | ADAU1978_REGBIT_SAI_OVERTEMP_DRV_C3 |
        ADAU1978_REGBIT_SAI_OVERTEMP_DRV_C4},
    {ADAU1978_REG_POSTADC_GAIN1, 0xA0},
    {ADAU1978_REG_POSTADC_GAIN2, 0xA0},
    {ADAU1978_REG_POSTADC_GAIN3, 0xA0},
    {ADAU1978_REG_POSTADC_GAIN4, 0xA0},
    {ADAU1978_REG_MISC, 0x02},
    {ADAU1978_REG_DC_HPF_CAL, 0x00},
    {ADAU1978_REG_PLLCTL, ADAU1978_REGBIT_PLLCTL_PLLMUTE | ADAU1978_REGBIT_PLLCTL_CLKS |
        ADAU1978_PLLCTL_MCS_256}
};

static const t_flash_info *f_supported_flash_devs[] = {
    &flash_info_sst25,
    &flash_info_sst26
};


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr25_to_ltr25cfg(const void *hcard, void *cfg) {
    size_t ich;
    const TLTR25 *hltr25 = hcard;
    struct LTR25Config *pcfg = cfg;

    strncpy(pcfg->info.name, hltr25->ModuleInfo.Name, ARRAY_LEN(pcfg->info.name));
    strncpy(pcfg->info.serial, hltr25->ModuleInfo.Serial, ARRAY_LEN(pcfg->info.serial));
    pcfg->info.ver_fpga = hltr25->ModuleInfo.VerFPGA;
    pcfg->info.ver_pld = hltr25->ModuleInfo.VerPLD;
    pcfg->info.rev_board = hltr25->ModuleInfo.BoardRev;
    pcfg->info.is_industrial = hltr25->ModuleInfo.Industrial;
    pcfg->info.afc.freq = hltr25->ModuleInfo.AfcCoef.AfcFreq;

    pcfg->cfg.sample_rate = hltr25->Cfg.FreqCode;
    pcfg->cfg.resolution = hltr25->Cfg.DataFmt;
    pcfg->cfg.current_source_out = hltr25->Cfg.ISrcValue;

    pcfg->state.fpga = hltr25->State.FpgaState;
    pcfg->state.is_running = hltr25->State.Run;
    pcfg->state.ison_low_power = hltr25->State.LowPowMode;

    for (ich = 0; (ich < LTR25_CHANNEL_CNT); ich++) {
        size_t ifq;

        pcfg->cfg.channels[ich].is_enabled = hltr25->Cfg.Ch[ich].Enabled;
        pcfg->info.afc.fir_coeffs[ich] = hltr25->ModuleInfo.AfcCoef.FirCoef[ich];
        for (ifq = 0; (ifq < LTR25_CBR_FREQ_CNT); ifq++) {
            pcfg->info.calibr[ich][ifq].offset = hltr25->ModuleInfo.CbrCoef[ich][ifq].Offset;
            pcfg->info.calibr[ich][ifq].gain = hltr25->ModuleInfo.CbrCoef[ich][ifq].Scale;
        }
    }
}

/*------------------------------------------------------------------------------------------------*/

static void conv_ltr25cfg_to_hltr25(const void *cfg, void *hcard) {
    size_t ich;
    TLTR25 *hltr25 = hcard;
    const struct LTR25Config *pcfg = cfg;

    strncpy(hltr25->ModuleInfo.Name, pcfg->info.name, ARRAY_LEN(hltr25->ModuleInfo.Name));
    strncpy(hltr25->ModuleInfo.Serial, pcfg->info.serial, ARRAY_LEN(hltr25->ModuleInfo.Serial));
    hltr25->ModuleInfo.VerFPGA = pcfg->info.ver_fpga;
    hltr25->ModuleInfo.VerPLD = pcfg->info.ver_pld;
    hltr25->ModuleInfo.BoardRev = pcfg->info.rev_board;
    hltr25->ModuleInfo.Industrial = pcfg->info.is_industrial;
    hltr25->ModuleInfo.AfcCoef.AfcFreq = pcfg->info.afc.freq;

    hltr25->Cfg.FreqCode = pcfg->cfg.sample_rate;
    hltr25->Cfg.DataFmt = pcfg->cfg.resolution;
    hltr25->Cfg.ISrcValue = pcfg->cfg.current_source_out;

    hltr25->State.AdcFreq = f_freq_params[hltr25->Cfg.FreqCode].AdcFreq;
    hltr25->State.EnabledChCnt = 0;
    hltr25->State.FpgaState = pcfg->state.fpga;
    hltr25->State.Run = pcfg->state.is_running;
    hltr25->State.LowPowMode = pcfg->state.ison_low_power;

    for (ich = 0; (ich < LTR25_CHANNEL_CNT); ich++) {
        size_t ifq;

        hltr25->ModuleInfo.AfcCoef.FirCoef[ich] = pcfg->info.afc.fir_coeffs[ich];
        hltr25->Cfg.Ch[ich].Enabled = pcfg->cfg.channels[ich].is_enabled;
        if (hltr25->Cfg.Ch[ich].Enabled)
            hltr25->State.EnabledChCnt++;

        for (ifq = 0; (ifq < LTR25_CBR_FREQ_CNT); ifq++) {
            hltr25->ModuleInfo.CbrCoef[ich][ifq].Offset = pcfg->info.calibr[ich][ifq].offset;
            hltr25->ModuleInfo.CbrCoef[ich][ifq].Scale = pcfg->info.calibr[ich][ifq].gain;
        }
    }

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

static BYTE onewire_rom_crc(const BYTE *msg, unsigned msg_len) {
    BYTE crc = 0;
    BYTE i;
    for (i = 0; i < msg_len; i++) {
        BYTE j;
        BYTE inbyte = msg[i];
        for (j = 0; j < 8; j++) {
            BYTE mix = (crc ^ inbyte) & 0x01;
            crc >>= 1;
            if (mix)
                crc ^= 0x8C;

            inbyte >>= 1;
        }
    }
    return crc;
}


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

static INT f_flash_wr_dis(TLTR25 *hnd) {
    /* пишем произвольную команду, чтобы запретить изменение статусного регистра */
    return ltrmodule_fpga_enable(&hnd->Channel,
           ltrmodule_fpga_is_enabled(hnd->State.FpgaState), &hnd->State.FpgaState);
}

/*------------------------------------------------------------------------------------------------*/
static INT f_adau1978_reg_op(TLTR25 *hnd, BYTE adc_msk, BYTE reg, BYTE wr, BYTE wr_data,
    BYTE *rd_val) {
    DWORD ack = 0;
    DWORD cmd = LTR25_MAKE_ADC_CMD(adc_msk, reg, wr, wr_data);
    INT err = LTR_OK;

    err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
    if (err == LTR_OK) {
        ack = LTR_MODULE_CMD_GET_DATA(ack);
        cmd = LTR_MODULE_CMD_GET_DATA(cmd);
        if ((cmd & 0xFF00) != (ack & 0xFF00)) {
            err = LTR25_ERR_I2C_INVALID_RESP;
        } else if (rd_val != NULL) {
            *rd_val = ack & 0xFF;
        }
    } else if ((err == LTR_ERROR_INVALID_CMD_RESPONSE) &&
               (LTR_MODULE_CMD_GET_CMDCODE(ack) == CMD_RESP_ADC_ERR)) {
        err = LTR25_ERR_I2C_ACK_STATUS;
    }

    return err;
}

/*------------------------------------------------------------------------------------------------*/
static void f_calc_afc_k(TLTR25 *hnd) {
    /* расчет коэффициентов фильтров */
    unsigned ch;
    t_internal_params *par = LTR25_INTERNAL_PARAMS(hnd);
    double set_freq = hnd->State.AdcFreq;

    if (set_freq > 9000) {
        par->afc_cor_enabled = TRUE;
        /* если уже были рассчитаны коэффициенты для этой частоты, то ничего делать
         * не нужно, иначе - выполняем перерасчет
         */
        if (!par->last_adc_freq_valid || (fabs(par->last_adc_freq-set_freq) > 0.1)) {
            double fi, a, k1, h;
            fi = set_freq / 4;

            for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
                a = 1.0 / hnd->ModuleInfo.AfcCoef.FirCoef[ch];

                k1 = -0.5 + sqrt(0.25 - (1 -a * a)/
                    (2 - 2 * cos(2*M_PI*hnd->ModuleInfo.AfcCoef.AfcFreq/f_freq_params[0].AdcFreq)));
                h = sqrt((1+k1)*(1+k1) - 2*(1+k1)*k1*cos(2*M_PI*fi/f_freq_params[0].AdcFreq) +
                    k1*k1);

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

            par->last_adc_freq_valid = 1;
            par->last_adc_freq = set_freq;
        }
    } else {
         par->afc_cor_enabled = FALSE;
    }
}

/*------------------------------------------------------------------------------------------------*/
static INT f_adc_check_config(TLTR25 *hnd, BYTE adc_msk) {
    unsigned r;
    INT err = LTR_OK;

    for (r = 0; ((r < sizeof(f_adc_regs)/sizeof(f_adc_regs[0])) && (err == LTR_OK)); r++) {
        BYTE set_val = f_adc_regs[r].val;
        const BYTE addr = f_adc_regs[r].addr;
        BYTE rd_val;
        if (addr == ADAU1978_REG_SAI_CTRL0)
            set_val |=  f_freq_params[hnd->Cfg.FreqCode].AdcFs;

        err = f_adau1978_reg_rd(hnd, adc_msk, addr, &rd_val);
        if (err == LTR_OK) {
            if (addr == ADAU1978_REG_PLLCTL) {
                set_val |= ADAU1978_REGBIT_PLLCTL_PLLLOCK;
                if (!(rd_val & ADAU1978_REGBIT_PLLCTL_PLLLOCK))
                    err = LTR25_ERR_ADC_PLL_NOT_LOCKED;
            }
            if ((err == LTR_OK) && (set_val != rd_val))
                err = LTR25_ERR_ADC_REG_CHECK;
        }
    }


    return err;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_adc_set_reset(TLTR25 *hnd, BYTE adc_msk) {
    return f_adau1978_reg_wr(hnd, adc_msk, ADAU1978_REG_MPOWER, ADAU1978_REGBIT_MPOWER_SRST | ADAU1978_REGBIT_MPOWER_PWUP);
}


static INT f_adc_configure(TLTR25 *hnd, BYTE adc_msk) {
    unsigned r;
    INT err = LTR_OK;

    for (r = 0; ((r < sizeof(f_adc_regs)/sizeof(f_adc_regs[0])) && (err == LTR_OK)); r++) {
        BYTE val = f_adc_regs[r].val;
        const BYTE addr = f_adc_regs[r].addr;
        if (addr == ADAU1978_REG_SAI_CTRL0)
            val |=  f_freq_params[hnd->Cfg.FreqCode].AdcFs;

        err = f_adau1978_reg_wr(hnd, adc_msk, addr, val);
    }



    return err;
}

static INT f_adc_wait_pll(TLTR25 *hnd, BYTE adc_msk) {
    /* ожидание захвата PLL */
    INT err = LTR_OK;

    t_ltimer lock_tmr;
    INT lock = 0;

    ltimer_set(&lock_tmr, LTIMER_MS_TO_CLOCK_TICKS(ADC_PLL_LOCK_TOUT));

    while ((err == LTR_OK) && (lock != PLL_LOCK_CHECK_CNT)) {
        BYTE wrd = 0;
        err = f_adau1978_reg_rd(hnd, adc_msk, ADAU1978_REG_PLLCTL, &wrd);
        if (wrd & ADAU1978_REGBIT_PLLCTL_PLLLOCK) {
            lock++;
        } else {
            lock = 0;
            if (ltimer_expired(&lock_tmr))
                err = LTR25_ERR_ADC_PLL_NOT_LOCKED;
        }
    }
    return err;
}


/*------------------------------------------------------------------------------------------------*/
static INT f_fpga_status_cmd(TLTR25 *hnd, WORD data) {
    DWORD cmd, ack;
    INT err;
    /* Получаем версию ПЛИС, если он был успешно загружен */
    cmd = LTR_MODULE_MAKE_CMD(CMD_STATUS_FPGA, data);
    err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);

    if (err == LTR_OK) {
        unsigned char industrial_firm;
        ack = LTR_MODULE_CMD_GET_DATA(ack);

        hnd->ModuleInfo.VerFPGA = ack & 0x03FF;
        hnd->ModuleInfo.BoardRev = (ack >> 11) & 0x1F;
        industrial_firm  = (ack >> 10) & 1;
        hnd->State.LowPowMode = (ack >> 9) & 1;


        if (industrial_firm != hnd->ModuleInfo.Industrial)
            err = LTR25_ERR_FPGA_FIRM_TEMP_RANGE;
    }

    return err;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_load_coef(TLTR25 *hnd) {
    unsigned ch;
    DWORD cmd[1+2*2*LTR25_CHANNEL_CNT];
    unsigned freq_idx = f_freq_params[hnd->Cfg.FreqCode].CoefIdx;
    unsigned put_pos = 0;
    INT err = LTR_OK;


    cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_K_ADDR, CMD_BIT_K_ADDR_WR | LTR25_COEF_ADDR_CBR);
    for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
        const double dval = (double)hnd->ModuleInfo.CbrCoef[ch][freq_idx].Scale * 0x40000000;
        const INT val = (int)((dval >= 0) ? dval + 0.5 : dval - 0.5);

        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_K_DATA1, (val >> 16) & 0xFFFF);
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_K_DATA2, val & 0xFFFF);
    }

    for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
        const double dval = (double)hnd->ModuleInfo.CbrCoef[ch][freq_idx].Offset;
        const INT val = (int)((dval >= 0) ? dval + 0.5 : dval - 0.5);

        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_K_DATA1, (val >> 16) & 0xFFFF);
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_K_DATA2, val & 0xFFFF);
    }

    err = ltr_module_send_cmd(&hnd->Channel, cmd, put_pos);


    return err;
}

/*------------------------------------------------------------------------------------------------*/
static INT f_set_flash_protection(TLTR25 *hnd, t_flash_prot_lvl protect_lvl) {
    t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
    INT err = LTR_OK;

    if (params->flash.flash_info == &flash_info_sst25) {

        const BYTE prot_mask = SST25_STATUS_BP0 | SST25_STATUS_BP1 | SST25_STATUS_BP2 | SST25_STATUS_BP3 | SST25_STATUS_BPL;
        const BYTE prot_codes[] = {
            SST25_STATUS_BP2 | SST25_STATUS_BP0 | SST25_STATUS_BPL, //default
            SST25_STATUS_BP0, //flga update
            0 //info update
        };

        BYTE status = 0;
        BYTE protect_code = prot_codes[protect_lvl];

        err = flash_sst25_set_status(&params->flash, protect_code);

        if (err == LTR_OK)
            err = flash_sst25_get_status(&params->flash, &status);

        if ((err == LTR_OK) && ((status & prot_mask) != (protect_code & prot_mask)))
            err = LTR_ERROR_FLASH_SET_PROTECTION;
    } else if (params->flash.flash_info == &flash_info_sst26) {
        t_flash_errs flash_res = FLASH_ERR_OK;
        if (protect_lvl == FLASH_PROT_DEFAULT) {
            flash_res = flash_lock(&params->flash, LTR25_FLASH_ADDR_FPGA_FIRM,
                                   LTR25_FLASH_SIZE_FPGA_FIRM);
            if (flash_res == FLASH_ERR_OK) {
                flash_res = flash_lock(&params->flash, LTR25_FLASH_ADDR_MODULE_INFO,
                                         LTR25_FLASH_SIZE_MODULE_INFO);
            }
        } else if (protect_lvl == FLASH_PROT_FPGA_UPDATE) {
            flash_res = flash_unlock(&params->flash, LTR25_FLASH_ADDR_FPGA_FIRM,
                                     LTR25_FLASH_SIZE_FPGA_FIRM);
        } else if (protect_lvl == FLASH_PROT_INFO_UPDATE) {
            flash_res = flash_unlock(&params->flash, LTR25_FLASH_ADDR_MODULE_INFO,
                                     LTR25_FLASH_SIZE_MODULE_INFO);
        }

        err = flash_iface_ltr_conv_err(flash_res);
    }
    return err;
}


static BYTE f_teds_calc_sum(const BYTE *data, DWORD size) {
    BYTE sum = 0;
    DWORD i;
    for (i = 0; i < size; i++) {
        sum += data[i];
    }
    return sum;
}

static INT f_teds_check_sum(const BYTE *data, DWORD size) {
    return f_teds_calc_sum(data, size) == 0 ? LTR_OK : LTR25_ERR_TEDS_DATA_CRC;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_AdcRegRead(TLTR25 *hnd, BYTE adc_msk, BYTE reg, BYTE *val) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_adau1978_reg_rd(hnd, adc_msk, reg, val);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_AdcRegWrite(TLTR25 *hnd, BYTE adc_msk, BYTE reg, BYTE val) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_adau1978_reg_wr(hnd, adc_msk, reg,  val);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_Close(TLTR25 *hnd) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (hnd->Internal != NULL) {
            t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
            flash_iface_ltr_close(&params->flash);
            free(hnd->Internal);
            hnd->Internal = NULL;
        }
        err = LTR_Close(&hnd->Channel);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FlashErase(TLTR25 *hnd, DWORD addr, DWORD size) {
    INT res = LTR25_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = LTR25_INTERNAL_PARAMS(hnd);
        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;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FlashRead(TLTR25 *hnd, DWORD addr, BYTE *data, DWORD size) {
    INT res = LTR25_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = LTR25_INTERNAL_PARAMS(hnd);
        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);
        flash_iface_flush(&pars->flash, flash_res);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FlashWrite(TLTR25 *hnd, DWORD addr, const BYTE *data, DWORD size) {
    INT res = LTR25_IsOpened(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = LTR25_INTERNAL_PARAMS(hnd);
        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);
        flash_iface_flush(&pars->flash, flash_res);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FPGAEnable(TLTR25 *hnd, BOOL enable) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = ltrmodule_fpga_enable(&hnd->Channel, enable, &hnd->State.FpgaState);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FPGAFirmwareWriteEnable(TLTR25 *hnd, BOOL en) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_flash_wr_en(hnd);
        if (err == LTR_OK) {
            INT dis_err;
            err = flash_iface_ltr_conv_err(f_set_flash_protection(hnd,
                en ? FLASH_PROT_FPGA_UPDATE : FLASH_PROT_DEFAULT));

            dis_err = f_flash_wr_dis(hnd);
            if (err == LTR_OK)
                err = dis_err;
        }
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_FPGAIsEnabled(TLTR25 *hnd, BOOL *enabled) {
    INT err = LTR25_IsOpened(hnd);
    if ((err == LTR_OK) && (enabled == NULL))
        err = LTR_ERROR_PARAMETERS;
    if (err == LTR_OK)
        *enabled = ltrmodule_fpga_is_enabled(hnd->State.FpgaState);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_GetConfig(TLTR25 *hnd) {
    INT err = LTR25_IsOpened(hnd);

    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
        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, LTR25_FLASH_ADDR_MODULE_INFO,
                (unsigned char *)&hdr, sizeof(hdr)));
        }

        if ((err == LTR_OK) && (hdr.sign != LTR25_FLASH_INFO_SIGN))
            err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
        if ((err == LTR_OK) && (hdr.format != LTR25_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && (hdr.size < LTR25_FLASH_INFO_MIN_SIZE))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltr25_flash_info *pinfo = malloc(hdr.size+LTR25_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,
                    LTR25_FLASH_ADDR_MODULE_INFO+sizeof(hdr), ((BYTE *)pinfo) + sizeof(hdr),
                    hdr.size+LTR25_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 = (WORD)(((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) {
                unsigned ch, freq;
                memcpy(hnd->ModuleInfo.Name, pinfo->module.name, LTR25_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTR25_SERIAL_SIZE);

                for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
                    for (freq = 0; (freq < LTR25_CBR_FREQ_CNT); freq++) {
                        hnd->ModuleInfo.CbrCoef[ch][freq] = pinfo->cbr[ch][freq];
                    }
                }

                if (LTR25_FLASH_HAS_INFO(pinfo, ICPPhaseCoefs)) {
                    BYTE cbrVerPLD = 0;
                    if (LTR25_FLASH_HAS_INFO(pinfo, VerPLD)) {
                        cbrVerPLD = pinfo->VerPLD;
                    }

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

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(LPCSTR) LTR25_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);
}

static void f_info_init(TLTR25 *hnd) {
    unsigned ch, freq;

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

    /* установка параметров, читаемых из flash-памяти в значения по умолчанию */
    for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
        for (freq = 0; (freq < LTR25_CBR_FREQ_CNT); freq++) {
            hnd->ModuleInfo.CbrCoef[ch][freq].Scale = 1.0;
            hnd->ModuleInfo.CbrCoef[ch][freq].Offset = 0.0;
        }
    }

    hnd->ModuleInfo.AfcCoef.AfcFreq = 19200;
    for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
        hnd->ModuleInfo.AfcCoef.FirCoef[ch] = 0.99515;
    }

    memset(&hnd->State, 0, sizeof(hnd->State));
}


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

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_IsOpened(TLTR25 *hnd) {
     return (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_Open(TLTR25 *hnd, DWORD ltrd_addr, WORD ltrd_port, const CHAR *crate_sn,
    INT slot) {
    return LTR25_OpenEx(hnd, ltrd_addr, ltrd_port, crate_sn, slot, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_OpenEx(TLTR25 *hnd, DWORD ltrd_addr, WORD ltrd_port,
    const CHAR *crate_sn, INT slot, DWORD in_flags, DWORD *out_flags) {
    DWORD cmd;
    int fatal_err;
    INT warning;
    DWORD open_flags = 0;
    DWORD out_flg = 0;
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (err == LTR_OK) {
        if (LTR25_IsOpened(hnd) == LTR_OK)
            LTR25_Close(hnd);
    }



    if ((err == LTR_OK) && (in_flags & LTR_OPENINFLG_REOPEN)) {
#ifdef LTRAPI_USE_KD_STORESLOTS
        err = ltrslot_restore_config(hnd, ltrd_addr, ltrd_port, crate_sn, (WORD)slot,
            sizeof(struct LTR25Config), conv_ltr25cfg_to_hltr25, &out_flg);
        if ((err == LTR_OK) && (out_flg & LTR_OPENOUTFLG_REOPEN))
                open_flags |= LTR_MOPEN_INFLAGS_DONT_RESET;
#else
        err = LTR_ERROR_NOT_IMPLEMENTED;
#endif
    }

    if (err == LTR_OK) {
        err = ltr_module_open(&hnd->Channel, ltrd_addr, ltrd_port, crate_sn, slot, LTR_MID_LTR25,
            &open_flags, &cmd, &warning);
        if (err == LTR_OK) {
            hnd->Internal = calloc(1, sizeof(t_internal_params));
            if (hnd->Internal == NULL)
                err = LTR_ERROR_MEMORY_ALLOC;
        }
    }

    if (err == LTR_OK) {
        f_info_init(hnd);
        hnd->ModuleInfo.VerPLD = (cmd >> 1) & 0x1F;
        hnd->ModuleInfo.Industrial = (cmd & 1) ? TRUE : FALSE;
        hnd->ModuleInfo.SupportedFeatures = 0;
        if (LTR25_PLD_ICP_NEW_RC(hnd->ModuleInfo.VerPLD))
            hnd->ModuleInfo.SupportedFeatures |= LTR25_FEATURE_EXT_BANDWIDTH_LF;
    }

#ifdef LTRAPI_USE_KD_STORESLOTS
    if ((err == LTR_OK) && !(in_flags & LTR_OPENINFLG_REOPEN))
        err = ltrslot_stop(&hnd->Channel);
#endif

    if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        INT fpga_status_res;
        INT flash_res = 0;
        unsigned ch;
        double def_phi;
        t_internal_params *internal = LTR25_INTERNAL_PARAMS(hnd);




        internal->r1 = LTR25_R1(hnd->ModuleInfo.VerPLD);

        def_phi = phasefilter_calc_phi(internal->r1, internal->r1 * LTR25_R2_MUL,
                                       LTR25_DEFAULT_C,
                                       LTR25_PHASE_REF_FREQ, 1e6);

        hnd->ModuleInfo.PhaseCoef.PhaseShiftRefFreq = LTR25_PHASE_REF_FREQ;

        /* установка параметров, читаемых из flash-памяти в значения по умолчанию */
        for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
            hnd->ModuleInfo.PhaseCoef.PhaseShift[ch] = def_phi;
        }

        fpga_status_res = ltrmodule_fpga_check_load(&hnd->Channel, &hnd->State.FpgaState);
        /* За исключением случая, когда не смогли дождаться готовности,
         * можем работать с flash-памятью
         */
        if (hnd->State.FpgaState != LTR_FPGA_STATE_LOAD_PROGRESS) {
            t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
            params->cur_freq_cfg = -1;

            flash_res = flash_iface_ltr_init(&params->flash, &hnd->Channel);
            if (!flash_res) {
                flash_res = flash_set_from_list(&params->flash, f_supported_flash_devs,
                                                sizeof(f_supported_flash_devs)/sizeof(f_supported_flash_devs[0]), NULL);
            }

            if (flash_res == 0) {
                if (params->flash.flash_info == &flash_info_sst25) {
                    BYTE status;
                    /* если разрешено изменение защиты страниц, то сразу
                     * устанавливаем защиту по-умолчанию и запрещаем изменение */
                    flash_res = flash_sst25_get_status(&params->flash, &status);

                    if ((flash_res == 0) && !(status & SST25_STATUS_BPL))
                        flash_res = f_set_flash_protection(hnd, FLASH_PROT_DEFAULT);
                } else if (params->flash.flash_info == &flash_info_sst26) {
                    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(LTR25_FLASH_ADDR_MODULE_INFO,
                                                             LTR25_FLASH_SIZE_MODULE_INFO,
                                                             bpr, 1) |
                                flash_sst26_bpr_bpr_diff_msk(LTR25_FLASH_ADDR_FPGA_FIRM,
                                                             LTR25_FLASH_ADDR_FPGA_FIRM,
                                                             bpr, 1) |
                                flash_sst26_bpr_bpr_diff_msk(LTR25_FLASH_USERDATA_ADDR,
                                                             LTR25_FLASH_USERDATA_SIZE,
                                                             bpr, 0);
                        /* если нет, то необходимо защитить.
                         * Для смены защиты требуется посылка команды 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);
                        }
                   }
                }
            }

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

        if ((err == LTR_OK) && (fpga_status_res == LTR_OK)) {
            err = LTR25_FPGAEnable(hnd, TRUE);
            if (err != LTR_OK)
                err = LTR_ERROR_FPGA_ENABLE;
            /* чтение версии и проверка прошивки */
            if (err == LTR_OK)
                err = f_fpga_status_cmd(hnd, 0);
        }


        if (err == LTR_OK)
            err = flash_res;
        if (err == LTR_OK)
            err = fpga_status_res;
    } /*if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT))*/

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

    fatal_err = ((err != LTR_OK) && (err != LTR_ERROR_FPGA_LOAD_DONE_TOUT) &&
        (err != LTR_ERROR_FPGA_ENABLE) && (err != LTR_ERROR_FLASH_INFO_NOT_PRESENT) &&
        (err != LTR_ERROR_FLASH_INFO_UNSUP_FORMAT) && (err != LTR25_ERR_FPGA_FIRM_TEMP_RANGE));
    if (fatal_err)
        LTR25_Close(hnd);

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

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_ProcessData(TLTR25 *hnd, const DWORD *src, double *dest, INT *size,
    DWORD flags, DWORD *ch_status) {
    INT res = LTR25_IsOpened(hnd);
    if ((res == LTR_OK) && ((src == NULL) || (size == NULL) || (*size == 0)))
        res = LTR_ERROR_PARAMETERS;
    if (res == LTR_OK) {
        BYTE chan_list[LTR25_CHANNEL_CNT];  /* Список номеров каналов в кадре. */
        t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
        BYTE i, ch_cnt;
        BYTE frame_pos = 0;
        INT rem_size = *size;
        INT put_size = 0;
        INT wrd_err = LTR_OK;
        BYTE cntr = params->cntr_val;
        BOOLEAN cntr_lost = (flags & LTR25_PROC_FLAG_NONCONT_DATA) ? TRUE : params->cntr_lost;
        BYTE exp_wrd_num = 0;
        WORD prev_wrd_data = 0;

        if (ch_status != NULL)
            memset(ch_status, 0, hnd->State.EnabledChCnt*sizeof(ch_status[0]));


        for (i = 0, ch_cnt = 0; (i < LTR25_CHANNEL_CNT); i++) {
            if (hnd->Cfg.Ch[i].Enabled)
                chan_list[ch_cnt++] = i;
        }

        if (flags & LTR25_PROC_FLAG_NONCONT_DATA) {
            if (flags & LTR25_PROC_FLAG_PHASE_COR) {
                for (i = 0; (i < LTR25_CHANNEL_CNT); i++) {
                    params->afc_last_valid[i] = FALSE;
                    phasefilter_reset(&params->Chs[i].phase_flt);
                }
            }
        }


        for (; (rem_size != 0); rem_size--, src++) {
            BOOLEAN drop_wrd = FALSE;
            INT wrd = 0;
            BYTE rcv_ch = (*src >> 4) & 7;
            DWORD cur_status = LTR25_CH_STATUS_OK;

            /* при несовпадении каналов выбрасываем весь кадр */
            if (rcv_ch != chan_list[frame_pos])
                wrd_err = (put_size == 0) ? LTR_ERROR_PROCDATA_UNALIGNED : LTR_ERROR_PROCDATA_CHNUM;

            if (hnd->Cfg.DataFmt == LTR25_FORMAT_20) {
                BYTE rcv_cntr = (*src >> 7) & 1;
                BYTE exp_cntr = (cntr == (FORMAT20_CNTR_MOD - 1));

                if (!cntr_lost && (exp_cntr != rcv_cntr)) {
                    res = LTR_ERROR_PROCDATA_CNTR;
                    cntr_lost = TRUE;
                }

                if (cntr_lost && (rcv_cntr != 0)) {
                    cntr_lost = FALSE;
                    cntr = 0;
                } else {
                    if (++cntr == FORMAT20_CNTR_MOD)
                        cntr = 0;
                }

                if (wrd_err == LTR_OK) {
                    wrd = (INT)(((*src & 0x0F) << 16) | ((*src >> 16) & 0xFFFF));
                    if (wrd == 0x07FFFF) {
                        cur_status = LTR25_CH_STATUS_OPEN;
                    } else if (wrd == 0x080000) {
                        cur_status = LTR25_CH_STATUS_SHORT;
                    } else {
                        wrd <<= 12;
                    }
                }
            } else if (hnd->Cfg.DataFmt == LTR25_FORMAT_32) {
                BYTE rcv_cntr = *src & 0x0F;
                BYTE rcv_wrd_num = (*src >> 7) & 1;

                if (cntr_lost) {
                    cntr = rcv_cntr;
                } else if (!cntr_lost && (cntr != rcv_cntr)) {
                    res = wrd_err = LTR_ERROR_PROCDATA_CNTR;
                    cntr = rcv_cntr;
                }

                if (++cntr == FORMAT32_CNTR_MOD)
                    cntr = 0;

                cntr_lost = FALSE;

                if (wrd_err == LTR_OK) {
                    /* проверка правильности следования слов (старший/младший) */
                    if (rcv_wrd_num != exp_wrd_num) {
                        wrd_err = (put_size == 0) ?
                            LTR_ERROR_PROCDATA_UNALIGNED : LTR_ERROR_PROCDATA_WORD_SEQ;
                    }
                }

                if (wrd_err == LTR_OK) {
                    if (!rcv_wrd_num) {
                        /* для первого слова сохраняем данные, чтобы объединить со вторым */
                        prev_wrd_data = (*src >> 16) & 0xFFFF;
                        drop_wrd = TRUE;
                    } else {
                        /* для второго - восстанавливаем весь 32-битный отсчет */
                        wrd = (INT)(((DWORD)prev_wrd_data << 16) | ((*src >> 16) & 0xFFFF));

                        if (wrd == 0x7FFFFFFF)           cur_status = LTR25_CH_STATUS_OPEN;
                        else if (wrd == (INT)0x80000000) cur_status = LTR25_CH_STATUS_SHORT;
                    }
                    exp_wrd_num ^= 1;
                }
            }

            /* при ошибке в слове выбрасываем весь соответствующий кадр и
             * будем искать начало следующего
             */
            if (wrd_err != LTR_OK) {
                put_size -= frame_pos;
                frame_pos = 0;
                exp_wrd_num = 0;
                if (res == LTR_OK)
                    res = wrd_err;

                for (i = 0; (i < LTR25_CHANNEL_CNT); i++) {
                    params->afc_last_valid[i] = FALSE;
                }
            } else if (!drop_wrd) {
                double val = 0;                
                t_internal_ch_params *ch_params;
                int ch_num = chan_list[frame_pos];
                ch_params = &params->Chs[ch_num];

                if (cur_status == 0) {
                    double x_fir;

                    val = wrd;

                    if (params->afc_cor_enabled) {
                        x_fir = val;
                        if (params->afc_last_valid[ch_num]) {
                            val = (val - params->afc_fir_last[ch_num]) * params->afc_fir_k[ch_num] +
                                x_fir;
                        }
                        params->afc_fir_last[ch_num] = x_fir;
                        params->afc_last_valid[ch_num] = TRUE;
                    }

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

                    if (flags & LTR25_PROC_FLAG_PHASE_COR) {
                        val = phasefilter_process_point(&params->Chs[ch_num].phase_flt, val);
                    }

                    if (flags & LTR25_PROC_FLAG_VOLT)
                        val = val * LTR25_ADC_RANGE_PEAK / LTR25_ADC_SCALE_CODE_MAX;

                    if (flags & LTR25_PROC_FLAG_SIGN_COR)
                        val = -val;
                } else {
                    params->afc_last_valid[ch_num] = FALSE;
                }

                if (dest != NULL)
                    dest[put_size] = val;
                if ((cur_status != LTR25_CH_STATUS_OK) && (ch_status != NULL))
                    ch_status[frame_pos] = cur_status;

                put_size++;
                if (++frame_pos == ch_cnt)
                    frame_pos = 0;
            }
        } /*for (; (rem_size != 0); rem_size--, src++)*/


        if (!(flags & LTR25_PROC_FLAG_NONCONT_DATA)) {
            params->cntr_lost = cntr_lost;
            params->cntr_val = cntr;
        }

        *size = put_size;
    } /*if (res == LTR_OK)*/

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_Recv(TLTR25 *hnd, DWORD *data, DWORD *tmark, DWORD size,
    DWORD timeout) {
    /* Получаем данные АЦП */
    int res = LTR25_IsOpened(hnd);
    if (res == LTR_OK)
        res = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);

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

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_SearchFirstFrame(TLTR25 *hnd, const DWORD *data, DWORD size,
    DWORD *frame_idx) {
    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR :
        ((data == NULL) || (size == 0) || (frame_idx == NULL)) ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (res == LTR_OK) {
        INT i;
        INT idx_first = -1;
        INT first_ch = -1;

        for (i = 0; ((i < LTR25_CHANNEL_CNT) && (first_ch < 0)); i++) {
            if (hnd->Cfg.Ch[i].Enabled) {
                first_ch = i;
                break;
            }
        }

        if (first_ch < 0) {
            res = LTR25_ERR_NO_ENABLED_CH;
        } else {
            for (i = 0; ((i < (INT)size) && (idx_first < 0)); i++, data++) {
                BYTE rcv_ch  = (*data >> 4) & 7;
                BYTE rcv_wrd_num = (hnd->Cfg.DataFmt == LTR25_FORMAT_20) ? 0 : (*data >> 7) & 1;

                if ((rcv_ch == first_ch) && (rcv_wrd_num == 0))
                    idx_first = i;
            }

            if (idx_first >= 0) {
                *frame_idx = (DWORD)idx_first;
            } else {
                res = LTR_ERROR_FIRSTFRAME_NOTFOUND;
            }
        }
    }

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_SetADC(TLTR25 *hnd) {
    /* Настройка АЦП. Не влияет на фазирование, если только не меняется частота дискретизации. */
    DWORD ch_ctl = 0;
    BYTE ch_cnt = 0;
    INT err = LTR25_IsOpened(hnd);

    if ((err == LTR_OK) && hnd->State.LowPowMode)
        err = LTR25_ERR_LOW_POW_MODE;
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    if ((err == LTR_OK) && (hnd->Cfg.FreqCode >= LTR25_FREQ_CNT))
        err = LTR25_ERR_INVALID_FREQ_CODE;
    if (err == LTR_OK) {
        if ((hnd->Cfg.DataFmt != LTR25_FORMAT_20) && (hnd->Cfg.DataFmt != LTR25_FORMAT_32))
            err = LTR25_ERR_INVALID_DATA_FORMAT;
    }
    if (err == LTR_OK) {
        const int isok_isrc = ((hnd->Cfg.ISrcValue == LTR25_I_SRC_VALUE_2_86) ||
            (hnd->Cfg.ISrcValue == LTR25_I_SRC_VALUE_10));
        if (!isok_isrc)
            err = LTR25_ERR_INVALID_I_SRC_VALUE;
    }

    if (err == LTR_OK) {
        INT ch;
        for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
            if (hnd->Cfg.Ch[ch].Enabled) {
                ch_cnt++;
                ch_ctl |= (1 << ch);
            }
        }

        if (ch_cnt == 0) {
            err = LTR25_ERR_NO_ENABLED_CH;
        } else {
            const BYTE nch = (hnd->Cfg.DataFmt == LTR25_FORMAT_20) ?
                f_freq_params[hnd->Cfg.FreqCode].MaxCh20 : f_freq_params[hnd->Cfg.FreqCode].MAXCh24;
            if (ch_cnt > nch)
                err = LTR25_ERR_CFG_UNSUP_CH_CNT;
        }
    }

    if (err == LTR_OK) {
        DWORD cmds[3], acks[3];
        t_ltimer tmr;
        DWORD cmd_cnt = 0;
        t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
        BOOL new_freq = (params->cur_freq_cfg != hnd->Cfg.FreqCode);

        if (new_freq) {
            cmds[cmd_cnt++] = LTR25_MAKE_ADC_FREQ_CMD(hnd, 0);
            cmds[cmd_cnt++] = LTR25_MAKE_ADC_FREQ_CMD(hnd, 1);
        }
        cmds[cmd_cnt++] = LTR_MODULE_MAKE_CMD(CMD_CH_CONTROL,
            (hnd->Cfg.ISrcValue << 12) | (hnd->Cfg.DataFmt << 8) | ch_ctl);

        err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, cmd_cnt, acks);
        if (err == LTR_OK) {
            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(ADC_WAIT_STABLE_TIME));

            if (new_freq) {
                /* Одновременный сброс обоих АЦП с последующей одновременной
                 * конфигурацией обеспечивает синхронизацию фазы преобразования */
                if (err == LTR_OK)
                    err = f_adc_set_reset(hnd, 0x03);
                if (err==LTR_OK)
                    err = f_adc_configure(hnd, 0x03);
                /* Ожидаем захват PLL в обоих АЦП */
                if (err==LTR_OK)
                    err = f_adc_wait_pll(hnd, 0x1);
                if (err==LTR_OK)
                    err = f_adc_wait_pll(hnd, 0x02);
            }

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

            if (err == LTR_OK)
                err = f_adc_check_config(hnd, 0x01);
            if (err == LTR_OK)
                err = f_adc_check_config(hnd, 0x02);

            if ((err == LTR_OK) && new_freq)
                LTRAPI_SLEEP_MS(LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(&tmr)));
        }


        if (err == LTR_OK) {
            BYTE ch_num;

            params->cur_freq_cfg = hnd->Cfg.FreqCode;

            for (ch_num = 0; ch_num < LTR25_CHANNEL_CNT; ch_num++) {
                t_internal_ch_params *ch_params = &params->Chs[ch_num];
                TLTR25_CHANNEL_CONFIG *ch_cfg = &hnd->Cfg.Ch[ch_num];

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

            hnd->State.EnabledChCnt = ch_cnt;
            hnd->State.AdcFreq = f_freq_params[hnd->Cfg.FreqCode].AdcFreq;
        }
    }

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

    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT)  LTR25_SetLowPowMode(TLTR25 *hnd, BOOL lowPowMode) {
    INT err = LTR25_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run) {
        err = LTR_ERROR_MODULE_STARTED;
    }

    if (err == LTR_OK) {
        err = f_fpga_status_cmd(hnd, lowPowMode ? 0x01 : 0x02);
        if (err == LTR_OK) {
            t_internal_params * params = LTR25_INTERNAL_PARAMS(hnd);
            params->cur_freq_cfg = -1;
        }
    }

    if (err == LTR_OK) {
        if (hnd->State.LowPowMode != lowPowMode)
            err = LTR25_ERR_LOW_POW_MODE_NOT_CHANGED;
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_Start(TLTR25 *hnd) {
    /* Запуск АЦП */
    INT err = LTR25_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.LowPowMode)
        err = LTR25_ERR_LOW_POW_MODE;
    if ((err == LTR_OK) && (hnd->State.SensorsPowerMode != LTR25_SENSORS_POWER_MODE_ICP))
        err = LTR25_ERR_ICP_MODE_REQUIRED;

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

    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = LTR_MODULE_MAKE_CMD(CMD_GO, 1);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
    }

    if (err == LTR_OK) {
        unsigned ch;
        t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
        hnd->State.Run = 1;
        params->cntr_val = 0;
        params->cntr_lost = TRUE;

        for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
            params->afc_last_valid[ch] = FALSE;
            if (hnd->Cfg.Ch[ch].Enabled) {
                phasefilter_init(&params->Chs[ch].phase_flt, params->r1, params->r1 * LTR25_R2_MUL,
                             hnd->ModuleInfo.PhaseCoef.PhaseShift[ch],
                             hnd->ModuleInfo.PhaseCoef.PhaseShiftRefFreq,
                             hnd->State.AdcFreq);
            }
        }


    }


#ifdef LTRAPI_USE_KD_STORESLOTS
    if (err == LTR_OK) {
        err = ltrslot_start_wconfig(hnd, &hnd->Channel, sizeof(struct LTR25Config), LTR_MID_LTR25,
            conv_hltr25_to_ltr25cfg);
    if (err == LTR_ERROR_CARDSCONFIG_UNSUPPORTED)
        err = LTR_OK;
    }
#endif

    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_Stop(TLTR25 *hnd) {
    /* Останов АЦП */
    INT err = LTR25_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);
    }
#ifdef LTRAPI_USE_KD_STORESLOTS
    if (err == LTR_OK)
        err = ltrslot_stop(&hnd->Channel);
#endif

    if (err == LTR_OK)
        hnd->State.Run = 0;

    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_StoreConfig(TLTR25 *hnd, TLTR_CARD_START_MODE start_mode) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK) {
        err = ltrslot_store_config(hnd, &hnd->Channel, sizeof(struct LTR25Config), LTR_MID_LTR25,
            start_mode, conv_hltr25_to_ltr25cfg);
    }
    return err;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTR25API_DllExport(INT) LTR25_WriteConfig(TLTR25 *hnd) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK) {
        BOOL en;
        err = LTR25_FPGAIsEnabled(hnd, &en);
        if (en && (err == LTR_OK))
            err = LTR25_FPGAEnable(hnd, FALSE);


        if (err == LTR_OK) {
            err = f_flash_wr_en(hnd);
            if (err == LTR_OK) {
                t_ltr25_flash_info info, info_ver;
                unsigned ch, freq;
                WORD crc, crc_ver;
                t_internal_params *params = LTR25_INTERNAL_PARAMS(hnd);
                t_flash_errs flash_res, dis_res;

                memset(&info, 0, sizeof(info));
                info.hdr.sign = LTR25_FLASH_INFO_SIGN;
                info.hdr.size = sizeof(info);
                info.hdr.format = LTR25_FLASH_INFO_FORMAT;
                info.hdr.flags = 0;
                memcpy(info.module.name, hnd->ModuleInfo.Name, LTR25_NAME_SIZE);
                memcpy(info.module.serial, hnd->ModuleInfo.Serial, LTR25_SERIAL_SIZE);

                for (ch = 0; (ch < LTR25_CHANNEL_CNT); ch++) {
                    for (freq = 0; (freq < LTR25_CBR_FREQ_CNT); freq++) {
                        info.cbr[ch][freq] = hnd->ModuleInfo.CbrCoef[ch][freq];
                    }
                }

                memcpy(&info.ICPPhaseCoefs, &hnd->ModuleInfo.PhaseCoef, sizeof(hnd->ModuleInfo.PhaseCoef));
                info.VerPLD = hnd->ModuleInfo.VerPLD;

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


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

                if (!flash_res)
                    flash_res = f_set_flash_protection(hnd, FLASH_PROT_INFO_UPDATE);
                if (!flash_res) {
                    flash_res = flash_erase(&params->flash, LTR25_FLASH_ADDR_MODULE_INFO,
                        LTR25_FLASH_INFO_MAX_SIZE);
                }
                if (!flash_res) {
                    flash_res = flash_write(&params->flash, LTR25_FLASH_ADDR_MODULE_INFO,
                        (unsigned char *)&info, sizeof(info), 0);
                }
                if (!flash_res) {
                    flash_res = flash_write(&params->flash,
                        LTR25_FLASH_ADDR_MODULE_INFO+sizeof(info), (unsigned char *)&crc,
                        sizeof(crc), 0);
                }


                dis_res = f_set_flash_protection(hnd, FLASH_PROT_DEFAULT);
                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, LTR25_FLASH_ADDR_MODULE_INFO,
                        (unsigned char *)&info_ver, sizeof(info));
                }
                if (!flash_res) {
                    flash_res = flash_read(&params->flash,
                        LTR25_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 (en) {
                INT en_err = LTR25_FPGAEnable(hnd, TRUE);
                if (err == LTR_OK)
                    err = en_err;
            }
        } /*if (err == LTR_OK)*/
    } /*if (err == LTR_OK)*/

    return err;
}




static INT f_teds_data_exchange(TLTR25 *hnd, const DWORD *cmds, DWORD *acks, INT cmd_cnt, BYTE *rx_buf, INT rx_buf_size, INT *rx_size) {
    INT i, err;
    INT rx_total_size = 0;
#ifdef LTR_DEBUG_PRINT
    printf("send %d words:\n", cmd_cnt);
    for (i = 0; i < cmd_cnt; i++) {
        WORD cmd_data = LTR_MODULE_CMD_GET_DATA(cmds[i]);
        WORD ch = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_CH);
        WORD opcode = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_OP);
        WORD teds_data = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_DATA);
        printf("s%02d 0x%08X, addr = %d, op = %d %s, data = 0x%02X\n",
               i+1,
               cmds[i],
               ch,
               opcode,
               opcode == TEDS_OP_RESET ? "RST" :
                 opcode == TEDS_OP_WRITE ? "WR " :
                 opcode == TEDS_OP_READ ? "RD " : "UNK",
               teds_data
               );
    }
    fflush(stdout);
    err = ltr_module_send_cmd(&hnd->Channel, cmds, (DWORD)cmd_cnt);
    if (err == LTR_OK) {
        printf("start recv:\n");
        for (i = 0; (i < cmd_cnt) && (err == LTR_OK); i++) {
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &acks[i], 1);
            if (err == LTR_OK) {
                WORD cmd_data = LTR_MODULE_CMD_GET_DATA(acks[i]);
                WORD ch = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_CH);
                WORD opcode = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_OP);
                WORD teds_data = LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_DATA);
                WORD Nbit = cmd_data & CMD_BIT_TEDS_OK;
                printf("r%02d: 0x%08X, addr = %d, op = %d %s, N = %s",
                       i+1,
                       acks[i],
                       ch,
                       opcode,
                       opcode == TEDS_OP_RESET ? "RST" :
                         opcode == TEDS_OP_WRITE ? "WR " :
                         opcode == TEDS_OP_READ ? "RD " : "UNK",
                       Nbit ? "OK ": "ERR"
                       );
                if (!Nbit) {
                    printf(", status = 0x%02X %s",
                           teds_data,
                           teds_data == TEDS_OP_STATUS_OK ? "OK" :
                           teds_data == TEDS_OP_STATUS_NO_PRESENSE_PULSE ? "NO PRESENCE"  :
                           teds_data == TEDS_OP_STATUS_NO_IDLE ? "NO IDLE" :
                           teds_data == TEDS_OP_STATUS_BAD_OPCODE ? "BAD OPCODE" :
                           teds_data == TEDS_OP_STATUS_PREV_ERR ? "PREV CYCLE ERR" :
                                                                         "UNKNOWN!!");
                } else {
                    printf(", data = 0x%02X", teds_data);
                }
                printf("\n");
            }
        }
        printf("-------\n");
        fflush(stdout);
    }
#else
    err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, (DWORD)cmd_cnt, acks);
#endif



    for (i = 0; (i < cmd_cnt) && (err == LTR_OK); i++) {
        WORD cmd_data = LTR_MODULE_CMD_GET_DATA(cmds[i]);
        WORD ack_data = LTR_MODULE_CMD_GET_DATA(acks[i]);
        if ((LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_CH) != LBITFIELD_GET(ack_data, CMD_BIT_TEDS_CH)) ||
                (LBITFIELD_GET(cmd_data, CMD_BIT_TEDS_OP) != LBITFIELD_GET(ack_data, CMD_BIT_TEDS_OP))) {
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
        } else if (ack_data & CMD_BIT_TEDS_OK) {
            if (LBITFIELD_GET(ack_data, CMD_BIT_TEDS_OP) == TEDS_OP_READ) {
                if ((rx_buf != NULL) && (rx_total_size < rx_buf_size))
                    *rx_buf++ = LBITFIELD_GET(ack_data, CMD_BIT_TEDS_DATA);
                rx_total_size++;
            }
        } else {
            BYTE err_data = LBITFIELD_GET(ack_data, CMD_BIT_TEDS_DATA);
            if (err_data == TEDS_OP_STATUS_NO_PRESENSE_PULSE) {
                err = LTR25_ERR_TEDS_1W_NO_PRESENSE_PULSE;
            } else if (err_data == TEDS_OP_STATUS_NO_IDLE) {
                err = LTR25_ERR_TEDS_1W_NOT_IDLE;
            } else if (err_data == TEDS_OP_STATUS_BAD_OPCODE) {
                err = LTR25_ERR_TEDS_1W_BAD_OPCODE;
            } else if (err_data == TEDS_OP_STATUS_PREV_ERR) {
                err = LTR25_ERR_TEDS_1W_PREV_CYCLE_ERR;
            } else {
                err = LTR25_ERR_TEDS_1W_UNKNOWN_ERR;
            }
        }
    }

    if (rx_size != NULL)
        *rx_size = rx_total_size;
    return err;
}

static INT f_check_teds_support(TLTR25 *hnd) {
    return (hnd->ModuleInfo.VerFPGA < 5) ? LTR_ERROR_UNSUP_BY_FIRM_VER :
           (hnd->ModuleInfo.BoardRev < 2) ? LTR_ERROR_UNSUP_BY_BOARD_REV : LTR_OK;
}


static INT f_check_teds_op(TLTR25 *hnd, int ch) {
    INT err = f_check_teds_support(hnd);
    return err != LTR_OK ? err :
            (hnd->State.SensorsPowerMode != LTR25_SENSORS_POWER_MODE_TEDS) ? LTR25_ERR_TEDS_MODE_REQUIRED :
            (ch < 0) || (ch >= LTR25_CHANNEL_CNT) ? LTR25_ERR_INVALID_CHANNEL_NUMBER : LTR_OK;
}

LTR25API_DllExport(INT) LTR25_CheckSupportTEDS(TLTR25 *hnd) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_check_teds_support(hnd);
    }
    return err;
}

LTR25API_DllExport(INT) LTR25_SetSensorsPowerMode(TLTR25 *hnd, DWORD mode) {
    INT err = LTR25_IsOpened(hnd);
    if ((err == LTR_OK) && (hnd->ModuleInfo.VerFPGA < 5))
        err = LTR_ERROR_UNSUP_BY_FIRM_VER;
    if ((err == LTR_OK) && (mode != LTR25_SENSORS_POWER_MODE_ICP) &&
            (mode != LTR25_SENSORS_POWER_MODE_OFF) &&
            (mode != LTR25_SENSORS_POWER_MODE_TEDS)) {
        err = LTR25_ERR_INVALID_SENSOR_POWER_MODE;
    }
    if (err == LTR_OK) {
        DWORD cmd, ack;

        cmd = LTR_MODULE_MAKE_CMD(CMD_POW_CTL, mode);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (err == LTR_OK) {
            DWORD set_mode = LTR_MODULE_CMD_GET_DATA(ack) & 0x3;
            if (set_mode != mode)
                err = LTR25_ERR_CHANGE_SENSOR_POWER_MODE;
        }
    }

    if (err == LTR_OK) {
        hnd->State.SensorsPowerMode = mode;
    }
    return err;
}

static INT f_teds_std_mem_read(TLTR25 *hnd, INT ch, DWORD addr, BYTE *data, DWORD size, DWORD flags);
static INT f_teds_teds_std_data_read(TLTR25 *hnd, INT ch, BYTE *data, DWORD size, DWORD *read_size);
static INT f_teds_ds2431_teds_data_write(TLTR25 *hnd, INT ch, const BYTE *data, DWORD size);

static const t_ltr25_teds_nodedev_type f_teds_mem_ds2431 = {
    LTR25_TEDS_NODE_FAMILY_EEPROM_1K,
    128, 124,
    f_teds_std_mem_read,
    f_teds_teds_std_data_read,
    f_teds_ds2431_teds_data_write
};

static INT f_teds_std_mem_read(TLTR25 *hnd, INT ch, DWORD addr, BYTE *data, DWORD size, DWORD flags) {
    INT err = LTR_OK;
    DWORD cmds[TEDS_READ_BLOCK_SIZE + 5], acks[TEDS_READ_BLOCK_SIZE + 5];
    (void)flags;

    while ((size > 0) && (err == LTR_OK)) {
        INT recvd_bytes, i;
        INT pos = 0;

        cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, ONEWIRE_CMD_SKIP_ROM);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, DS2431_CMD_READ_MEMORY);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, addr & 0xFF);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, (addr >> 8) & 0xFF);
        for (i = 0; (i < (INT)size) && (i < TEDS_READ_BLOCK_SIZE); i++) {
            cmds[pos++] = MAKE_TEDS_RD_CMD(ch);
        }

        err = f_teds_data_exchange(hnd, cmds, acks, pos, data, (INT)size, &recvd_bytes);
        if (err == LTR_OK) {
            addr += (DWORD)recvd_bytes;
            data += (DWORD)recvd_bytes;
            size -= (DWORD)recvd_bytes;
        }
    }
    return err;
}

static INT f_teds_teds_std_data_read(TLTR25 *hnd, INT ch, BYTE *data, DWORD size, DWORD *read_size) {
    DWORD put_pos = 0;
    INT err = LTR_OK;
    DWORD addr;
    BYTE block[TEDS_READ_BLOCK_SIZE];
    t_ltr25_teds_dev_state *ch_mem = &LTR25_INTERNAL_PARAMS(hnd)->teds_node[ch];

    for (addr = 0; (addr < ch_mem->devtype->mem_size) && (put_pos < size) && (err == LTR_OK);
         addr += TEDS_READ_BLOCK_SIZE) {
        err = f_teds_std_mem_read(hnd, ch, addr, block, TEDS_READ_BLOCK_SIZE, 0);
        if (err == LTR_OK) {
            DWORD block_pos;
            for (block_pos = 0; (block_pos < TEDS_READ_BLOCK_SIZE)&& (err == LTR_OK);
                 block_pos+=TEDS_CHECKSUM_BLOCK_SIZE) {
                err = f_teds_check_sum(&block[block_pos], TEDS_CHECKSUM_BLOCK_SIZE);

                if ((err == LTR_OK) && (put_pos < size)) {
                    DWORD cpy_size = size - put_pos;
                    if (cpy_size > (TEDS_CHECKSUM_BLOCK_SIZE - 1))
                        cpy_size = TEDS_CHECKSUM_BLOCK_SIZE - 1;
                    memcpy(&data[put_pos], &block[block_pos+1], cpy_size);
                    put_pos+= cpy_size;
                }
            }

        }

    }
    if (read_size != NULL)
        *read_size = put_pos;
    return err;
}


static INT  f_teds_ds2431_memblock_write(TLTR25 *hnd, INT ch, DWORD addr, const BYTE *data, DWORD size) {
    INT err = LTR_OK;
    if ((err == LTR_OK) && (((addr & (LTR25_TEDS_MEMORY_BLOCK_SIZE -1)) != 0)
                          || ((size & (LTR25_TEDS_MEMORY_BLOCK_SIZE -1)) != 0))) {
        err = LTR_ERROR_FLASH_UNALIGNED_ADDR;
    }
    while ((size != 0) && (err == LTR_OK)) {
        INT i;
        INT pos = 0;
        DWORD cmds[29], acks[29];
        BYTE rx_data[20];

        /* сперва запись выполняется в Scratchpad.
         * Запись в Scratchpad и чтение из нее для проверки можно
         * выполнить за один обмен. CRC не проверяем, т.к. итак сверяются
         * все данные */
        cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, ONEWIRE_CMD_SKIP_ROM);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, DS2431_CMD_WRITE_SCRATCHPAD);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, addr & 0xFF);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, (addr >> 8) & 0xFF);
        for (i = 0; i < 8; i++) {
            cmds[pos++] = MAKE_TEDS_WR_CMD(ch, data[i]);
        }

        cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, ONEWIRE_CMD_SKIP_ROM);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, DS2431_CMD_READ_SCRATCHPAD);
        for (i = 0; i < (8 + 3 + 2); i++) {
            cmds[pos++] = MAKE_TEDS_RD_CMD(ch);
        }

        err = f_teds_data_exchange(hnd, cmds, acks, pos, rx_data, sizeof(rx_data), NULL);
        if (err == LTR_OK) {
            WORD rx_addr = (WORD)((rx_data[1] << 8) | rx_data[0]);
            BYTE status_reg = rx_data[2];

            if ((rx_addr != addr) || (status_reg & 0xA0)) {
                err = LTR25_ERR_TEDS_MEM_STATUS;
            } else if (memcmp(data, &rx_data[3], 8)) {
                err = LTR_ERROR_FLASH_VERIFY;
            }
        }
        if (err == LTR_OK) {
            pos = 0;
            cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
            cmds[pos++] = MAKE_TEDS_WR_CMD(ch, ONEWIRE_CMD_SKIP_ROM);
            cmds[pos++] = MAKE_TEDS_WR_CMD(ch, DS2431_CMD_COPY_SCRATCHPAD);
            /* повторение 3-х прочитанных статусных регистра */
            for (i = 0; i < 3; i++) {
                cmds[pos++] = MAKE_TEDS_WR_CMD(ch, rx_data[i]);
            }
            err = f_teds_data_exchange(hnd, cmds, acks, pos, rx_data, sizeof(rx_data), NULL);
            if (err == LTR_OK) {
                /* после выполнения команды копирования необходимо подождать время
                 * записи, в течение которого линия должна быть в высоком состоянии */
                LTRAPI_SLEEP_MS(DS2431_TIME_PROG_MS);
                /* После записи память должна выдавать чередующиеся 0 и 1, проверяем, что
                 * чтение соответствующего кода (0xAA) выполняется корректно */
                pos = 0;
                cmds[pos++] = MAKE_TEDS_RD_CMD(ch);
                err = f_teds_data_exchange(hnd, cmds, acks, pos, rx_data, sizeof(rx_data), NULL);
                if ((err == LTR_OK) && (rx_data[0] != 0xAA)) {
                    err = LTR_ERROR_FLASH_WAIT_RDY_TOUT;
                }
            }
        }

        if (err == LTR_OK) {
            addr += LTR25_TEDS_MEMORY_BLOCK_SIZE;
            data += LTR25_TEDS_MEMORY_BLOCK_SIZE;
            size -= LTR25_TEDS_MEMORY_BLOCK_SIZE;
        }
    }
    return err;
}

static INT f_teds_ds2431_teds_data_write(TLTR25 *hnd, INT ch, const BYTE *data, DWORD size) {
    INT err = LTR_OK;
    DWORD get_pos = 0;
    DWORD addr;
    BYTE block[TEDS_CHECKSUM_BLOCK_SIZE];
    for (addr = 0; (addr < f_teds_mem_ds2431.mem_size) && (err == LTR_OK); addr += TEDS_CHECKSUM_BLOCK_SIZE) {
        DWORD cpy_size = size > get_pos ? size - get_pos : 0;
        if (cpy_size > (TEDS_CHECKSUM_BLOCK_SIZE - 1))
            cpy_size = TEDS_CHECKSUM_BLOCK_SIZE - 1;

        if (cpy_size > 0) {
            memcpy(&block[1], &data[get_pos], cpy_size);
        }
        if (cpy_size < (TEDS_CHECKSUM_BLOCK_SIZE - 1)) {
            memset(&block[1 + cpy_size], 0, (TEDS_CHECKSUM_BLOCK_SIZE - 1) - cpy_size);
        }
        block[0] = -f_teds_calc_sum(&block[1], TEDS_CHECKSUM_BLOCK_SIZE - 1);

        err = f_teds_ds2431_memblock_write(hnd, ch, addr, block, TEDS_CHECKSUM_BLOCK_SIZE);
        if (err == LTR_OK) {
            get_pos += cpy_size;
        }
    }
    return err;
}

static INT f_teds_ds2430a_mem_read(TLTR25 *hnd, INT ch, DWORD addr, BYTE *data, DWORD size, DWORD flags);
static INT f_teds_ds2430a_teds_data_read(TLTR25 *hnd, INT ch, BYTE *data, DWORD size, DWORD *read_size);

static const t_ltr25_teds_nodedev_type f_teds_mem_ds2430a = {
    LTR25_TEDS_NODE_FAMILY_EEPROM_256_OTP,
    40, 39,
    f_teds_ds2430a_mem_read,
    f_teds_ds2430a_teds_data_read,
    NULL
};


static INT f_teds_ds2430a_mem_read(TLTR25 *hnd, INT ch, DWORD addr, BYTE *data, DWORD size, DWORD flags) {
    INT err = LTR_OK;
    DWORD cmds[TEDS_READ_BLOCK_SIZE + 5], acks[TEDS_READ_BLOCK_SIZE + 5];

    while ((size > 0) && (err == LTR_OK)) {
        INT recvd_bytes, i;
        INT pos = 0;

        cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, ONEWIRE_CMD_SKIP_ROM);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, flags & LTR25_TEDS_NODE_MEMRD_FLAG_OTP ?
                                           DS2430A_CMD_READ_APP_REG : DS2430A_CMD_READ_MEMORY);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, addr & 0x1F);
        for (i = 0; (i < (INT)size) && (i < TEDS_READ_BLOCK_SIZE); i++) {
            cmds[pos++] = MAKE_TEDS_RD_CMD(ch);
        }

        err = f_teds_data_exchange(hnd, cmds, acks, pos, data, (INT)size, &recvd_bytes);
        if (err == LTR_OK) {
            addr += (DWORD)recvd_bytes;
            data += (DWORD)recvd_bytes;
            size -= (DWORD)recvd_bytes;
        }
    }
    return err;
}

static INT f_teds_ds2430a_teds_data_read(TLTR25 *hnd, INT ch, BYTE *data, DWORD size, DWORD *read_size) {
    INT err = LTR_OK;
    DWORD put_size = 0;
    BYTE flash_data[40];
    /* Для DS2430A первые 8 байт (basic-teds) читаются из Application Register,
     * а остальные данные - из EEPROM. Так как EEPROM небольшая, можем сразу
     * прочитать все данные. Контрольная сумма при этом находится в первом байте
     * данных, т.е. в девятом байте общего потока и защищает всю память */
    err = f_teds_ds2430a_mem_read(hnd, ch, 0, flash_data, 8, LTR25_TEDS_NODE_MEMRD_FLAG_OTP);
    if (err == LTR_OK) {
        err = f_teds_ds2430a_mem_read(hnd, ch, 0, flash_data, 32, 0);
    }
    if (err == LTR_OK) {
        err = f_teds_check_sum(flash_data, sizeof(flash_data));
    }
    if (err == LTR_OK) {
        /* убираем байт с контрольной суммой в позиции 8, смещая остальные данные */
        memmove(&flash_data[8], &flash_data[9], 31);
        put_size = f_teds_mem_ds2430a.teds_data_size;
        if (put_size > size)
            put_size = size;
        memcpy(data, flash_data, put_size);
    }

    if (read_size != NULL)
        *read_size = put_size;

    return err;
}


static const t_ltr25_teds_nodedev_type f_teds_mem_ds2433 = {
    LTR25_TEDS_NODE_FAMILY_EEPROM_4K,
    1024, 1024-32,
    f_teds_std_mem_read,
    f_teds_teds_std_data_read,
    NULL
};

static const t_ltr25_teds_nodedev_type f_teds_mem_ds28ec20 = {
    LTR25_TEDS_NODE_FAMILY_EEPROM_20K,
    2560, 2560-80,
    f_teds_std_mem_read,
    f_teds_teds_std_data_read,
    NULL
};

static const t_ltr25_teds_nodedev_type *f_teds_memtypes[] = {
    &f_teds_mem_ds2430a,
    &f_teds_mem_ds2431,
    &f_teds_mem_ds2433,
    &f_teds_mem_ds28ec20
};








LTR25API_DllExport(INT) LTR25_TEDSNodeDetect(TLTR25 *hnd, INT ch, TLTR25_TEDS_NODE_INFO *meminfo) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_check_teds_op(hnd, ch);

    if (meminfo != NULL) {
        memset(meminfo, 0, sizeof(TLTR25_TEDS_NODE_INFO));
    }

    if (err == LTR_OK) {                
        INT i;
        INT pos = 0;
        DWORD cmds[10], acks[10];
        BYTE rom[8];
        t_ltr25_teds_dev_state *ch_mem = &LTR25_INTERNAL_PARAMS(hnd)->teds_node[ch];
        ch_mem->valid = FALSE;

        cmds[pos++] = MAKE_TEDS_RST_CMD(ch);
        cmds[pos++] = MAKE_TEDS_WR_CMD(ch, 0x33);
        for (i = 0; i < 8; i++) {
            cmds[pos++] = MAKE_TEDS_RD_CMD(ch);
        }
        err = f_teds_data_exchange(hnd, cmds, acks, pos, rom, sizeof(rom), NULL);
        if (err == LTR_OK) {
            BYTE crc = onewire_rom_crc(rom, 8);
            if (crc == 0) {
                ch_mem->devid = rom[0];
                ch_mem->valid = TRUE;
                ch_mem->devtype = NULL;
                for (i = 0; (i < (INT)ARRAY_LEN(f_teds_memtypes)) && (ch_mem->devtype == NULL); i++) {
                    if (ch_mem->devid == f_teds_memtypes[i]->devid) {
                        ch_mem->devtype = f_teds_memtypes[i];
                    }
                }

                if (meminfo) {
                    meminfo->DevFamilyCode = ch_mem->devid;
                    meminfo->Valid = ch_mem->valid;
                    for (i = 0; i < LTR25_TEDS_NODE_SERIAL_SIZE; i++) {
                        meminfo->DevSerial[i] = rom[i+1];
                    }
                    if (ch_mem->devtype != NULL) {
                        meminfo->TEDSDataSize =  ch_mem->devtype->teds_data_size;
                        meminfo->MemorySize  = ch_mem->devtype->mem_size;
                    }
                }

                if (!ch_mem->devtype) {
                    err = LTR25_ERR_TEDS_UNSUP_NODE_FAMILY;
                }
            } else {
                err = LTR25_ERR_TEDS_NODE_URN_CRC;
            }
        }
    }
    return err;
}



LTR25API_DllExport(INT) LTR25_TEDSMemoryRead(TLTR25 *hnd, INT ch, DWORD addr, BYTE *data, DWORD size, DWORD flags) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_check_teds_op(hnd, ch);
    if (err == LTR_OK) {
        t_ltr25_teds_dev_state *ch_mem = &LTR25_INTERNAL_PARAMS(hnd)->teds_node[ch];
        if (!ch_mem->valid) {
            err = LTR25_TEDSNodeDetect(hnd, ch, NULL);
        }
        if (err == LTR_OK) {
            if (!ch_mem->devtype || !ch_mem->devtype->teds_mem_read) {
                err = LTR25_ERR_TEDS_UNSUP_NODE_OP;
            } else {
                err = ch_mem->devtype->teds_mem_read(hnd, ch, addr, data, size, flags);
            }
        }
    }
    return err;
}

LTR25API_DllExport(INT) LTR25_TEDSReadData(TLTR25 *hnd, INT ch, BYTE *data, DWORD size, DWORD *read_size) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_check_teds_op(hnd, ch);
    if (err == LTR_OK) {
        t_ltr25_teds_dev_state *ch_mem = &LTR25_INTERNAL_PARAMS(hnd)->teds_node[ch];
        if (!ch_mem->valid) {
            err = LTR25_TEDSNodeDetect(hnd, ch, NULL);
        }
        if (err == LTR_OK) {
            if (!ch_mem->devtype || !ch_mem->devtype->teds_data_read) {
                err = LTR25_ERR_TEDS_UNSUP_NODE_OP;
            } else {
                err = ch_mem->devtype->teds_data_read(hnd, ch, data, size, read_size);
            }
        }
    }
    return err;
}

LTR25API_DllExport(INT) LTR25_TEDSWriteData(TLTR25 *hnd, INT ch, BYTE *data, DWORD size) {
    INT err = LTR25_IsOpened(hnd);
    if (err == LTR_OK)
        err = f_check_teds_op(hnd, ch);
    if (err == LTR_OK) {
        t_ltr25_teds_dev_state *ch_mem = &LTR25_INTERNAL_PARAMS(hnd)->teds_node[ch];
        if (!ch_mem->valid) {
            err = LTR25_TEDSNodeDetect(hnd, ch, NULL);
        }
        if (err == LTR_OK) {
            if (!ch_mem->devtype || !ch_mem->devtype->teds_data_write) {
                err = LTR25_ERR_TEDS_UNSUP_NODE_OP;
            } else {
                err = ch_mem->devtype->teds_data_write(hnd, ch, data, size);
            }
        }
    }
    return err;
}



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