#include "ltr212api.h"

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

#include "ltrmodule.h"
#include "crc.h"
#include "config.h"

#ifndef _WIN32
    #include "strings.h"
    #define _stricmp strcasecmp
#endif


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

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

/* сколько команд может принять LTR212 в буфер.
 * не дожидаясь подтверждения можно высылать не более этого числа команд */
#define LTR212_CMD_BUFFER_SIZE 2100UL

#define LTR212_DAC_CBR_RECV_SIZE        8
#define LTR212_DAC_CBR_8CH_RECV_SIZE    2




/* Низкоуровневые команды для DSP */
#define SET_BRIDGE_TYPE     (LTR010CMD_INSTR | 0x00)  // тип подключения мостов
#define RESET_CODECS        (LTR010CMD_INSTR | 0x01)  // Сброс АЦП
#define SEL_CODEC           (LTR010CMD_INSTR | 0x02)  // Выбор кодека (АЦП)
#define WRITE_8REG          (LTR010CMD_INSTR | 0x03)    // Запись в 8-битный регистр кодека
#define READ_8REG           (LTR010CMD_INSTR | 0x04)  // Чтнение из 8-битного регистра кодека
#define WRITE_16REG         (LTR010CMD_INSTR | 0x05)  // Запись в 16-битный регистр кодека
#define READ_16REG          (LTR010CMD_INSTR | 0x06)  // Чтнение из 16-битного регистра кодека
#define WRITE_24REG         (LTR010CMD_INSTR | 0x07)  // Запись в 24-битный регистр кодека
#define READ_24REG          (LTR010CMD_INSTR | 0x08)  // Чтнение из 24-битного регистра кодека
#define START_ACQ           (LTR010CMD_INSTR | 0x09)  // Старт сбора данных
#define CALIBR_4CH          (LTR010CMD_INSTR | 0x0A)  // Калибровка в 4-канальном режиме
#define WR_FLASH            (LTR010CMD_INSTR | 0x0B)  // Запись в ППЗУ
#define RD_FLASH            (LTR010CMD_INSTR | 0x0C)  // Чтение из ППЗУ
#define LOAD_CHANNEL        (LTR010CMD_INSTR | 0x0D)  // Загрузить информацию о канале
#define SET_REF             (LTR010CMD_INSTR | 0x0E)  // Установка опорного напряжения
#define LOAD_FIR            (LTR010CMD_INSTR | 0x0F)  // Загрузить коэффициенты КИХ-фильтра
#define LOAD_IIR            (LTR010CMD_INSTR | 0x10)  // Загрузить коэффициенты БИХ-фильтра
#define ENA_DIS_FILTERS     (LTR010CMD_INSTR | 0x11)  // Включить или отключить программные фильтры
#define TEST_BIOS           (LTR010CMD_INSTR | 0x12)  // Тестирование загруженного БИОСа
#define LOAD_DAC            (LTR010CMD_INSTR | 0x13)  // Запись параметров встроенного ЦАПа
#define CALIBR_8CH          (LTR010CMD_INSTR | 0x14)  // Калибровка в 8-канальном режиме
#define TEST_INTERFACE      (LTR010CMD_INSTR | 0x15)  // Процедура тестирования интерфейса модуля
#define WRITE_SN_CHECK      (LTR010CMD_INSTR | 0x16)  // Проверка кода доступа при записи серийного номера в ППЗУ
#define GET_SW_VER          (LTR010CMD_INSTR | 0x17)  // Получение версии БИОСа
#define GET_SW_DATE         (LTR010CMD_INSTR | 0x18)  // Получение даты создания БИОСа
#define CLEANUP_CMD         (LTR010CMD_INSTR | 0x19)  // Вспомогательная для отладки
#define GET_MODULE_TYPE     (LTR010CMD_INSTR | 0x1A)  // Тип модуля: LTR212(LTR212M-3), LTR212M-1 или LTR212M-2
#define PARITY_ERR          (LTR010CMD_INSTR | 0x1B)  // Вернулся код ошибки четности
#define COMAND_SEQUENCE_ERR (LTR010CMD_INSTR | 0x1C)
#define COMMAND_FOR_IF_TEST (LTR010CMD_INSTR | 0x1D)  // Используется при тестировании интерфейса модуля
#define FIRST_INSTR         (LTR010CMD_INSTR | 0x1E)  // Первая команда после загрузки БИОСа
#define STOP_ACQ            (LTR010CMD_INSTR | 0x1F)  // Остановка сбора данных


// Коды регистров кодеков:

#define COMM_REG_RD         (0x10)    // коммуникационный регистра, последующее чтение
#define COMM_REG_WR         (0x0 )    // коммуникационный регистр, последующая запись

#define STATUS_REG          (0x0)
#define DATA_REG            (0x1)
#define MODE_REG            (0x2)
#define FILTER_REG          (0x3)
#define DAC_REG             (0x4)
#define OFFSET_REG          (0x5)
#define GAIN_REG            (0x6)
#define TEST_REG            (0x7)


/* биты регистра mode для AD7730 */
#define AD7730_MODE_U       (1UL << 12)
#define AD7730_MODE_DEN     (1UL << 11)
#define AD7730_MODE_D1      (1UL << 10)
#define AD7730_MODE_D0      (1UL << 9)
#define AD7730_MODE_WL      (1UL << 8)
#define AD7730_MODE_HIREF   (1UL << 7)
#define AD7730_MODE_CLKDIS  (1UL << 3)
#define AD7730_MODE_BO      (1UL << 2)
#define AD7730_MODE_CH      (1UL << 0)
#define AD7730_MODE_RN_Pos  (0x4)
#define AD7730_MODE_RN_Msk  (0x3UL << AD7730_MODE_RN_Pos)









#define LTR212_PHY_CH_CNT      (8) // Макс. кол-во физических каналов
#define LTR212_SCALES_CNT      (8)


#define LTR212_PM_CRC_CMD_SIZE   0x3FFD
#define AT93C66_SIZE       512


#define AT25DF041A_SIZE    512*1024


#define LTR212_LOAD_DM_TOUT                                 10000
#define LTR212_LOAD_PM_TOUT                                 10000


#define PHYCH_CODEC(phy_ch) ((phy_ch) < 4 ? phy_ch : phy_ch-4)

//Адреса в EEPROM
#define EEPROM_ADDR_USER_CBR                0
/* базовый адрес для коэффициентов для опорного напряжения 5 В */
#define EEPROM_ADDR_INTCBR_5V               (100)
/* базовый адрес для коэффициентов для опорного напряжения 2.5 В
   (только для LTR212-M1 и LTR212-M2) */
#define EEPROM_ADDR_INTCBR_2_5V             (600)
#define EEPROM_ADDR_SERIAL                  60
#define EEPROM_ADDR_NAME                    80

/* адреса в области пользовательских коэффициентов */
#define EEPROM_USERCBR_CHPARAMS_SIZE            7
#define EEPROM_USER_CBR_SIZE                    (EEPROM_USERCBR_CHPARAMS_SIZE*LTR212_PHY_CH_CNT)
#define EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch)    (EEPROM_USERCBR_CHPARAMS_SIZE*(phy_ch))
#define EEPROM_ADDR_USERCBR_OFFSET(phy_ch)      (EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch)+0)
#define EEPROM_ADDR_USERCBR_GAIN(phy_ch)        (EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch)+3)
#define EEPROM_ADDR_USERCBR_DAC(phy_ch)         (EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch)+6)

/* смещения полей в области внутренней калибровки относительно
 * LTR212_EEPROM_ADDR_INTCBR_5V или LTR212_EEPROM_ADDR_INTCBR_2_5V */
#define INTCBR_ADDR_OFFSET(scale, phy_ch) ((scale)*48+(phy_ch)*6)
#define INTCBR_ADDR_GAIN(scale, phy_ch)   ((scale)*48+(phy_ch)*6+3)
#define LTR212_EEPROM_INTCBR_SIZE         (48*LTR212_PHY_CH_CNT)



#define LTR212_EEPROM_NAME_SIZE             7
#define LTR212_MODULE_NAME                  "LTR212"




#define SCALE_UNIPOLAR(scale)  ((scale) >= LTR212_SCALE_U_10)
#define PHYCH_GROUP(ch)        ((ch)<4 ? 0 : 1)


#define PHYCH_MODE_REG_VAL(ch) (PHYCH_GROUP(ch) ? 0x1B1 : 0x1B0)


/* режимы калибровки, передваемые в f_calibr_stages() */
#define CBR_MODE_INT_ZERO 0
#define CBR_MODE_INT_SCALE 1
#define CBR_MODE_EXT_ZERO 2
#define CBR_MODE_EXT_SCALE 3


/* Если физ. канал нечетный, выбираем канал AIN2 в соотв. кодеке, иначе AIN2 */
#define PHYCH_MODE_REG_SEL_AIN(modereg_val, phy_ch) do {\
        if (PHYCH_GROUP(phy_ch)) { \
            modereg_val|=0x0001; \
        } else { \
            modereg_val&=0xFFFE; \
        } \
    } while (0)



#define LTR212_STOP_TIMEOUT  5000

#define LCH_SCALE_POS         0
#define LCH_PHYCH_POS         16
#define LCH_BRIDGE_TYPE_POS   24

#define LCH_SCALE_MSK         (0xFUL << LCH_SCALE_POS)
#define LCH_PHYCH_MSK         (0xFUL << LCH_PHYCH_POS)
#define LCH_BRIDGE_TYPE_MSK   (0xFUL << LCH_BRIDGE_TYPE_POS)

#define LCH_SET_SCALE(ch, val)       (ch = (ch&~LCH_SCALE_MSK) | ((val << LCH_SCALE_POS) & LCH_SCALE_MSK))
#define LCH_SET_PHYCH(ch, val)       (ch = (ch&~LCH_PHYCH_MSK) | ((val << LCH_PHYCH_POS) & LCH_PHYCH_MSK))
#define LCH_SET_BRIDGE_TYPE(ch, val) (ch = (ch&~LCH_BRIDGE_TYPE_MSK) | ((val << LCH_BRIDGE_TYPE_POS) & LCH_BRIDGE_TYPE_MSK))


#define LCH_SCALE(ch)       ((ch & LCH_SCALE_MSK)       >> LCH_SCALE_POS)
#define LCH_PHYCH(ch)       (((ch & LCH_PHYCH_MSK)      >> LCH_PHYCH_POS)-1)
#define LCH_BRIDGE_TYPE(ch) ((ch & LCH_BRIDGE_TYPE_MSK) >> LCH_BRIDGE_TYPE_POS)

#define WR_REG16_CMDS_CNT 2
#define WR_REG16_ACK_CNT  2
#define WR_REG24_CMDS_CNT 3
#define WR_REG24_ACK_CNT  2




#define DAC_SIGN_BIT  0x20
#define DAC_ZERO_CODE 0x20
#define DAC_CODE_MAX  0x3F


#define CHECK_ACQ_MODE(mode) (((mode==LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) || \
    (mode==LTR212_FOUR_CHANNELS_WITH_HIGH_RESOLUTION) || \
    (mode==LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION)) ? \
    LTR_OK : LTR212_ERR_INVALID_ACQ_MODE)


/* параметры для разбора файла с фильтрами */
#define FILTER_FILE_LINE_SYM_CNT 127
#define FILTER_FILE_STR_DEC "DECIMATION= "
#define FILTER_LINE_STR_FS "FS= "
#define FILTER_LINE_STR_FS_LEN  (sizeof(FILTER_LINE_STR_FS)-1)
#define FILTER_FILE_STR_DEC_LEN (sizeof(FILTER_FILE_STR_DEC)-1)

enum {
    CLEANUP_FLASH_WRITE_PARS_COUNT = 0x1,
    CLEANUP_FLASH_READ_PARS_COUNT  = 0x2
};


typedef INT (*t_write_eeprom)(TLTR212 *hnd, INT size, INT addr, const BYTE *data);
static INT f_write_eeprom_at25df041a(TLTR212 *hnd, INT size, INT addr, const BYTE *image);
static INT f_write_eeprom_at93c66(TLTR212 *hnd, INT size, INT addr, const BYTE *image);

typedef struct {
    DWORD size;
    DWORD erase_size;
    DWORD crc_addr;
    BYTE cmd_per_rd;
    t_write_eeprom wr_eeprom;
} t_eeprom_cfg;


static const t_eeprom_cfg f_eeprom_at93C66 =  {
    512, 0, 510, 1,
    f_write_eeprom_at93c66
};

static const t_eeprom_cfg f_eeprom_at25df041a =  {
    512*1024, 4096, 4094, 2,
    f_write_eeprom_at25df041a
};



#pragma pack(push, 1)
typedef struct {
    CHAR  signature[14+1];             // bio signature
    CHAR  reserved0[4];
    WORD  ver;                       // file version
    WORD  subver;                    // file sub-version
    CHAR  reserved1[12];
    WORD  dsptype;                   // target dsp type
    WORD  exetype;                   // exe type
    CHAR  reserved2[12];
    DWORD DOff;                      // DM offset in file
    DWORD DSize;                     // DM size (bytes)
    CHAR  reserved3[8];
    DWORD POff;                      // PM offset in file
    DWORD PSize;                     // PM size (bytes)
    CHAR  reserved4[8];
    DWORD LOff;                      // Loader offset in file
    DWORD LSize;                     // Loader size (bytes)
    CHAR  reserved5[8];
    DWORD SOff;
    DWORD SSize;
    CHAR  reserved6[6+(9*16)];
    BYTE  PM[65535];//
    BYTE  DM[65535];//
    BYTE  L32[4*32];
    BYTE  Smart[4096];
} t_ltr212_bios_info;
#pragma pack(pop)


// коды резистора для четверть-мостового подключения
enum QB_RESISTOR_INDEX {
    QB_RESISTOR_INVALID         = 0x0,
    QB_RESISTOR_IS_200_Ohm      = 0x1,
    QB_RESISTOR_IS_350_Ohm      = 0x2,
    QB_RESISTOR_IS_CUSTOM_Ohm   = 0x4
};




static const double f_mul_coef[8] = {0.01, 0.02, 0.04, 0.08, 0.01/2, 0.02/2, 0.04/2, 0.08/2};

/*******************************************************************************
    Некоторые внутренние функциии, используемые в библиотеке
*******************************************************************************/
static double f_eval_lch_rate(INT divider, INT Is_CHOP_Ena, INT Delay);
static INT f_load_fir_filter(TLTR212 *hnd, const SHORT *coeff);
static INT f_load_iir_filter(TLTR212 *hnd, const SHORT *coeff);



static int f_check_lch_params(TLTR212 *hnd);
static INT f_set_lchannels(TLTR212 *hnd);
static INT f_set_filter_mode(TLTR212 *hnd, const INT* chop, const INT* skip,
                             const INT* fast, const INT *fdivider);

// Недоступные пользователю  
static INT f_calibr_stages(TLTR212 *hnd, BYTE *LChannel_Mask, BYTE *BadChannel_Mask, INT mode, INT reset);
static INT f_reset_AD7730(TLTR212 *hnd);
static INT f_write_reg_16bit(TLTR212 *hnd, INT RegCode, WORD value);
static INT f_read_reg_16bit(TLTR212 *hnd, INT RegCode, WORD *value);
static INT f_write_reg_24bit(TLTR212 *hnd, INT RegCode, DWORD value);
static INT f_read_reg_24bit(TLTR212 *hnd, INT RegCode, DWORD *value);
static INT f_select_codec(TLTR212 *hnd, INT CodecNum);
static INT f_select_ch(TLTR212 *hnd, INT phy_ch);
static INT f_test_firmware(TLTR212 *hnd, WORD *CRC_Rcv);
static INT f_evaluate_dac_value(TLTR212 *hnd, BYTE lch_msk, BYTE *DAC_Val, BYTE *bad_ch_msk);
static INT f_evaluate_flash_crc(TLTR212 *hnd, WORD *Flash_CRC);
static INT f_write_flash_crc(TLTR212 *hnd, WORD Flash_CRC);
static INT f_set_adc_calibr(TLTR212 *hnd);
static INT f_read_eeprom(TLTR212 *hnd, INT n, INT Address, BYTE *image);
static INT f_write_eeprom(TLTR212 *hnd, INT n, INT Address, const BYTE *image);
static INT f_read_serial(TLTR212 *hnd, CHAR *sn);
static INT f_read_firm_ver(TLTR212 *hnd, CHAR *version);
static INT f_read_bios_date(TLTR212 *hnd, CHAR *date);
static INT f_read_module_name(TLTR212 *hnd, CHAR *name);

static INT f_load_fabric_cbr_coeffs(TLTR212 *hnd);
static INT f_get_fabric_cbr_addr(TLTR212 *hnd, DWORD *base_addr);






static INT f_check_resp(DWORD ack, DWORD cmd) {
    INT err = ltr_module_check_parity(ack);
    if (err == LTR_OK) {
        if (LTR_MODULE_CMD_GET_CMDCODE(ack) == PARITY_ERR) {
            err = LTR_ERROR_INVALID_CMD_PARITY;
        } else if (LTR_MODULE_CMD_GET_CMDCODE(ack) != LTR_MODULE_CMD_GET_CMDCODE(cmd)) {
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
        }
    }
    return err;
}


static INT f_send_cmds_with_resp(TLTR212 *hnd, DWORD *cmds, DWORD *resps, DWORD cmd_cnt, DWORD ack_cnt) {
    INT err;
    err = ltr_module_send_cmd(&hnd->Channel, cmds, cmd_cnt);
    if (err == LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, resps, ack_cnt);
    }

    if (err == LTR_OK) {
        DWORD i, cmd_pos;
        for (i = 0, cmd_pos = 0; (i < ack_cnt) && (err == LTR_OK); ++i) {
            err = f_check_resp(cmds[cmd_pos], resps[i]);
            /* на две WRITE_24REG всегда один ответ */
            if (LTR_MODULE_CMD_GET_CMDCODE(cmds[cmd_pos]) == WRITE_24REG) {
                cmd_pos++;
            }
            cmd_pos++;

            if (cmd_pos >= cmd_cnt)
                cmd_pos = cmd_cnt - 1;
       }
    }
    return err;
}


//-----------------------------------------------------------------------------------
// Функция получения типа модуля: LTR212(LTR212M-3), LTR212M-1 или LTR212M-2
//-----------------------------------------------------------------------------------
static INT f_read_module_type(TLTR212 *hnd, BYTE *ModuleType) {
    DWORD cmd, ack;
    INT err = LTR_OK;

    cmd = ltr_module_fill_cmd_parity(GET_MODULE_TYPE, 0);
    err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    if (err == LTR_OK) {
        BYTE type = (BYTE)((ack >> 16) & 0xFF);
        if (ModuleType != NULL)
            *ModuleType = type;

        if ((type != LTR212_OLD) && (type != LTR212_M_1) && (type != LTR212_M_2))
            err = LTR212_ERR_UNSUPPORTED_MODULE_TYPE;
    }
    return err;
}





static BYTE f_get_quarter_bridge_resistor_code(DWORD LogChannel) {
    BYTE ret_code = QB_RESISTOR_INVALID;
    BYTE bridget_type = LCH_BRIDGE_TYPE(LogChannel);

    if ((bridget_type == LTR212_QUARTER_BRIDGE_WITH_200_Ohm) ||
        (bridget_type == LTR212_UNBALANCED_QUARTER_BRIDGE_WITH_200_Ohm)) {
        ret_code = QB_RESISTOR_IS_200_Ohm;
    } else if ((bridget_type == LTR212_QUARTER_BRIDGE_WITH_350_Ohm) ||
          (bridget_type == LTR212_UNBALANCED_QUARTER_BRIDGE_WITH_350_Ohm)) {
        ret_code = QB_RESISTOR_IS_350_Ohm;
    } else if ((bridget_type == LTR212_QUARTER_BRIDGE_WITH_CUSTOM_Ohm) ||
          (bridget_type == LTR212_UNBALANCED_QUARTER_BRIDGE_WITH_CUSTOM_Ohm)) {
        ret_code = QB_RESISTOR_IS_CUSTOM_Ohm;
    }

    return ret_code;
}


//-----------------------------------------------------------------------------------
// Функция установки типа мостов для всех активных логических каналов
//-----------------------------------------------------------------------------------
static INT f_set_bridge_type(TLTR212 *hnd) {
    INT i;
    INT err = LTR_OK;
    BYTE birdget_type_msk, qb_resistor_idx, en_test_unbalance_msk;

    // управление подключением мостов возможно только у модуля LTR212M-1
    if ((hnd->ModuleInfo.Type == LTR212_OLD) || (hnd->ModuleInfo.Type == LTR212_M_2)) {
        for (i = 0; i < LTR212_LCH_CNT_MAX; ++i) {
            LCH_SET_BRIDGE_TYPE(hnd->LChTbl[i], LTR212_FULL_OR_HALF_BRIDGE);
        }
    } else {
        // проверим наличие четверть-мостовых подключений при EIGHT_CHANNELS_WITH_HIGH_RESOLUTION
        // а также проверим одинаковость резисторов всех четверть-мостовых подключений
        qb_resistor_idx = QB_RESISTOR_INVALID;
        en_test_unbalance_msk = 0x0;
        birdget_type_msk = 0x0F;


        for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
            if (phy_ch < 0x4) {
                BYTE birdget_type = LCH_BRIDGE_TYPE(hnd->LChTbl[i]);
                /* Для всех четвертьмостовых схем должны быть выбраны одинаковые резистроы */
                if (birdget_type != LTR212_FULL_OR_HALF_BRIDGE) {
                    if (qb_resistor_idx == QB_RESISTOR_INVALID) {
                        qb_resistor_idx = f_get_quarter_bridge_resistor_code(hnd->LChTbl[i]);
                        if (qb_resistor_idx == QB_RESISTOR_INVALID) {
                            err = LTR212_ERR_CANT_SET_BRIDGE_CONNECTIONS;
                        }
                    } else {
                        if (qb_resistor_idx != f_get_quarter_bridge_resistor_code(hnd->LChTbl[i])) {
                            err = LTR212_ERR_QB_RESISTORS_IN_ALL_CHANNELS_MUST_BE_EQUAL;
                        }
                    }
                }

                if (birdget_type != LTR212_FULL_OR_HALF_BRIDGE) {
                    /* 1 в birdget_type_msk указывает на полу или
                     * полномостовое подключение (0 - четвертьмоста) */
                    birdget_type_msk        &= ~(0x1 << phy_ch);
                }

                if (birdget_type >= LTR212_UNBALANCED_QUARTER_BRIDGE_WITH_200_Ohm) {
                    /* бит указывает разрешение резистора для тестового разбаланса моста */
                    en_test_unbalance_msk   |= 0x1 << phy_ch;
                }
            } else {
                /* Для каналов 5-8 всегда используется полу или полномостовая схема */
                LCH_SET_BRIDGE_TYPE(hnd->LChTbl[i], LTR212_FULL_OR_HALF_BRIDGE);
            }
        }


        if (err == LTR_OK) {
            DWORD cmd, ack;
            cmd = ltr_module_fill_cmd_parity(SET_BRIDGE_TYPE, birdget_type_msk |
                                                              (qb_resistor_idx << 4) |
                                                              (en_test_unbalance_msk << 8));
            err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
        }
    }
    return err;
}

LTR212API_DllExport(void) LTR212_GetDllVersion(LTR212_DLL_VERSION * const DllVersion) {
    DllVersion->Value       = 0x0;
    DllVersion->Major       = LTR212_VERSION_MAJOR;
    DllVersion->Minor       = LTR212_VERSION_MINOR;
    DllVersion->Build       = LTR212_VERSION_BUILD;
    DllVersion->Revision    = LTR212_VERSION_REVISION;
}


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

    strcpy((CHAR *)hnd->ModuleInfo.BiosVersion, "\0"); // Версия БИОСа по умолчанию
    strcpy((CHAR *)hnd->ModuleInfo.BiosDate, "\0"); // Дата БИОСа по умолчанию
}


/**************************************************************************************
Функция инициализации полей структуры описания модуля и открытия каналы связи с модулем
**************************************************************************************/
LTR212API_DllExport(INT) LTR212_Init(TLTR212 *hnd) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        INT i;

        memset(hnd, 0, sizeof(*hnd));
        hnd->size = sizeof(TLTR212); // Размер структуры
        hnd->UseClb = 0;   // Не задействуем калибровочные коэффициенты, хранящиеся в ППЗУ модуля
        hnd->LChQnt = 4;   // Количество логических каналов - 4.

        // Инициализируем таблицу логических каналов
        for(i = 0; i < LTR212_LCH_CNT_MAX; ++i)
            hnd->LChTbl[i] = 0;

        hnd->LChTbl[0] = LTR212_CreateLChannel(1,LTR212_SCALE_B_80); // Устанавливаем диапазон -80 мВ/+80 мВ для 4-х виртуальных каналов
        hnd->LChTbl[1] = LTR212_CreateLChannel(2,LTR212_SCALE_B_80); // или лучше 0 по умолчанию? Чтобы не работало, пока каналы не созданы?
        hnd->LChTbl[2] = LTR212_CreateLChannel(3,LTR212_SCALE_B_80);
        hnd->LChTbl[3] = LTR212_CreateLChannel(4,LTR212_SCALE_B_80);

        hnd->filter.IIR = 0;  // Программные фильтры отключаем по умолчанию
        hnd->filter.FIR = 0;
        hnd->filter.Decimation = 1; //Устанавливаем по умолчанию 1 в качестве порядка и коэффициента децимации КИХ-фильтра
        hnd->filter.TAP = 0;

        // По умолчанию установлен режим сбора данных повышенной точности
        hnd->AcqMode = LTR212_FOUR_CHANNELS_WITH_HIGH_RESOLUTION;

        f_info_init(hnd);

        err = LTR_Init(&(hnd->Channel));        // Инициализируем канал
    }
    return err;
}


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


#ifdef _WIN32
    #define LTR212_READ_BIO(err, buf, size, file, resource, total_size) \
        if ((resource) != NULL) { \
            if ((size) <= rem_size) {\
                memcpy(buf, resource, size); \
                (resource) += (size); \
                (rem_size) -= (size); \
            } else { \
                err = LTR212_ERR_WRONG_BIOS_FILE; \
            }\
        } else { \
            if (fread(buf, 1, size, file) != (size))  \
                err = LTR212_ERR_WRONG_BIOS_FILE; \
        }
#else
    #define LTR212_READ_BIO(err, buf, size, file, resource, total_size) \
        if (fread(buf, 1, size, file) != (size))  \
            err = LTR212_ERR_WRONG_BIOS_FILE;
#endif



/*******************************************************************************
    Функция открытия канала и загрузки БИОСа во внутреннюю память DSP модуля
*******************************************************************************/
LTR212API_DllExport(INT) LTR212_Open(TLTR212 *hnd, DWORD net_addr, WORD net_port,
                                     const CHAR *crate_sn, INT slot_num, const CHAR *biosname) {
    DWORD open_flags = LTR_MOPEN_INFLAGS_DEF_SEC_STOP;
    INT err = LTR_OK;
    INT warning = 0;
    // нулевой указатель или пустая строка соответствует встроенной прошивке
    INT internal_name = (biosname == NULL) || (biosname[0]=='\0') || (biosname[0]=='\n') || (biosname[0]=='\r');

    err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = ltr_module_open(&hnd->Channel, net_addr, net_port, crate_sn, slot_num,
                         LTR_MID_LTR212, &open_flags, NULL, &warning);
        if (err == LTR_OK) {
            f_info_init(hnd);
        }
    }


    if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        DWORD i, j; /* счетчики */
        FILE *firm_file = NULL;  // Указатель на файл с БИОСом
        WORD CRC_From_CC;
        t_ltr212_bios_info* bios_info = NULL;
#ifdef _WIN32
        const BYTE *res_data = NULL;
        DWORD rem_size;
#endif


        if (internal_name) {
#ifndef _WIN32
            /* для ОС, отличных от Windows, используем файл из места, указанного
                как путь установки при сборке */
            biosname = LTRMODULE_INSTALL_DATA_FILE;
#else
            /* Для Windows загружаем файл из ресурса библиотеки */
            HRSRC res_info = FindResource(hInstance, "LTR212_BIO", RT_RCDATA);
            HGLOBAL rhnd = NULL;
            if (res_info != NULL) {
                rhnd = LoadResource(hInstance, res_info);
                if (rhnd != NULL) {
                    res_data = LockResource(rhnd);
                    if (res_data != NULL)
                        rem_size = SizeofResource(hInstance, res_info);
                }
            }

            if (res_data==NULL)
                err = LTR_ERROR_FIRM_FILE_OPEN;
#endif
        }

        if ((err == LTR_OK)
#ifdef _WIN32
                && (res_data == NULL)
#endif
                ) {
            /* Открываем файл с прошивкой */
            firm_file = fopen(biosname, "rb");
            if (firm_file == NULL) {
                err = LTR_ERROR_FIRM_FILE_OPEN;
            }
        }


        if (err == LTR_OK) {
            bios_info = malloc(sizeof(t_ltr212_bios_info));
            if (bios_info == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            }
        }


        if (err == LTR_OK) {
            LTR212_READ_BIO(err, bios_info->signature, 14, firm_file, res_data, rem_size);
            if (err == LTR_OK) {
                bios_info->signature[14] = 0x0;
                if (strcmp("SAEXE@L - Card", bios_info->signature) != 0) {
                    err = LTR212_ERR_WRONG_BIOS_FILE;
                }
            }
        }


        if (err == LTR_OK) {
            LTR212_READ_BIO(err, bios_info->reserved0, offsetof(t_ltr212_bios_info, reserved6) +
                            sizeof(bios_info->reserved6) - offsetof(t_ltr212_bios_info, reserved0),
                            firm_file, res_data, rem_size);
        }

        if (err == LTR_OK) {
            LTR212_READ_BIO(err, bios_info->DM, bios_info->DSize, firm_file, res_data, rem_size);
        }
        if (err == LTR_OK) {
            LTR212_READ_BIO(err, bios_info->PM, bios_info->PSize, firm_file, res_data, rem_size);
        }
        if (err == LTR_OK) {
            LTR212_READ_BIO(err, bios_info->L32, bios_info->LSize, firm_file, res_data, rem_size);
        }


        if (firm_file != NULL) {
            fclose(firm_file);
        }

        if (err==LTR_OK) {
            LTRAPI_SLEEP_MS(100);
        }


        /**************Выполняем загрузку DSP ****************************/

        /* Загружаем программу-загрузчик через BDMA */
        if (err == LTR_OK) {
            DWORD LoadCommand[48];

            for(i=0, j=0; i < 96; i = i + 2, j++) {
                LoadCommand[j] = LTR_MODULE_MAKE_CMD_BYTES(
                            LTR010CMD_PROGR, bios_info->L32[i], bios_info->L32[i+1]);
            }

            err = ltr_module_send_cmd(&hnd->Channel, LoadCommand,
                                      sizeof(LoadCommand)/sizeof(LoadCommand[0]));
        }

        if (err == LTR_OK) {
             DWORD *DM_LoadCommand = NULL;
             DWORD DM_Size = (DWORD) bios_info->DSize/2;   // Размер памяти данных В СЛОВАХ!!!!!!! (по 2 байта)

             DM_LoadCommand = malloc(DM_Size*sizeof(DM_LoadCommand[0]));

             if (DM_LoadCommand == NULL) {
                 err = LTR_ERROR_MEMORY_ALLOC;
             } else {
                /* Создаем массив команд для памяти данных
                    Массивы команд PROGR для пересылки в DSP */
                for (j = 0, i = 0; j < DM_Size; j++, i += 2) {
                    DM_LoadCommand[j] = LTR_MODULE_MAKE_CMD_BYTES(
                                LTR010CMD_PROGR, bios_info->DM[i+1], bios_info->DM[i]);
                }
                /* Загружаем память данных */
                err = ltr_module_send_cmd(&hnd->Channel, DM_LoadCommand, DM_Size);

                free(DM_LoadCommand);
             }
        }

        if (err == LTR_OK) {
            DWORD PM_Size = (DWORD) 3 * bios_info->PSize / 4; // Размер памяти программ  (по 3 байта);

            BYTE *PM_ord = NULL;
            DWORD *PM_LoadCommand = NULL;
            /* Каждое содержимое ячейки памяти передается в 2-х командах LTR.
               При нечетном количестве нужно отдельное слово на последний байт */
            DWORD PM_cmd_cnt = (PM_Size+1)/2;

            PM_ord = malloc(PM_Size*sizeof(PM_ord[0]));            
            PM_LoadCommand = malloc(PM_cmd_cnt * sizeof(PM_LoadCommand[0]));

            if ((PM_ord == NULL) || (PM_LoadCommand == NULL)) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                memset(PM_ord, 0, LTR212_PM_CRC_CMD_SIZE*3);
                /* Подготавливаем массив для загрузки памяти программ.
                    Байты необходимо расположить в нужном порядке: старший, средний, младший.
                    В исходном массиве они расположены в ином порядке: средний, младший, старший */
                for (i = 0, j = 0; i < bios_info->PSize / 4; ++i) {
                    PM_ord[j++]=bios_info->PM[4*i+1];
                    PM_ord[j++]=bios_info->PM[4*i];
                    PM_ord[j++]=bios_info->PM[4*i+2];
                }

                /* Расчет CRC */
                hnd->CRC_PM = eval_crc16(0, PM_ord, LTR212_PM_CRC_CMD_SIZE*3);

                //***********************************************************
                //Создаем массив команд для памяти программ
                for (i = 0; i < PM_cmd_cnt; ++i) {
                    PM_LoadCommand[i] = LTR_MODULE_MAKE_CMD_BYTES(
                                LTR010CMD_PROGR, PM_ord[2*i], (2*i+1) < PM_Size ? PM_ord[2*i+1] : 0);
                }

                /* Загружаем память программ */
                err = ltr_module_send_cmd(&hnd->Channel, PM_LoadCommand, PM_cmd_cnt);

                if (err == LTR_OK) {
                   LTRAPI_SLEEP_MS(300);
                }
            }

            free(PM_ord);
            free(PM_LoadCommand);
        }

        if (err == LTR_OK) {
            DWORD stop = LTR010CMD_STOP;
            err = ltr_module_send_cmd(&hnd->Channel, &stop, 1);            
            if (err == LTR_OK) {
                LTRAPI_SLEEP_MS(200);
            }
        }

        if (err == LTR_OK) {
            /* Посылаем "пустую" команду, чтобы загруженный БИОС начал выполняться */
            DWORD Dummy_command = FIRST_INSTR;
            err = ltr_module_send_cmd(&hnd->Channel, &Dummy_command, 1);
            if (err == LTR_OK) {
                DWORD rwrd;
                err = ltr_module_recv_cmd_resp(&hnd->Channel, &rwrd, 1);
                if ((err == LTR_OK) && (LTR_MODULE_CMD_GET_CMDCODE(rwrd) != Dummy_command)) {
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                }
            }

            if (err == LTR_OK) {
                LTRAPI_SLEEP_MS(200);
            }
        }


        if (err == LTR_OK) {
            /* Проверяем контрольную сумму БИОСа */
            err = f_test_firmware(hnd, &CRC_From_CC);
        }

        if (err == LTR_OK) {
            /* Сбрасываем все АЦП AD7730 */
            err = f_reset_AD7730(hnd);
        }

        if (err == LTR_OK) {
            err = f_read_module_type(hnd, &hnd->ModuleInfo.Type);
        }

        /* Считываем серийный номер и заполняем соответствующее поле структуры
           описания модуля полученным значением */
        if (err == LTR_OK) {
            err = f_read_serial(hnd, hnd->ModuleInfo.Serial);
        }

        if (err == LTR_OK) {
            err = f_read_firm_ver(hnd, hnd->ModuleInfo.BiosVersion);
        }

        if (err == LTR_OK) {
            err = f_read_bios_date(hnd, hnd->ModuleInfo.BiosDate);
        }

        if (err == LTR_OK) {
            err = f_read_module_name(hnd, hnd->ModuleInfo.Name);
        }

        free(bios_info);
    }

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

    return err == LTR_OK ? warning : err;
}

/*******************************************************************************
    Функция закрытия канала
*******************************************************************************/

LTR212API_DllExport(INT) LTR212_Close(TLTR212 *hnd) {
    INT err = (hnd==NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR_Close(&hnd->Channel);
    }
    return err;
}


/* получаем параметры EEPROM по типу модуля */
static INT f_get_eeprom_cfg(TLTR212 *hnd, const t_eeprom_cfg **cfg) {
    if (hnd->ModuleInfo.Type == LTR212_OLD) {
        *cfg = &f_eeprom_at93C66;
    } else {
        *cfg = &f_eeprom_at25df041a;
    }
    return LTR_OK;
}



/* вспомогательная функция зачистки счетчиков EEPROM в прошивке ADSP */
static INT f_eeprom_cntrs_cleanup(TLTR212 *hnd, DWORD Mask) {
    DWORD cmd, ack;
    cmd = ltr_module_fill_cmd_parity(CLEANUP_CMD, Mask & 0xFFFF);
    return f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
}

/*******************************************************************************
    Функция чтения n байт из ППЗУ модуля, начиная с адреса Address
*******************************************************************************/
static INT f_read_eeprom(TLTR212 *hnd, INT size, INT addr, BYTE *image) {

    INT err=LTR_OK;
    DWORD *cmd = NULL;
    DWORD *ack = NULL;
    INT cur_pos = 0;
    const t_eeprom_cfg *cfg = NULL;

    if (hnd == NULL) {
        err = LTR_ERROR_INVALID_MODULE_DESCR;
    }

    if (err == LTR_OK) {
        err = f_get_eeprom_cfg(hnd, &cfg);
    }

    if ((err == LTR_OK) && ((addr < 0) || (((DWORD)(addr + size)) > cfg->size))) {
        err = LTR_ERROR_FLASH_INVALID_ADDR;
    }

    if (err == LTR_OK) {
        cmd = malloc(LTR212_CMD_BUFFER_SIZE*sizeof(cmd[0]));
        ack = malloc(LTR212_CMD_BUFFER_SIZE*sizeof(ack[0]));

        if ((cmd == NULL) || (ack == NULL)) {
            err = LTR_ERROR_MEMORY_ALLOC;
        }
    }

    if (err==LTR_OK) {
        err = f_eeprom_cntrs_cleanup(hnd, CLEANUP_FLASH_READ_PARS_COUNT);
    }

    while ((err == LTR_OK) && (cur_pos < size)) {
        DWORD block_size,i;

        for (block_size = 0; (block_size < LTR212_CMD_BUFFER_SIZE / cfg->cmd_per_rd) && (cur_pos < size);
                ++block_size, ++cur_pos, ++addr) {
            if (cfg->cmd_per_rd == 1) {
                cmd[block_size] = ltr_module_fill_cmd_parity(RD_FLASH, addr);
            } else {
                cmd[2*block_size] = ltr_module_fill_cmd_parity(RD_FLASH, addr & 0xFFFF);
                cmd[2*block_size+1] = ltr_module_fill_cmd_parity(RD_FLASH, (addr>>16) & 0x0007);
            }
        }

        err = f_send_cmds_with_resp(hnd, cmd, ack, block_size*cfg->cmd_per_rd, block_size);
        if (err == LTR_OK) {
            for (i = 0; i < block_size; ++i) {
                *image++ = (ack[i] >> 16) & 0xFF;
            }
        }
    } /* while ((err==LTR_OK) && (cur_pos < n)) */


    free(cmd);
    free(ack);

    return err;
} 



//-----------------------------------------------------------------------------------
// Функция записи массива байт в ППЗУ модуля типа AT25DF041A
// для новых типов модуля: LTR212M_1 и LTR212M_3
//-----------------------------------------------------------------------------------
static INT f_write_eeprom_at25df041a(TLTR212 *hnd, INT size, INT addr, const BYTE *image) {
    DWORD *cmds = NULL;
    DWORD ack;
    INT err = LTR_OK;
    INT cur_pos = 0;

    cmds = malloc(LTR212_CMD_BUFFER_SIZE*sizeof(cmds[0]));
    if (cmds == NULL) {
        err = LTR_ERROR_MEMORY_ALLOC;
    }

    if (err == LTR_OK) {
        // -=== формируем команды начала записи во FLASH
        // команда с младшими 16-ю битами адреса
        cmds[0x0] = ltr_module_fill_cmd_parity(WR_FLASH, addr & 0x0000FFFFUL);
        // команда со старшими 16-ю битами адреса
        cmds[0x1] = ltr_module_fill_cmd_parity(WR_FLASH, (addr >> 16) & 0x00000007UL);
        // команда с младшими 16-ю битами кол-ва передаваемых данных
        cmds[0x2] = ltr_module_fill_cmd_parity(WR_FLASH, size & 0x0000FFFFUL);
        // команда со старшими 16-ю битами кол-ва передаваемых данных
        cmds[0x3] = ltr_module_fill_cmd_parity(WR_FLASH, (size >> 16) & 0x00000007UL);
        // отсылаем блок команд
        err = f_send_cmds_with_resp(hnd, cmds, &ack, 4, 1);
    }

    if (err == LTR_OK) {
        // проверим возвращаемый статус
        BYTE flash_status = ((ack >> 16) & 0x000000FFUL);
        if (flash_status & 0x1) {
            err = LTR_ERROR_FLASH_OP_FAILED;
        }
    }


    while ((err == LTR_OK) && (cur_pos < size)) {
        DWORD block_size;

        for (block_size = 0; (block_size < LTR212_CMD_BUFFER_SIZE) && (cur_pos < size);
             ++block_size, ++cur_pos, ++addr) {
            cmds[block_size] = ltr_module_fill_cmd_parity(WR_FLASH, *image++);
        }
        err = ltr_module_send_cmd(&hnd->Channel, cmds, block_size);
    }

    if (err == LTR_OK) {
        err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack, 1);
        if (err == LTR_OK)
             err = f_check_resp(cmds[0], ack);
    }

    // освободим ресурсы
    free(cmds);

    return err;
}


static INT f_write_eeprom_at93c66(TLTR212 *hnd, INT size, INT addr, const BYTE *image) {
    INT err = LTR_OK;
    INT cur_pos = 0;


    while ((err == LTR_OK) && (cur_pos < size)) {
        /* для записи одна команда - команда записи с адресом,  вторая -
           команда с данными для записи */
        DWORD command[200], command_ack[100];
        DWORD block_size;

        for(block_size = 0; (block_size < sizeof(command)/sizeof(command[0]))
            && (cur_pos < size); ++cur_pos, ++addr) {
            command[block_size++] = ltr_module_fill_cmd_parity(WR_FLASH, addr);
            command[block_size++] = ltr_module_fill_cmd_parity(WR_FLASH, *image++);
        }

        /* принимаем в два раза меньше данных (так как на пару адрес-данные -
          одно подтверждение) */
        err = f_send_cmds_with_resp(hnd, command, command_ack, block_size, block_size/2);
    } /* while ((err==LTR_OK) && (cur_pos < size)) */


    return err;
}


/*******************************************************************************
    Функция записи массива n байт в ППЗУ модуля, начиная с адреса Address
*******************************************************************************/
static INT f_write_eeprom(TLTR212 *hnd, INT size, INT addr, const BYTE *image) {
    INT err = LTR_OK;
    const t_eeprom_cfg *cfg;

    if (hnd == NULL) {
        err = LTR_ERROR_INVALID_MODULE_DESCR;
    }

    if (err == LTR_OK) {
        err = f_get_eeprom_cfg(hnd, &cfg);
    }

    if ((err == LTR_OK) && ((addr < 0) || (((DWORD)(addr + size)) > cfg->size))) {
        err = LTR_ERROR_FLASH_INVALID_ADDR;
    }

    if (err == LTR_OK) {
        err = f_eeprom_cntrs_cleanup(hnd, CLEANUP_FLASH_WRITE_PARS_COUNT);
    }

    if (err == LTR_OK) {
        err = cfg->wr_eeprom(hnd, size, addr, image);
    }

    return err;
}

/*******************************************************************************
    Функция загрузки в ДСП коэффициентов БИХ-фильтра
*******************************************************************************/

static INT f_load_iir_filter(TLTR212 *hnd, const SHORT *coeff) {
    DWORD cmds[5], ack;
    DWORD i;
    for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); ++i) {
        // Формируем массив команд с коэффициентами БИХ-фильтра
        cmds[i] = ltr_module_fill_cmd_parity(LOAD_IIR, coeff[i]);
    }
    return f_send_cmds_with_resp(hnd, cmds, &ack,
                                sizeof(cmds)/sizeof(cmds[0]), 1);
}  

/*******************************************************************************
        Функция загрузки в DSP коэффициентов выбранного КИХ-фильтра
********************************************************************************/
static INT f_load_fir_filter(TLTR212 *hnd, const SHORT *coeff) {
    DWORD cmds[LTR212_FIR_ORDER_MAX+1], ack;
    BYTE fir_order, dec_factor, i;/* Порядок фильтра и коэффициент децимации */
    /* Посылаем в DSP порядок фильтра и коэффициент децимации */
    fir_order = hnd->filter.TAP;
    dec_factor = hnd->filter.Decimation;

    cmds[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LOAD_FIR, dec_factor, fir_order);
    /* Формируем массив команд с коэффициентами КИХ-фильтра */
    for(i = 0; i < fir_order; ++i) {
        cmds[i+1] = ltr_module_fill_cmd_parity(LOAD_FIR, coeff[i]);
    }

    return f_send_cmds_with_resp(hnd, cmds, &ack, 1 + fir_order, 1);
}    


static int f_check_lch_params(TLTR212 *hnd) {
    INT i;
    INT err = LTR_OK;
    if(hnd == NULL) {
        err = LTR_ERROR_INVALID_MODULE_DESCR;
    } else if ((hnd->LChQnt == 0) || (hnd->LChQnt > LTR212_LCH_CNT_MAX)) {
        err = LTR212_ERR_INVALID_VCH_CNT;
    }

    for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
        /* Переменные, используемые для проверки правильности составления таблицы виртуальных каналов */
        INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
        INT scale  = LCH_SCALE(hnd->LChTbl[i]);

        if ((phy_ch < 0) || (phy_ch >= LTR212_PHY_CH_CNT)) {
            err = LTR212_ERR_INVALID_CHNUM;
        } else if ((hnd->AcqMode != LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION) && (phy_ch > 3)) {
            err = LTR212_ERR_CANT_USE_CHNUM;
        } else if((scale < 0) || (scale >= LTR212_SCALES_CNT)) {
            err = LTR212_ERR_INVALID_SCALE;
        }

        if (i != 0) {
            INT phy_ch_prev = LCH_PHYCH(hnd->LChTbl[i-1]);
            if (phy_ch <= phy_ch_prev) {
                err = LTR212_ERR_WRONG_VCH_SETTINGS;
            }
        }
    }
    return err;
}

static INT f_set_lchannels(TLTR212 *hnd) {
    DWORD cmd[LTR212_LCH_CNT_MAX], ack[LTR212_LCH_CNT_MAX];
    INT i;
    for (i = 0; i < hnd->LChQnt; ++i) {
        INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
        INT scale = LCH_SCALE(hnd->LChTbl[i]);
        INT ch_group = PHYCH_GROUP(phy_ch);
        WORD ch_mode = 0;
        /* ch_mode соответствует значению регистра MODE для AD7730,
           за исключением того, что в старших 3-х битах передается
           номер физического канала (по сути - номер микросхемы) */

        ch_mode |= (scale << AD7730_MODE_RN_Pos) & AD7730_MODE_RN_Msk;
        //ch_mode |= AD7730_MODE_BO;

        /* Определяем, следует ли в выбранном диапазоне
           устанавливать бит униполярного режима */
        if (SCALE_UNIPOLAR(scale)) {
            ch_mode |= AD7730_MODE_U;
        }

        if (hnd->REF != LTR212_REF_2_5V) {
            ch_mode |= AD7730_MODE_HIREF;
        }
        if (ch_group) {
            ch_mode |= AD7730_MODE_CH;
        }

        ch_mode |= (phy_ch << 13);

        /*  Для 24-битного сбора!!!!! */
        if (!(hnd->filter.IIR || hnd->filter.FIR)) {
            ch_mode |= AD7730_MODE_WL;
        }

         cmd[i] = ltr_module_fill_cmd_parity(LOAD_CHANNEL, ch_mode);
    } /* for(i=0; i<hnd->LChQnt... */

    /* Передаем массив с параметрами каналов */
    return f_send_cmds_with_resp(hnd, cmd, ack, hnd->LChQnt, hnd->LChQnt);
}

static INT f_set_filter_mode(TLTR212 *hnd, const INT* chop, const INT* skip,
                             const INT* fast, const INT *fdivider) {
    INT err = LTR_OK;
    /* Проверяем Fdivider на допустимые пределы */
    if (chop[hnd->AcqMode] == 0) {
        if (skip[hnd->AcqMode] == 0) {
            if ((fdivider[hnd->AcqMode] > 2048) || (fdivider[hnd->AcqMode] < 150)) {
                err = LTR212_ERR_WRONG_ADC_SETTINGS;
            }
        } else {
            if ((fdivider[hnd->AcqMode] > 2048) || (fdivider[hnd->AcqMode] < 40)) {
                err = LTR212_ERR_WRONG_ADC_SETTINGS;
            }
        }
    }

    if (chop[hnd->AcqMode] == 1) {
        if (skip[hnd->AcqMode] == 0) {
            if ((fdivider[hnd->AcqMode] > 2048) || (fdivider[hnd->AcqMode] < 75)) {
                err = LTR212_ERR_WRONG_ADC_SETTINGS;
            }
        } else {
            if ((fdivider[hnd->AcqMode] > 2048) || (fdivider[hnd->AcqMode] < 20)) {
                err = LTR212_ERR_WRONG_ADC_SETTINGS;
            }
        }
    }

    if (err == LTR_OK) {
        INT i;
        DWORD filter_mode = (fdivider[hnd->AcqMode] << 12)
                | (skip[hnd->AcqMode] << 9)
                | (fast[hnd->AcqMode] << 8)
                | (hnd->AC << 5)
                | (chop[hnd->AcqMode] << 4);

        /* Загрузка сформированного слова в регистр фильтра всех кодеков */
        for (i = 0; (i < 4) && (err == LTR_OK); ++i) {
            /** @todo можно тоже оптимизировать посылки/ответы */
            err = f_select_codec(hnd, i);
            if (err == LTR_OK)
                err = f_write_reg_24bit(hnd, FILTER_REG, filter_mode);
        }
    }
    return err;
}


/*******************************************************************************
        Функция создания логического канала
*******************************************************************************/
LTR212API_DllExport(INT) LTR212_CreateLChannel(INT PhysChannel, INT Scale) {
    /* Старшие 16 бит содержат номер физического канала,  Младшие 16 бит - код диапазона */
    INT lch = 0;
    LCH_SET_SCALE(lch, Scale);
    LCH_SET_PHYCH(lch, PhysChannel);
    return lch;
} 

//-----------------------------------------------------------------------------------
// Расширенная функция создания логического канала
//-----------------------------------------------------------------------------------
LTR212API_DllExport(INT) LTR212_CreateLChannel2(DWORD PhysChannel, DWORD Scale, DWORD BridgeType) {
    INT lch = 0;
    LCH_SET_SCALE(lch, Scale);
    LCH_SET_PHYCH(lch, PhysChannel);
    LCH_SET_BRIDGE_TYPE(lch, BridgeType);
    return lch;
}

LTR212API_DllExport(INT) LTR212_SetADC(TLTR212 *hnd) {
    TLTR212_FILTER iirFlt;
    TLTR212_FILTER firFlt;
    INT err = LTR_OK;
    if (hnd->filter.FIR || hnd->filter.IIR) {
        if (hnd->AcqMode != LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) {
            err = LTR212_ERR_CANT_USE_FILTER;
        } else {
            if(hnd->filter.IIR) {
                err = LTR212_ReadFilter(hnd->filter.IIR_Name,  &iirFlt);
            }

            if ((err == LTR_OK) && hnd->filter.FIR) {
                err = LTR212_ReadFilter(hnd->filter.FIR_Name,  &firFlt);
            }
        }
    }

    if (err == LTR_OK) {
        err = LTR212_SetADCExpFlt(hnd, &firFlt, &iirFlt);
    }
    return err;
}


LTR212API_DllExport(INT) LTR212_SetADCExpFlt(TLTR212 *hnd,
                                             const TLTR212_FILTER *firFlt,
                                             const TLTR212_FILTER *iirFlt) {
    static const INT chop[3] = {0, 1, 1};
    static const INT skip[3] = {1, 0, 0};
    static const INT fast[3] = {0, 0, 0};
    static const INT fdivider[3] = {40, 682, 682};
    INT err = LTR212_IsOpened(hnd);
    INT i;

    if (err == LTR_OK) {
        err = CHECK_ACQ_MODE(hnd->AcqMode);
    }

    /*** Выполняем проверку правильности таблицы логических каналов ***
    (физические каналы могут быть только в следующей последовательности: 1,2,3,4,5,6,7,8)
    Если какие-то каналы не задействованы, последовательность все равно не должна нарушаться */
    if (err == LTR_OK) {
        err = f_check_lch_params(hnd);
    }

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


    /* При низком опорном напр. или режиме средней точности зав. коэфф. неприменимы */
    if ((err == LTR_OK) && hnd->UseFabricClb) {
        /* в режиме средней точности заводские коэф. неприменимы */
        if (hnd->AcqMode == LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) {
            err = LTR212_ERR_CANT_USE_CALIBR_MODE;
        } else {
            /* Для опорного напряжения 2.5 В заводские коэф. применимы только в
             * LTR212-M1/M2 */
            if ((hnd->ModuleInfo.Type == LTR212_OLD) && (hnd->REF == LTR212_REF_2_5V)) {
                err = LTR212_ERR_CANT_USE_CALIBR_MODE;
            }
        }
    }

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

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

    /* Передаем массив со значениями ЦАПа */
    if (err == LTR_OK) {
        /* Если исп.калибр. коэфф-ты, то вызываем функцию их загрузки */
        if (hnd->UseClb) {
            err = LTR212_Load_Clb(hnd);
        } else {
            DWORD cmd[LTR212_LCH_CNT_MAX], ack[LTR212_LCH_CNT_MAX];
            WORD dac_cnt = 0;
            /* Буфер ЦАПа загружаем только тогда зн. по умолчанию,
                когда не используем калибр. коэфф-ты */
            for (i = 0; i < hnd->LChQnt; ++i) {
                cmd[dac_cnt++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                            LOAD_DAC, LCH_PHYCH(hnd->LChTbl[i]), DAC_ZERO_CODE);
            }
            err = f_send_cmds_with_resp(hnd, cmd, ack, dac_cnt, dac_cnt);
        }
    }


    if ((err == LTR_OK) && hnd->UseFabricClb) {
        err = f_load_fabric_cbr_coeffs(hnd);
    }

    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(SET_REF, hnd->REF == LTR212_REF_2_5V ? 0 : 1);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    }

    if (err == LTR_OK) {
        err = f_set_filter_mode(hnd, chop, skip, fast, fdivider);
    }

    if (err == LTR_OK) {
        if (hnd->AcqMode != LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) {
            if (hnd->filter.IIR || hnd->filter.FIR) {
                err = LTR212_ERR_CANT_USE_FILTER;
            }
        } else {
            /* Определяем значение _базовой_ частоты АЦП */
            hnd->Fs = f_eval_lch_rate(fdivider[hnd->AcqMode], chop[hnd->AcqMode], 0);
            if (hnd->filter.IIR) {
                if (err == LTR_OK) {
                    /** @todo так сравнивать два double нехоршо.... */
                    /*if (hnd->Fs != iirFlt.fs) {
                        err = LTR212_ERR_CANT_MANAGE_FILTERS;
                    } else */
                    if (iirFlt->taps != LTR212_IIR_ORDER) {
                        err = LTR212_ERR_FILTER_ORDER;
                    } else {
                        err = f_load_iir_filter(hnd, iirFlt->koeff);
                    }
                }
            }

            if ((err == LTR_OK) && hnd->filter.FIR) {
                if (err == LTR_OK) {
                    hnd->filter.TAP = firFlt->taps;
                    hnd->filter.Decimation = firFlt->decimation;
                    err = f_load_fir_filter(hnd, firFlt->koeff);
                }
            }
        }
    }

    /* независимо от режима пересчитываем значение частоты   ;-)) */
    if (err == LTR_OK) {
        double fsBase;
        err = LTR212_CalcFS(hnd, &fsBase, &(hnd->Fs));
    }

    if (err == LTR_OK) {
        /* Включаем или отключаем программные фильтры */
        DWORD cmd, ack;
        cmd = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                    ENA_DIS_FILTERS, hnd->filter.IIR ? 1 : 0, hnd->filter.FIR ? 1 : 0);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    }
    return err;
}



/******************************************************************************
    Функция калибровки выбранного канала и записи калибровочных
    коэффициентов в ППЗУ
*******************************************************************************/
static INT f_calibr_stages(TLTR212 *hnd, BYTE *LChannel_Mask, BYTE *BadChannel_Mask,
                           INT mode, INT reset) {
    INT i;
    INT UseClb_buf = hnd->UseClb;
    INT err = LTR_OK;
    BYTE bad_ch_msk = 0;
    BYTE lch_msk = *LChannel_Mask;

    hnd->UseClb = 0;
    hnd->UseFabricClb = 0;

    if ((reset < 0) || (reset > 1))
        err = LTR_ERROR_PARAMETERS;

    if ((err == LTR_OK) && reset) {
        err = f_reset_AD7730(hnd);  // Сброс кодеков

        /* Если не внешняя калибровка нуля, то обнуляем ЦАП */
        if ((err == LTR_OK) && (mode != CBR_MODE_EXT_ZERO)) {
            DWORD cmds[LTR212_PHY_CH_CNT];
            INT dac_cntr = 0;

            for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
                if ((lch_msk >> i) & 0x1) {
                    INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                    cmds[dac_cntr++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LOAD_DAC, phy_ch,
                                                                        DAC_ZERO_CODE);
                    /* Если это не внутрення калибровка нуля, то записываем
                        в ППЗУ значение ЦАП по умолчанию (0x20) */
                    if(mode != CBR_MODE_INT_ZERO) {
                        BYTE val = DAC_ZERO_CODE; // Значение ЦАП по умолчанию
                        err = f_write_eeprom(hnd, 1, EEPROM_ADDR_USERCBR_DAC(phy_ch), &val);
                    }
                }
            } /* for(i=0;...) */

            if (err == LTR_OK) {
                DWORD acks[LTR212_PHY_CH_CNT];
                err = f_send_cmds_with_resp(hnd, cmds, acks, dac_cntr, dac_cntr);
            }
        } //if ((err==LTR_OK) && (mode!=2))
    } //if(reset)

    /* внутренняя калибровка нуля */
    if ((err == LTR_OK) && (mode == CBR_MODE_INT_ZERO)) {
        for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
            if ((lch_msk >> i) & 0x1) {
                BYTE eeprom_data[EEPROM_USERCBR_CHPARAMS_SIZE];
                INT phy_ch  = LCH_PHYCH(hnd->LChTbl[i]);
                INT scale   = LCH_SCALE(hnd->LChTbl[i]);
                DWORD offset_reg_val = 0, gainreg_val = 0;
                /* Заводские коэффициенты переписываем только в случае
                    постоянного опорного напряжения */
                if(!hnd->AC) {
                    DWORD base_addr;
                    err = f_get_fabric_cbr_addr(hnd, &base_addr);

                    if (err == LTR_OK) {
                        err = f_read_eeprom(hnd, 3, base_addr + INTCBR_ADDR_OFFSET(scale, phy_ch),
                                            eeprom_data);
                    }

                    if (err == LTR_OK) {
                        offset_reg_val = (eeprom_data[2] << 16) | (eeprom_data[1] << 8) | eeprom_data[0];
                    }
                } else {
                    /* В противном случае ставим коэфф-т смещения по умолчанию */
                    offset_reg_val = 0x800000;
                }

                /* Записываем калибр. коэфф-ты в регистр смещения выбранного кодека */
                err = f_select_ch(hnd, phy_ch);
                if (err == LTR_OK)
                    err = f_write_reg_24bit(hnd, OFFSET_REG, offset_reg_val);
                if (err == LTR_OK)
                    err = f_read_reg_24bit(hnd, GAIN_REG, &gainreg_val);

                if (err==LTR_OK) {
                    eeprom_data[0] = (BYTE)offset_reg_val & 0xFF;
                    eeprom_data[1] = (BYTE)(offset_reg_val >> 8)  & 0xFF;
                    eeprom_data[2] = (BYTE)(offset_reg_val >> 16) & 0xFF;

                    eeprom_data[3] = (BYTE)gainreg_val & 0xFF;
                    eeprom_data[4] = (BYTE)(gainreg_val >> 8) & 0xFF;
                    eeprom_data[5] = (BYTE)(gainreg_val >> 16) & 0xFF;

                    eeprom_data[6] = DAC_ZERO_CODE; // Также обнуляем значение ЦАПа в ППЗУ

                    err = f_write_eeprom(hnd, EEPROM_USERCBR_CHPARAMS_SIZE,
                                         EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch), eeprom_data);
                }
            } /* if((*LChannel_Mask>>i)&0x1) */
        } /* for (i=0; (i<hnd->LChQnt)...) */
        //goto End_Calibr;
    } else if ((err == LTR_OK) && (mode == CBR_MODE_INT_SCALE)) {
        for(i=0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
            if((lch_msk >> i) & 0x1) {
                BYTE eeprom_data[6];
                INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                INT scale  = LCH_SCALE(hnd->LChTbl[i]);
                DWORD offset_reg_val, gain_val;
                DWORD base_addr;

                err = f_get_fabric_cbr_addr(hnd, &base_addr);
                if (err == LTR_OK) {
                    err = f_read_eeprom(hnd, 3, base_addr + INTCBR_ADDR_GAIN(scale, phy_ch),
                                        eeprom_data);
                }
                if (err == LTR_OK) {
                    gain_val = (eeprom_data[2] << 16) | (eeprom_data[1] << 8) | eeprom_data[0];
                }

                if (err == LTR_OK) {
                    err = f_select_ch(hnd, phy_ch);
                }
                if (err == LTR_OK) {
                    err = f_read_reg_24bit(hnd, OFFSET_REG, &offset_reg_val);
                }
                if (err == LTR_OK) {
                    err = f_write_reg_24bit(hnd, GAIN_REG, gain_val);
                }
                if (err == LTR_OK) {
                    eeprom_data[0] = (BYTE)offset_reg_val;
                    eeprom_data[1] = (BYTE)(offset_reg_val >> 8);
                    eeprom_data[2] = (BYTE)(offset_reg_val >> 16);

                    eeprom_data[3] = (BYTE)gain_val;
                    eeprom_data[4] = (BYTE)(gain_val >> 8);
                    eeprom_data[5] = (BYTE)(gain_val >> 16);
                    err = f_write_eeprom(hnd, 6, EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch), eeprom_data);
                }
            } /* if((lch_msk>>i)&0x1) */
        } /* for(i=0; (i<hnd->LChQnt)... */
        //goto End_Calibr;
    } else if (err == LTR_OK) {  /* т.е. для внешней калибровки */
        err = f_set_adc_calibr(hnd);
        /* При калибровке внешнего нуля отдельным этапом подбираем
         * значения грубого ЦАП  */
        if ((err == LTR_OK) && (mode == CBR_MODE_EXT_ZERO)) {
            BYTE dac_shift[LTR212_PHY_CH_CNT] = {0,0,0,0,0,0,0,0};

            err = f_evaluate_dac_value(hnd, lch_msk, dac_shift, &bad_ch_msk);

            if (err == LTR_OK) {
                DWORD cmds[LTR212_LCH_CNT_MAX], acks[LTR212_LCH_CNT_MAX];
                WORD dac_cntr = 0;

                /* удаляем каналы, которые не удалось откалибровать */
                lch_msk &= ~bad_ch_msk;
                /* загружаем полученные значения в регистры ЦАП */
                for (i = 0; i < hnd->LChQnt; ++i) {
                    if ((lch_msk >> i) & 0x1) {
                        INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                        cmds[dac_cntr++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                                    LOAD_DAC, phy_ch, dac_shift[phy_ch]);
                    }
                }

                if (dac_cntr) {
                    /* загружаем также их в EEPROM в область польз. коэф. */
                    err = f_send_cmds_with_resp(hnd, cmds, acks, dac_cntr, dac_cntr);
                    for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
                        if ((lch_msk >> i) & 0x1) {
                            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                            BYTE eeprom_data = dac_shift[phy_ch];
                            err = f_write_eeprom(hnd, 1, EEPROM_ADDR_USERCBR_DAC(phy_ch), &eeprom_data);
                        }
                    }
                }
            }
        } /* if ((err==LTR_OK) && (mode==CBR_MODE_EXT_ZERO)) */


        /* Далее этап внешней калибровки и нуля и шкалы выполняется самим АЦП по команде DSP */
        if (err == LTR_OK) {
            DWORD ack, cmd;
            /** @note почему физический канал всегда 0? */
            /* Формируем значение режима калибровки, применяемое непосредственно на кодеке */
            cmd = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                        hnd->AcqMode == LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION
                        ? CALIBR_8CH : CALIBR_4CH, 0, mode+4);
            err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1,1);

            /* Запишим полученное значение регистров смещения и усиления
                в соответствующие ячейки ППЗУ */
            for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
                if ((lch_msk >> i) & 0x1) {
                    INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                    DWORD offset_reg_val, gain_reg_val;

                    err = f_select_ch(hnd, phy_ch);
                    if (err == LTR_OK) {
                        err = f_read_reg_24bit(hnd, OFFSET_REG, &offset_reg_val);
                    }
                    if (err == LTR_OK) {
                        err = f_read_reg_24bit(hnd, GAIN_REG, &gain_reg_val);
                    }
                    if (err == LTR_OK) {
                        /* Здесь DAC-Shift не меняется */
                        BYTE eeprom_data[6];
                        eeprom_data[0] = (BYTE)offset_reg_val;
                        eeprom_data[1] = (BYTE)(offset_reg_val >> 8);
                        eeprom_data[2] = (BYTE)(offset_reg_val >> 16);

                        eeprom_data[3] = (BYTE)gain_reg_val;
                        eeprom_data[4] = (BYTE)(gain_reg_val >> 8);
                        eeprom_data[5] = (BYTE)(gain_reg_val >> 16);

                        err = f_write_eeprom(hnd, 6, EEPROM_ADDR_USERCBR_CHPARAMS(phy_ch), eeprom_data);
                    }
                } /* if ((*LChannel_Mask>>i)&0x1) */
            } /* for(i=0;(i<hnd->LChQnt) && (err==LTR_OK);i++) */
        }
    } /*  else для (mode!=0) && (mode!=1) */




    if (err == LTR_OK) {
        WORD Flash_CRC=0;
        /* После произведенных изменений в ППЗУ вычисляем контр.сумму */
        err = f_evaluate_flash_crc(hnd, &Flash_CRC);
        if (err == LTR_OK)
            err = f_write_flash_crc(hnd, Flash_CRC);
    }

    hnd->UseClb = UseClb_buf;

    if (BadChannel_Mask != NULL)
        *BadChannel_Mask = bad_ch_msk;
    return err;
}


/*******************************************************************************
        Функция выбора режима калибровки
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_Calibrate(TLTR212 *hnd, BYTE *LChannel_Mask,
                                           INT Calibr_Mode, INT reset) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    BYTE bad_ch_msk = 0;

    if (err == LTR_OK) {
        switch (Calibr_Mode) {
            case LTR212_CALIBR_MODE_INT_ZERO:
                /* Внутренняя калибровка нуля */
                if ((hnd->AcqMode == LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION)||
                        ((hnd->REF == LTR212_REF_2_5V) && (hnd->ModuleInfo.Type == LTR212_OLD))) {
                    /* В режиме средней точности зав. коэфф. неприменимы,
                       а для 2.5В только возможны для M1 и M2 */
                    err = LTR212_ERR_CANT_USE_CALIBR_MODE;
                } else {
                    err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_INT_ZERO, reset);
                }
                break;
            case LTR212_CALIBR_MODE_INT_SCALE: /* Внутренняя калибровка диапазона */
                if((hnd->REF == LTR212_REF_2_5V) && (hnd->ModuleInfo.Type == LTR212_OLD)) {
                    err = LTR212_ERR_CANT_USE_CALIBR_MODE;
                } else {
                    err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_INT_SCALE, reset);
                }
                break;
            case LTR212_CALIBR_MODE_INT_FULL: /* полная внутренняя калибровка */
                if ((hnd->AcqMode == LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) ||
                        ((hnd->REF == LTR212_REF_2_5V) && (hnd->ModuleInfo.Type == LTR212_OLD))) {
                    /* В режиме средней точности зав. коэфф. неприменимы,
                       а для 2.5В только возможны для M1 и M2 */
                    err = LTR212_ERR_CANT_USE_CALIBR_MODE;
                } else {
                    err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_INT_ZERO, 1); // 1-я стадия
                    if (err == LTR_OK) {
                        err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_INT_SCALE, 0);  // 2-я стадия
                    }
                }
                break;
            case LTR212_CALIBR_MODE_EXT_ZERO: /* Внешняя калибровка нуля */
                err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_EXT_ZERO, reset);
                break;
            case LTR212_CALIBR_MODE_EXT_SCALE: /* Внешняя калибровка диапазона */
                err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_EXT_SCALE, reset);
                break;
            case LTR212_CALIBR_MODE_EXT_ZERO_INT_SCALE: /* Внутренний диапазон + внешний 0 */
                if ((hnd->REF == LTR212_REF_2_5V) && (hnd->ModuleInfo.Type == LTR212_OLD)) {
                    err = LTR212_ERR_CANT_USE_CALIBR_MODE;
                } else {
                    err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk,  CBR_MODE_INT_SCALE, 1);
                    if (err == LTR_OK) {
                        err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_EXT_ZERO, 0);
                    }
                }
                break;
            case LTR212_CALIBR_MODE_EXT_FULL_2ND_STAGE: /* Внешняя калибровка диапазона (только вторая стадия после
                                                                    внешней или внутренней калибровки нуля)!!! */
                err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_EXT_SCALE, 0);
                break;
            case LTR212_CALIBR_MODE_EXT_ZERO_SAVE_SCALE: {
                    int i;
                    TLTR212_USER_TARE_COEFS usr_cbr_before;//каллибровочные коэффициэнты До каллибровки 0
                    TLTR212_USER_TARE_COEFS usr_cbr_after; //каллибровочные коэффициэнты После каллибровки 0
                    TLTR212_USER_TARE_COEFS usr_cbr_result;//каллибровочные коэффициэнты Результирующие.

                    //Сохраняем масштабные коэффициэнты
                    err = LTR212_ReadUSR_Clb(hnd,&usr_cbr_before);
                    if (err == LTR_OK) {
                        //Проводим каллиброывку "0"
                        err = f_calibr_stages(hnd, LChannel_Mask, &bad_ch_msk, CBR_MODE_EXT_ZERO, reset);
                    }

                    if (err == LTR_OK) {
                        //Читаем новые коэффициэнты
                        err = LTR212_ReadUSR_Clb(hnd, &usr_cbr_after);
                    }

                    if (err == LTR_OK) {
                        //Смешиваем каллибровки
                        for(i = 0; i < LTR212_PHY_CH_CNT; ++i) {
                            if (((LChannel_Mask[0] >> i) & 0x01) && !((bad_ch_msk >> i) & 0x01)) {
                                //Для выбранных каналов формируем каллибровку
                                usr_cbr_result.Offset[i]    = usr_cbr_after.Offset[i];    // Сдвиг из свежих коэффициэнтов
                                usr_cbr_result.DAC_Value[i] = usr_cbr_after.DAC_Value[i]; // Значение ЦАП тоже из новых коэффициэнтов
                                usr_cbr_result.Scale[i]     = usr_cbr_before.Scale[i];    // Масштаб из старых....
                            } else {
                                //Для невыбранных - востанавливаем.
                                usr_cbr_result.Offset[i]   =usr_cbr_before.Offset[i];   // Сдвиг из свежих коэффициэнтов
                                usr_cbr_result.DAC_Value[i]=usr_cbr_before.DAC_Value[i];// Значение ЦАП тоже из новых коэффициэнтов
                                usr_cbr_result.Scale[i]    =usr_cbr_before.Scale[i];    // Масштаб из старых....
                            }
                        }
                        //Записываем калибровки.
                        err = LTR212_WriteUSR_Clb(hnd, &usr_cbr_result);
                    }
                }
                break;
        } /* switch (Calibr_Mode) */
    }


    /* Если не было критических ошибок, но часть каналов не откалибровалось
     * из-за невозможности подобрать нужное значение ЦАП, то из маски удаляем
     * те каналы, которые откалибровать не удалось */
    if ((err == LTR_OK) && (bad_ch_msk != 0)) {
        err = LTR212_ERR_CANT_CALIBRATE;
        *LChannel_Mask = *LChannel_Mask & ~bad_ch_msk;
    }

    return err;
}


/*******************************************************************************
        Функция запуска сбора данных
*******************************************************************************/
LTR212API_DllExport(INT) LTR212_Start(TLTR212 *hnd) {
    BYTE phy_ch_msk = 0; // Маска физических каналов
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (err == LTR_OK) {
        INT i;
        /* Формируем маску физических каналов, которая будет использоваться DSP */
        for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
            if ((hnd->AcqMode != LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION) && (phy_ch > 3)) {
                err = LTR212_ERR_CANT_USE_CHNUM;
            } else {
                phy_ch_msk |= (1 << phy_ch);
            }
        }
    }

    if (err == LTR_OK) {
        DWORD command, ack;
        /* В старшем байте указываем, какой режим используется:
           4-канальный или 8-канальный */
        command = LTR_MODULE_FILL_CMD_PARITY_BYTES(START_ACQ,
                    hnd->AcqMode == LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION ? 1 : 0,
                    phy_ch_msk);
        err = f_send_cmds_with_resp(hnd, &command, &ack, 1, 1);
    }
    return err;
}  


/*******************************************************************************
        Функция остановки сбора данных
*******************************************************************************/
LTR212API_DllExport(INT) LTR212_Stop(TLTR212 *hnd) {
     INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
     if (err == LTR_OK) {
         DWORD cmd = STOP_ACQ;
         err = ltr_module_stop(&hnd->Channel, &cmd, 1, cmd,
                              LTR_MSTOP_FLAGS_CHECK_PARITY, 0, NULL);
     }
     return err;
} 

/*******************************************************************************
        Функция получения массива данных из модуля
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_Recv(TLTR212 *hnd, DWORD *data, DWORD *tmark,
                                      DWORD size, DWORD timeout) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
        if ((err >= 0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF)) {
            err = LTR_ERROR_RECV_OVERFLOW;
        }
    }
    return err;
}




/*******************************************************************************
    Функция расчета частоты виртуального канала. вызывать до загрузки фильров
*******************************************************************************/
/** @todo --- не учтены фильтры и 8-канальный режим работы !!! */
static double f_eval_lch_rate(INT divider, INT Is_CHOP_Ena, INT Delay) {
    return Is_CHOP_Ena == 0 ? (4915200 / (16*divider)) : (4915200 / (Delay + 3*16*divider));
}   


/******************************************************************************
        Функция сброса всех кодеков
*******************************************************************************/
static INT f_reset_AD7730(TLTR212 *hnd) {
    DWORD command, ack;
    command = ltr_module_fill_cmd_parity(RESET_CODECS, 0);
    return f_send_cmds_with_resp(hnd, &command, &ack, 1,1);

}

/*******************************************************************************
        Функция выбора желаемого кодека (0-3)
*******************************************************************************/
static INT f_select_codec(TLTR212 *hnd, INT CodecNum) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(SEL_CODEC, CodecNum);
        err = f_send_cmds_with_resp(hnd, &command, &ack, 1,1);
    }
    return err;
}

static INT f_select_ch(TLTR212 *hnd, INT phy_ch) {
    WORD modereg_val;
    INT err = f_select_codec(hnd, PHYCH_CODEC(phy_ch));
    if (err == LTR_OK) {
        err = f_read_reg_16bit(hnd, MODE_REG, &modereg_val);
    }
    if (err == LTR_OK) {
        PHYCH_MODE_REG_SEL_AIN(modereg_val, phy_ch);
        err = f_write_reg_16bit(hnd, MODE_REG, modereg_val);
    }
    return err;
}



static void f_fill_write_reg_16bit(DWORD *cmds, INT RegCode, WORD value) {
    /* адрес в коммуникационный регистр пишем той же 8-ми битной командой */
    cmds[0] = ltr_module_fill_cmd_parity(WRITE_8REG, COMM_REG_WR | RegCode);
    /* а значение - 16-битной */
    cmds[1] = ltr_module_fill_cmd_parity(WRITE_16REG, value);
}

/*******************************************************************************
    Функция записи значения в 16-битный регистр выбранного кодека
*******************************************************************************/
static INT f_write_reg_16bit(TLTR212 *hnd, INT RegCode, WORD value) {
    DWORD cmds[2], ack[2];
    f_fill_write_reg_16bit(cmds, RegCode, value);
    return f_send_cmds_with_resp(hnd, cmds, ack, 2,2);
} 


/*****************************************************************
    Функция чтения значения из 16-битного регистра выбранного кодека
******************************************************************/
static INT f_read_reg_16bit(TLTR212 *hnd, INT RegCode, WORD *value) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command, ack;
        /* записываем адрес в конммуникационный регистр */
        command = ltr_module_fill_cmd_parity(WRITE_8REG, COMM_REG_RD | RegCode);
        err = f_send_cmds_with_resp(hnd, &command, &ack, 1,1);
        if (err == LTR_OK) {
            /* считываем содержимое */
            command = ltr_module_fill_cmd_parity(READ_16REG, 0);
            err = f_send_cmds_with_resp(hnd, &command, &ack, 1,1);
        }

        if ((err == LTR_OK) && (value != NULL)) {
            *value = (ack >> 16) & 0xFFFF;
        }
    }
    return err;
} 

static void f_fill_write_reg_24bit(DWORD *cmds, INT RegCode, DWORD value) {
    cmds[0] = ltr_module_fill_cmd_parity(WRITE_8REG, COMM_REG_WR | RegCode);
    cmds[1] = ltr_module_fill_cmd_parity(WRITE_24REG, (value >> 16) & 0xFF);
    cmds[2] = ltr_module_fill_cmd_parity(WRITE_24REG, value & 0xFFFF);
}

/*******************************************************************************
    Функция записи значения в 24-битный регистр выбранного кодека
*******************************************************************************/
static INT f_write_reg_24bit(TLTR212 *hnd, INT RegCode, DWORD value) {
    DWORD cmds[3], ack[2];
    f_fill_write_reg_24bit(cmds,  RegCode, value);
    /* на 2 команды  WRITE_24REG только одно подтверждение */
    return f_send_cmds_with_resp(hnd, cmds, ack, 3, 2);
}  


/*******************************************************************************
        Функция чтения значения из 24-битного регистра выбранного кодека
*******************************************************************************/
static INT f_read_reg_24bit(TLTR212 *hnd, INT RegCode, DWORD *value) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command, ack[2];
        /* записываем адрес в конммуникационный регистр */
        command = ltr_module_fill_cmd_parity(WRITE_8REG, COMM_REG_RD | RegCode);
        err = f_send_cmds_with_resp(hnd, &command, ack, 1,1);

        if (err == LTR_OK) {
            /* считываем содержимое - на одну команду 2 ответа */
            command = ltr_module_fill_cmd_parity(READ_24REG, 0);
            err = f_send_cmds_with_resp(hnd, &command, ack, 1, 2);
        }

        if ((err == LTR_OK) && (value != NULL)) {
            *value= (ack[0] & 0xFF0000) | ((ack[1] >> 16) & 0xFFFF);
        }
    }
    return err;
}

static INT f_read_reg_8bit(TLTR212 *hnd, INT RegCode, BYTE *value) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command, ack;
        /* записываем адрес в конммуникационный регистр */
        command = ltr_module_fill_cmd_parity(WRITE_8REG, COMM_REG_RD | RegCode);
        err = f_send_cmds_with_resp(hnd, &command, &ack, 1, 1);
        if (err == LTR_OK) {
            /* считываем содержимое */
            command = ltr_module_fill_cmd_parity(READ_8REG, 0);
            err = f_send_cmds_with_resp(hnd, &command, &ack, 1,1);
        }

        if ((err == LTR_OK) && (value != NULL)) {
            *value=(ack>>16) & 0xFF;
        }
    }
    return err;
}



/* !!! вызывать для обновления поля VChRate __после__ успешного
        программирования АЦП и загрузки фильтров */
LTR212API_DllExport(INT) LTR212_CalcFS(TLTR212 *hnd, double *fsBase, double *fs) {
    const double fs_for_modes[3] = {7680.0, 150.15, 150.15 };

    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                            ((fsBase == NULL) || (fs==NULL)) ? LTR_ERROR_PARAMETERS :
                                                               CHECK_ACQ_MODE(hnd->AcqMode);

    if (err == LTR_OK) {
        *fsBase = fs_for_modes[hnd->AcqMode];

        if (hnd->filter.FIR && (hnd->filter.Decimation <= 0)) {
            err = LTR212_ERR_INVALID_FIR_DECIMATION;
        } else {
            if (hnd->AcqMode == LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION) {
                *fs = 3.4;
            } else {
                *fs = *fsBase / (hnd->filter.FIR ? hnd->filter.Decimation : 1);
            }
        }
    }
    return err;
}


/*******************************************************************************
    Функция чтения данных о программном фильтре и его коэффициентов
        из файла в спец. структуру
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_ReadFilter(CHAR *fname, TLTR212_FILTER *filter) {
    FILE *f = NULL;
    CHAR  line[FILTER_FILE_LINE_SYM_CNT+1];
    INT err = LTR_OK;
    INT intval = 0;

    memset(filter, 0, sizeof(TLTR212_FILTER));
    f = fopen(fname, "r");
    if (f == NULL)
        err = LTR212_ERR_FILTER_FILE_OPEN;
    if ((err == LTR_OK) && (fgets(line, FILTER_FILE_LINE_SYM_CNT, f) == NULL))
        err = LTR212_ERR_FILTER_FILE_READ;
    if (err == LTR_OK) {
        char prev_ch = line[FILTER_LINE_STR_FS_LEN];
        line[FILTER_LINE_STR_FS_LEN] = '\0';
        if (_stricmp(line, FILTER_LINE_STR_FS)) {
            err = LTR212_ERR_FILTER_FILE_FORMAT;
        } else {
            line[FILTER_LINE_STR_FS_LEN] = prev_ch;
        }
    }
    if ((err == LTR_OK) && (sscanf(line + FILTER_LINE_STR_FS_LEN, "%lf", &(filter->fs)) != 1))
        err = LTR212_ERR_FILTER_FILE_READ;
    if ((err == LTR_OK) && (fgets(line, FILTER_FILE_LINE_SYM_CNT, f) == NULL))
        err = LTR212_ERR_FILTER_FILE_READ;
    if (err == LTR_OK) {
        char prev_ch = line[FILTER_FILE_STR_DEC_LEN];
        line[FILTER_FILE_STR_DEC_LEN] = '\0';
        if (_stricmp(line, FILTER_FILE_STR_DEC)) {
            err = LTR212_ERR_FILTER_FILE_FORMAT;
        } else {
            line[FILTER_FILE_STR_DEC_LEN] = prev_ch;
        }
    }

    if ((err == LTR_OK) && (sscanf(line + FILTER_FILE_STR_DEC_LEN, "%d", &intval) != 1)) {
        err = LTR212_ERR_FILTER_FILE_READ;
    } else {
        filter->decimation = intval;
    }

    while ((err == LTR_OK) && fgets(line, FILTER_FILE_LINE_SYM_CNT, f) != NULL) {
        INT   k;
        if (sscanf(line, "%X", &k) == 1) {
            if(filter->taps < LTR212_FIR_ORDER_MAX) {
                filter->koeff[filter->taps] = k;
                filter->taps++;
            }
        }
    }

    if ((err == LTR_OK) && (filter->taps < LTR212_FIR_ORDER_MIN))
        err = LTR212_ERR_FILTER_ORDER;


    if (f != NULL)
        fclose(f);

    return err;
}


/*******************************************************************************
    Функция записи серийного номера модуля в ППЗУ по адресу 60 (0x3C)
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_WriteSerialNumber(TLTR212 *hnd, const CHAR *sn, WORD Code) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(WRITE_SN_CHECK, Code);
        err = f_send_cmds_with_resp(hnd, &command, &ack, 1, 1);
    }

    if (err == LTR_OK) {
        /* проверяем длину серийного номера */
        size_t sn_length = strnlen(sn, sizeof(hnd->ModuleInfo.Serial));
        if (sn_length >= (sizeof(hnd->ModuleInfo.Serial)-1)) {
            err = LTR_ERROR_PARAMETERS;
        } else {
            err = f_write_eeprom(hnd, (INT)(sn_length+1),
                                 EEPROM_ADDR_SERIAL, (BYTE *) sn);
        }
    }

    /* Записываем название устройства */
    if (err == LTR_OK) {
        err = f_write_eeprom(hnd, sizeof(LTR212_MODULE_NAME), EEPROM_ADDR_NAME,
                             (BYTE*)LTR212_MODULE_NAME);
    }

    if (err == LTR_OK) {
        WORD Flash_CRC;
        err = f_evaluate_flash_crc(hnd, &Flash_CRC);
        if (err == LTR_OK)
            err = f_write_flash_crc(hnd, Flash_CRC);
    }

    return err;
}

/*******************************************************************************
        Функция чтения серийного номера модуля в ППЗУ по адресу 60 (0x3C)
*******************************************************************************/
static INT f_read_serial(TLTR212 *hnd, CHAR *sn) {
    /* описатель уже проверяется в f_read_eeprom, так что можно забить на
       доп проверку */
    INT err = f_read_eeprom(hnd, sizeof(hnd->ModuleInfo.Serial), EEPROM_ADDR_SERIAL,
                            (BYTE *) sn);
    /* проверяем, что есть завершающий 0 в считанной строке. Если нет =>
       серийный не записан => устанавливаем пустую строку */
    if ((err == LTR_OK) && (strnlen(sn, sizeof(hnd->ModuleInfo.Serial))
                            == sizeof(hnd->ModuleInfo.Serial))) {
        sn[0] = '\0';
    }
    return err;
}

/* Проверка CRC прошивки => сравнивается ответ на команду TEST_BIOS с CRC
   записанной прошивки. Если CRC_Rcv не NULL, в нем сохраняется принятый в ответе
   CRC */
static INT f_test_firmware(TLTR212 *hnd, WORD *CRC_Rcv) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(TEST_BIOS, 0);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
        if (err != LTR_OK) {
            if (CRC_Rcv != NULL)
                *CRC_Rcv = 0;
        } else {
            WORD CRC_DSP = (ack >> 16) & 0xFFFF;
            if(CRC_DSP != hnd->CRC_PM) {
                err = LTR212_ERR_FIRMWARE_TEST_NOT_PASSED;
            }
            if (CRC_Rcv != NULL)
                *CRC_Rcv = CRC_DSP;
        }
    }
    return err;
}  






/*******************************************************************************
    Функция проверки и обработки полученных из модуля данных,
    применяемая только при тестировании
    @note до этого bad_num возвращался номер последней ошибки, сейчас - первой
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_ProcessDataTest(TLTR212 *hnd, const DWORD *src,
                                                 double *dest, DWORD *size,
                                                 BOOL volt, DWORD *bad_num) {
    DWORD output_size = 0;
    INT lch_pos = 0;
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : (src == NULL) || (size == NULL) ?
                              LTR_ERROR_PARAMETERS : LTR_OK;

    if ((err == LTR_OK) && ((hnd->LChQnt == 0) || (hnd->LChQnt > LTR212_LCH_CNT_MAX)))
        err = LTR212_ERR_INVALID_VCH_CNT;

    if (err == LTR_OK) {
        INT k;
        INT Frame_Begin =src[0] & 0x00000007;//((hnd->LChTbl[0])>>16)-1;    // Номер первого канала кадра

        for (k = 0, lch_pos = -1; (k < hnd->LChQnt) && (lch_pos == -1); ++k) {
            if (((hnd->LChTbl[k] >> 16) - 1) == Frame_Begin) {
                lch_pos = k;
            }
        }

        if (lch_pos == -1)
            err = LTR212_ERR_INV_ADC_DATA;
    }

    if (err == LTR_OK) {
        DWORD i;
        BYTE sample_cntr = ((*src) >> 4) & 0x0000000F; // 4-битный счетчик сэмплов в массиве

        for (i = 0; (i < *size); ++i, ++src) {
            INT cur_err = LTR_OK;
            if (lch_pos >= hnd->LChQnt) {
                lch_pos = 0;      //j-счетчик по  таблице виртуальных каналов
            }

            /* Проверка значения счетчика сэмплов,формируемого DSP и считанного
                из служебного поля сэмпла. Здесь же проверяем, что сэмпл
                не является командой*/
            if ((*src & 0x8000) || ((sample_cntr + i) & 0x0000000F) != ((*src >> 4) & 0x0000000F)) {
                cur_err = LTR212_ERR_INV_ADC_DATA;
            } else {
                INT phy_ch_tbl, phy_ch_recv;
                /* Номер физического канала, определенный в таблице виртуальных каналов */
                phy_ch_tbl = LCH_PHYCH(hnd->LChTbl[lch_pos]);
                /* Номер физического канала, записанный DSP в служебное поле полчуенного сэмпла*/
                phy_ch_recv = *src & 0x00000007;
                if(phy_ch_tbl != phy_ch_recv) {
                    cur_err = LTR212_ERR_INV_ADC_DATA;
                    /* Выкидываем весь кадр с несовпавшим номером */
                    if (output_size >= (DWORD)lch_pos) {
                        output_size -= lch_pos;
                    } else {
                        output_size = 0;
                    }
                    lch_pos = 0;
                }
            }

            if (cur_err == LTR_OK) {
                if (dest != NULL) {
                    dest[output_size++] = (double)(src[i] >> 16);
                }
                lch_pos++;
            } else {
                if (err == LTR_OK) {
                    err = LTR212_ERR_INV_ADC_DATA;
                    if (bad_num != NULL) {
                        *bad_num = i;
                    }
                }
            }
        }  // скобка к for
        *size = output_size;
    }
    return err;
}



/*******************************************************************************
        Функция проверки полученного массива данных
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_ProcessData(TLTR212 *hnd, const DWORD *src,
                                             double *dest, DWORD *size, BOOL volt) {
    WORD lch_pos = 0;
    DWORD output_size = 0;
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : (src == NULL) || (size == NULL) ?
                              LTR_ERROR_PARAMETERS : LTR_OK;
    if ((err == LTR_OK) && ((hnd->LChQnt == 0) || (hnd->LChQnt > LTR212_LCH_CNT_MAX))) {
        err = LTR212_ERR_INVALID_VCH_CNT;
    }


    if (err == LTR_OK) {
        DWORD i;
        INT phy_ch_first = LCH_PHYCH(hnd->LChTbl[0]);
        INT bad_frame = 0;
        BYTE sample_cntr = ((*src)>>4)&0x0000000F; // 4-битный счетчик сэмплов в массиве
        WORD high_half = 0;

        for(i = 0; (i < *size); ++i, ++src) {
            INT cur_err = LTR_OK;
            BYTE phy_ch_rcv = *src & 0x00000007;

            /* Проверка значения счетчика сэмплов,формируемого DSP и считанного
                из служебного поля сэмпла. Здесь же проверяем, что сэмпл
                не является командой*/
            /** @note Вообще возможно следует выкидывать кадр, в котором
                сбит счетчик */
            if ((*src & 0x8000) || ((sample_cntr + i) & 0x0000000F) != ((*src >> 4) & 0x0000000F)) {
                cur_err = LTR212_ERR_INV_ADC_DATA;
            }

            /* проверяем, что номер принятого физического канала совпадает
               с требуемым номером по логической таблице */
            if (!bad_frame) {
                if (phy_ch_rcv != LCH_PHYCH(hnd->LChTbl[lch_pos])) {
                    cur_err = LTR212_ERR_INV_ADC_DATA;
                    /* если не совпадает, то пропускаем все данные, пока не найдем
                       первый канал в таблице*/
                    bad_frame = 1;
                    /* викидываем остаток кадра в выходном массиве, чтобы
                        не перепутались каналы */
                    if (output_size >= lch_pos) {
                        output_size -= lch_pos;
                    } else {
                        output_size = 0;
                    }
                }
            }

            /* как только поподаем на слово от первого логического канала -
               пробуем принять новый кадр */
            /** @note Вообще использовать четность для определения
                первого или второго слова не есть хорошо, так как
                потеря нечетного числа приведет к тому, что не восстановимся */
            if (bad_frame && !(i & 1) && (phy_ch_rcv == phy_ch_first)) {
                bad_frame = 0;
            }

            /* обрабатываем нужный кадр */
            if (!bad_frame) {
                if ((i & 1) == 0) {
                    /* в первом слове передается старший байт 24-битного слова */
                    high_half = (*src >> 16) & 0xFF;
                } else {
                    INT sample;
                    BYTE scale = LCH_SCALE(hnd->LChTbl[lch_pos]);
                    INT  range_k;

                    /* всегда передается отсчет в 2 слова, но при фильтрах реально
                       используются только 16 бит из второго */
                    if(!((hnd->filter.IIR) || (hnd->filter.FIR))) {
                        sample = ((DWORD)high_half << 16) | ((*src >> 16) & 0xFFFF);
                        if (!SCALE_UNIPOLAR(scale)) {
                            sample -= 0x800000;
                        }
                        range_k = 8388608;
                    } else {
                        SHORT sample16 = (*src >> 16) & 0xFFFF;
                        if (SCALE_UNIPOLAR(scale)) {
                            sample16 += 0x8000;
                        }
                        sample = sample16;
                        range_k = 32768;
                    }

                    if (volt) {
                        if (scale < sizeof(f_mul_coef)/sizeof(f_mul_coef[0])) {
                            dest[output_size++] = (double)sample * f_mul_coef[scale] / range_k;
                        }
                    } else {
                        dest[output_size++] = (double)sample;
                    }


                    if (++lch_pos >= hnd->LChQnt) {
                        lch_pos = 0;
                    }
                }
            } /* if (!bad_frame) */

            if (err == LTR_OK)
                err = cur_err;
        } /* for(i=0; (i<*size); i++, src++) */
    } /* if (err==LTR_OK) */

    /* выкидываем не целый остаток кадра */
    if (lch_pos != 0) {
        if (output_size >= lch_pos) {
            output_size -= lch_pos;
        } else {
            output_size = 0;
        }
    }

    if (size != NULL) {
        *size = output_size;
    }
    return err;
}



static int f_recv_dac_vals(TLTR212 *hnd, int set_adc, const BYTE lch_mask, double* vals) {
    DWORD calibr_data[LTR212_LCH_CNT_MAX*LTR212_DAC_CBR_RECV_SIZE*2];
    double calibr_volts[LTR212_LCH_CNT_MAX*LTR212_DAC_CBR_RECV_SIZE*2];
    double mean_val[LTR212_LCH_CNT_MAX];
    INT cnt[LTR212_LCH_CNT_MAX];
    DWORD samples_qnt;
    DWORD i;
    WORD mode_val;
    INT IIR_buf = 0, FIR_buf = 0;
    INT err = LTR_OK;

    /* На время калибровки отключаем программные фильтры */
    if (hnd->AcqMode == LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) {
        IIR_buf = hnd->filter.IIR;
        FIR_buf = hnd->filter.FIR;
        hnd->filter.IIR = 0;
        hnd->filter.FIR = 0;
    }

    if (set_adc)
        err = f_set_adc_calibr(hnd);
    if (err == LTR_OK)
        err = LTR212_Start(hnd);

    if (err == LTR_OK){
        INT stop_err;
        DWORD tout = LTR212_CalcTimeOut(hnd, LTR212_DAC_CBR_RECV_SIZE);
        /* Кол-во получаемых пакетов в 2 раза больше кол-ва точек */
        samples_qnt = hnd->LChQnt * ((hnd->AcqMode == LTR212_EIGHT_CHANNELS_WITH_HIGH_RESOLUTION) ?
                                         LTR212_DAC_CBR_8CH_RECV_SIZE : LTR212_DAC_CBR_RECV_SIZE) * 2;
        //  Проверка на правильность заданного физ. канала!!! ???
        err=ltr_module_recv_cmd_resp_tout(&hnd->Channel, calibr_data, samples_qnt, tout);

        stop_err = LTR212_Stop(hnd);
        if (err == LTR_OK)
            err = stop_err;
    }

    /** @note нужно ли это чтение, значение ведь не используется ?? */
    if (err == LTR_OK)
        err = f_read_reg_16bit(hnd, MODE_REG, &mode_val);
    if (err == LTR_OK)
        err = LTR212_ProcessData(hnd, calibr_data, calibr_volts, &samples_qnt, 1);

    if (err == LTR_OK) {
        for (i = 0; i < LTR212_LCH_CNT_MAX; ++i) {
            mean_val[i] = 0.0;
            cnt[i] = 0;
        }

        for(i = 0; i < samples_qnt; ++i) {
            INT phy_ch = calibr_data[2*i] & 0x00000007;
            mean_val[phy_ch] = mean_val[phy_ch] + calibr_volts[i];
            ++cnt[phy_ch];
        }
    }

    for (i = 0; (i < (DWORD)hnd->LChQnt) && (err == LTR_OK); ++i) {
        if ((lch_mask >> i) & 0x1) {
            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
            // Если были сэмплы в этом канале.
            if (cnt[phy_ch] != 0) {
                /* Находим среднее значение в вольтах */
                vals[phy_ch] = mean_val[phy_ch] / cnt[phy_ch];
            } else {
                vals[phy_ch] = 0;
            }

        }
    }

    /* восстанавливаем измененные параметры */
    if (hnd->AcqMode == LTR212_FOUR_CHANNELS_WITH_MEDIUM_RESOLUTION) {
        hnd->filter.IIR = IIR_buf;
        hnd->filter.FIR = FIR_buf;
    }

    return err;
}


/*******************************************************************************
    Функция используется при внешней калибровке нуля (определение кода втроенного
    ЦАПа для грубого смещения нуля)
*******************************************************************************/
static INT f_evaluate_dac_value(TLTR212 *hnd, BYTE lch_msk, BYTE *dac_vals, BYTE *bad_ch_msk) {
    double meas_vals_prev[LTR212_LCH_CNT_MAX], meas_vals_next[LTR212_LCH_CNT_MAX];
    BYTE dac_vals_prev[LTR212_LCH_CNT_MAX];
    //CHAR DAC_Correct[LTR212_LCH_CNT_MAX];
    INT cbr_done = 0, correction_flag = 0;
    BYTE tmp_bad_cbr_msk = 0;
    INT err = LTR_OK;
    BOOL restore_tbl = FALSE;
    INT LChTbl_buf[LTR212_LCH_CNT_MAX];
    BYTE init_msk = lch_msk;


    if (err == LTR_OK) {
        DWORD cmds[LTR212_LCH_CNT_MAX], acks[LTR212_LCH_CNT_MAX];
        WORD dac_cntr = 0;
        INT i;

         /* Обнуляем значения ЦАПа */
        for (i = 0; i < hnd->LChQnt; ++i) {
            if ((lch_msk >> i) & 0x1) {
                cmds[dac_cntr++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                            LOAD_DAC, LCH_PHYCH(hnd->LChTbl[i]), DAC_ZERO_CODE);
            }
        }
        err = f_send_cmds_with_resp(hnd, cmds, acks, dac_cntr, dac_cntr);
    }

    if (err == LTR_OK) {
        INT i;
        for (i = 0; i < hnd->LChQnt; ++i) {
            if ((lch_msk >> i) & 0x1) {
                /* Сохраняем значения виртуальных каналов, т.е. диапазонов */
                LChTbl_buf[i] = hnd->LChTbl[i];
                if(hnd->REF == LTR212_REF_2_5V) {
                    /* Подменяем их на значения с диапазоном +/-40 мВ */
                    LCH_SET_SCALE(hnd->LChTbl[i], LTR212_SCALE_B_40);
                } else {
                    /* Подменяем их на значения с диапазоном +/-80 мВ */
                    LCH_SET_SCALE(hnd->LChTbl[i], LTR212_SCALE_B_80);
                }
            }
        }

        restore_tbl = TRUE;

        err = f_recv_dac_vals(hnd, 1, lch_msk, meas_vals_prev);

        for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
            if ((lch_msk >> i) & 0x1) {
                static const double f_v_ref[] = {2.5, 5.0};
                INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
                /* Проверка, чтобы полученное смещение было не больше максимально возможного */
                if (fabs(meas_vals_prev[phy_ch]) <= (f_v_ref[hnd->REF] * 31 / 2000)) {
                    DWORD cmd, ack;
                    WORD dac_code;

                    /* получаем код ЦАП, округленный до ближайшего целого */
                    dac_code = (INT)(fabs(meas_vals_prev[phy_ch]) / (f_v_ref[hnd->REF] / 2000) + 0.5);
                    /* Смещение, которое будет давать ЦАП (посчитано для информации) */
                    //Correct_Vtg=DAC_Code*V_REF[hnd->REF]/2000;
                    if (dac_code == 0) {
                        dac_code = DAC_ZERO_CODE;
                    } else if (meas_vals_prev[phy_ch] > 0) {
                        /* Если значение напряжения положительно, бит знака для регистра ЦАПа устанавливаем в "1" */
                        dac_code |= DAC_SIGN_BIT;
                    }

                    cmd = LTR_MODULE_FILL_CMD_PARITY_BYTES(LOAD_DAC, phy_ch, dac_code);
                    err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);

                    if (err == LTR_OK) {
                        dac_vals[phy_ch] = (BYTE)dac_code;
                        dac_vals_prev[phy_ch] = dac_vals[phy_ch];
                        //DAC_Correct[phy_ch]=(CHAR)dac_code;
                    }
                } else {
                    /* нельзя вот так прям выкидывать из процедуры, просто
                        нужно пропустить дальнейшие действия с канналом */
                    tmp_bad_cbr_msk |= (1 << i);
                    lch_msk &= ~(1 << i);
                }
            } /* if((*LChannel_Mask>>i)&0x1) */
        } /* for(i=0;... */
    }

    /* до ошибки или пока не откалибруемся - делаем новые попытки */
    while (!cbr_done && (err == LTR_OK)) {
        INT i;
        cbr_done = 1;
        err = f_recv_dac_vals(hnd, 0, lch_msk, meas_vals_next);
        if (err == LTR_OK) {
            for (i = 0; (i < hnd->LChQnt) && (err == LTR_OK); ++i) {
                INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);

                if ((lch_msk >> i) & 0x1) {
                    if (correction_flag) {
                        // Если перешли через "0"
                        if (((meas_vals_next[phy_ch] > 0) && (meas_vals_prev[phy_ch] < 0))
                                || ((meas_vals_next[phy_ch] < 0) && (meas_vals_prev[phy_ch] > 0))) {
                            // Смотрим, какое напряжение по модулю меньше
                            if (fabs(meas_vals_next[phy_ch]) >= fabs(meas_vals_prev[phy_ch])) {
                                dac_vals[phy_ch] = dac_vals_prev[phy_ch];
                            }
                             // Завершаем калибровку по этому каналу
                            lch_msk &= ~(1 << i);
                        }
                    }
                }

                /** @todo проверить правильность изменения кода DAC */
                if ((lch_msk >> i) & 0x1) {
                    meas_vals_prev[phy_ch] = meas_vals_next[phy_ch];
                    dac_vals_prev[phy_ch] = dac_vals[phy_ch];

                    if (meas_vals_next[phy_ch] > 0) {
                        if (dac_vals[phy_ch] >= DAC_ZERO_CODE) {
                            if (dac_vals[phy_ch] == DAC_CODE_MAX) {
                                /* перебрали все коды ЦАП => выходим по ошибке */
                                tmp_bad_cbr_msk |= (1 << i);
                                lch_msk &= ~(1 << i);
                            } else {
                                dac_vals[phy_ch]++;
                            }
                        } else {
                            dac_vals[phy_ch]--;
                            if (((CHAR) dac_vals[phy_ch]) == 0) {
                                 dac_vals[phy_ch] = DAC_ZERO_CODE;
                            }
                        }
                    } else {
                        if (dac_vals[phy_ch] >= DAC_ZERO_CODE)     {
                            dac_vals[phy_ch]--;
                            if (dac_vals[phy_ch] < DAC_ZERO_CODE) {
                                dac_vals[phy_ch] = 1;
                            }
                        } else {
                            dac_vals[phy_ch]++;
                            if (dac_vals[phy_ch] == DAC_ZERO_CODE) {
                                /* перебрали все коды ЦАП => выходим по ошибке */
                                tmp_bad_cbr_msk |= (1 << i);
                                lch_msk &= ~(1 << i);
                            }
                        }
                    }
                    cbr_done = 0;
                } /* if (((*LChannel_Mask>>i)&0x1) && !ch_bad_calibr[i]) */
            } /* for (i=0; i < hnd->LChCnt... */

            /* В случае напряжения, превышающего пределы, повторяем процедуру вновь */
            if (!cbr_done) {
                DWORD dac_command[LTR212_LCH_CNT_MAX], ack[LTR212_LCH_CNT_MAX];
                WORD dac_cntr = 0;
                for (i = 0; i < hnd->LChQnt; ++i) {
                    if ((lch_msk >> i) & 0x1) {
                        INT Phys_Channel = LCH_PHYCH(hnd->LChTbl[i]);
                        dac_command[dac_cntr++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                                    LOAD_DAC, LCH_PHYCH(hnd->LChTbl[i]), dac_vals[Phys_Channel]);
                    }
                }
                err = f_send_cmds_with_resp(hnd, dac_command, ack, dac_cntr, dac_cntr);
                correction_flag = 1;
            }
        } /* if (err==LTR_OK) */
    } /* while (!cbr_done && (err==LTR_OK)) */


    if (restore_tbl) {
        INT i;
        /* восстанавливаем лог. таблицу, включая лог таблицу
           неоткалиброванных каналов */
        for(i = 0; i < hnd->LChQnt; ++i) {
            if ((init_msk >> i) & 0x1)
                hnd->LChTbl[i] = LChTbl_buf[i];
        }

        if (err == LTR_OK)
            err = f_set_adc_calibr(hnd);
    }



    if ((err == LTR_OK) && (bad_ch_msk != NULL))
        *bad_ch_msk = tmp_bad_cbr_msk;

    return err;
}


/*******************************************************************************
    Функция записи контрольной суммы в ППЗУ
*******************************************************************************/
static INT f_write_flash_crc(TLTR212 *hnd, WORD Flash_CRC) {
    const t_eeprom_cfg *cfg;
    INT err = LTR_OK;

    err = f_get_eeprom_cfg(hnd, &cfg);
    if (err == LTR_OK) {
        BYTE crc_bytes[2];
        crc_bytes[0] = Flash_CRC & 0xFF;
        crc_bytes[1] = (Flash_CRC >> 8) & 0xFF;
        err = f_write_eeprom(hnd, sizeof(crc_bytes), cfg->crc_addr, crc_bytes);
    }

    return err;
}

/*******************************************************************************
        Функция проверки контрольной суммы ППЗУ
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_TestEEPROM(TLTR212 *hnd) {
    INT err = LTR_OK;
    WORD eval_crc = 0;
    const t_eeprom_cfg *cfg;

    err = f_evaluate_flash_crc(hnd, &eval_crc);
    if (err == LTR_OK) {
        err = f_get_eeprom_cfg(hnd, &cfg);
    }
    if (err == LTR_OK) {
        BYTE crc_flash_bytes[2];
        err = f_read_eeprom(hnd, sizeof(crc_flash_bytes),
                            cfg->crc_addr, crc_flash_bytes);
        if (err == LTR_OK) {
            hnd->CRC_Flash_Read = ((WORD)crc_flash_bytes[1] << 8) | crc_flash_bytes[0];
            hnd->CRC_Flash_Eval = eval_crc;

            if (hnd->CRC_Flash_Read != hnd->CRC_Flash_Eval)
                err = LTR_ERROR_FLASH_INFO_CRC;
        }
    }
    return err;
}

/*******************************************************************************
        Функция вычисления контрольной суммы ППЗУ
*******************************************************************************/
static INT f_evaluate_flash_crc(TLTR212 *hnd, WORD *Flash_CRC) {
    BYTE *flash_cont = 0;
    const t_eeprom_cfg *cfg;
    DWORD crc_prot_size;
    INT err;

    /* CRC защищается начальная область flash-памяти. ее размер зависит от
     * модификации модуля */
    err = f_get_eeprom_cfg(hnd, &cfg);
    if (err == LTR_OK) {
        crc_prot_size = cfg->crc_addr;
        flash_cont = malloc(crc_prot_size);
        if (flash_cont == NULL)
            err = LTR_ERROR_MEMORY_ALLOC;
    }

    if (err == LTR_OK)
        err = f_read_eeprom(hnd, crc_prot_size, 0, flash_cont);

    if (err == LTR_OK) {
        WORD crc = eval_crc16(0, flash_cont, crc_prot_size);
        *Flash_CRC = crc;
    }

    free(flash_cont);

    return err;
}


/*******************************************************************************
        Функция переводит модуль в режим тестирования
*******************************************************************************/
LTR212API_DllExport (INT) LTR212_TestInterfaceStart(TLTR212 *hnd, INT PackDelay) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD command=LTR_MODULE_FILL_CMD_PARITY_BYTES(TEST_INTERFACE, PackDelay, 0);
        DWORD command_ack;
        err = f_send_cmds_with_resp(hnd, &command, &command_ack, 1, 1);
    }
    return err;
}


/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR212_ERR_WRONG_ADC_SETTINGS, "Неверные установки АЦП"},
    {LTR212_ERR_WRONG_VCH_SETTINGS, "Неверная последовательность логических каналов"},
    {LTR212_ERR_CANT_CALIBRATE, "Не удалось подобрать нужные калибровочные значения для некоторых каналов"},
    {LTR212_ERR_INVALID_ACQ_MODE, "Неверный режим сбора данных"},
    {LTR212_ERR_INV_ADC_DATA, "Неверный формат принятых данных"},
    {LTR212_ERR_FIRMWARE_TEST_NOT_PASSED, "Тест целостности прошивки DSP завершился с ошибкой!"},
    {LTR212_ERR_CANT_USE_FABRIC_AND_USER_CALIBR_SYM,  "Нельзя применить пользовательские и заводские калибровочные коэффициенты одновременно!"},
    {LTR212_ERR_WRONG_BIOS_FILE,    "Неверный формат файла с прошивкой DSP!"},
    {LTR212_ERR_CANT_USE_CALIBR_MODE, "Использование калибровки невозможно для установленных параметров"},
    {LTR212_ERR_CANT_SET_BRIDGE_CONNECTIONS, "Ошибка установки мостовых подключений каналов"},
    {LTR212_ERR_QB_RESISTORS_IN_ALL_CHANNELS_MUST_BE_EQUAL, "Четверть-мостовые резисторы должны быть одинаковы для всех каналов АЦП"},
    {LTR212_ERR_INVALID_VCH_CNT, "Неверное количество логических каналов"},
    {LTR212_ERR_FILTER_FILE_OPEN, "Невозможно открыть файл с параметрами фильтра"},
    {LTR212_ERR_FILTER_FILE_READ, "Ошибка чтения файла с параметрами фильра"},
    {LTR212_ERR_FILTER_FILE_FORMAT, "Неверный формат файла с параметрами фильтра"},
    {LTR212_ERR_FILTER_ORDER, "Неверный порядок фильтра"},
    {LTR212_ERR_UNSUPPORTED_MODULE_TYPE, "Неподдерживаемый тип модуля"},
    {LTR212_ERR_CANT_USE_FILTER, "Невозможно использовать фильтры в заданном режиме сбора данных"},
    {LTR212_ERR_CANT_USE_CHNUM, "Невозможно использовать в данном режиме каналы с номером выше 4"},
    {LTR212_ERR_INVALID_CHNUM,  "Неверно задан номер физического канала"},
    {LTR212_ERR_INVALID_SCALE,  "Неверно задан диапазон канала"},
    {LTR212_ERR_INVALID_FIR_DECIMATION, "Неверное значение децимации для КИХ-фильтра"}
};

LTR212API_DllExport (LPCSTR) LTR212_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 INT f_set_adc_calibr(TLTR212 *hnd) {
    INT chop[3] = {0, 1, 1};
    INT skip[3] = {0, 0, 0};
    INT fast[3] = {0, 0, 0};
    INT fdivider[3] = {2048, 682, 682};
    INT err = f_check_lch_params(hnd);

    /* Эта функция вызывается только при тарировке и не должна использовать
     * заводские коэффициенты */
    if ((err == LTR_OK) && hnd->UseFabricClb) {
        err = LTR212_ERR_CANT_USE_FABRIC_AND_USER_CALIBR_SYM;
    }

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

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

    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(SET_REF, hnd->REF & 1);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    }

    if (err == LTR_OK) {
        err = f_set_filter_mode(hnd, chop, skip, fast, fdivider);
    }

    if (err==LTR_OK) {
        /* На время калибровки программные фильтры отключаются */
        DWORD cmd, ack;
        cmd = ltr_module_fill_cmd_parity(ENA_DIS_FILTERS, 0);
        err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    }
    return err;
}




/*******************************************************************************
        Функция быстрой загрузки калибровочных коэффициентов.
        Считывает коэффициенты из EEPROM и записывает в регистры АЦП
        (шкала, смещение, значение ЦАП)
*******************************************************************************/
LTR212API_DllExport(INT) LTR212_Load_Clb(TLTR212 *hnd) {
    BYTE data[EEPROM_USER_CBR_SIZE];
    INT err = LTR_OK;
    DWORD *cmds = NULL, *acks=NULL;
    INT cmd_cnt = hnd->LChQnt*(1+WR_REG16_CMDS_CNT+2*WR_REG24_CMDS_CNT+1);
    INT ack_cnt = hnd->LChQnt*(1+WR_REG16_ACK_CNT+2*WR_REG24_ACK_CNT+1);

    cmds = malloc(sizeof(cmds[0])*cmd_cnt);
    acks = malloc(sizeof(acks[0])*ack_cnt);

    if ((cmds == NULL) || (acks == NULL)) {
        err = LTR_ERROR_MEMORY_ALLOC;
    }

    if (err == LTR_OK) {
        err = f_read_eeprom(hnd, sizeof(data), 0, data);
    }

    if (err == LTR_OK) {
        INT i, snd_pos;
        for(i = 0, snd_pos = 0; i < hnd->LChQnt; ++i)  {
            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
            DWORD offs = ((DWORD)data[7*phy_ch + 2] << 16) |
                            ((DWORD)data[7*phy_ch + 1] << 8) |
                            data[7*phy_ch];
            DWORD gain = ((DWORD)data[7*phy_ch + 5] << 16) |
                    ((DWORD)data[7*phy_ch + 4] << 8) |
                    data[7*phy_ch + 3];

            cmds[snd_pos++] = ltr_module_fill_cmd_parity(SEL_CODEC, PHYCH_CODEC(phy_ch));

            f_fill_write_reg_16bit(&cmds[snd_pos], MODE_REG, PHYCH_MODE_REG_VAL(phy_ch));
            snd_pos += WR_REG16_CMDS_CNT;

            /* Теперь надо записать требуемые калибровочные коэффициенты в регистры */
            f_fill_write_reg_24bit(&cmds[snd_pos], OFFSET_REG, offs);
            snd_pos += WR_REG24_CMDS_CNT;

            f_fill_write_reg_24bit(&cmds[snd_pos], GAIN_REG, gain);
            snd_pos += WR_REG24_CMDS_CNT;


            cmds[snd_pos++] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LOAD_DAC, phy_ch, data[7*phy_ch + 6]);
        } // к for(i...)


        err = f_send_cmds_with_resp(hnd, cmds, acks, cmd_cnt, ack_cnt);
    }
    free(cmds);
    free(acks);
    return err;
}

static INT f_get_fabric_cbr_addr(TLTR212 *hnd, DWORD *base_addr) {
    /* адрес с коэффициентами зависит от выбранного диапазона */
    INT err = LTR_OK;
    if (hnd->REF == LTR212_REF_5V) {
        *base_addr = EEPROM_ADDR_INTCBR_5V;
    } else if (hnd->ModuleInfo.Type != LTR212_OLD) {
        *base_addr = EEPROM_ADDR_INTCBR_2_5V;
    } else {
        err = LTR212_ERR_CANT_USE_CALIBR_MODE;
    }

    return err;
}

/*******************************************************************************
        Функция быстрой загрузки заводских калибровочных коэффициентов
*******************************************************************************/
static INT f_load_fabric_cbr_coeffs(TLTR212 *hnd) {
    BYTE data[LTR212_EEPROM_INTCBR_SIZE];
    INT err = LTR_OK;

    DWORD base_addr;

    DWORD *cmds = NULL, *acks=NULL;
    INT cmd_cnt = hnd->LChQnt*(1+WR_REG16_CMDS_CNT+2*WR_REG24_CMDS_CNT);
    INT ack_cnt = hnd->LChQnt*(1+WR_REG16_ACK_CNT+2*WR_REG24_ACK_CNT);

    err = f_get_fabric_cbr_addr(hnd, &base_addr);

    if (err == LTR_OK) {
        err = f_read_eeprom(hnd, sizeof(data), base_addr, data);
    }

    if (err==LTR_OK) {
        cmds = malloc(sizeof(cmds[0]) * cmd_cnt);
        acks = malloc(sizeof(acks[0]) * ack_cnt);

        if ((cmds == NULL) || (acks == NULL))
            err = LTR_ERROR_MEMORY_ALLOC;
    }

    if (err == LTR_OK) {
        INT i, snd_pos;
        for (i = 0, snd_pos = 0; i < hnd->LChQnt; ++i) {
            INT phy_ch = LCH_PHYCH(hnd->LChTbl[i]);
            INT scale  = LCH_SCALE(hnd->LChTbl[i]);
            DWORD offs, gain;
            WORD cbr_gain_addr = INTCBR_ADDR_GAIN(scale, phy_ch);

            /* В AC-режиме смещение не корректируем */
            if (hnd->AC) {
                offs = 0x800000;
            } else {
                WORD cbr_ofs_addr = INTCBR_ADDR_OFFSET(scale, phy_ch);
                offs = ((DWORD)data[cbr_ofs_addr + 2] << 16) |
                        ((DWORD)data[cbr_ofs_addr + 1] << 8) |
                        data[cbr_ofs_addr];
            }

            gain = ((DWORD)data[cbr_gain_addr + 2] << 16) |
                   ((DWORD)data[cbr_gain_addr + 1] << 8) |
                   data[cbr_gain_addr];



            cmds[snd_pos++] = ltr_module_fill_cmd_parity(SEL_CODEC, PHYCH_CODEC(phy_ch));

            f_fill_write_reg_16bit(&cmds[snd_pos], MODE_REG, PHYCH_MODE_REG_VAL(phy_ch));
            snd_pos += WR_REG16_CMDS_CNT;

            f_fill_write_reg_24bit(&cmds[snd_pos], OFFSET_REG, offs);
            snd_pos += WR_REG24_CMDS_CNT;

            f_fill_write_reg_24bit(&cmds[snd_pos], GAIN_REG, gain);
            snd_pos += WR_REG24_CMDS_CNT;
        }

        err = f_send_cmds_with_resp(hnd, cmds, acks, cmd_cnt, ack_cnt);
    }

    free(cmds);
    free(acks);

    return err;
}


/*******************************************************************************
        Функция получения версии БИОСа
*******************************************************************************/
static INT f_read_firm_ver(TLTR212 *hnd, CHAR *version) {
    INT err = LTR_OK;
    DWORD cmd, ack;
    cmd = ltr_module_fill_cmd_parity(GET_SW_VER, 0);
    err = f_send_cmds_with_resp(hnd, &cmd, &ack, 1, 1);
    if (err == LTR_OK) {
        sprintf(version, "%d.%d", (ack >> 24) & 0xFF, (ack >> 16) & 0xFF);
    }

    return err;
} 


/*******************************************************************************
        Функция получения даты создания БИОСа
*******************************************************************************/     
static INT f_read_bios_date(TLTR212 *hnd, CHAR *date) {
    INT err = LTR_OK;
    DWORD cmd, ack[2];
    cmd = ltr_module_fill_cmd_parity(GET_SW_DATE, 0);
    err = f_send_cmds_with_resp(hnd, &cmd, ack, 1, 2);
    if (err == LTR_OK) {
        BYTE Month, Day;
        WORD Year;

        Year =  (ack[0] >> 16) & 0xFFFF;
        Day =   (ack[1] >> 16) & 0xFF;
        Month = (ack[1] >> 24) & 0xFF;

        sprintf(date, "%d.%d.%d",Day, Month, Year);
    }
    return err;
} 

/*******************************************************************************
    Функция чтения имени модуля
*******************************************************************************/
static INT f_read_module_name(TLTR212 *hnd, CHAR* ModuleName) {
    return f_read_eeprom(hnd, LTR212_EEPROM_NAME_SIZE,
                         EEPROM_ADDR_NAME, (BYTE*) ModuleName);
}


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

Вспомогательная функция расчета таймаута при получении данных от модуля

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

LTR212API_DllExport (DWORD) LTR212_CalcTimeOut(TLTR212 *hnd, DWORD n) {

    // Параметр n- количество точек НА КАНАЛ!
    double to;
    const double Fs[3] = {7680, 150.1, 3.4};
    int AcqMode = hnd->AcqMode;
    int div1;


    if (hnd->filter.FIR) {
        div1 = hnd->filter.Decimation;
    } else {
        div1 = 1;
    }

    to = (int) (((1000 * div1 * n) / (Fs[AcqMode])) + LTR_MODULE_CMD_RECV_TIMEOUT);

    return((DWORD)to);
}


LTR212API_DllExport (INT) LTR212_ReadUSR_Clb(TLTR212 *hnd, TLTR212_USER_TARE_COEFS *cbr) {
    BYTE data[EEPROM_USER_CBR_SIZE]; // буфер для храниния данных считанных из EEPROM
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                            cbr == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (err == LTR_OK)
        err = f_read_eeprom(hnd, sizeof(data), EEPROM_ADDR_USER_CBR, data);

    if (err == LTR_OK) {
        BYTE i;
        for (i = 0; i < LTR212_PHY_CH_CNT; ++i) {
            cbr->Offset[i] = 0;
            cbr->Scale[i] = 0;

            cbr->Offset[i]  = data[i*7];
            cbr->Offset[i] |= (DWORD)data[i*7 + 1] << 8;
            cbr->Offset[i] |= (DWORD)data[i*7 + 2] << 16;

            cbr->Scale[i]  = data[i*7+3];
            cbr->Scale[i] |= (DWORD)data[i*7 + 4] << 8;
            cbr->Scale[i] |= (DWORD)data[i*7 + 5] << 16;

            cbr->DAC_Value[i] = data[i*7 + 6];
        }
    }
    return err;
}


LTR212API_DllExport (INT) LTR212_WriteUSR_Clb(TLTR212 *hnd, const TLTR212_USER_TARE_COEFS *cbr) {
    BYTE data[EEPROM_USER_CBR_SIZE]; // буфер для храниния данных считанных из EEPROM
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                            cbr == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        /*читаем область пользовательских коэффициэнтов */
        err = f_read_eeprom(hnd, sizeof(data), EEPROM_ADDR_USER_CBR, data);
    }
    if (err == LTR_OK) {
        BYTE i;
        //Переписываем байты коэффициэнтов
        for (i =0; i < LTR212_PHY_CH_CNT;i++) {
            data[i*7]   = (cbr->Offset[i])         & 0xFF;
            data[i*7+1] = (cbr->Offset[i] >> 8)    & 0xFF;
            data[i*7+2] = (cbr->Offset[i] >> 16)   & 0xFF;

            data[i*7+3] = (cbr->Scale[i])       & 0xFF;
            data[i*7+4] = (cbr->Scale[i] >> 8)  & 0xFF;
            data[i*7+5] = (cbr->Scale[i] >> 16) & 0xFF;

            /* Это скорее к сдвигу относиться, хоть и идёт отдельным значением */
            data[i*7+6]= cbr->DAC_Value[i];
        }
        //Записываем обратно 56 байт пользовательской каллибровки
        err=f_write_eeprom(hnd, sizeof(data), EEPROM_ADDR_USER_CBR, data);
    }

    if (err == LTR_OK) {
        WORD crc = 0;
        err = f_evaluate_flash_crc(hnd, &crc);
        if (err == LTR_OK) {
            err = f_write_flash_crc(hnd, crc);
        }
    }
    return err;
}


LTR212API_DllExport (INT) LTR212_ReadCodec_Clb(TLTR212 *hnd, DWORD ch_mask, TLTR212_USER_TARE_COEFS *cbr) {
    INT err = LTR212_IsOpened(hnd);
    if (err == LTR_OK) {
        INT phy_ch;
        for (phy_ch = 0; (phy_ch < LTR212_CHANNEL_CNT) && (err == LTR_OK); ++phy_ch) {
            if ((1 << phy_ch) & ch_mask) {
                DWORD offset_reg_val, gain_reg_val;
                BYTE dac_reg_val;

                err = f_select_ch(hnd, phy_ch);
                if (err == LTR_OK)
                    err = f_read_reg_24bit(hnd, OFFSET_REG, &offset_reg_val);
                if (err == LTR_OK)
                    err = f_read_reg_24bit(hnd, GAIN_REG, &gain_reg_val);
                if (err == LTR_OK)
                    err = f_read_reg_8bit(hnd, DAC_REG, &dac_reg_val);

                if (err == LTR_OK) {
                    cbr->DAC_Value[phy_ch] = dac_reg_val;
                    cbr->Offset[phy_ch] = offset_reg_val;
                    cbr->Scale[phy_ch] = gain_reg_val;
                }
            }
        }
    }
    return err;
}


LTR212API_DllExport (INT) LTR212_ReadEeprom(TLTR212 *hnd, WORD addr, BYTE *buf, WORD size) {
    return f_read_eeprom(hnd, size, addr, buf);
}


LTR212API_DllExport (INT) LTR212_WriteEeprom(TLTR212 *hnd, WORD addr, const BYTE *buf, WORD size) {
    return f_write_eeprom(hnd, size, addr, buf);
}
