/*
 * Библиотека для работы с модулем LTR11.
 * Обеспечивается выполнение базовых операций (старт, стоп, тест и т.д.) с модулем LTR11.
 * Для работы с модулем необходимо иметь запущенный сервер и открытый канал связи с модулем.
 * Взаимодействие с модулем осуществляется передачей и приемом команд через сервер.
 * Данные от модуля принимаются с помощью функции из общей библиотеки.
 */

#include <string.h>
#include <stdlib.h>
#include "ltr11api.h"
#include "crc.h"
#include "ltrmodule.h"
#include "ltimer.h"

#define CONFIG_DATA_SIZE (75)                    /* размер конфигурационных данных в байтах */


/*================================================================================================*/
/* Сообщения об ошибках (соответсвуют кодам ошибок, сообщение 0 - код -1000, 1 - -1001 и т.д.) */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR11_ERR_INVALID_DESCR, "Указатель на описатель модуля равен NULL"},
    {LTR11_ERR_INVALID_ADCMODE, "Недопустимый режим запуска модуля"},
    {LTR11_ERR_INVALID_ADCLCHQNT, "Недопустимое количество логических каналов"},
    {LTR11_ERR_INVALID_ADCRATE, "Недопустимое значение частоты дискретизации АЦП модуля"},
    {LTR11_ERR_INVALID_ADCSTROBE, "Недопустимый источник тактовой частоты для АЦП"},
    {LTR11_ERR_GETFRAME, "Не удалось получить кадр данных с АЦП"},
    {LTR11_ERR_GETCFG, "Не удалось получить конфигурацию модуля"},
    {LTR11_ERR_CFGDATA, "Ошибка при чтении конфигурационных данных"},
    {LTR11_ERR_CFGSIGNATURE, "Неверное значение первого байта конфигурационной записи модуля"},
    {LTR11_ERR_CFGCRC, "Ошибочная контрольная сумма конфигурационной записи"},
    {LTR11_ERR_INVALID_ARRPOINTER, "Указатель на массив равен NULL"},
    {LTR11_ERR_ADCDATA_CHNUM, "Неверный номер канала в массиве полученных от АЦП данных"},
    {LTR11_ERR_INVALID_CRATESN, "Указатель на строку с серийным номером крейта равен NULL"},
    {LTR11_ERR_INVALID_SLOTNUM, "Недопустимый номер слота в крейте"},
    {LTR11_ERR_NOACK, "Нет подтверждения от модуля LTR11"},
    {LTR11_ERR_MODULEID, "Модуль не является LTR11"},
    {LTR11_ERR_INVALIDACK, "Неверное подтверждение от модуля LTR11"},
    {LTR11_ERR_ADCDATA_SLOTNUM, "Неверный номер слота в массиве полученных от АЦП данных"},
    {LTR11_ERR_ADCDATA_CNT, "Неверный счетчик пакетов в массиве полученных от АЦП данных"},
    {LTR11_ERR_INVALID_STARTADCMODE, "Неверный режим старта сбора данных"}
};


/* Константы подтверждений от модуля LTR11 */

static const DWORD modulemode_ack = LTR010CMD_INSTR | 0x012;   /* установка режима модуля */
static const DWORD start_ack = LTR010CMD_INSTR | 0x11;        /* подтверждение начала сбора данных */
static const DWORD stop_ack = LTR010CMD_INSTR | 0x10;         /* переход в режим ожидания */
static const DWORD lch_sw_state_mask = 0x3F;
/* Передаваемые в модуль команды */
static const unsigned dummy_cmd      = LTR010CMD_INSTR | 0x00;               /* неиспользуемая модулем команда */
static const unsigned getconfig_cmd  = LTR010CMD_INSTR | 0x0C;           /* запрос конфигурационных данных */
static const unsigned modulemode_cmd = LTR010CMD_INSTR | 0x18;          /* установка режимов АЦП */
static const unsigned start_cmd      = LTR010CMD_INSTR | 0x0A;               /* запуск сбора данных */
static const unsigned stop_cmd       = LTR010CMD_INSTR | 0x05;                /* переход в режим ожидания */



/* Возможные значения пределителя частоты модуля, отсортированные по возрастанию */
static const int prescalers[] = {1, 8, 64, 256, 1024};
/*================================================================================================*/

/*================================================================================================*/
static double eval_adcrate(int p, long d);
static INT start_ltr11(PTLTR11 hnd, int frame);
/*================================================================================================*/

/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/

#ifdef _WIN32
int WINAPI DllEntryPoint (HINSTANCE hinst, unsigned long reason, void* lpReserved) {
    return 1;
}
#endif
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
static double eval_adcrate (int p, long d) {
    /*
     * Вычисление частоты дискретизации АЦП.
     * ОПИСАНИЕ
     *   По значениям делителя и пределителя вычисляется частота дискретизации.
     * ПАРАМЕТРЫ
     *   p - значение пределителя;
     *   d - значение делителя.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Частота дискретизации модуля. При ошибках в вычислениях - 0.
     */

    double denom;
    double ret_val = 0.0;

    if ((p == 1) && (d == 36)) {              /* особый случай - частота 400 кГц */
        ret_val = LTR11_MAX_ADC_FREQ;
    } else if ((denom = (double)p * ((double)d + 1)) > 0) {
        ret_val = (LTR11_CLOCK * 1000) / denom;
    }

    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/


LTR11API_DllExport(INT) LTR11_FindAdcFreqParams(double adcFreq, INT *prescaler, INT *divider, double *resultAdcFreq) {
    INT fnd_presc;
    INT fnd_divider;
    double nonmax_high = eval_adcrate(1, 37);

    if (adcFreq >= (LTR11_MAX_ADC_FREQ + nonmax_high)/2) {
        fnd_presc = 1;
        fnd_divider = 36;
    } else {
        unsigned p_idx=0;
        int fnd = 0;
        for (p_idx = 0; (p_idx < sizeof(prescalers)/sizeof(prescalers[0])) && !fnd; p_idx++) {
            fnd_presc = prescalers[p_idx];
            fnd_divider = (INT)((LTR11_CLOCK*1000./fnd_presc)/adcFreq + 0.5) - 1;
            if (fnd_divider < LTR11_MAX_ADC_DIVIDER) {
                fnd = 1;
                if (fnd_divider < LTR11_MIN_ADC_DIVIDER) {
                    fnd_divider = LTR11_MIN_ADC_DIVIDER;
                }
            }
        }

        /* если не нашли => выбираем минимальную частоту */
        if (!fnd) {
            fnd_divider = LTR11_MAX_ADC_DIVIDER;
        }
    }

    if (prescaler!=NULL)
        *prescaler=fnd_presc;
    if (divider!=NULL)
        *divider=fnd_divider;
    if (resultAdcFreq!=NULL)
        *resultAdcFreq = eval_adcrate(fnd_presc, fnd_divider);
    return LTR_OK;
}


LTR11API_DllExport(INT) LTR11_SearchFirstFrame(TLTR11 *hnd, const DWORD *data, DWORD size,
                                                DWORD *frame_idx) {
    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR :
        ((data == NULL) || (size == 0) || (frame_idx == NULL)) ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (res == LTR_OK) {
        INT i;
        INT idx_first = -1;
        INT first_ch = hnd->LChTbl[0] & lch_sw_state_mask;


        for (i = 0; ((i < (INT)size) && (idx_first < 0)); i++, data++) {
            INT rcv_ch  = *data & lch_sw_state_mask;
            if (rcv_ch == first_ch)
                idx_first = i;
        }

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


/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_Close(PTLTR11 hnd) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK)
        err = LTR_Close(&hnd->Channel);

    return err;
}

LTR11API_DllExport(INT) LTR11_IsOpened(PTLTR11 hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_GetConfig(PTLTR11 hnd) {
    /*
     * Получение данных о конфигурации модуля LTR11.
     * ОПИСАНИЕ
     *   В модуль передается команда выдачи данных конфигурационных данных и ожидается прием
     *   этих данных и заполнение соответствующих полей в структуре, описывающей модуль.
     *   Формат принимаемого массива (после преобразования из 2-битного вида в байтный):
     *     Байт                   Содержимое
     *      0            сигнатура конфигурационной записи
     *      1-2          версия
     *      3-16         дата создания ПО
     *      17-24        наименование модуля
     *      25-40        серийный номер модуля
     *      41-44        смещение нуля для первого диапазона
     *      45-48        масштабный коэффициент для первого диапазона
     *      49-52        смещение нуля для второго диапазона
     *      53-56        масштабный коэффициент для второго диапазона
     *      57-60        смещение нуля для третьего диапазона
     *      61-64        масштабный коэффициент для третьего диапазона
     *      65-68        смещение нуля для четвертого диапазона
     *      69-72        масштабный коэффициент для четвертого диапазона
     *      73-74        контрольная сумма
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель состояния модуля.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

    BYTE cfg_arr[CONFIG_DATA_SIZE];
    /* Первый байт конфигурационных данных */
    const int cfg_signature = 0x5A;
    /* Тайм-аут на прием одной команды при чтении конфигурации в мс */
    const DWORD cmd_tmout = 10;
    int i;
    DWORD rd_buf[CONFIG_DATA_SIZE * 4];          /* один байт передается 4-мя командами */
    INT ret_val = LTR11_IsOpened(hnd);

    if (ret_val == LTR_OK) {
        DWORD cmd;

        cmd = ltr_module_fill_cmd_parity(getconfig_cmd, 0);
        ret_val = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        if (ret_val == LTR_OK) {
            ret_val = ltr_module_recv_cmd_resp_tout(&hnd->Channel, rd_buf, CONFIG_DATA_SIZE * 4,
                            CONFIG_DATA_SIZE * 4 * cmd_tmout + LTR_MODULE_CMD_RECV_TIMEOUT);
        }
        if (ret_val == LTR_OK) {
            /* преобразование принятых данных в последовательность байт с проверкой */
            if ((rd_buf[0] & 0x803CUL) != 0x801CUL) {   /* проверка первого байта */
                ret_val = LTR11_ERR_CFGDATA;
            }
        }

        if (ret_val == LTR_OK) {
            int j;
            DWORD *pbuf = rd_buf;

            rd_buf[0] &= ~0x10UL;                 /* сброс признака первого слова */
            for (i = 0; ((i < CONFIG_DATA_SIZE) && (ret_val == LTR_OK)); i++) {
                cfg_arr[i] = 0;
                for (j = 6; ((j >= 0) && (ret_val == LTR_OK)); j-= 2) {
                    if ((*pbuf & 0x803CUL) != 0x800CUL) {
                        ret_val = LTR11_ERR_CFGDATA;
                    } else {
                        cfg_arr[i] |= (*pbuf & 0x03UL) << j;
                    }
                    pbuf++;
                }
            }
        }

        /* занесение принятых данных в структуру с конфигурацией */
        if (ret_val == LTR_OK) {
            if (cfg_arr[0] != cfg_signature) {
                    ret_val = LTR11_ERR_CFGSIGNATURE;
            } else if (eval_crc16(0, cfg_arr, CONFIG_DATA_SIZE - 2) !=
                       (((unsigned short)cfg_arr[CONFIG_DATA_SIZE - 2] << 8 |
                       (unsigned short)cfg_arr[CONFIG_DATA_SIZE - 1]) & 0xFFFFU)
                      ) {
                ret_val = LTR11_ERR_CFGCRC;
            } else  {
                float *pcfg;

                hnd->ModuleInfo.Ver = (unsigned)cfg_arr[1] << 8 | (unsigned)cfg_arr[2];
                (void)memcpy((char *)hnd->ModuleInfo.Date, (char *)(cfg_arr + 3), 14);
                (void)memcpy((char *)hnd->ModuleInfo.Name, (char *)(cfg_arr + 17), 8);
                (void)memcpy((char *)hnd->ModuleInfo.Serial, (char *)(cfg_arr + 25), 16);
                pcfg = (float *)(cfg_arr + 41);
                for (i = 0; (i < LTR11_ADC_RANGEQNT); i++) {
                    hnd->ModuleInfo.CbrCoef[i].Offset = *pcfg++;
                    hnd->ModuleInfo.CbrCoef[i].Gain = *pcfg++;
                }
            }
        }
    }

    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(LPCSTR) LTR11_GetErrorString(INT err) {
    /*
     * Определение строки с сообшением об ошибке, соответсвующей заданному коду ошибки.
     * ОПИСАНИЕ
     * ПАРАМЕТРЫ
     *   err - код ошибки (должен быть отрицительным), возвращается функциями работы с LTR11.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Указатель на строку с сообщением об ошибке, соответсвующему заданному коду.
     */
     
    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);
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_GetFrame(PTLTR11 hnd, DWORD *buf) {
    /*
     * Сбор одного кадра с АЦП модуля LTR11.
     * ОПИСАНИЕ
     *   Модуль LTR11 переводится в режим сбора одного кадра данных.
     *   Принятый поток данных от модуля заносится в буфер. Окончание сбора данных производится
     *   по приему подтверждения перехода в режим ожидания или тайм-ауту (длительность тайм-аута
     *   зависит от частоты дискретизации).
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель состояния модуля;
     *   buf - указатель на буфер, в который будут записываться принимаемые от модуля данные.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Положительное число или 0 - количество байт в принятом кадре.
     *   Отрицательное число       - код ошибки (см. заголовочный файл).
     */

    DWORD rd_buf[LTR11_MAX_LCHANNEL + 1];
    INT ret_val = LTR11_IsOpened(hnd);

    if (ret_val == LTR_OK) {
        if ((hnd->LChQnt <= 0) || (LTR11_MAX_LCHANNEL < hnd->LChQnt)) {
            ret_val = LTR11_ERR_INVALID_ADCLCHQNT;
        } else if (hnd->ChRate <= 0.0) {
            ret_val = LTR11_ERR_INVALID_ADCRATE;
        }
    }
    if (ret_val == LTR_OK) {
        ret_val = start_ltr11(hnd, 1);
        if (ret_val == LTR_OK) {
            DWORD tm_out;
            /* вычисление значения тайм-аута сбора данных для данной частоты дискретизации */
            tm_out = (DWORD)(1. / hnd->ChRate + LTR_MODULE_CMD_RECV_TIMEOUT + 0.5);
            ret_val = ltr_module_recv_cmd_resp_tout(&hnd->Channel, rd_buf, hnd->LChQnt + 1, tm_out);
            if (ret_val == LTR_OK) {
                unsigned a;
                /* проверка окончания сбора кадра (по приходу подтверждения) */
                a = LTR_MODULE_CMD_GET_CMDCODE(rd_buf[hnd->LChQnt]);

                if (a == stop_ack) {                   /* получен один кадр данных */
                    ret_val = hnd->LChQnt;
                    memcpy(buf, rd_buf, hnd->LChQnt * sizeof(DWORD));
                } else {       /* нет подтверждения окончания сбора данных */
                    ret_val = LTR11_ERR_GETFRAME;
                }
            }
        }
    }
    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/

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

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

    for (ch = 0; (ch < LTR11_ADC_RANGEQNT); ++ch) {
        hnd->ModuleInfo.CbrCoef[ch].Offset = 0.0;
        hnd->ModuleInfo.CbrCoef[ch].Gain   = 1.0;
    }
}


/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_Init(PTLTR11 hnd) {
    /*
     * Инициализация описателя модуля LTR11.
     * ОПИСАНИЕ
     *   Полям структуры описателя модуля присваиваются значения "по умолчанию".
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель модуля.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

    INT res = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        int i;
        memset(hnd, 0, sizeof(*hnd));

        hnd->size = sizeof(TLTR11);
        hnd->StartADCMode = LTR11_STARTADCMODE_INT;
        hnd->InpMode = LTR11_INPMODE_INT;
        hnd->LChQnt = 0;
        for (i = 0; (i < LTR11_MAX_LCHANNEL); i++) {
            hnd->LChTbl[i] = 0;
        }
        hnd->ADCMode = LTR11_ADCMODE_ACQ;
        hnd->ADCRate.divider = 36;               /* 400 кГц */
        hnd->ADCRate.prescaler = 1;
        hnd->ChRate = eval_adcrate(hnd->ADCRate.prescaler, hnd->ADCRate.divider)/1000;

        f_info_init(hnd);

        res = LTR_Init(&hnd->Channel);
    }
    return res;
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_Open(PTLTR11 hnd, DWORD net_addr, WORD net_port,
                                   const CHAR *crate_sn, INT slot_num) {
    /*
     * Начало работы с модулем LTR11.
     * ОПИСАНИЕ
     *   Открывается канал связи с модулем и выполняется аппаратный сброс модуля.
     * ПАРАМЕТРЫ
     *   hnd      - указатель на описатель модуля;
     *   net_addr - сетевой адрес клиента (вызывающего функцию приложения) в формате (hex):
     *              MsbAABBCCDDLsb - AA.BB.CC.DD;
     *   net_port - сетевой порт сервера;
     *   crate_sn - серийный номер крейта, в котором установлен модуль;
     *   slot_num - номер слота крейта, в который установлен модуль.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */
    DWORD open_flags = 0;
    INT warning = LTR_OK;
    INT 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_LTR11, &open_flags, NULL, &warning);
    }

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

    if ((err==LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        LTRAPI_SLEEP_MS(200);
    }

    if (err != LTR_OK) {
        LTR11_Close(hnd);
    }

    return err==LTR_OK ? warning : err;
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_ProcessData(PTLTR11 hnd, const DWORD *src, double *dest,
                                          INT *size, BOOL calibr, BOOL volt) {
    /*
     * Обработка полученных от модуля данных.
     * ОПИСАНИЕ
     *   Полученные данные калибруются (калибровочные коэффициенты предварительно должны быть
     *   получены из модуля) и, если требуется, приводятся к диапазону измерений.
     * ПАРАМЕТРЫ
     *   hnd    - указатель на описатель модуля;
     *   src    - указатель на полученный от модуля массив данных;
     *   dest   - указатель на массив, в который будут записаны обработанные данные;
     *   size   - размер полученных данных в отсчетах АЦП (т.е. словах от модуля), на выходе из
     *            функции равен количеству обработанных и помещенных в выходной массив отсчетов
     *            АЦП;
     *   calibr - признак необходимости применения калибровочных коэффициентов;
     *   volt   - признак необходимости перевода кодов АЦП в вольты (в соответствии с диапазоном).
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

    INT ret_val = LTR_OK;

    if (hnd == NULL) {
        ret_val = LTR_ERROR_INVALID_MODULE_DESCR;
    } else if ((hnd->LChQnt <= 0) || (LTR11_MAX_LCHANNEL < hnd->LChQnt)) {
        ret_val = LTR11_ERR_INVALID_ADCLCHQNT;
    } else if ((src == NULL) || (dest == NULL)) {
        ret_val = LTR11_ERR_INVALID_ARRPOINTER;
    } else {
        /* порядок следования данных от каналов и их диапазоны */
        struct {
            double adc_gain;
            double adc_offset;
            double range;
        } ch_tbl[LTR11_MAX_LCHANNEL];
        /* двухбитный счетчик слов данных от модуля */
        DWORD cnt;
        /* один кадр данных */
        SHORT frame[LTR11_MAX_LCHANNEL];
        /* диапазоны измерений в Вольтах */
        const double ranges[LTR11_ADC_RANGEQNT] = {10.0, 2.5, 0.625, 0.156};
        /* разрядность АЦП (макс. значение по модулю) */
        const int resolution = 8192;
        double *pdest = dest;
        const DWORD *psrc = src;
        WORD slot_num = hnd->Channel.cc - LTR_CC_CHNUM_MODULE1;
        int vch_ind;
        int vch_qnt;                             /* количество виртуальный каналов */


        /* определение порядка следования и диапазонов каналов */
        vch_qnt = hnd->LChQnt;
        for (vch_ind = 0; (vch_ind < vch_qnt); vch_ind++) {
            BYTE range_num = (hnd->LChTbl[vch_ind] >> 6) & 0x03;
            ch_tbl[vch_ind].range = ranges[range_num];
            ch_tbl[vch_ind].adc_offset = hnd->ModuleInfo.CbrCoef[range_num].Offset;
            ch_tbl[vch_ind].adc_gain = hnd->ModuleInfo.CbrCoef[range_num].Gain;
        }

        cnt = (*psrc >> 6) & 0x03;
        vch_ind = 0;
        for (; (psrc < src + *size); psrc++) {
            /* проверки:
             *  1) слово не является командой (маска 0x80) и номер слота (0x0F)
             *  2) двухбитный счетчик слов
             */
            if (((*psrc >> 8) & 0x8F) != slot_num)
                ret_val = LTR11_ERR_ADCDATA_SLOTNUM;
            if (cnt != ((*psrc >> 6) & 0x03))
                ret_val = LTR11_ERR_ADCDATA_CNT;

            if ((*psrc & lch_sw_state_mask) != (hnd->LChTbl[vch_ind] & lch_sw_state_mask)) {
                ret_val = LTR11_ERR_ADCDATA_CHNUM;
                vch_ind = 0;                             /* поиск начала следующего кадра */
            } else {
                /* сохранение отсчета АЦП */
                frame[vch_ind] = (SHORT)(*psrc >> 16 & 0xFFFFU);
                if (++vch_ind >= vch_qnt) {              /* обработан кадр отсчетов АЦП */
                    /* обработка и сохранение данных в выходном массиве */
                    for (vch_ind = 0; (vch_ind < vch_qnt); vch_ind++) {
                        double dt;

                        dt = frame[vch_ind];
                        /* применение калибровочных коэффициентов */
                        if (calibr == TRUE)
                            dt = (dt + ch_tbl[vch_ind].adc_offset) * ch_tbl[vch_ind].adc_gain;
                        if (volt == TRUE)                /* приведение к Вольтам */
                            dt = dt * ch_tbl[vch_ind].range / resolution;
                        *pdest++ = dt;
                    }
                    vch_ind = 0;
                }
            }
            cnt = (cnt + 1) & 0x03;
        }
        *size = (INT)(pdest - dest);
    } /*else @else if ((src == NULL) || (dest == NULL))@*/

    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_SetADC(PTLTR11 hnd) {
    /*
     * Установка режимов работы модуля LTR11.
     * ОПИСАНИЕ
     *   Выполняется формирование и передача управляющей таблицы, значений делителя и пределителя в
     *   частоты для АЦП и количество логических каналов модуль.
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель состояния модуля.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

#if (LTR11_MAX_LCHANNEL % 2 != 0)
#error Channel quantity must be even
#endif
#define PARAM_ARR_SIZE (LTR11_MAX_LCHANNEL / 2 + 3)

    INT ret_val = (hnd->LChQnt <= 0) || (LTR11_MAX_LCHANNEL < hnd->LChQnt) ?
                LTR11_ERR_INVALID_ADCLCHQNT :LTR11_IsOpened(hnd);

    if (ret_val == LTR_OK) {
        /* подготовка передаваемой в модуль команды
         * 1 - определение размера передаваемых данных в байтах;
         * 2 - формирование управляющей таблицы;
         * 3 - получение значений делителя и пределителя тактовой частоты модуля для заданной
         *     частоты дискретизации.
         */
        DWORD cmdbuf[PARAM_ARR_SIZE];            /* команда для передачи в модуль */
        WORD tx_data;
        long divider = 0;
        int i;
        DWORD *pcmdbuf;
        unsigned int presc;

        pcmdbuf = cmdbuf;                                                                     /*1*/
        tx_data = (((unsigned)PARAM_ARR_SIZE * 2) << 8) & 0xFFFFU;
        *pcmdbuf++ = ltr_module_fill_cmd_parity(modulemode_cmd | 0x01, tx_data);

        for (i = 0; (i < LTR11_MAX_LCHANNEL); i += 2) {                                       /*2*/
            tx_data = (((unsigned)hnd->LChTbl[i] & 0xFF) << 8 |
                ((unsigned)hnd->LChTbl[i + 1] & 0xFF)) & 0xFFFFU;
            *pcmdbuf++ = ltr_module_fill_cmd_parity(modulemode_cmd, tx_data);
        }

        presc = 0;                                                                            /*3*/
        switch (hnd->InpMode) {
            case LTR11_INPMODE_EXTRISE:
            case LTR11_INPMODE_EXTFALL:
                divider = hnd->InpMode;
                break;
            case LTR11_INPMODE_INT: {
                    double adc_rate;
                /* поиск значения пределителя в массиве с пределителями */
                
                    while ((presc < sizeof prescalers / sizeof prescalers[0])
                           && (prescalers[presc] != hnd->ADCRate.prescaler)) {
                        presc++;
                    }
                    divider = hnd->ADCRate.divider;
                    adc_rate = eval_adcrate(hnd->ADCRate.prescaler, divider);
                    if ((presc >= sizeof prescalers / sizeof prescalers[0])
                        || (divider < LTR11_MIN_ADC_DIVIDER)
                        || (LTR11_MAX_ADC_DIVIDER < divider)
                        || (adc_rate > LTR11_MAX_ADC_FREQ)
                       ) {
                        ret_val = LTR11_ERR_INVALID_ADCRATE;
                    } else {
                        hnd->ChRate = (adc_rate / hnd->LChQnt) / 1000;
                    }
                }
                break;
            default:
                ret_val = LTR11_ERR_INVALID_ADCSTROBE;
        }

        if (ret_val == LTR_OK) {
            DWORD ack = modulemode_ack;
        
            /* передача команды в модуль и проверка наличия подтверждения */
            tx_data = ((unsigned)divider & 0xFFFFU);
            *pcmdbuf++ = ltr_module_fill_cmd_parity(modulemode_cmd, tx_data);
            tx_data = (((unsigned)presc & 0xFF) << 8) | (((unsigned)hnd->LChQnt - 1) & 0xFF);
            *pcmdbuf = ltr_module_fill_cmd_parity(modulemode_cmd, tx_data);
            ret_val = ltr_module_send_with_single_resp(&hnd->Channel, cmdbuf, PARAM_ARR_SIZE,
                                                   &ack);
        }
    }

    return ret_val;
#undef PARAM_ARR_SIZE
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_Start(PTLTR11 hnd) {
    /*
     * Запуск сбора данных модулем LTR11.
     * ОПИСАНИЕ
     *   Выполняется передача команды запуска преобразования на бесконечный сбор данных.
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель модуля.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

    return start_ltr11(hnd, 0);
}

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

/*------------------------------------------------------------------------------------------------*/
LTR11API_DllExport(INT) LTR11_Stop(PTLTR11 hnd) {
    /*
     * Перевод модуля LTR11 в режим ожидания.
     * ОПИСАНИЕ
     *   Выполняется передача команды останова модуля и ожидается подтверждение выполнения команды
     *   от модуля.
     * ПАРАМЕТРЫ
     *   hnd - указатель на описатель состояния модуля.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */

    DWORD cmd = stop_cmd;
    INT ret_val = LTR11_IsOpened(hnd);
    if (ret_val==LTR_OK)
        ret_val = ltr_module_stop(&hnd->Channel, &cmd, 1, stop_ack | LTR010CMD_INSTR, 0, 0, NULL);
    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
static INT start_ltr11(PTLTR11 hnd, int frame) {
    /*
     * Запуск сбора данных модулем LTR11.
     * ОПИСАНИЕ
     *   Выполняется передача команды запуска преобразования в заданном режиме и ожидается (с
     *   тайм-аутом) подтверждение команды.
     * ПАРАМЕТРЫ
     *   hnd   - указатель на описатель модуля;
     *   frame - признак необходимости запуска АЦП на сбор одного кадра.
     * ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
     *   Код ошибки (см. заголовочный файл).
     */


    INT ret_val = LTR11_IsOpened(hnd);

    if (ret_val == LTR_OK) {
        int mode = hnd->ADCMode;
        if ((mode != LTR11_ADCMODE_ACQ) &&
                (mode != LTR11_ADCMODE_TEST_U1P) &&
                (mode != LTR11_ADCMODE_TEST_U1N) &&
                (mode != LTR11_ADCMODE_TEST_U2N) &&
                (mode != LTR11_ADCMODE_TEST_U2P)) {
            ret_val = LTR11_ERR_INVALID_ADCMODE;
        } else if ((hnd->StartADCMode != LTR11_STARTADCMODE_INT)
                   && (hnd->StartADCMode != LTR11_STARTADCMODE_EXTRISE)
                   && (hnd->StartADCMode != LTR11_STARTADCMODE_EXTFALL) ) {
            ret_val = LTR11_ERR_INVALID_STARTADCMODE;
        } else {
            DWORD ack;
            DWORD cmd;


            mode |= hnd->StartADCMode << 6;
            if (frame)
                mode |= 0x8000U;
            cmd = ltr_module_fill_cmd_parity(start_cmd, mode);
            ack = start_ack;

            ret_val = ltr_module_send_with_single_resp(&hnd->Channel, &cmd, 1, &ack);
        }
    }
    return ret_val;
}
/*------------------------------------------------------------------------------------------------*/
/*================================================================================================*/


/* получение данных из модуля */
LTR11API_DllExport(INT) LTR11_Recv(TLTR11 *hnd, DWORD *data, DWORD *tmark,
                                   DWORD size, DWORD timeout) {
    int ret_val = LTR11_IsOpened(hnd);
    if (ret_val==LTR_OK)
        ret_val=LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
    if ((ret_val>=0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF))
        ret_val=LTR_ERROR_RECV_OVERFLOW;
    return ret_val;
}

LTR11API_DllExport(BYTE) LTR11_CreateLChannel(BYTE phy_ch, BYTE mode, BYTE range) {
    BYTE lch = ((range & 0x3) << 6) | (phy_ch & 0xF);
    if (mode==LTR11_CHMODE_COMM)
        lch |= ((phy_ch >= 16) ? 3 : 2) << 4;
    else if (mode==LTR11_CHMODE_ZERO)
        lch |= (1 << 4);
    return lch;
}
