#include "lwintypes.h"
#include "ltr35api.h"
#include "ltrmodule.h"
#include "ltrmodule_cyclone.h"
#include "ltimer.h"
#include "pcm4104.h"
#include "adf4360.h"
#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "flash.h"
#include "devices/flash_dev_at45db.h"
#include "ports/ltr/flash_iface_ltr.h"

#include "ltrmodule_fpga_autoload.h"
#include "lbitfield.h"

#include "crc.h"



#ifndef M_PI
    #define M_PI 3.1415926535897932384626433832795
#endif

/* коды команд, передаваемых в ПЛИС */
#define CMD_EN_FPGA                     (LTR010CMD_INSTR | 0x22)
#define CMD_PAGES_SW                    (LTR010CMD_INSTR | 0x01)
#define CMD_SPI_DAC1                    (LTR010CMD_INSTR | 0x02)
#define CMD_SPI_DAC2                    (LTR010CMD_INSTR | 0x03)
#define CMD_SYNT_SPI                    (LTR010CMD_INSTR | 0x04)
#define CMD_COEF_ADDR                   (LTR010CMD_INSTR | 0x05)
#define CMD_COEF_DATA                   (LTR010CMD_INSTR | 0x06)

#define CMD_MAIN_CTL                    (LTR010CMD_INSTR | 0x08)
#define CMD_OUT_CTL                     (LTR010CMD_INSTR | 0x09)
#define CMD_GET_STATUS                  (LTR010CMD_INSTR | 0x0A)
#define CMD_SET_STREAM_STATUS_PERIOD    (LTR010CMD_INSTR | 0x0B)
#define CMD_SET_DATAIN                  (LTR010CMD_INSTR | 0x0C)
#define CMD_SET_SYNCMODE                (LTR010CMD_INSTR | 0x0D)

#define CMD_RESP_STREAM_STATUS          (LTR010CMD_INSTR | 0x00)
#define CMD_FPGA_HOLD_SPI               (LTR010CMD       | 0x70)

#define COEF_ADDR_CBR_B                 0x00
#define COEF_ADDR_CBR_K                 0x40
#define COEF_ADDR_ARITH_PHASE           0x80
#define COEF_ADDR_ARITH_DELTA           0x90
#define COEF_ADDR_CBR_AFC               0xA0


/* CMD_MAIN_CTL */
#define CMD_BIT_MAIN_CTL_RATE_QUAD      (0x1UL<<0)
#define CMD_BIT_MAIN_CTL_FCLK_SRC       (0x1UL<<1)
#define CMD_BIT_MAIN_CTL_RATE_SINGLE    (0x1UL<<2)
#define CMD_BIT_MAIN_CTL_FORMAT         (0x1UL<<8)
#define CMD_BIT_MAIN_CTL_MODE           (0x1UL<<9)

/* CMD_PAGES_SW */
#define CMD_BIT_SW_PAGE                 (0x1UL << 8)
#define CMD_BIT_SW_GO                   (0x1UL << 9)
#define CMD_BIT_SW_BUF_CLEAR            (0x1UL << 10)
#define CMD_BIT_SW_HOLD_ARITH           (0x1UL << 11)
#define CMD_BIT_SW_RESPONSE             (0x1UL << 15)
#define CMD_BIT_SW_SLAVE_EXT_RESP       (0x1UL << 14) /* запрос двойного ответа для ведомого
                                                         (на готовность приема внешнего сигнала и на
                                                          выполнение запуска по внешнему сигналу) */

/* CMD_SET_DATAIN */
#define CMD_MSK_DATAIN_CH_NUM           (0xFUL << 0)
#define CMD_MSK_DATAIN_MODE             (0x7UL << 4)
#define CMD_MSK_DATAIN_STAT_DIS         (0x1UL << 7)
#define CMD_MSK_DATAIN_DIV              (0xFUL << 8)

/* CMD_SET_SYNCMODE */
#define CMD_MSK_SYNCMODE_MODE          (0x3 << 0)
#define CMD_MSK_SYNCMODE_SLAVE_POL     (0x1 << 8)


#define CMD_DATAIN_MODE_OFF     0
#define CMD_DATAIN_MODE_CH_ECHO 1
#define CMD_DATAIN_MODE_DI1     2
#define CMD_DATAIN_MODE_DI2     3
#define CMD_DATAIN_MODE_DI1_2   4


#define LTR35_FPGA_SUPPORT_SLAVE_EXT_RESP(hnd) ((hnd)->ModuleInfo.VerFPGA >= 25)



/** Номер канала, соответствующий выводу на цифровые линии (если считать от 0) */
#define LTR35_CH_NUM_DIGOUTS        8

#define ARITH_PHASE_CODE_MAX            0xFFFFFFFF

#define LTR35_ARITH_SRC(src)  ( \
    ((src) == LTR35_CH_SRC_COS1) || \
    ((src) == LTR35_CH_SRC_SIN1) || \
    ((src) == LTR35_CH_SRC_COS2) || \
    ((src) == LTR35_CH_SRC_SIN2) || \
    ((src) == LTR35_CH_SRC_COS3) || \
    ((src) == LTR35_CH_SRC_SIN3) || \
    ((src) == LTR35_CH_SRC_COS4) || \
    ((src) == LTR35_CH_SRC_SIN4))




#define LTR35_DAC_CNT        2
#define LTR35_CH_PER_DAC     4

#define LTR35_WAIT_SYNT_LOCK_TOUT 2000


#define SYNT_IN_FREQ         30000000.
#define SYNT_FREQ(a, b, r)   ((SYNT_IN_FREQ*b/r)/(a*2))



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


static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR35_ERR_INVALID_SYNT_FREQ,           "Задана неподдерживаемая частота синтезатора"},
    {LTR35_ERR_PLL_NOT_LOCKED,              "Ошибка захвата PLL"},
    {LTR35_ERR_INVALID_CH_SOURCE,           "Задано неверное значение источника данных для канала"},
    {LTR35_ERR_INVALID_CH_RANGE,            "Задано неверное значение диапазона канала"},
    {LTR35_ERR_INVALID_DATA_FORMAT,         "Задано неверное значение формата данных"},
    {LTR35_ERR_INVALID_OUT_MODE,            "Задано неверное значение режима вывода"},
    {LTR35_ERR_INVALID_DAC_RATE,            "Задано неверное значение скорости выдачи на ЦАП"},
    {LTR35_ERR_INVALID_SYNT_CNTRS,          "Задано недопустимое значение счетчиков синтезатора"},
    {LTR35_ERR_UNSUPPORTED_CONFIG,          "Заданная конфигурация ЦАП не поддерживается"},
    {LTR35_ERR_INVALID_STREAM_STATUS_PERIOD,"Задано неверное количество отсчетов для выдачи статуса в потоковом режиме"},
    {LTR35_ERR_DAC_CH_NOT_PRESENT,          "Выбранный канал ЦАП отсутствует в данном модуле"},
    {LTR35_ERR_DAC_NO_SDRAM_CH_ENABLED,     "Не разрешен ни один канал ЦАП на вывод из SDRAM"},
    {LTR35_ERR_DAC_DATA_NOT_ALIGNED,        "Данные ЦАП не выравнены на кол-во разрешенных каналов"},
    {LTR35_ERR_NO_DATA_LOADED,              "Не было загружено ни одного отсчета для циклического вывода"},
    {LTR35_ERR_LTRD_UNSUP_STREAM_MODE,      "Данная версия службы ltrd не поддерживает контроль переполнения буфера в потоковом режиме"},
    {LTR35_ERR_MODE_UNSUP_FUNC,             "Данная функция не поддерживается в установленном режиме"},
    {LTR35_ERR_INVALID_ARITH_GEN_NUM,       "Задано неверное значение номера арифметического генератора"},
    {LTR35_ERR_INVALID_IN_STREAM_MODE,      "Задан неверный режим потокового ввода"},
    {LTR35_ERR_INVALID_IN_DI_FREQ_DIV_POW,  "Задано неверное значение степени делителя частоты синхронного ввода с цифровых линий"},
    {LTR35_ERR_IN_DI_NO_CHANNELS,           "Не разрешен ни один вход для синхронного ввода с цифровых линий"},
    {LTR35_ERR_INVALID_IN_DI_CH_LIST,       "Задан неверный список каналов для синхронного ввода с цифровых линий"},
    {LTR35_ERR_INVALID_SYNC_MODE,           "Задано неверное значение режима синхронизации"},
    {LTR35_ERR_INVALID_SYNC_SLAVE_SRC,      "Задано неверное значение источника синхронизации для ведомого модуля"},
    {LTR35_ERR_SYNC_MODE_NOT_SET,           "Не удалось установить заданный режим синхронизации"},
    {LTR35_ERR_NO_START_REQUESTED,          "Не был выполнен запрос старта, завершение которого бы следовало ожидать"},
    {LTR35_ERR_DAC_REG_VALUE_CHECK,         "Ошибка контроля записи значений в регистры ЦАП"}
};

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


#define LTR35_FLASH_INFO_SIGN          0xA55AC035
#define LTR35_FLASH_INFO_FORMAT        0x1
#define LTR35_FLASH_INFO_CRC_SIZE      2

#define LTR35_FLASH_INFO_MIN_SIZE      (offsetof(t_ltr35_flash_info, cbr_affc_coef))

/* информация в том формате, в котором она хранится во Flash-памяти модуля */
typedef struct {
    DWORD sign;
    DWORD size;
    DWORD format;
    DWORD flags;
    char  name[LTR35_NAME_SIZE];
    char  serial[LTR35_SERIAL_SIZE];
    TLTR35_CBR_COEF cbr[LTR35_DAC_CHANNEL_CNT][LTR35_DAC_CH_OUTPUT_CNT];
    TLTR35_AFC_COEF cbr_affc_coef;
} t_ltr35_flash_info;


/* информация по модификациям модулей */
static struct {
    BYTE                    Modification;
    BYTE                    DoutLineCnt;
    TLTR35_DAC_OUT_DESCR    DacOutDescr[LTR35_DAC_CH_OUTPUT_CNT];
} f_mod_info[] = {
    {LTR35_MOD_1,        8, {{10.,  -10., LTR35_DAC_SCALE_CODE_MAX, -LTR35_DAC_SCALE_CODE_MAX, {0,0,0}},
                             {1.,  -1., LTR35_DAC_SCALE_CODE_MAX, -LTR35_DAC_SCALE_CODE_MAX, {0,0,0}}}}, //LTR35_MOD_1
    {LTR35_MOD_2,        8, {{20.,  0, LTR35_DAC_SCALE_CODE_MAX, 0, {0,0,0}},
                             {0,    0, 0, 0, {0,0,0}}}}, //LTR35_MOD_2
    {LTR35_MOD_3,       16, {{ 0.,  0., 0, 0, {0,0,0}}, {0, 0, 0, 0, {0,0,0}}}},//LTR35_MOD_3
    {LTR35_MOD_UNKNOWN,  0, {{ 0.,  0., 0, 0, {0,0,0}}, {0, 0, 0, 0, {0,0,0}}}}
};




typedef struct {
    t_flash_iface flash;
    DWORD send_wrds;
    BYTE synt_configured;
    BYTE DacRate;
    TLTR35_FREQ_SYNT_CONFIG SyntCfg;
} t_internal_params;




static unsigned f_dac_rate_div[] = {768, 384, 192};



static t_code_tbl  f_ch_sources[] = {
    { LTR35_CH_SRC_SDRAM, 1},
    { LTR35_CH_SRC_SIN1,  8},
    { LTR35_CH_SRC_COS1,  9},
    { LTR35_CH_SRC_SIN2, 10},
    { LTR35_CH_SRC_COS2, 11},
    { LTR35_CH_SRC_SIN3, 12},
    { LTR35_CH_SRC_COS3, 13},
    { LTR35_CH_SRC_SIN4, 14},
    { LTR35_CH_SRC_COS4, 15}
};



#define FPGA_SECT_MSK0          AT45DB_PROT_SECT0_B
#define FPGA_SECT_MSK           AT45DB_PROT_SECTOR(1)

#define MODULEINFO_SECT_MSK0    AT45DB_PROT_SECT0_A
#define MODULEINFO_SECT_MSK     0


#define RESERVED_SECT_MSK0      0
#define RESERVED_SECT_MSK       AT45DB_PROT_SECTOR(1) | AT45DB_PROT_SECTOR(2)

#define MODULEINFO_FLASH_ADDR   0
#define MODULEINFO_FLASH_SIZE   2048




#define DEF_PORT_SECT_MSK0     (MODULEINFO_SECT_MSK0 | FPGA_SECT_MSK0 | RESERVED_SECT_MSK0)
#define DEF_PORT_SECT_MSK      (MODULEINFO_SECT_MSK  | FPGA_SECT_MSK  | RESERVED_SECT_MSK)


#define LTR35_CALC_ARITH_DELTA_CODE(delta) (DWORD)((double)delta*ARITH_PHASE_CODE_MAX/360)


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


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

static INT f_dac_register_set(TLTR35 *hnd, BYTE dac, BYTE reg, BYTE value);
static INT f_dac_register_get(TLTR35 *hnd, INT dac, INT reg, BYTE* value);
static INT f_dac_register_set_checked(TLTR35 *hnd, BYTE dac, BYTE reg, BYTE value);

static BYTE f_calc_din_ch_cnt(TLTR35_CONFIG *cfg) {
    BYTE ch_cnt = 0;
    const BYTE ch_msk = cfg->InStream.DIChEnMask;
    if (ch_msk & LTR35_IN_STREAM_DI1)
        ++ch_cnt;
    if (ch_msk & LTR35_IN_STREAM_DI2)
        ++ch_cnt;
    return ch_cnt;
}

static double f_calc_din_acq_freq(double synt_freq, BYTE div_pow) {
    return (synt_freq / LTR35_DIN_SYNT_FREQ_DIV) / pow(2, div_pow);
}

static void f_prepare_wrd(TLTR35 *hnd, unsigned ch, DWORD data, DWORD *res, DWORD *upd_size) {
    DWORD put_size = 0;

    if (hnd->Cfg.OutDataFmt == LTR35_OUTDATA_FORMAT_24) {
        res[put_size++] = (data & 0x00FF0000) | (ch << 4);
        res[put_size++] = ((data & 0x0000FFFF) << 16) | 1;
    } else {
        if (ch != LTR35_CH_NUM_DIGOUTS) {
            data >>= 4; /* оставляем только старшие 20 бит для 20-битного формате.
                           цифровые линии - без изменений */
        }
        res[put_size++] = (ch << 4) | ((data >> 16) & 0xF)  | ((data & 0x0000FFFF) << 16);
    }

    if (upd_size != NULL)
        *upd_size += put_size;
}



static INT f_synt_send_word(TLTR *hnd, DWORD data) {
    DWORD cmds[3], acks[3];
    INT res;

    cmds[0] = LTR_MODULE_MAKE_CMD_BYTES(CMD_SYNT_SPI, (data >> 16)&0xFF, 0);
    cmds[1] = LTR_MODULE_MAKE_CMD_BYTES(CMD_SYNT_SPI, (data >> 8)&0xFF, 0);
    cmds[2] = LTR_MODULE_MAKE_CMD_BYTES(CMD_SYNT_SPI, (data & 0xFF), 1);
    res = ltr_module_send_with_echo_resps(hnd, cmds, 3, acks);
    return res;
}



static INT f_synt_set_cntrs(TLTR *hnd, BYTE a, WORD b, WORD r) {
    INT res = LTR_OK;

    /* установка R */
    res = f_synt_send_word(hnd, ADF4360_LATCH_RCOUNTER | ADF4360_LATCHR_BSC_8 | ADF4360_LATCHR_ABP_3_0
        | (((DWORD)r << ADF4360_LATCHR_R_Pos) & ADF4360_LATCHR_R_Msk));

    if (res==LTR_OK) {
        /* установка Control */
        res = f_synt_send_word(hnd, ADF4360_LATCH_CONTROL | ADF4360_LATCHC_PD_NORMAL_OP
             | (7 << ADF4360_LATCHC_CPI1_Pos) | (7 << ADF4360_LATCHC_CPI2_Pos) | ADF4360_LATCHC_PL_3_5mA
             | ADF4360_LATCHC_PDP_Msk | ADF4360_LATCHC_DIVOUT_A2 | ADF4360_LATCHC_PC_5_0mA
             );
    }

    if (res==LTR_OK) {
        LTRAPI_SLEEP_MS(20);

        res = f_synt_send_word(hnd, ADF4360_LATCH_NCOUNTER
            | (((DWORD)a << ADF4360_LATCHN_A_Pos) & ADF4360_LATCHN_A_Msk)
            | (((DWORD)b << ADF4360_LATCHN_B_Pos) & ADF4360_LATCHN_B_Msk));
    }
    return res;
}

static INT f_wait_pll(TLTR35 *hnd) {
    INT err = LTR_OK;
    t_ltimer tmr;
    DWORD status;

    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR35_WAIT_SYNT_LOCK_TOUT));

    do {
        err = LTR35_GetStatus(hnd, &status);
    } while ((err==LTR_OK) && !(status & LTR35_STATUS_FLAG_PLL_LOCK) && !ltimer_expired(&tmr));

    if (!(status & LTR35_STATUS_FLAG_PLL_LOCK)) {
        err = LTR35_ERR_PLL_NOT_LOCKED;
    }
    return err;
}

static BYTE f_dac_get_sysctl(e_LTR35_RATE rate) {
    return  rate == LTR35_DAC_RATE_QUAD ? PCM4104_SCR_FS_QUAD_RATE :
            (rate == LTR35_DAC_RATE_DOUBLE ?  PCM4104_SCR_FS_DOUBLE_RATE :
                                             PCM4104_SCR_FS_SINGLE_RATE);
}

static INT f_cfg_dac(TLTR35 *hnd) {
    INT err = LTR_OK;
    BYTE dac_num;
    /* Сброс ЦАП и настройка режимов */
    for (dac_num = 0; (err == LTR_OK) && (dac_num < hnd->ModuleInfo.DacChCnt / LTR35_CH_PER_DAC); ++dac_num) {
        BYTE sysctl = f_dac_get_sysctl(hnd->Cfg.DacRate);
        err = f_dac_register_set_checked(hnd, dac_num, PCM4104_REGADDR_SCR, sysctl);
        if (err == LTR_OK) {
            BYTE fcr = 0;
            BYTE dac_ch;
            for (dac_ch = 0; dac_ch < LTR35_CH_PER_DAC; ++dac_ch) {
                BYTE ch = LTR35_CH_PER_DAC * dac_num + dac_ch;
                if (!hnd->Cfg.Ch[ch].Enabled) {
                    fcr |= (1UL << (PCM4104_FCR_MUT1_Pos + dac_ch));
                }
            }
            err = f_dac_register_set_checked(hnd, dac_num, PCM4104_REGADDR_FCR, fcr);
        }
        if (err==LTR_OK)
            err = f_dac_register_set_checked(hnd, dac_num, PCM4104_REGADDR_ASPC, PCM4104_ASPC_FMT_TDM);
    }

    if (err==LTR_OK) {
        t_internal_params *params = (t_internal_params *)hnd->Internal;
        params->DacRate = hnd->Cfg.DacRate;
    }
    return err;
}

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

    err = f_synt_set_cntrs(&hnd->Channel, hnd->Cfg.FreqSynt.a, hnd->Cfg.FreqSynt.b, hnd->Cfg.FreqSynt.r);
    if (err == LTR_OK) {
        err = f_wait_pll(hnd);
    }
    if (err == LTR_OK) {
        params->synt_configured = TRUE;
        params->SyntCfg = hnd->Cfg.FreqSynt;
    } else {
        params->synt_configured = FALSE;
    }
    return err;
}

static INT f_check_open_and_stopped(TLTR35 *hnd) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && (hnd->State.RunState != LTR35_RUNSTATE_STOPPED)) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    return err;
}


/************************************************************************************
   Поиск значений счетчиков синтезатора для генерации нужной частоты
   Параметры :
       freq    -  (in) требуемая частота в Герцах
       quartz  -  (in) входная частота для синтезатора
       b_counter, r_counter, a_counter - (out) найденные значения счетчиков
       set_freq    (out) реально установленная частота (ближайшая возможная к freq)
   Возвращаемое значение:
       код ошибки
 *************************************************************************************/
static INT f_synt_find_freq(double freq, BYTE *a_counter, WORD *b_counter,
                            WORD *r_counter, double *set_freq) {

    double f0_min, f0_max, f, quartz_mhz, freq_temp, freq_error, error, freq_found=0, freq_mhz;
    int a, b, r, a_base = 0, b_base = 0, r_base = 0;
    INT res = LTR_OK;
    double div;
    double quartz = SYNT_IN_FREQ;

    f0_min = 297;
    f0_max = 378;

    quartz_mhz = quartz / 1000000.;
    freq_mhz = freq / 1e6;
    freq_error = -1;

    div = quartz/ (4 * freq);

    /** @todo посмотреть границы */
    for (b = 3; b < 8192; ++b) {
        int cnt;

        for (a = 2; a < 32; ++a)  {
            r = (int)(div * 2 * b / a);

            for (cnt = 0; cnt < 2; ++cnt) {
                if ((r < 100) || (r >  16383))
                    continue;

                if (((quartz_mhz)/r) > 1)
                    continue;

                f = (quartz_mhz*b)/r;
                if(f < f0_min)
                    continue;

                if(f > f0_max)
                    continue;


                freq_temp = f/(a*2);
                error = ABS(freq_mhz - freq_temp);

                if((freq_error < 0) || (error < freq_error)) {
                    freq_error = error;
                    a_base = a;
                    b_base = b;
                    r_base = r;
                    freq_found = freq_temp;
                }

                ++r;
            }
        }
    }

    if(freq_error < 0) {
        res =  LTR35_ERR_INVALID_SYNT_FREQ;
    } else {
        if (b_counter != NULL)
            *b_counter=b_base & 0xFFFF;
        if (r_counter != NULL)
            *r_counter=r_base & 0xFFFF;
        if (a_counter != NULL)
            *a_counter=a_base & 0xFF;
        if (set_freq != NULL)
            *set_freq = freq_found*1000000.;
    }
    return res;
}






static INT f_dac_register_set(TLTR35 *hnd, BYTE dac, BYTE reg, BYTE value) {
    INT err = LTR_OK;
    static WORD dac_cmds[] = {CMD_SPI_DAC1, CMD_SPI_DAC2};


    if (dac >= (INT)(sizeof(dac_cmds)/sizeof(dac_cmds[0]))) {
        err = LTR_ERROR_PARAMETERS;
    } else {
        DWORD ack, cmd = LTR_MODULE_MAKE_CMD_BYTES(dac_cmds[dac], PCM4104_CTRL_WR_BYTE(reg), value);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
    }
    return err;
}


static INT f_dac_register_get(TLTR35 *hnd, INT dac, INT reg, BYTE* value) {
    INT err = LTR_OK;
    static WORD dac_cmds[] = {CMD_SPI_DAC1, CMD_SPI_DAC2};


    if (dac >= (INT)(sizeof(dac_cmds)/sizeof(dac_cmds[0]))) {
        err = LTR_ERROR_PARAMETERS;
    } else {        
        DWORD ack, cmd = LTR_MODULE_MAKE_CMD_BYTES(dac_cmds[dac], PCM4104_CTRL_RD_BYTE(reg), 0);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (value != NULL)
            *value = LTR_MODULE_CMD_GET_DATA(ack) & 0xFF;
    }
    return err;
}

static INT f_dac_register_set_checked(TLTR35 *hnd, BYTE dac, BYTE reg, BYTE value) {
    INT err = f_dac_register_set(hnd, dac, reg, value);
    if (err == LTR_OK) {
        BYTE rd_val;
        err = f_dac_register_get(hnd, dac, reg, &rd_val);
        if ((err == LTR_OK) && (value != rd_val)) {
            err = LTR35_ERR_DAC_REG_VALUE_CHECK;
        }
    }
    return err;
}


/* рассчет кодов коэффициентов для арифметических каналов для получения заданной
 * амплитуды и смещения */
static INT f_calc_arith_amp_offs(TLTR35 *hnd, BYTE ch, double ArithAmp,
                                  double ArithOffs, double *pscale, double *poffset) {
    INT err = LTR35_ARITH_SRC(hnd->Cfg.Ch[ch].Source) ? LTR_OK : LTR35_ERR_INVALID_CH_SOURCE;
    if (err == LTR_OK) {
        double orig_scale = (double)hnd->ModuleInfo.CbrCoef[ch][hnd->Cfg.Ch[ch].Output].Scale;
        double orig_offs  = (double)hnd->ModuleInfo.CbrCoef[ch][hnd->Cfg.Ch[ch].Output].Offset;
        double offs, scale;

        /* сложение идет до умножения => должны учитывать результирующий
         * коэф. шкалы */
        offs = (ArithOffs*LTR35_DAC_SCALE_CODE_MAX/hnd->ModuleInfo.DacOutDescr[hnd->Cfg.Ch[ch].Output].AmpMax * orig_scale + orig_offs); //


        /* В арифметическом режиме корректируем амплитуду с помощью
         *  коэффициента шкалы, а смещения с помощью коэф. нуля */
        double dk = ArithAmp/hnd->ModuleInfo.DacOutDescr[hnd->Cfg.Ch[ch].Output].AmpMax;
        dk *= ((double)LTR35_DAC_SCALE_CODE_MAX)/LTR35_DAC_CODE_MAX;
        scale = orig_scale * dk;

        if (pscale != NULL)
            *pscale = scale;
        if (poffset != NULL)
            *poffset = offs;
    }
    return err;
}


static void f_fill_coef_float_cmd(DWORD *cmds, DWORD *pput_pos, double scale) {
    DWORD scale_code = (DWORD)(scale *0x40000000);
    DWORD put_pos = *pput_pos;
    cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, scale_code&0xFFFF);
    cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, (scale_code>>16)&0xFFFF);
    *pput_pos = put_pos;
}

static void f_fill_offset_cmd(DWORD *cmds, DWORD *pput_pos, double offs) {
    INT offs_code = (INT)(offs >= 0 ? offs+0.5 : offs - 0.5);
    DWORD put_pos = *pput_pos;

    cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, offs_code&0xFFFF);
    cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, (offs_code>>16)&0xFFFF);
    *pput_pos = put_pos;
}

static INT f_get_fpga_status(TLTR35 *hnd, BYTE *status, BYTE *ver) {
    DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_GET_STATUS, 0);
    DWORD ack;
    INT err;

    err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);

    if (err == LTR_OK) {
        ack = LTR_MODULE_CMD_GET_DATA(ack);

        if (status != NULL)
            *status = ack & 0xFF;

        if (ver != NULL)
            *ver = (ack>>8) & 0xFF;
    }
    return err;
}

static INT f_flash_prot_bits_set(t_flash_iface *flash, DWORD msk0, DWORD msk, BOOL set) {
    unsigned sect0, sectors;
    INT err;

    err = flash_at45db_get_sector_protection(flash, &sect0, &sectors);
    if (err == LTR_OK) {
        DWORD chk_msk0 = set ? msk0 : 0;
        DWORD chk_msk  = set ? msk : 0;


        if (((sect0 & msk0) != chk_msk0) || ((sectors & msk) != chk_msk)) {

            if (set) {
                sect0 |= msk0;
                sectors |= msk;
            } else {
                sect0 &= ~msk0;
                sectors &= ~msk;
            }

            err = flash_at45db_set_sector_protection(flash, sect0, sectors);
            if (err == LTR_OK) {
                sect0 = sectors = 0;
                err = flash_at45db_get_sector_protection(flash, &sect0, &sectors);

                if ((err == LTR_OK) &&  (((sect0 & msk0) != chk_msk0)
                                         || ((sectors & msk) != chk_msk))) {
                    err = LTR_ERROR_FLASH_SET_PROTECTION;
                }
            }
        }
    }
    return err;
}

static INT f_clear_buf(TLTR35 *hnd, WORD data) {

    DWORD cmd;
    INT err;
    /* в 0-ой версии данная команда была без ответа */
    if (hnd->ModuleInfo.VerFPGA == 0) {
        cmd = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, data | CMD_BIT_SW_BUF_CLEAR);
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
    } else {
        DWORD resp;
        /* при сбросе буфера модуль высылает помимо заголовка еще и слово
         * статуса с нужными параметрами для ltrd,  которое обрабатывает внутри
         * себя ltrd. Если эта версия ltrd не поддерживает данной возможности,
         * то это статусное слово мы примем тут. Если мы работаем без потокового
         * режима или в потоковом режиме без контроля заполненности буфера по статусам,
         * то его можно игнорировать, в противном случае мы полагаемся на нереализованную возможность 
         * ltrd и поэтому возвращаем ошибку */

        cmd = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, data | CMD_BIT_SW_RESPONSE | CMD_BIT_SW_BUF_CLEAR);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &resp);

        if (err == LTR_ERROR_INVALID_CMD_RESPONSE) {
            if (LTR_MODULE_CMD_GET_CMDCODE(resp) == CMD_RESP_STREAM_STATUS) {
                int next_err = ltr_module_recv_cmd_resp(&hnd->Channel, &resp, 1);
                if (!next_err && (LTR_MODULE_CMD_GET_CMDCODE(cmd) == LTR_MODULE_CMD_GET_CMDCODE(resp))) {
                    if ((hnd->Cfg.OutMode == LTR35_OUT_MODE_STREAM) && !(hnd->Cfg.Flags & LTR35_CFG_FLAG_DISABLE_PERIODIC_STATUS)) {
                        err = LTR35_ERR_LTRD_UNSUP_STREAM_MODE;
                    } else {
                        err = LTR_OK;
                    }
                }
            }
        }
    }
    return err;
}

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

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

    /* Заполнение информации начальными значениями */
    for (ch = 0; ch < LTR35_DAC_CHANNEL_CNT; ++ch) {
        for (range = 0; range < LTR35_DAC_CH_OUTPUT_CNT; ++range)  {
            hnd->ModuleInfo.CbrCoef[ch][range].Offset = 0.;
            hnd->ModuleInfo.CbrCoef[ch][range].Scale  = 0.96F;
        }
    }

    hnd->ModuleInfo.CbrAfcCoef.Valid = TRUE;
    hnd->ModuleInfo.CbrAfcCoef.Fd = 192000;
    hnd->ModuleInfo.CbrAfcCoef.SigFreq = 10000;
    for (ch = 0; ch < LTR35_DAC_CHANNEL_CNT; ++ch) {
        hnd->ModuleInfo.CbrAfcCoef.K[ch] = 0.16;
    }
}

/*******************************************************************************
    Функция инициализации интерфейсного канала связи с модулем
*******************************************************************************/
LTR35API_DllExport(INT)  LTR35_Init(TLTR35 *hnd) {
    INT res = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if(res == LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));
        hnd->size = sizeof(TLTR35);

        res = LTR_Init(&hnd->Channel);
        if (res == LTR_OK) {
            unsigned ch;
            for (ch = 0; ch < LTR35_DAC_CHANNEL_CNT; ++ch) {
                hnd->Cfg.Ch[ch].Output = LTR35_DAC_OUT_FULL_RANGE;
                hnd->Cfg.Ch[ch].ArithAmp = 10.;
                hnd->Cfg.Ch[ch].Enabled = FALSE;
                hnd->Cfg.Ch[ch].Source = LTR35_CH_SRC_SDRAM;
            }
            LTR35_FillOutFreq(&hnd->Cfg, LTR35_DAC_QUAD_RATE_FREQ_STD, NULL);
            f_info_init(hnd);
        }
    }
    return res;
}

static INT f_read_flash_info(TLTR35 *hnd) {
    struct {
        DWORD sign;
        DWORD size;
        DWORD format;
    } hdr;

    INT err;
    t_internal_params * params = (t_internal_params*)hnd->Internal;

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

    if (err == LTR_OK) {
        /* вначале читаем только минимальный заголовок, чтобы определить
        сразу признак информации и узнать полный размер */
        err = flash_iface_ltr_conv_err(flash_at45db_read(&params->flash, MODULEINFO_FLASH_ADDR,
                                                     (BYTE*)&hdr, sizeof(hdr)));
    }

    if ((err == LTR_OK) && (hdr.sign != LTR35_FLASH_INFO_SIGN))
        err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
    if ((err == LTR_OK) && (hdr.format != LTR35_FLASH_INFO_FORMAT))
        err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
    if ((err == LTR_OK) && (hdr.size < LTR35_FLASH_INFO_MIN_SIZE))
        err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;

    if (err == LTR_OK) {
        t_ltr35_flash_info *pinfo = malloc(hdr.size + LTR35_FLASH_INFO_CRC_SIZE);
        if (pinfo == NULL)
            err = LTR_ERROR_MEMORY_ALLOC;

        /* читаем оставшуюся информацию */
        if (err == LTR_OK) {
            memcpy(pinfo, &hdr, sizeof(hdr));
            err = flash_iface_ltr_conv_err(flash_at45db_read(&params->flash, MODULEINFO_FLASH_ADDR+sizeof(hdr),
                                                             ((BYTE*)pinfo) + sizeof(hdr),
                                                             hdr.size + LTR35_FLASH_INFO_CRC_SIZE - sizeof(hdr)));
        }

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

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

            hnd->ModuleInfo.CbrFlags = 0;

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

            if (hdr.size >= (offsetof(t_ltr35_flash_info, cbr_affc_coef) + sizeof(TLTR35_AFC_COEF))) {
                if (pinfo->cbr_affc_coef.Valid) {
                    hnd->ModuleInfo.CbrAfcCoef = pinfo->cbr_affc_coef;
                    hnd->ModuleInfo.CbrFlags |= LTR35_CBR_FLAG_HAS_FLASH_AFC_CBR;
                }
            }
        }
        free(pinfo);
    }
    flash_iface_flush(&params->flash, err);

    return err;
}

/*******************************************************************************
    Функция открытия интерфейсного канала связи с модулем
*******************************************************************************/
LTR35API_DllExport (INT)  LTR35_Open(TLTR35 *hnd, DWORD ltrd_addr, WORD ltrd_port,
                                     const CHAR *crate_sn, WORD slot_num) {
    DWORD open_flags = 0;
    INT warning = LTR_OK;
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    INT fpga_err = LTR_OK;

    if (err == LTR_OK) {
        DWORD ack;
        if (LTR35_IsOpened(hnd)==LTR_OK) {
            LTR35_Close(hnd);
        }

        err = ltr_module_open(&hnd->Channel, ltrd_addr, ltrd_port, crate_sn, slot_num,
                              LTR_MID_LTR35, &open_flags, &ack, &warning);
        if (err == LTR_OK) {
            BYTE mod;
            unsigned range;

            f_info_init(hnd);

            /* выделяем версию CPLD из ответа на Reset */
            hnd->ModuleInfo.VerPLD = (ack >> 2) & 0xF;

            /* младшие два бита данных определяют модификацию модуля.
             * берем параметры модификации из таблицы */
            mod = ack & 0x3;
            hnd->ModuleInfo.Modification    = f_mod_info[mod].Modification;
            hnd->ModuleInfo.DoutLineCnt     = f_mod_info[mod].DoutLineCnt;
            for (range = 0; range < LTR35_DAC_CH_OUTPUT_CNT; ++range) {
                hnd->ModuleInfo.DacOutDescr[range] = f_mod_info[mod].DacOutDescr[range];
            }

            free(hnd->Internal);
            hnd->Internal = calloc(1, sizeof(t_internal_params));
            if (hnd->Internal == NULL)
                err = LTR_ERROR_MEMORY_ALLOC;
        }
    }

    if ((err == LTR_OK)  && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        INT fpga_status_res = LTR_OK;
        INT flash_res = LTR_OK;


        fpga_status_res = ltrmodule_fpga_check_load(&hnd->Channel, &hnd->State.FpgaState);

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


            flash_res = flash_iface_ltr_init(&params->flash, &hnd->Channel);
            if (!flash_res) {
                flash_res = flash_at45db_set(&params->flash);
            }

            if  (!flash_res)
                flash_res = flash_at45db_get_status(&params->flash, &flash_status);
            if (!flash_res && !(flash_status & AT45DB_STATUS_PAGE_SIZE)) {
                /* Если установлен размер страницы flash-памяти 1056 КБайт,
                 * то перепрограммируем на 1024 и возвращаем ошибку, так как
                 * для корректной работы еще унжно сбросить питание */
                flash_res = flash_at45db_set_page_size_pow2(&params->flash);
                if (!flash_res) {
                    flash_res = LTR_ERROR_FLASH_UNSUP_PAGE_SIZE;
                }
            }

            /* проверяем, что разрешена защита памяти */
            if (!flash_res && !(flash_status & AT45DB_STATUS_PROTECT)) {
                flash_res = flash_at45db_protection_enable(&params->flash);
                if (!flash_res) {
                    flash_res = flash_at45db_get_status(&params->flash, &flash_status);
                    if (!flash_res && !(flash_status & AT45DB_STATUS_PROTECT))
                        flash_res = LTR_ERROR_FLASH_SET_PROTECTION;
                }
            }

            /* проверяем, что запрещена запись в требуемые секторы */
            if (!flash_res) {
                flash_res = f_flash_prot_bits_set(&params->flash, DEF_PORT_SECT_MSK0,
                                            DEF_PORT_SECT_MSK, TRUE);
            }


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

        if ((err == LTR_OK) && (fpga_status_res == LTR_OK)) {
            fpga_err = LTR35_FPGAEnable(hnd, TRUE);
            if (fpga_err == LTR_OK) {
                /* Получаем версию ПЛИС, если он был успешно загружен */
                BYTE fpga_ver;

                fpga_err = f_get_fpga_status(hnd, NULL, &fpga_ver);
                if (fpga_err==LTR_OK) {
                    hnd->ModuleInfo.VerFPGA = fpga_ver;
                }

                if (fpga_err == LTR_OK) {
                    fpga_err = f_cfg_synt(hnd);
                }

                if (fpga_err == LTR_OK) {
                    BYTE ch_cnt = 0;
                    INT dac;
                    /* определяем наличие микросхем ЦАП чтением из 0-ого регистра */
                    for (dac = 0; (dac < LTR35_DAC_CNT) && (fpga_err == LTR_OK); dac++) {
                        BYTE val;
                        fpga_err = f_dac_register_get(hnd, dac, PCM4104_REGADDR_ZERO, &val);
                        if ((fpga_err == LTR_OK) && (val == 0)) {
                            ch_cnt+=LTR35_CH_PER_DAC;
                        } else {
                            break;
                        }
                    }

                    if (fpga_err == LTR_OK)
                        hnd->ModuleInfo.DacChCnt = ch_cnt;
                }

                if (fpga_err == LTR_OK) {
                    fpga_err = f_cfg_dac(hnd);
                }

                if (fpga_err != LTR_OK) {
                    LTR35_FPGAEnable(hnd, FALSE);
                }
            }
        }

        if (err==LTR_OK)
            err = fpga_status_res;

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


    if ((err!=LTR_OK)
            && (err != LTR_ERROR_FPGA_LOAD_DONE_TOUT)
            && (err != LTR_ERROR_FPGA_ENABLE)
            && (err != LTR_ERROR_FLASH_INFO_NOT_PRESENT)
            && (err != LTR_ERROR_FLASH_INFO_UNSUP_FORMAT)
            && (err != LTR_ERROR_FLASH_NOT_PRESENT)) {
        LTR35_Close(hnd);
    }

    /* Ошибку FPGA считаем критической, чтобы была возможность обновить
     * проблемную прошивку */
    if (err == LTR_OK)
        err = fpga_err;

    return err == LTR_OK ? warning : err;
}


LTR35API_DllExport(INT) LTR35_FlashRead(TLTR35 *hnd, DWORD addr, BYTE *data, DWORD size) {
    INT res = f_check_open_and_stopped(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_at45db_read(&pars->flash, addr, data, size);
        flash_iface_flush(&pars->flash, flash_res);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR35API_DllExport(INT) LTR35_FlashWrite(TLTR35 *hnd, DWORD addr, const BYTE *data, DWORD size, DWORD flags) {
    INT res = f_check_open_and_stopped(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res) {
            flash_res = flash_at45db_write(&pars->flash, addr, data, size,
                                           flags & LTR35_FLASH_WRITE_ALREADY_ERASED ?
                                               0 : FLASH_WR_FLAGS_WITH_ERASE);
        }
        flash_iface_flush(&pars->flash, flash_res);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}

LTR35API_DllExport(INT) LTR35_FlashErase(TLTR35 *hnd, DWORD addr, DWORD size) {
    INT res = f_check_open_and_stopped(hnd);
    if (res == LTR_OK) {
        t_internal_params *pars = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = flash_at45db_erase(&pars->flash, addr,size);
        flash_iface_flush(&pars->flash, flash_res);
        if (flash_res)
            res = flash_iface_ltr_conv_err(flash_res);
    }
    return res;
}



/*******************************************************************************
        Функция закрытия интерфейсного канала связи с модулем
*******************************************************************************/
LTR35API_DllExport (INT) LTR35_Close(TLTR35 *hnd) {
    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        if (hnd->Internal != NULL) {
            t_internal_params *params = (t_internal_params*)hnd->Internal;
            flash_iface_ltr_close(&params->flash);
            free(hnd->Internal);
            hnd->Internal = NULL;
        }

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

/*******************************************************************************
    Функция, определяющая, открыт ли интерфейсный канал связи с модулем
*******************************************************************************/
LTR35API_DllExport (INT) LTR35_IsOpened(TLTR35 *hnd) {
    return (hnd==NULL) ? LTR_ERROR_INVALID_MODULE_DESCR :
                            LTR_IsOpened(&hnd->Channel);
}


/*******************************************************************************
    Получение строки, описывающей ошибку, соответствующую коду ErrorCode
*******************************************************************************/
LTR35API_DllExport (LPCSTR) LTR35_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);
}



LTR35API_DllExport(INT) LTR35_Configure(TLTR35 *hnd) {
    INT err = f_check_open_and_stopped(hnd);

    if (err==LTR_OK) {
        if ((hnd->Cfg.OutDataFmt!=LTR35_OUTDATA_FORMAT_20) &&
                (hnd->Cfg.OutDataFmt!=LTR35_OUTDATA_FORMAT_24)) {
            err = LTR35_ERR_INVALID_DATA_FORMAT;
        } else if ((hnd->Cfg.DacRate != LTR35_DAC_RATE_SINGLE) &&
                   (hnd->Cfg.DacRate != LTR35_DAC_RATE_DOUBLE) &&
                   (hnd->Cfg.DacRate != LTR35_DAC_RATE_QUAD)) {
            err = LTR35_ERR_INVALID_DAC_RATE;
        } else if ((hnd->Cfg.OutMode != LTR35_OUT_MODE_CYCLE) &&
                   (hnd->Cfg.OutMode != LTR35_OUT_MODE_STREAM)) {
            err = LTR35_ERR_INVALID_OUT_MODE;
        } else if ((hnd->Cfg.StreamStatusPeriod > LTR35_STREAM_STATUS_PERIOD_MAX) ||
                   ((hnd->Cfg.StreamStatusPeriod < LTR35_STREAM_STATUS_PERIOD_MIN)
                    && (hnd->Cfg.StreamStatusPeriod!=0))) {
            err = LTR35_ERR_INVALID_STREAM_STATUS_PERIOD;
        } else if ((hnd->Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_OFF) &&
                   (hnd->Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_CH_ECHO) &&
                   (hnd->Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_DI)) {
            err = LTR35_ERR_INVALID_IN_STREAM_MODE;
        } else if ((hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_INTERNAL) &&
                   (hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_MASTER) &&
                   (hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_SLAVE)) {
            err = LTR35_ERR_INVALID_SYNC_MODE;
        } else if ((hnd->Cfg.Sync.SyncMode == LTR35_SYNC_MODE_SLAVE) &&
                   (hnd->Cfg.Sync.SlaveSrc != LTR35_SYNC_SLAVE_SRC_DI2_RISE) &&
                   (hnd->Cfg.Sync.SlaveSrc != LTR35_SYNC_SLAVE_SRC_DI2_FALL)) {
            err = LTR35_ERR_INVALID_SYNC_SLAVE_SRC;
        }


        if ((err == LTR_OK) && (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_DI)) {
            int in_ch_cnt = f_calc_din_ch_cnt(&hnd->Cfg);
            if (hnd->Cfg.InStream.DIChEnMask & ~(LTR35_IN_STREAM_DI1 | LTR35_IN_STREAM_DI2)) {
                err = LTR35_ERR_INVALID_IN_DI_CH_LIST;
            } else if (in_ch_cnt == 0) {
                err = LTR35_ERR_IN_DI_NO_CHANNELS;
            } else if ((hnd->Cfg.InStream.DIFreqDivPow > LTR35_DI_STREAM_FREQ_DIV_POW_MAX) ||
                       ((in_ch_cnt > 1) && (hnd->Cfg.InStream.DIFreqDivPow == 0))) {
                err = LTR35_ERR_INVALID_IN_DI_FREQ_DIV_POW;
            }
        }

        if (err==LTR_OK) {
            /* поток и 20-битный формат появились только в 1-ой версии прошивки */
            if ((hnd->ModuleInfo.VerFPGA == 0)
                    && ((hnd->Cfg.OutMode != LTR35_OUT_MODE_CYCLE)
                        || (hnd->Cfg.OutDataFmt != LTR35_OUTDATA_FORMAT_24))) {
                err = LTR_ERROR_UNSUP_BY_FIRM_VER;
            }
        }
    }

    if (err==LTR_OK) {
        /* значение частоты вывода вычисляем тут сразу, а не в конце функции,
           т.к. они используются для расчета коэффициентов фильтра */
        const double synt_freq = SYNT_FREQ(hnd->Cfg.FreqSynt.a, hnd->Cfg.FreqSynt.b, hnd->Cfg.FreqSynt.r);
        const unsigned div = f_dac_rate_div[hnd->Cfg.DacRate];
        const double out_freq = synt_freq / div;
        BYTE i;
        BYTE last_ch = 0;

        hnd->State.ArithChCnt = hnd->State.SDRAMChCnt = hnd->State.EnabledChCnt = 0;

        /* Настройка режимов каналов ЦАП */
        if (err == LTR_OK) {
            DWORD ch_cmds[LTR35_DAC_CHANNEL_CNT + 1];
            int fnd = 0;
            WORD data = 0;

            for (i = 0; (i < LTR35_DAC_CHANNEL_CNT) && (err == LTR_OK); ++i) {
                if (hnd->Cfg.Ch[i].Enabled) {
                    if (i >= hnd->ModuleInfo.DacChCnt) {
                        err = LTR35_ERR_DAC_CH_NOT_PRESENT;
                    } else {
                        GET_CODE(f_ch_sources, hnd->Cfg.Ch[i].Source, fnd, data);
                        if (!fnd)
                            err = LTR35_ERR_INVALID_CH_SOURCE;

                        if ((err == LTR_OK) && (hnd->Cfg.Ch[i].Source == LTR35_CH_SRC_SDRAM)) {
                            last_ch = i;
                            hnd->State.SDRAMChCnt++;
                        } else if (LTR35_ARITH_SRC(hnd->Cfg.Ch[i].Source)) {
                            hnd->State.ArithChCnt++;
                        }
                        hnd->State.EnabledChCnt++;
                    }
                } else {
                    data = 0;
                }

                if (err == LTR_OK) {
                    ch_cmds[i] = LTR_MODULE_MAKE_CMD_BYTES(CMD_OUT_CTL, i, data);
                }
            }


            /* значения для цифровых выходов всегда берутся из SDRAM */
            GET_CODE(f_ch_sources, LTR35_CH_SRC_SDRAM, fnd, data);
            ch_cmds[LTR35_DAC_CHANNEL_CNT] = LTR_MODULE_MAKE_CMD_BYTES(
                        CMD_OUT_CTL, LTR35_CH_NUM_DIGOUTS, data);
            /* если ни один канал ЦАП не разрешен, то обновление будем делать по
             * выводу на цифровые линии */
            if (hnd->State.SDRAMChCnt == 0)
                last_ch = LTR35_CH_NUM_DIGOUTS;


            if (err == LTR_OK)
                err = ltr_module_send_cmd(&hnd->Channel, ch_cmds, LTR35_DAC_CHANNEL_CNT+1);
        }

        if (err == LTR_OK) {
            DWORD cmd[4], data = CMD_BIT_MAIN_CTL_FCLK_SRC;
            DWORD put_size = 0;
            if (hnd->Cfg.OutDataFmt == LTR35_OUTDATA_FORMAT_20)
                data |= CMD_BIT_MAIN_CTL_FORMAT;
            if (hnd->Cfg.OutMode == LTR35_OUT_MODE_STREAM)
                data |= CMD_BIT_MAIN_CTL_MODE;

            if (hnd->Cfg.DacRate == LTR35_DAC_RATE_SINGLE) {
                data |= CMD_BIT_MAIN_CTL_RATE_SINGLE;
            } else if (hnd->Cfg.DacRate == LTR35_DAC_RATE_QUAD) {
                data |= CMD_BIT_MAIN_CTL_RATE_QUAD;
            }

            data |= (last_ch << 4);


            cmd[put_size++] = LTR_MODULE_MAKE_CMD(CMD_MAIN_CTL, data);
            if (hnd->ModuleInfo.VerFPGA >= 1) {
                cmd[put_size++] = LTR_MODULE_MAKE_CMD(CMD_SET_STREAM_STATUS_PERIOD,
                                                        hnd->Cfg.StreamStatusPeriod==0 ?
                                                        LTR35_STREAM_STATUS_PERIOD_DEFAULT-1 :
                                                        hnd->Cfg.StreamStatusPeriod-1);
            }
            data = 0;
            if (hnd->Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_OFF) {
                if (hnd->ModuleInfo.VerFPGA < 16) {
                    if (hnd->Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_CH_ECHO) {
                        err = LTR_ERROR_UNSUP_BY_FIRM_VER;
                    } else {
                        /* старый формат для работы с эхо-каналом */
                        data = (1 << 7) | LBITFIELD_SET(CMD_MSK_DATAIN_CH_NUM, hnd->Cfg.InStream.EchoChannel);
                    }
                } else {
                    BYTE mode = CMD_DATAIN_MODE_OFF;
                    if (hnd->Cfg.Flags & LTR35_CFG_FLAG_DISABLE_PERIODIC_STATUS) {
                        data |= CMD_MSK_DATAIN_STAT_DIS;
                    }
                    if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_CH_ECHO) {
                        mode = CMD_DATAIN_MODE_CH_ECHO;
                        data |= LBITFIELD_SET(CMD_MSK_DATAIN_CH_NUM, hnd->Cfg.InStream.EchoChannel);
                    } else if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_DI) {
                        if (hnd->Cfg.InStream.DIChEnMask  == LTR35_IN_STREAM_DI1) {
                            mode = CMD_DATAIN_MODE_DI1;
                        } else if (hnd->Cfg.InStream.DIChEnMask  == LTR35_IN_STREAM_DI2) {
                            mode = CMD_DATAIN_MODE_DI2;
                        } else {
                            mode = CMD_DATAIN_MODE_DI1_2;
                        }
                        data |= LBITFIELD_SET(CMD_MSK_DATAIN_DIV, hnd->Cfg.InStream.DIFreqDivPow);
                    }
                    data |= LBITFIELD_SET(CMD_MSK_DATAIN_MODE, mode);
                }
            }

            data |= LBITFIELD_SET(CMD_MSK_DATAIN_DIV, hnd->Cfg.InStream.DIFreqDivPow);
            cmd[put_size++] = LTR_MODULE_MAKE_CMD(CMD_SET_DATAIN, data);

            if (hnd->ModuleInfo.VerFPGA >= 19) {
                if ((hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_INTERNAL) &&
                        (hnd->Cfg.OutMode != LTR35_OUT_MODE_STREAM)) {
                    err = LTR_ERROR_UNSUP_BY_FIRM_VER;
                } else {
                    data = LBITFIELD_SET(CMD_MSK_SYNCMODE_MODE, hnd->Cfg.Sync.SyncMode);
                    if (hnd->Cfg.Sync.SyncMode == LTR35_SYNC_MODE_SLAVE) {
                        data |= LBITFIELD_SET(CMD_MSK_SYNCMODE_SLAVE_POL, hnd->Cfg.Sync.SlaveSrc);
                    }
                    cmd[put_size++] = LTR_MODULE_MAKE_CMD(CMD_SET_SYNCMODE, data);
                }
            } else if (hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_INTERNAL) {
                err = LTR_ERROR_UNSUP_BY_FIRM_VER;
            }

            if (err == LTR_OK) {
                err = ltr_module_send_cmd(&hnd->Channel, cmd, put_size);
            }
        }


        /* Настройка синтезатора */
        if (err == LTR_OK) {
            t_internal_params *params = (t_internal_params *)hnd->Internal;
            if (!params->synt_configured ||
                    (params->SyntCfg.a != hnd->Cfg.FreqSynt.a) ||
                    (params->SyntCfg.b != hnd->Cfg.FreqSynt.b) ||
                    (params->SyntCfg.r != hnd->Cfg.FreqSynt.r)) {

                err = f_cfg_synt(hnd);
            }
        }

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

        /* Запись коэффициентов */
        if (err == LTR_OK) {
            DWORD coef_cmd[(LTR35_DAC_CHANNEL_CNT*2+1)*3];
            DWORD put_pos=0;
            BYTE ch_num;
            double offsets[LTR35_DAC_CHANNEL_CNT];

            coef_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_CBR_K);
            for (ch_num = 0; ch_num < LTR35_DAC_CHANNEL_CNT; ++ch_num) {

                double scale, offs;
                /* В арифметическом режиме корректируем амплитуду с помощью
                 *  коэффициента шкалы, а смещения с помощью коэф. нуля */
                if (LTR35_ARITH_SRC(hnd->Cfg.Ch[ch_num].Source)) {
                    f_calc_arith_amp_offs(hnd, ch_num, hnd->Cfg.Ch[ch_num].ArithAmp,
                                          hnd->Cfg.Ch[ch_num].ArithOffs, &scale, &offs);
                } else {
                    scale = (double)hnd->ModuleInfo.CbrCoef[ch_num][hnd->Cfg.Ch[ch_num].Output].Scale;
                    offs  = (double)hnd->ModuleInfo.CbrCoef[ch_num][hnd->Cfg.Ch[ch_num].Output].Offset;
                }
                offsets[ch_num] = offs;
                f_fill_coef_float_cmd(coef_cmd, &put_pos, scale);
            }

            coef_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_CBR_B);
            for (ch_num = 0; ch_num < LTR35_DAC_CHANNEL_CNT; ++ch_num) {
                double offs = offsets[ch_num];
                f_fill_offset_cmd(coef_cmd, &put_pos, offs);
            }

            coef_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_CBR_AFC);
            for (ch_num = 0; ch_num < LTR35_DAC_CHANNEL_CNT; ++ch_num) {
                double k = 0;
                if (hnd->ModuleInfo.CbrAfcCoef.Valid && !(hnd->Cfg.Flags & LTR35_CFG_FLAG_DISABLE_AFC_COR)) {
                    double f0 = hnd->ModuleInfo.CbrAfcCoef.SigFreq;
                    double fd0 = hnd->ModuleInfo.CbrAfcCoef.Fd;
                    double fd = out_freq;

                    double k1 = hnd->ModuleInfo.CbrAfcCoef.K[ch_num];
                    k = k1 * ((1 - cos(2.*M_PI * f0/fd0)) / ( 1 - cos(2.*M_PI * f0/fd)));
                }
                f_fill_coef_float_cmd(coef_cmd, &put_pos, k);
            }

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

        if (err == LTR_OK) {
            BYTE status = 0;
            err = f_get_fpga_status(hnd, &status, NULL);
            if (hnd->ModuleInfo.VerFPGA >= 19) {
                const BYTE syncmode = LBITFIELD_GET(status, LTR35_STATUS_FLAG_SYNC_MODE);
                if (syncmode != hnd->Cfg.Sync.SyncMode) {
                    err = LTR35_ERR_SYNC_MODE_NOT_SET;
                }
            }
        }



        /* сброс буфера и настройка арифм. источников при необходимости */
        if (err == LTR_OK) {
            if (hnd->State.ArithChCnt != 0) {
                err = f_clear_buf(hnd, CMD_BIT_SW_HOLD_ARITH);
                if (err==LTR_OK) {
                    DWORD arith_cmd[(LTR35_ARITH_SRC_CNT * 2 + 1) * 2];
                    DWORD put_pos = 0;
                    DWORD src;

                    arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_ARITH_PHASE);
                    for (src = 0; src < LTR35_ARITH_SRC_CNT; ++src) {
                        DWORD code = hnd->Cfg.ArithSrc[src].Phase.CodeH;
                        arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, code & 0xFFFF);
                        arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, (code >> 16) & 0xFFFF);
                    }

                    arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_ARITH_DELTA);
                    for (src = 0; src < LTR35_ARITH_SRC_CNT; ++src) {
                        DWORD code = hnd->Cfg.ArithSrc[src].Delta.CodeH;
                        arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, code & 0xFFFF);
                        arith_cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, (code >> 16) & 0xFFFF);
                    }

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


                if (err==LTR_OK) {
                    DWORD cmd[5];
                    /* между сбросом CMD_BIT_SW_HOLD_ARITH и запуском сбора должно
                     * быть не меньше 4-х команд, т.к. когда нет потока или удержания
                     * арифметических источников, то CMD_PAGES_SW со всеми нулевыми
                     * битами ничего не делает, то используем ее для задержки,
                     * повторяя еще 4 раза */
                    for (i = 0; i < sizeof(cmd)/sizeof(cmd[0]); ++i) {
                        cmd[i] = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, 0);
                    }
                    err = ltr_module_send_cmd(&hnd->Channel, cmd, sizeof(cmd)/sizeof(cmd[0]));
                }                
            } else {
                err = f_clear_buf(hnd,0);
            }
        }

        if (err == LTR_OK) {
            t_internal_params *pars = (t_internal_params*)hnd->Internal;
            hnd->State.SyntFreq = synt_freq;
            hnd->State.OutFreq = out_freq;
            hnd->State.InStreamDIAcqFreq = 0;
            hnd->State.InStreamDIChCnt = 0;

            if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_OFF) {
                hnd->State.InStreamWordFreq = 0;
            } else if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_CH_ECHO) {
                hnd->State.InStreamWordFreq = hnd->State.OutFreq;
            } else if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_DI) {
                hnd->State.InStreamDIChCnt = f_calc_din_ch_cnt(&hnd->Cfg);
                hnd->State.InStreamDIAcqFreq = f_calc_din_acq_freq(synt_freq, hnd->Cfg.InStream.DIFreqDivPow);
                hnd->State.InStreamWordFreq = hnd->State.InStreamDIAcqFreq * hnd->State.InStreamDIChCnt / 24;
            }
            pars->send_wrds = 0;
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_FillDIAcqFreq(TLTR35_CONFIG *cfg, double freq, double *fnd_freq) {
    INT err = LTR_OK;
    double res_freq = 0;
    const BYTE mode = cfg->InStream.InStreamMode;
    if (mode == LTR35_IN_STREAM_MODE_DI) {
        const BYTE ch_cnt = f_calc_din_ch_cnt(cfg);
        const double synt_freq = SYNT_FREQ(cfg->FreqSynt.a, cfg->FreqSynt.b, cfg->FreqSynt.r);
        double fnd_df, fnd_f;
        BOOLEAN dfreq_valid = FALSE;
        BYTE fnd_div = 0;
        BYTE div;


        for (div = ch_cnt == 2 ? 1 : 0; div <= LTR35_DI_STREAM_FREQ_DIV_POW_MAX; ++div) {
            double f = f_calc_din_acq_freq(synt_freq, div);
            double df = fabs(f - freq);
            if (!dfreq_valid || (df < fnd_df)) {
                dfreq_valid = TRUE;
                fnd_df = df;
                fnd_f = f;
                fnd_div = div;
            }
        }

        cfg->InStream.DIFreqDivPow = fnd_div;
        res_freq = fnd_f;
    } else {
        err = LTR35_ERR_INVALID_IN_STREAM_MODE;
    }

    if (fnd_freq)
        *fnd_freq = res_freq;
    return err;
}

LTR35API_DllExport(INT) LTR35_Send(TLTR35 *hnd, const DWORD *data,
                                    DWORD size, DWORD timeout) {
    INT res = LTR35_IsOpened(hnd);
    if (res==LTR_OK) {
        res = LTR_Send(&hnd->Channel, data, size, timeout);        
        if (res > 0) {
            t_internal_params *pars = (t_internal_params*)hnd->Internal;
            pars->send_wrds += res;
        }
    }

    return res;
}





LTR35API_DllExport(INT) LTR35_PrepareData(TLTR35 *hnd, const double *dac_data,
                                          DWORD *dac_size, const DWORD *dout_data,
                                          DWORD *dout_size, DWORD flags,
                                          DWORD *result, DWORD *snd_size) {
    INT err = (result == NULL) || (snd_size == NULL)  || (*snd_size == 0) ?
                LTR_ERROR_PARAMETERS : LTR35_IsOpened(hnd);
    BYTE ch_idx[LTR35_DAC_CHANNEL_CNT], ram_ch_cnt = 0;
    unsigned range;

    DWORD put_size = 0;

    DWORD max_snd_size = *snd_size;
    DWORD max_dac_size = 0, max_dout_size = 0;
    DWORD rem_dac_size = 0, rem_dout_size = 0;
    DWORD frame_size = 0;

    if (err == LTR_OK) {
        if ((dac_data != NULL) && (dac_size != NULL) && (hnd->State.SDRAMChCnt != 0)) {
            max_dac_size = rem_dac_size = *dac_size;
            if (max_dac_size != 0)
                frame_size += hnd->State.SDRAMChCnt;
        }

        if ((dout_data != NULL) && (dout_size != NULL)) {
            max_dout_size = rem_dout_size = *dout_size;
            if (max_dout_size != 0)
                frame_size++;
        }

        if (hnd->Cfg.OutDataFmt == LTR35_OUTDATA_FORMAT_24) {
            frame_size *= 2;
        }


        if ((max_dac_size != 0) && ((max_dac_size % hnd->State.SDRAMChCnt) != 0)) {
            err = LTR35_ERR_DAC_DATA_NOT_ALIGNED;
        }
    }


    if ((err == LTR_OK) && ((rem_dac_size > 0) || (rem_dout_size > 0))) {
        BYTE ch;
        int done = 0;
        double mul[LTR35_DAC_CH_OUTPUT_CNT];

        for (range = 0; range < LTR35_DAC_CH_OUTPUT_CNT; ++range) {
            mul[range] = hnd->ModuleInfo.DacOutDescr[range].CodeMax / hnd->ModuleInfo.DacOutDescr[range].AmpMax;
        }

        for (ch = 0; ch < LTR35_DAC_CHANNEL_CNT; ++ch) {
            if (hnd->Cfg.Ch[ch].Enabled && (hnd->Cfg.Ch[ch].Source == LTR35_CH_SRC_SDRAM)) {
                ch_idx[ram_ch_cnt++] = ch;
            }
        }

        while (!done && ((max_snd_size - put_size) >= frame_size)) {
            if (rem_dout_size != 0) {
                f_prepare_wrd(hnd, LTR35_CH_NUM_DIGOUTS, *dout_data++, &result[put_size], &put_size);
                if (--rem_dout_size == 0) {
                    done = 1;
                }
            }

            if (rem_dac_size) {
                for (ch = 0; ch < ram_ch_cnt; ++ch) {
                    double dval = *dac_data++;
                    if (flags & LTR35_PREP_FLAGS_VOLT) {
                        dval = dval * mul[hnd->Cfg.Ch[ch_idx[ch]].Output];
                    }

                    if (dval > LTR35_DAC_CODE_MAX) {
                        dval = LTR35_DAC_CODE_MAX;
                    } else if (dval < -LTR35_DAC_CODE_MAX) {
                        dval = -LTR35_DAC_CODE_MAX;
                    }
                    f_prepare_wrd(hnd, ch_idx[ch], (DWORD)dval, &result[put_size], &put_size);
                    rem_dac_size--;
                }
                if (rem_dac_size == 0) {
                    done = 1;
                }
            }
        }
    }

    if (snd_size != NULL)
        *snd_size = put_size;
    if (dac_size != NULL)
        *dac_size = max_dac_size - rem_dac_size;
    if (dout_size != NULL)
        *dout_size = max_dout_size - rem_dout_size;

    return err;
}

LTR35API_DllExport(INT) LTR35_PrepareDacData(TLTR35 *hnd, const double *dac_data,
                                          DWORD size, DWORD flags, DWORD *result,
                                          DWORD *snd_size) {
    DWORD loc_snd_size = 2 * size;
    INT err = LTR35_PrepareData(hnd, dac_data, &size, NULL, NULL, flags, result,
                                &loc_snd_size);
    if ((err == LTR_OK) && (snd_size != NULL)) {
        *snd_size = loc_snd_size;
    }
    return err;
}



static INT f_wait_switch_response(TLTR35 *hnd, DWORD tout, WORD data) {
    INT err = LTR_OK;

    if (hnd->State.RunState == LTR35_RUNSTATE_STOPPED) {
        err = LTR35_ERR_NO_START_REQUESTED;
    } else if (hnd->State.RunState != LTR35_RUNSTATE_RUNNING) {
        DWORD ack;
        int repeat = 0;

        do {
            repeat = 0;
            err = ltr_module_recv_cmd_resp_tout(&hnd->Channel, &ack, 1, tout);
            if (err == LTR_ERROR_NO_CMD_RESPONSE) {
                err = LTR_ERROR_OP_DONE_WAIT_TOUT;
            } else if (err == LTR_OK) {
                WORD cmd_data = LTR_MODULE_CMD_GET_DATA(ack);
                if ((LTR_MODULE_CMD_GET_CMDCODE(ack) != CMD_PAGES_SW) ||
                   ((cmd_data & ~CMD_BIT_SW_SLAVE_EXT_RESP) != (data & ~CMD_BIT_SW_SLAVE_EXT_RESP))) {
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                } else {
                    if (cmd_data & CMD_BIT_SW_SLAVE_EXT_RESP) {
                        if (hnd->State.RunState == LTR35_RUNSTATE_SLAVE_RDY_WAITING) {
                            hnd->State.RunState = LTR35_RUNSTATE_START_ACK_WAITING;
                            if (!(data & CMD_BIT_SW_SLAVE_EXT_RESP)) {
                                repeat = 1;
                            }
                        } else {
                            err = LTR_ERROR_INVALID_CMD_RESPONSE;
                        }
                    } else {
                        hnd->State.RunState = LTR35_RUNSTATE_RUNNING;
                    }
                }
            }
        } while (repeat);
    }
    return err;
}

LTR35API_DllExport(INT) LTR35_SwitchCyclePage(TLTR35 *hnd, DWORD flags, DWORD tout) {
    INT err = LTR35_SwitchCyclePageRequest(hnd, flags);
    if (err == LTR_OK) {
        err = LTR35_SwitchCyclePageWaitDone(hnd, tout);
    }
    return err;
}



LTR35API_DllExport(INT) LTR35_SwitchCyclePageRequest(TLTR35 *hnd, DWORD flags) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->Cfg.OutMode != LTR35_OUT_MODE_CYCLE)
        err = LTR35_ERR_MODE_UNSUP_FUNC;
    if (err == LTR_OK) {
        t_internal_params *pars = (t_internal_params *)hnd->Internal;
        if (pars->send_wrds == 0) {
            /* если не было записано ни одного слова в буфер, так как
             * разрешены только арифметические каналы, и при этом
             * не было выведено ничего на цифровые линии, то нужно записать
             * хотя бы одно слово. Записываем слово, устанавливающее
             * 3-е состояние на выходах */
            if ((hnd->State.SDRAMChCnt == 0) && (hnd->State.ArithChCnt != 0)) {
                DWORD data[2];
                DWORD size = 0;
                f_prepare_wrd(hnd, LTR35_CH_NUM_DIGOUTS, LTR35_DIGOUT_WORD_DIS_H | LTR35_DIGOUT_WORD_DIS_L, data, &size);
                err = ltr_module_send_cmd(&hnd->Channel, data, size);
            } else {
                err = LTR35_ERR_NO_DATA_LOADED;
            }
        }

        if (err == LTR_OK) {
            DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, CMD_BIT_SW_PAGE | CMD_BIT_SW_RESPONSE);
            err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
            if (err == LTR_OK) {
                hnd->State.RunState = LTR35_RUNSTATE_START_ACK_WAITING;
                hnd->State.Run = TRUE;
                pars->send_wrds = 0;
            }
        }
    }
    return err;
}

LTR35API_DllExport(INT) LTR35_SwitchCyclePageWaitDone(TLTR35 *hnd, DWORD tout) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->Cfg.OutMode != LTR35_OUT_MODE_CYCLE) {
        err = LTR35_ERR_MODE_UNSUP_FUNC;
    }
    if (err == LTR_OK) {
        err = f_wait_switch_response(hnd, tout, CMD_BIT_SW_PAGE | CMD_BIT_SW_RESPONSE);
    }
    return err;
}



LTR35API_DllExport(INT) LTR35_StreamStart(TLTR35 *hnd, DWORD flags) {
    INT err = LTR35_StreamStartRequest(hnd, flags);
    if (err == LTR_OK) {
        err = LTR35_StreamStartWaitDone(hnd, LTR_MODULE_CMD_RECV_TIMEOUT);
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_StreamStartRequest(TLTR35 *hnd, DWORD flags) {
    (void)flags;
    INT err = f_check_open_and_stopped(hnd);
    if ((err == LTR_OK) && (hnd->Cfg.OutMode != LTR35_OUT_MODE_STREAM)) {
        err = LTR35_ERR_MODE_UNSUP_FUNC;
    }

    if ((err == LTR_OK) && (hnd->State.RunState == LTR35_RUNSTATE_STOPPED)) {
        DWORD cmd;
        WORD cmd_data = CMD_BIT_SW_GO | CMD_BIT_SW_RESPONSE;
        int ext_resp = (hnd->Cfg.Sync.SyncMode == LTR35_SYNC_MODE_SLAVE)
                && LTR35_FPGA_SUPPORT_SLAVE_EXT_RESP(hnd);
        if (ext_resp) {
            cmd_data |= CMD_BIT_SW_SLAVE_EXT_RESP;
        }

        cmd = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, cmd_data);
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        if (err == LTR_OK) {
            hnd->State.Run = TRUE;
            hnd->State.RunState = ext_resp ? LTR35_RUNSTATE_SLAVE_RDY_WAITING :
                                             LTR35_RUNSTATE_START_ACK_WAITING;
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_StreamStartWaitSlaveReady(TLTR35 *hnd, DWORD tout) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && ((hnd->Cfg.OutMode != LTR35_OUT_MODE_STREAM) ||
                          (hnd->Cfg.Sync.SyncMode != LTR35_SYNC_MODE_SLAVE))) {
        err = LTR35_ERR_MODE_UNSUP_FUNC;
    }

    if (err == LTR_OK) {
        /* Если состояние LTR35_RUNSTATE_START_ACK_WAITING, то уже ожидаем запуска,
         * т.е. ожидание готовности выполнено и можем сразу завершать функцию */
        if (hnd->State.RunState != LTR35_RUNSTATE_START_ACK_WAITING) {
            err = f_wait_switch_response(hnd, tout, CMD_BIT_SW_GO | CMD_BIT_SW_RESPONSE |
                                         CMD_BIT_SW_SLAVE_EXT_RESP);
        }
    }
    return err;
}



LTR35API_DllExport(INT) LTR35_StreamStartWaitDone(TLTR35 *hnd, DWORD tout) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && (hnd->Cfg.OutMode != LTR35_OUT_MODE_STREAM))
        err = LTR35_ERR_MODE_UNSUP_FUNC;
    if (err == LTR_OK) {
        err = f_wait_switch_response(hnd, tout, CMD_BIT_SW_GO | CMD_BIT_SW_RESPONSE);
    }
    return err;
}



LTR35API_DllExport(INT) LTR35_StopWithTout(TLTR35 *hnd, DWORD flags, DWORD tout) {
    INT err = LTR35_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_PAGES_SW, CMD_BIT_SW_RESPONSE | CMD_BIT_SW_BUF_CLEAR | CMD_BIT_SW_HOLD_ARITH);
        /* В ответе на STOP ожидаем полное совпадаение, как кода команды, так и данных.
         * Однако, для прошивки FPGA 25, для слейва, если был старт и не было принято
         * признака начала данных, то модуль может вернуть в ответе бит CMD_BIT_SW_SLAVE_EXT_RESP,
         * хотя в команде его не было. Поэтому игнорируем этот бит при проверке
         * ответа */
        err = ltr_module_stop_tout(&hnd->Channel, &cmd, 1, cmd,
                                   LTR_MSTOP_FLAGS_SPEC_MASK,
                                   LTR_MODULE_CMD_CODE_MSK | (LTR_MODULE_CMD_DATA_MSK &
                                                              ~LBITFIELD_SET(LTR_MODULE_CMD_DATA_MSK, CMD_BIT_SW_SLAVE_EXT_RESP)),
                                   tout, NULL);
        if (err == LTR_OK) {
            hnd->State.RunState = LTR35_RUNSTATE_STOPPED;
            hnd->State.Run = FALSE;
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_Stop(TLTR35 *hnd, DWORD flags) {
    return LTR35_StopWithTout(hnd, flags, LTR_MODULE_STOP_TIMEOUT);
}


LTR35API_DllExport(INT) LTR35_SetArithSrcDelta(TLTR35 *hnd, BYTE gen_num, const TLTR35_ARITH_PHASE *delta) {
    INT err = LTR35_IsOpened(hnd);
    if ((err==LTR_OK) && (gen_num > LTR35_ARITH_SRC_CNT)) {
        err =  LTR35_ERR_INVALID_ARITH_GEN_NUM;
    }

    if (err == LTR_OK) {
        DWORD code = delta->CodeH;
        DWORD cmd[3];
        DWORD addr =  COEF_ADDR_ARITH_DELTA + 2*gen_num;
        DWORD put_pos = 0;
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, addr);
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, code & 0xFFFF);
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_DATA, (code >> 16) & 0xFFFF);
        err = ltr_module_send_cmd(&hnd->Channel, cmd, put_pos);
        if (err == LTR_OK) {
            hnd->Cfg.ArithSrc[gen_num].Delta = *delta;
        }
    }
    return err;
}

LTR35API_DllExport(INT) LTR35_SetArithAmp(TLTR35 *hnd, BYTE ch_num,
                                          double amp, double offset) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && (ch_num >= hnd->ModuleInfo.DacChCnt)) {
        err =  LTR35_ERR_DAC_CH_NOT_PRESENT;
    }

    if (err == LTR_OK) {
        double wr_scale, wr_offset;

        err = f_calc_arith_amp_offs(hnd, ch_num, amp, offset, &wr_scale, &wr_offset);

        if (err == LTR_OK) {
            DWORD cmds[6];
            DWORD put_pos = 0;
            cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_CBR_K + 2*ch_num);
            f_fill_coef_float_cmd(cmds, &put_pos, wr_scale);
            cmds[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_COEF_ADDR, COEF_ADDR_CBR_B + 2*ch_num);
            f_fill_offset_cmd(cmds, &put_pos, wr_offset);
            err = ltr_module_send_cmd(&hnd->Channel, cmds, put_pos);
        }

        if (err == LTR_OK) {
            hnd->Cfg.Ch[ch_num].ArithAmp = amp;
            hnd->Cfg.Ch[ch_num].ArithOffs = offset;
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_RecvInStreamData(TLTR35 *hnd, INT *data, DWORD *tmark, DWORD size,
                                           DWORD timeout) {
    INT ret_val = LTR35_IsOpened(hnd);
    if (ret_val == LTR_OK) {
        ret_val = LTR_Recv(&hnd->Channel, (DWORD*)data, tmark, size, timeout);
        if (ret_val > 0) {
            INT i;
            INT put_pos = 0;
            for (i = 0; i < ret_val; ++i) {
                if ((data[i] & LTR010_CMD_DATA_MASK) == LTR010DATA) {
                    INT put_wrd = ((data[i] >> 16) & 0xFFFF) | ((data[i] & 0xFF) << 16);
                    if (hnd->Cfg.InStream.InStreamMode == LTR35_IN_STREAM_MODE_CH_ECHO) {
                        if (data[i] & 0x80) {
                            put_wrd |= 0xFF000000;
                        }
                    }
                    data[put_pos++] = put_wrd;
                }
            }
            ret_val = put_pos;
        }
    }
    return ret_val;
}




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


LTR35API_DllExport(INT) LTR35_FillOutFreq(TLTR35_CONFIG *cfg, double freq, double *fnd_freq) {
    INT err = (freq < LTR35_DAC_FREQ_MIN) || (freq > LTR35_DAC_FREQ_MAX) ? LTR35_ERR_INVALID_SYNT_FREQ : LTR_OK;
    if (err == LTR_OK) {
        double synt_freq;
        int div;

        cfg->DacRate = (freq > LTR35_DAC_DOUBLE_RATE_FREQ_MAX) ? LTR35_DAC_RATE_QUAD :
                       (freq > LTR35_DAC_SINGLE_RATE_FREQ_MAX) ? LTR35_DAC_RATE_DOUBLE :
                                                                 LTR35_DAC_RATE_SINGLE;
        div = f_dac_rate_div[cfg->DacRate];

        err = f_synt_find_freq(freq*div, &cfg->FreqSynt.a, &cfg->FreqSynt.b, &cfg->FreqSynt.r, &synt_freq);

        if (err==LTR_OK) {
            if (fnd_freq!=NULL)
                *fnd_freq = synt_freq/div;
        }
    }

    return err;
}


LTR35API_DllExport(INT) LTR35_GetStatus(TLTR35 *hnd, DWORD *status) {
    INT err = LTR35_IsOpened(hnd);
    if (err == LTR_OK) {
        BYTE fpga_st;
        err = f_get_fpga_status(hnd, &fpga_st, NULL);
        if ((err == LTR_OK) && (status != NULL)) {
            *status = fpga_st;
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_FPGAIsEnabled(TLTR35 *hnd, BOOL *enabled) {
    INT err = LTR35_IsOpened(hnd);
    if ((err == LTR_OK) && (enabled == NULL))
        err = LTR_ERROR_PARAMETERS;
    if ((err == LTR_OK) && (enabled != NULL)) {
        *enabled = ltrmodule_fpga_is_enabled(hnd->State.FpgaState);
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_FPGAEnable(TLTR35 *hnd, BOOL en) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        err = ltrmodule_fpga_enable(&hnd->Channel, en, &hnd->State.FpgaState);
    }
    return err;
}

/* Блокировка доступа к SPI flash интерфейсу со строны FPGA, и предоставление
 * экслюзивного доступа CPLD.
 * Позволяет избежать случая, когда FPGA с неверно настроенными ногами блокирует
 * интерфейс и не дает перезаписать flash даже при FPGAEnabled(FALSE).
 * Нештатный режим, должен использоваться только при перепрошивке.
 * Функция доступна только для версии CPLD 2 или выше */
LTR35API_DllExport(INT) LTR35_FPGAHoldFlashIface(TLTR35 *hnd, BOOL en) {
    INT err = f_check_open_and_stopped(hnd);
    if ((err == LTR_OK) && (hnd->ModuleInfo.VerPLD >= 2)) {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_FPGA_HOLD_SPI, en ? 1 : 0);
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
    }
    return  err;
}



LTR35API_DllExport(INT) LTR35_FPGAFirmwareWriteEnable(TLTR35 *hnd, BOOL en) {
    INT err = LTR35_IsOpened(hnd);
    if (err == LTR_OK) {
        t_internal_params *params = (t_internal_params*)hnd->Internal;
        t_flash_errs flash_res = FLASH_ERR_OK;
        if (!params->flash.flash_info) {
            flash_res = flash_at45db_set(&params->flash);
        }
        if (flash_res == FLASH_ERR_OK) {
            flash_res = f_flash_prot_bits_set(
                        &params->flash, FPGA_SECT_MSK0, FPGA_SECT_MSK, !en);
        }
        flash_iface_flush(&params->flash, flash_res);
        if (flash_res != FLASH_ERR_OK) {
            err = flash_iface_ltr_conv_err(flash_res);
        }

    }
    return err;
}



LTR35API_DllExport(INT) LTR35_SaveModuleInfo(TLTR35 *hnd) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        t_ltr35_flash_info info, info_ver;
        unsigned ch, range;
        WORD crc, crc_ver;
        t_flash_errs flash_res;
        t_internal_params *pars = (t_internal_params*)hnd->Internal;

        memset(&info, 0, sizeof(info));
        info.sign = LTR35_FLASH_INFO_SIGN;
        info.size = sizeof(info);
        info.format = LTR35_FLASH_INFO_FORMAT;
        info.flags = 0;
        memcpy(info.name, hnd->ModuleInfo.Name, LTR35_NAME_SIZE);
        memcpy(info.serial, hnd->ModuleInfo.Serial, LTR35_SERIAL_SIZE);

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

        if (hnd->ModuleInfo.CbrFlags & LTR35_CBR_FLAG_HAS_FLASH_AFC_CBR) {
            info.cbr_affc_coef = hnd->ModuleInfo.CbrAfcCoef;
        }

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

        flash_res = flash_iface_ltr_set_channel(&pars->flash, &hnd->Channel);
        if (!flash_res)
            flash_res = f_flash_prot_bits_set(&pars->flash, MODULEINFO_SECT_MSK0, MODULEINFO_SECT_MSK0, FALSE);
        if (!flash_res) {
            t_flash_errs dis_err;

            flash_res = flash_at45db_erase(&pars->flash, MODULEINFO_FLASH_ADDR, MODULEINFO_FLASH_SIZE);
            if (!flash_res) {
                flash_res = flash_at45db_write(&pars->flash, MODULEINFO_FLASH_ADDR,
                                                (BYTE*)&info, sizeof(info), TRUE);
            }
            if (!flash_res) {
                flash_res = flash_at45db_write(&pars->flash,
                                         MODULEINFO_FLASH_ADDR+sizeof(info),
                                         (BYTE*)&crc, sizeof(crc), TRUE);
            }

            dis_err = f_flash_prot_bits_set(&pars->flash, MODULEINFO_SECT_MSK0, MODULEINFO_SECT_MSK0, TRUE);



            if (!flash_res) {
                flash_res = flash_at45db_read(&pars->flash, MODULEINFO_FLASH_ADDR,
                                        (BYTE*)&info_ver, sizeof(info));
            }
            if (!flash_res) {
                flash_res = flash_at45db_read(&pars->flash,
                                        MODULEINFO_FLASH_ADDR+sizeof(info),
                                        (BYTE*)&crc_ver, sizeof(crc));
            }

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

            if (!flash_res)
                flash_res = dis_err;
        }
        flash_iface_flush(&pars->flash, flash_res);
        if (err == LTR_OK)
            err = flash_iface_ltr_conv_err(flash_res);
    }

    return err;
}

LTR35API_DllExport(INT) LTR35_GetConfig(TLTR35 *hnd) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        err = f_read_flash_info(hnd);
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_DacReset(TLTR35 *hnd) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        const BYTE sysctl = f_dac_get_sysctl(hnd->Cfg.DacRate);
        BYTE dac_num;

        for (dac_num = 0; (err == LTR_OK) && (dac_num < hnd->ModuleInfo.DacChCnt / LTR35_CH_PER_DAC); ++dac_num) {
            err = f_dac_register_set_checked(hnd, dac_num, PCM4104_REGADDR_SCR, sysctl | PCM4104_SCR_RST);
        }

        if (err == LTR_OK) {
            LTRAPI_SLEEP_MS(10);

            for (dac_num = 0; (err == LTR_OK) && (dac_num < hnd->ModuleInfo.DacChCnt / LTR35_CH_PER_DAC); ++dac_num) {
                err = f_dac_register_set_checked(hnd, dac_num, PCM4104_REGADDR_SCR, sysctl);
            }
        }
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_DIAsyncIn(TLTR35 *hnd, BYTE *di_state) {
    INT err;
    DWORD status;
    err = LTR35_GetStatus(hnd, &status);
    if ((err == LTR_OK) && (di_state != NULL)) {
        BYTE di_res = 0;
        if (status & LTR35_STATUS_FLAG_DI1_STATE)
            di_res |= 1;
        if (status & LTR35_STATUS_FLAG_DI2_STATE)
            di_res |= 2;
        *di_state = di_res;
    }
    return err;

}


LTR35API_DllExport(INT) LTR35_DacRegWrite(TLTR35 *hnd, BYTE dac_num, BYTE reg, BYTE value) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        err = f_dac_register_set(hnd, dac_num, reg, value);
    }
    return err;
}

LTR35API_DllExport(INT) LTR35_DacRegRead(TLTR35 *hnd, BYTE dac_num, BYTE reg, BYTE *value) {
    INT err = f_check_open_and_stopped(hnd);
    if (err == LTR_OK) {
        err = f_dac_register_get(hnd, dac_num, reg, value);
    }
    return err;
}


LTR35API_DllExport(INT) LTR35_FillArithPhaseDegree(TLTR35_ARITH_PHASE *phase_code, double degree, double *res_degree) {
    INT err = ((phase_code == NULL) || (degree < 0) || !(degree < 360)) ? LTR_ERROR_PARAMETRS : LTR_OK;
    if (err == LTR_OK) {
         phase_code->CodeH = (DWORD)((degree * 65536. * 65536.)/360 + 0.5);
         phase_code->Reserved = 0;
         if (res_degree) {
            *res_degree = ((double)(phase_code->CodeH * 360)/ 65536) / 65536.;
         }
    }
    return err;
}
