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

#include <string.h>
#include <stdlib.h>
#include "ltr12api.h"
#include "crc.h"
#include "ltrmodule.h"
#include "lbitfield.h"


#define CMD_GET_MODULEINFO              (LTR010CMD_INSTR | 0x0C)
#define CMD_SET_ADC                     (LTR010CMD_INSTR | 0x18)
#define CMD_SET_ADC_FIRST               (LTR010CMD_INSTR | 0x19)
#define CMD_START_ACQ                   (LTR010CMD_INSTR | 0x0A)
#define CMD_STOP_ACQ                    (LTR010CMD_INSTR | 0x05)
#define CMD_FLASH_WRITE                 (LTR010CMD_INSTR | 0x14)
#define CMD_FLASH_WRITE_FIRST           (LTR010CMD_INSTR | 0x15)
#define CMD_FLASH_READ                  (LTR010CMD_INSTR | 0x16)
#define CMD_FLASH_SET_RDSIZE            (LTR010CMD_INSTR | 0x17)


#define CMD_RESP_SET_ADC                (LTR010CMD_INSTR | 0x12)   /* установка режима модуля */
#define CMD_RESP_START_ACQ              (LTR010CMD_INSTR | 0x11)    /* подтверждение начала сбора данных */
#define CMD_RESP_STOP_ACQ               (LTR010CMD_INSTR | 0x10)         /* переход в режим ожидания */
#define CMD_RESP_FLASH_WRITE            (LTR010CMD_INSTR | 0x13)
#define CMD_RESP_SET_RDSIZE_ACK         (LTR010CMD_INSTR | 0x14)
#define CMD_RESP_MODULEINFO             (LTR010CMD_INSTR | 0x0C)
#define CMD_RESP_MODULEINFO_FIRST       (LTR010CMD_INSTR | 0x1C)
#define CMD_RESP_FLASH_DATA             (LTR010CMD_INSTR | 0x08)
#define CMD_RESP_FLASH_DATA_FIRST       (LTR010CMD_INSTR | 0x18)


#define CMD_RESPWDATA_DATA_MSK          0x3UL
#define CMD_RESPWDATA_CMD_MSK           (LTR_MODULE_CMD_CODE_MSK & ~CMD_RESPWDATA_DATA_MSK)
#define CMD_RESPWDATA_GET_CMDCODE(cmd)  (cmd & CMD_RESPWDATA_CMD_MSK)
#define CMD_RESPWDATA_GET_DATA(cmd)     (cmd & CMD_RESPWDATA_DATA_MSK)


#define DATA_WRD_CNTR                   (0x3UL << 6)
#define DATA_WRD_SWSTATE                (0x3FUL)
#define DATA_WRD_ADCCODE                (0xFFFFU << 16)
#define DATA_CNTR_MODULE                3

#define MCU_MAX_PARAM_ARR_SIZE          (MCU_MODULE_INFO_PAGE_SIZE + 6)

#define MCU_MODULE_SPI_BUF_SIZE         2
#define MCU_FLASHPAGE_SIZE              64

#define MCU_FLASH_USERDATA_PAGE_ADDR    0x1000
#define MCU_FLASH_USERDATA_PAGE_SIZE    0x800

#define MCU_MODULE_INFO_PAGE_ADDR       0x80
#define MCU_MODULE_INFO_PAGE_SIZE       320
#define MCU_MODULE_INFO_SIGNATURE       0x5B
#define MCU_MODULE_INFO_FMT_VERSION     1
#define MCU_MODULE_INFO_MIN_SIZE        sizeof(t_mcu_module_info)

#if MCU_FLASH_USERDATA_PAGE_SIZE != LTR12_FLASH_USERDATA_SIZE
    #error inconsistent user flash address size in api and mcu
#endif

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


#pragma pack(1)
typedef struct {
    WORD firmware_ver; /* версия ПО */
    BYTE firmware_date[14]; /* дата создания ПО (компиляции) */
    BYTE module_name[8]; /* название модуля */
    BYTE serial_num[16]; /* серийный номер модуля */
    BYTE reserved[8];
    struct {
        float offset;
        float k[32];
    } adc_calibr;
} t_mcu_module_info;


typedef struct {
    BYTE signature;
    BYTE fmt_ver;
    WORD info_size;
    t_mcu_module_info info;
    BYTE reserved[MCU_MODULE_INFO_PAGE_SIZE - sizeof(t_mcu_module_info) - 6];
    WORD crc; /* контрольная сумма конфигурационной записи (старшим байтом вперед) */
} t_mcu_module_info_page;

typedef struct {
    WORD divider;
    BYTE  prescaler;
    BYTE  start_scr;
    BYTE  reserved[7];
    BYTE  lch_cnt;
    BYTE  lch_tbl[LTR12_MAX_LCHANNELS_CNT];
} t_mcu_acq_cfg;

#pragma pack ()


/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTR12_ERR_INVALID_MODULEINFO_RESP_FORMAT, "Неверный формат ответов с информацией о модуле"},
    {LTR12_ERR_INVALID_CFG_LCH_CNT,            "Задано неверное количество логических каналов в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_PHY_CH_NUM,         "Задан неверный номер физического канала в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_CH_MODE,            "Задан неверный режим измерения канала в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_ADC_PRESCALER,      "Задано неверное значение предварительного делителя частоты АЦП в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_ADC_DIVIDER,        "Задано неверное значение делителя частоты АЦП в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_ADC_RATE,           "Заданная частота АЦП находится вне допустимого диапазона"},
    {LTR12_ERR_INVALID_CFG_CONV_START_SRC,     "Задан неверный источник запуска преобразования АЦП в настройках модуля"},
    {LTR12_ERR_INVALID_CFG_ACQ_START_SRC,      "Задан неверный источник запуска сбора данных в настройках модуля"},
    {LTR12_ERR_NO_END_OF_FRAME_RECVD,          "Не обнаружено действительного признака конца кадра в принятых данных"}
};


#define AVG_PROC_MAX   10000

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

typedef struct {
    BOOLEAN cntr_lost;
    BYTE cntr;
    t_avg_ctx offset;
} t_internal_params;

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

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


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

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

static INT f_get_prescaler_idx(WORD prescaler) {
    DWORD presc_idx = 0;
    while ((presc_idx < sizeof f_prescalers / sizeof f_prescalers[0])
           && (f_prescalers[presc_idx] != prescaler)) {
        presc_idx++;
    }
    return presc_idx >= sizeof f_prescalers / sizeof f_prescalers[0] ? -1 : (INT)presc_idx;
}


static BYTE f_fill_mcu_lch(const TLTR12_LCHANNEL *lch_cfg) {
    BYTE lch = (lch_cfg->PhyChannel & 0xF);
    if (lch_cfg->Mode == LTR12_CH_MODE_COMM)
        lch |= ((lch_cfg->PhyChannel >= 16) ? 3 : 2) << 4;
    else if (lch_cfg->Mode == LTR12_CH_MODE_ZERO)
        lch |= (1 << 4);
    return lch;
}

static INT f_acq_start(TLTR12 *hnd, int oneframe) {
    DWORD ack;
    DWORD cmd;
    WORD mode = 0;
    INT err = LTR_OK;
    if (oneframe)
        mode |= 0x8000U;
    cmd = ltr_module_fill_cmd_parity(CMD_START_ACQ, mode);
    ack = CMD_RESP_START_ACQ;

    err = ltr_module_send_with_single_resp(&hnd->Channel, &cmd, 1, &ack);
    if (err == LTR_OK) {
        t_internal_params *internal = LTR12_INTERNAL_PARAMS(hnd);
        hnd->State.Run = TRUE;
        internal->cntr_lost = TRUE;        
    }
    return err;
}


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

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

    hnd->ModuleInfo.Cbr.Offset = 0;
    for (ch = 0; (ch < LTR12_CHANNELS_CNT); ++ch) {
        hnd->ModuleInfo.Cbr.ChScale[ch] = 1.F;
    }

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


LTR12API_DllExport(INT) LTR12_Init(TLTR12 *hnd) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));

        hnd->Size = sizeof(*hnd);
        hnd->Internal = NULL;

        hnd->Cfg.AdcFreqParams.Is400KHz = TRUE;
        hnd->Cfg.AdcFreqParams.Divider = 36;
        hnd->Cfg.AdcFreqParams.Prescaler = 1;

        err = LTR_Init(&hnd->Channel);

        f_info_init(hnd);
    }
    return err;
}

LTR12API_DllExport(INT) LTR12_Open(TLTR12 *hnd, DWORD ltrd_addr, WORD ltrd_port, const CHAR *csn, INT slot) {
    INT warning = LTR_OK;
    DWORD open_flags = 0;
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (err == LTR_OK) {
        if (LTR12_IsOpened(hnd) == LTR_OK)
            LTR12_Close(hnd);
    }

    if (err == LTR_OK) {
        err = ltr_module_open(&hnd->Channel, ltrd_addr, ltrd_port, csn, slot, LTR_MID_LTR12,
            &open_flags, NULL, &warning);
        if (err == LTR_OK) {
            f_info_init(hnd);
            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)) {
        LTRAPI_SLEEP_MS(200);

        err = LTR12_GetConfig(hnd);
    }

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

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

LTR12API_DllExport(INT) LTR12_IsOpened(TLTR12 *hnd) {
    return (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

LTR12API_DllExport(INT) LTR12_Close(TLTR12 *hnd) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK) {
        err = LTR_Close(&hnd->Channel);
        free(hnd->Internal);
        hnd->Internal = NULL;
    }

    return err;
}


LTR12API_DllExport(INT) LTR12_GetConfig(TLTR12 *hnd) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;

    if (err == LTR_OK) {
         BYTE info_arr[MCU_MODULE_INFO_PAGE_SIZE];
         DWORD acks[MCU_MODULE_INFO_PAGE_SIZE * 4];
         DWORD cmd;

         cmd = ltr_module_fill_cmd_parity(CMD_GET_MODULEINFO, 0);
         err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
         if (err == LTR_OK) {
             err = ltr_module_recv_cmd_resp_tout(
                         &hnd->Channel, acks, sizeof(acks)/sizeof(acks[0]),
                         LTR_MODULE_CMD_RECV_TIMEOUT);
         }

         if (err == LTR_OK) {
             /* преобразование принятых данных в последовательность байт с проверкой */
             if (CMD_RESPWDATA_GET_CMDCODE(acks[0]) != CMD_RESP_MODULEINFO_FIRST) {   /* проверка первого байта */
                 err = LTR_ERROR_INVALID_CMD_RESPONSE;
             }
         }

         if (err == LTR_OK) {
             unsigned put_idx;
             const DWORD *ack_ptr = &acks[0];

             acks[0] &= ~0x10UL;                 /* сброс признака первого слова */
             for (put_idx = 0; ((put_idx < MCU_MODULE_INFO_PAGE_SIZE) && (err == LTR_OK)); put_idx++) {
                 int byte_pos_idx;
                 info_arr[put_idx] = 0;
                 for (byte_pos_idx = 6; ((byte_pos_idx >= 0) && (err == LTR_OK)); byte_pos_idx-= 2) {

                     if (CMD_RESPWDATA_GET_CMDCODE(*ack_ptr) != CMD_RESP_MODULEINFO) {
                         err = LTR12_ERR_INVALID_MODULEINFO_RESP_FORMAT;
                     } else {
                         info_arr[put_idx] |= CMD_RESPWDATA_GET_DATA(*ack_ptr) << byte_pos_idx;
                     }
                     ack_ptr++;
                 }
             }
         }

         /* занесение принятых данных в структуру с конфигурацией */
         if (err == LTR_OK) {
             const t_mcu_module_info_page *info_page = (const t_mcu_module_info_page *)info_arr;
             if (info_page->signature != MCU_MODULE_INFO_SIGNATURE) {
                 err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
             } else if (eval_crc16(0, info_arr, MCU_MODULE_INFO_PAGE_SIZE - 2) !=
                        info_page->crc) {
                 err = LTR_ERROR_FLASH_INFO_CRC;
             } else if (info_page->fmt_ver != MCU_MODULE_INFO_FMT_VERSION)  {
                 err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
             } else {
                 BYTE ch_idx;

                 hnd->ModuleInfo.FirmwareVersion = info_page->info.firmware_ver;
                 (void)memcpy((char *)hnd->ModuleInfo.FirmwareDate, (const char *)(info_page->info.firmware_date), LTR12_FIRMDATE_SIZE);
                 (void)memcpy((char *)hnd->ModuleInfo.Name, (const char *)(info_page->info.module_name), LTR12_NAME_SIZE);
                 (void)memcpy((char *)hnd->ModuleInfo.Serial, (const char *)(info_page->info.serial_num), LTR12_SERIAL_SIZE);
                 hnd->ModuleInfo.Cbr.Offset = info_page->info.adc_calibr.offset;
                 for (ch_idx = 0; (ch_idx < LTR12_CHANNELS_CNT); ch_idx++) {
                     hnd->ModuleInfo.Cbr.ChScale[ch_idx] = info_page->info.adc_calibr.k[ch_idx];
                 }

                 hnd->State.AdcZeroOffsetCode = -(double)hnd->ModuleInfo.Cbr.Offset;
             }
         }
    }
    return err;

}



LTR12API_DllExport(INT) LTR12_FillLChannel(TLTR12 *hnd, DWORD lch_num, BYTE phy_ch, BYTE mode) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if ((err == LTR_OK) && (lch_num >= LTR12_MAX_LCHANNELS_CNT))
        err = LTR_ERROR_PARAMETERS;

    if (err == LTR_OK) {
        hnd->Cfg.LChTbl[lch_num].PhyChannel = phy_ch;
        hnd->Cfg.LChTbl[lch_num].Mode = mode;
        hnd->Cfg.LChTbl[lch_num].Reserved = 0;
    }
    return err;
}


LTR12API_DllExport(INT) LTR12_CalcAdcFreq(const TLTR12_ADCFREQ_CFG *cfg, double *resultAdcFreq) {
    int err = LTR_OK;
    double res_freq = 0.0;
    if (cfg->Is400KHz || ((cfg->Prescaler == 1) && (cfg->Divider == 36))) {              /* особый случай - частота 400 кГц */
        res_freq = LTR12_MAX_ADC_FREQ;
    } else  {
        if (f_get_prescaler_idx(cfg->Prescaler) < 0) {
            err = LTR12_ERR_INVALID_CFG_ADC_PRESCALER;
        } else if ((cfg->Divider < LTR12_MIN_ADC_DIVIDER)
                || (cfg->Divider > LTR12_MAX_ADC_DIVIDER)) {
            err = LTR12_ERR_INVALID_CFG_ADC_DIVIDER;
        } else {
            DWORD denom = cfg->Prescaler * (cfg->Divider + 1);
            res_freq = ((double)LTR12_IN_CLOCK_FREQ) / denom;
            if (res_freq > LTR12_MAX_ADC_FREQ) {
                err = LTR12_ERR_INVALID_CFG_ADC_RATE;
            }
        }
    }

    if (resultAdcFreq)
        *resultAdcFreq = res_freq;

    return err;
}

LTR12API_DllExport(INT) LTR12_FindAdcFreqParams(double adcFreq, TLTR12_ADCFREQ_CFG *params, double *resultAdcFreq) {
    BOOLEAN fnd_400khz = FALSE;
    WORD fnd_presc = 0;
    INT fnd_divider = 0;
    const TLTR12_ADCFREQ_CFG nonmax_high_cfg = {FALSE, 1, 37, {0}};
    TLTR12_ADCFREQ_CFG res_params;
    double nonmax_high;

    LTR12_CalcAdcFreq(&nonmax_high_cfg, &nonmax_high);

    if (adcFreq >= (LTR12_MAX_ADC_FREQ + nonmax_high)/2) {
        fnd_presc = 1;
        fnd_divider = 36;
        fnd_400khz = TRUE;
    } else {
        unsigned p_idx=0;
        int fnd = 0;
        for (p_idx = 0; (p_idx < sizeof(f_prescalers)/sizeof(f_prescalers[0])) && !fnd; p_idx++) {
            fnd_presc = f_prescalers[p_idx];
            fnd_divider = (INT)(((double)LTR12_IN_CLOCK_FREQ/fnd_presc)/adcFreq + 0.5) - 1;
            if (fnd_divider < LTR12_MAX_ADC_DIVIDER) {
                fnd = 1;
                if (fnd_divider < LTR12_MIN_ADC_DIVIDER) {
                    fnd_divider = LTR12_MIN_ADC_DIVIDER;
                }
            }
        }

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

    memset(&res_params, 0, sizeof(TLTR12_ADCFREQ_CFG));
    res_params.Divider = (DWORD)fnd_divider;
    res_params.Prescaler = fnd_presc;
    res_params.Is400KHz = fnd_400khz;
    if (params != NULL) {
        *params = res_params;
    }
    return LTR12_CalcAdcFreq(&res_params, resultAdcFreq);
}

LTR12API_DllExport(INT) LTR12_FillAdcFreqParams(TLTR12 *hnd, double adcFreq, double *resultAdcFreq) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR12_FindAdcFreqParams(adcFreq, &hnd->Cfg.AdcFreqParams, resultAdcFreq);
    }
    return err;
}


static INT f_set_adc(TLTR12 *hnd, int rst) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;

    if (err == LTR_OK) {
        DWORD ch_idx;
        t_mcu_acq_cfg mcu_cfg;
        double adc_rate = 0;
        DWORD total_ch_cnt = hnd->Cfg.LChCnt + hnd->Cfg.BgLChCnt;

        memset(&mcu_cfg, 0, sizeof(mcu_cfg));

        if ((hnd->Cfg.LChCnt == 0) || (total_ch_cnt > LTR12_MAX_LCHANNELS_CNT)) {
            err = LTR12_ERR_INVALID_CFG_LCH_CNT;
        } else {
            mcu_cfg.lch_cnt = 0;
        }

        for (ch_idx = 0; (ch_idx < hnd->Cfg.LChCnt) && (err == LTR_OK); ch_idx++) {
            TLTR12_LCHANNEL lch = hnd->Cfg.LChTbl[ch_idx];
            if ((lch.Mode != LTR12_CH_MODE_COMM) &&
                    (lch.Mode != LTR12_CH_MODE_DIFF) &&
                    (lch.Mode != LTR12_CH_MODE_ZERO) &&
                    (lch.Mode != LTR12_CH_MODE_DIFF_X_Y0) &&
                    (lch.Mode != LTR12_CH_MODE_DIFF_Y_X0)) {
                err = LTR12_ERR_INVALID_CFG_CH_MODE;
            } else if (((lch.Mode == LTR12_CH_MODE_COMM) && (lch.PhyChannel > LTR12_CHANNELS_CNT))
                       || (((lch.Mode == LTR12_CH_MODE_DIFF)
                            || (lch.Mode == LTR12_CH_MODE_DIFF_X_Y0)
                            || (lch.Mode == LTR12_CH_MODE_DIFF_Y_X0))
                          && (lch.PhyChannel > LTR12_DIFF_CHANNELS_CNT))) {
                err  = LTR12_ERR_INVALID_CFG_PHY_CH_NUM;
            } else {
                mcu_cfg.lch_tbl[mcu_cfg.lch_cnt++] = f_fill_mcu_lch(&lch);
            }
        }

        for (ch_idx = 0; (ch_idx < hnd->Cfg.BgLChCnt) && (err == LTR_OK); ch_idx++) {

            mcu_cfg.lch_tbl[mcu_cfg.lch_cnt++] = f_fill_mcu_lch(&f_zero_lch_cfg);
        }


        if (err == LTR_OK) {
            err = LTR12_CalcAdcFreq(&hnd->Cfg.AdcFreqParams, &adc_rate);
        }
        if (err == LTR_OK) {
            if (hnd->Cfg.ConvStartSrc == LTR12_CONV_START_SRC_INT) {                
                if (err == LTR_OK) {
                    if (adc_rate < LTR12_MAX_ADC_FREQ) {
                        mcu_cfg.divider = hnd->Cfg.AdcFreqParams.Divider & 0xFFFF;
                        mcu_cfg.prescaler = (f_get_prescaler_idx(hnd->Cfg.AdcFreqParams.Prescaler) + 1) & 0xFF;
                    } else {
                        mcu_cfg.divider = 36;
                        mcu_cfg.prescaler = 1;
                    }
                }
            } else if (hnd->Cfg.ConvStartSrc == LTR12_CONV_START_SRC_EXT_RISE) {
                mcu_cfg.divider = 0;
            } else if (hnd->Cfg.ConvStartSrc == LTR12_CONV_START_SRC_EXT_FALL) {
                mcu_cfg.divider = 1;
            } else {
                err = LTR12_ERR_INVALID_CFG_CONV_START_SRC;
            }
        }

        if (err == LTR_OK) {
            if ((hnd->Cfg.AcqStartSrc == LTR12_ACQ_START_SRC_INT) ||
                    (hnd->Cfg.AcqStartSrc == LTR12_ACQ_START_SRC_EXT_RISE) ||
                    (hnd->Cfg.AcqStartSrc == LTR12_ACQ_START_SRC_EXT_FALL)) {
                mcu_cfg.start_scr = hnd->Cfg.AcqStartSrc & 0xFF;
            } else {
                err = LTR12_ERR_INVALID_CFG_ACQ_START_SRC;
            }
        }


        if (err == LTR_OK) {
            WORD crc;
            DWORD cmds[(sizeof(mcu_cfg) + 4)/2];
            DWORD *cmd_ptr = cmds;
            const BYTE *cfg_ptr = (const BYTE *)(&mcu_cfg);
            WORD pkt_size = 2*sizeof(cmds)/sizeof(cmds[0]);
            DWORD i;
            DWORD ack = CMD_RESP_SET_ADC;

            crc = eval_crc16(0, (const unsigned char*)&pkt_size, 2);
            crc = eval_crc16(crc, (const unsigned char*) &mcu_cfg, sizeof(mcu_cfg));

            *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_SET_ADC | 0x01, pkt_size);
            for (i = 0; i < sizeof(mcu_cfg)/2; i++) {
                WORD data = *cfg_ptr++;
                data |= (*cfg_ptr++) << 8;
                *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_SET_ADC, data);
            }
            *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_SET_ADC, crc);

            err = ltr_module_send_with_single_resp(&hnd->Channel, cmds, pkt_size/2, &ack);
        }

        if (err == LTR_OK) {


            hnd->State.Configured = TRUE;
            hnd->State.FrameWordsCount = total_ch_cnt;
            hnd->State.AdcFreq = adc_rate;
            hnd->State.FrameFreq = adc_rate / hnd->State.FrameWordsCount;

            if (rst) {
                t_internal_params *internal = LTR12_INTERNAL_PARAMS(hnd);
                avg_init(&internal->offset,  (DWORD)(500 * hnd->State.AdcFreq/1000));
            }

        }
    }

    return err;
}

LTR12API_DllExport(INT) LTR12_SetADC(TLTR12 *hnd) {
    return f_set_adc(hnd, 1);
}



LTR12API_DllExport(INT) LTR12_Start(TLTR12 *hnd) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Configured)
        err = LTR_ERROR_MODULE_NOT_CONFIGURED;
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    if (err == LTR_OK)
        err = f_acq_start(hnd, 0);
    return err;
}
LTR12API_DllExport(INT) LTR12_Stop(TLTR12 *hnd) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run) {
        DWORD cmd = CMD_STOP_ACQ;
        err = ltr_module_stop(&hnd->Channel, &cmd, 1, CMD_RESP_STOP_ACQ, 0, 0, NULL);
        if (err == LTR_OK) {
            hnd->State.Run = FALSE;
        }
    }
    return err;
}



LTR12API_DllExport(INT) LTR12_Recv(TLTR12 *hnd, DWORD *data, DWORD *tmark,
                                   DWORD size,  DWORD timeout) {
    int res = LTR12_IsOpened(hnd);
    if (res == LTR_OK)
        res = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);

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

    return res;
}

LTR12API_DllExport(INT) LTR12_MeasAdcZeroOffset(TLTR12 *hnd, DWORD flags) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Configured)
        err = LTR_ERROR_MODULE_NOT_CONFIGURED;
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;
    if (err == LTR_OK) {
        TLTR12_CONFIG prev_cfg = hnd->Cfg;
        INT restore_err;

        hnd->Cfg.AcqStartSrc = LTR12_ACQ_START_SRC_INT;
        hnd->Cfg.ConvStartSrc = LTR12_CONV_START_SRC_INT;
        hnd->Cfg.LChCnt = 1;
        hnd->Cfg.BgLChCnt = 0;
        hnd->Cfg.LChTbl[0] = f_zero_lch_cfg;
        err = f_set_adc(hnd, 1);
        if (err == LTR_OK)
            err = LTR12_Start(hnd);
        if (err == LTR_OK) {
            INT stop_err;
            t_internal_params *internal = LTR12_INTERNAL_PARAMS(hnd);

            DWORD rcv_size = internal->offset.block_size;
            DWORD tout = (DWORD)(2000 + 1000 * rcv_size/hnd->State.FrameFreq);
            DWORD *rbuf = malloc(rcv_size * sizeof(DWORD));
            if (rbuf == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                INT recvd = LTR12_Recv(hnd, rbuf, NULL, rcv_size, tout);
                if (recvd < 0) {
                    err = recvd;
                } else if ((DWORD)recvd != rcv_size) {
                    err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                } else {
                    err = LTR12_ProcessData(hnd, rbuf, NULL, &recvd,
                                            LTR12_PROC_FLAG_ZERO_OFFS_AUTORECALC);
                }
                free(rbuf);
            }

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


        hnd->Cfg = prev_cfg;
        restore_err = f_set_adc(hnd, 0);
        if (err == LTR_OK)
            err = restore_err;
    }


    return err;

}




LTR12API_DllExport(INT) LTR12_ProcessData(TLTR12 *hnd, const DWORD *src,
                                          double *dest, INT *size, DWORD flags) {
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK)  && ((src == NULL) || (size == NULL)))
        err = LTR_ERROR_PARAMETRS;

    if (err == LTR_OK) {
        /* порядок следования данных от каналов и их диапазоны */
        struct {
            BYTE is_zero;
            BYTE swmode;
            double scale;
            double *poffs;
        } ch_tbl[LTR12_MAX_LCHANNELS_CNT];
        /* двухбитный счетчик слов данных от модуля */
        BYTE cntr = 0;
        BOOLEAN cntr_lost = TRUE;
        /* один кадр данных */
        SHORT frame[LTR12_MAX_LCHANNELS_CNT];
        double *pdest = dest;
        const DWORD *psrc = src;
        DWORD vch_ind;
        DWORD vch_cnt;                             /* количество виртуальный каналов */
        DWORD vch_cnt_out;
        double zero_ch_offset = 0;
        double offset = 0;
        t_internal_params *internal = LTR12_INTERNAL_PARAMS(hnd);


        if (flags & LTR12_PROC_FLAG_CALIBR) {
            zero_ch_offset = offset = (double)hnd->ModuleInfo.Cbr.Offset;
        }

        if (!(flags & LTR12_PROC_FLAG_NONCONT_DATA)) {
            cntr_lost = internal->cntr_lost;
            cntr = internal->cntr;
        }


        /* определение порядка следования и диапазонов каналов */
        vch_cnt_out = hnd->Cfg.LChCnt;
        vch_cnt = hnd->Cfg.LChCnt + hnd->Cfg.BgLChCnt;

        for (vch_ind = 0; (vch_ind < vch_cnt); vch_ind++) {
            const TLTR12_LCHANNEL *plch =  vch_ind <  vch_cnt_out ? &hnd->Cfg.LChTbl[vch_ind] : &f_zero_lch_cfg;
            BYTE mode = plch->Mode;

            ch_tbl[vch_ind].swmode = f_fill_mcu_lch(plch);
            ch_tbl[vch_ind].is_zero = mode == LTR12_CH_MODE_ZERO;
            ch_tbl[vch_ind].scale = 1.;
            ch_tbl[vch_cnt].poffs = &zero_ch_offset;

            /* для режима y - x0 инвертируем знак измеряемой величины тока */
            if (mode == LTR12_CH_MODE_DIFF_Y_X0) {
                ch_tbl[vch_ind].scale *= -1.;
            }

            if (flags & LTR12_PROC_FLAG_CALIBR) {
                if (mode == LTR12_CH_MODE_COMM) {
                    ch_tbl[vch_ind].scale *= (double)hnd->ModuleInfo.Cbr.ChScale[plch->PhyChannel];
                } else if (mode == LTR12_CH_MODE_DIFF) {
                    ch_tbl[vch_ind].scale *= (double)(hnd->ModuleInfo.Cbr.ChScale[plch->PhyChannel] +
                                                      hnd->ModuleInfo.Cbr.ChScale[plch->PhyChannel + 16])/2;
                } else if (mode == LTR12_CH_MODE_DIFF_X_Y0) {
                    ch_tbl[vch_ind].scale *= (double)hnd->ModuleInfo.Cbr.ChScale[plch->PhyChannel];
                } else if (mode == LTR12_CH_MODE_DIFF_Y_X0) {
                    ch_tbl[vch_ind].scale *= (double)hnd->ModuleInfo.Cbr.ChScale[plch->PhyChannel + 16];
                }
            }

            if (flags & LTR12_PROC_FLAG_CONV_UNIT) {
                if (!ch_tbl[vch_ind].is_zero) {
                    ch_tbl[vch_ind].scale *= (double)LTR12_ADC_RANGE_MA / LTR12_ADC_SCALE_CODE_MAX;
                }
            }

            if ((flags & LTR12_PROC_FLAG_ZERO_OFFS_COR) && (mode != LTR12_CH_MODE_ZERO)) {
                offset = -hnd->State.AdcZeroOffsetCode;
            }
        }

        vch_ind = 0;
        for (; (psrc < src + *size) && (err == LTR_OK); psrc++) {
            /* проверки:
             *  1) слово не является командой (маска 0x80) и номер слота (0x0F)
             *  2) двухбитный счетчик слов
             */
            DWORD src_wrd = *psrc;
            INT wrd_err = LTR_OK;
            BYTE wrd_cnt = LBITFIELD_GET(src_wrd, DATA_WRD_CNTR);


            if (cntr_lost) {
                cntr = wrd_cnt;
                cntr_lost = FALSE;
            } else if (cntr != wrd_cnt) {
                wrd_err = LTR_ERROR_PROCDATA_CNTR;
                cntr = wrd_cnt;
            }


            if ((wrd_err == LTR_OK) && ((src_wrd & DATA_WRD_SWSTATE)
                                        != (ch_tbl[vch_ind].swmode & DATA_WRD_SWSTATE))) {
                wrd_err = LTR_ERROR_PROCDATA_CHNUM;
                vch_ind = 0;                             /* поиск начала следующего кадра */
            }

            if (err == LTR_OK) {
                /* сохранение отсчета АЦП */
                frame[vch_ind] = (SHORT)LBITFIELD_GET(src_wrd, DATA_WRD_ADCCODE);
                if (++vch_ind >= vch_cnt) {              /* обработан кадр отсчетов АЦП */
                    /* если установлен флаг автокорректировки нуля, то сперва
                     * учитываем измерения нуля в кадре, чтобы первый кадр уже
                     * был с корретно измеренным нулем */
                    if (flags & LTR12_PROC_FLAG_ZERO_OFFS_AUTORECALC) {
                        for (vch_ind = 0; (vch_ind < vch_cnt); vch_ind++) {
                            if ( ch_tbl[vch_ind].is_zero) {
                                avg_add_val(&internal->offset, frame[vch_ind]);
                            }
                        }
                        if (internal->offset.cur_size > 0) {
                            hnd->State.AdcZeroOffsetCode = internal->offset.result;
                            if (flags & LTR12_PROC_FLAG_ZERO_OFFS_COR)
                                offset = -internal->offset.result;
                        }
                    }

                    if (dest) {
                        /* обработка и сохранение данных в выходном массиве */
                        for (vch_ind = 0; (vch_ind < vch_cnt_out); vch_ind++) {
                            double dt;
                            dt = ((double)frame[vch_ind] + (ch_tbl[vch_ind].is_zero ? zero_ch_offset : offset))
                                    * ch_tbl[vch_ind].scale;
                            *pdest++ = dt;
                        }
                    }
                    vch_ind = 0;
                }
            }

            if (++cntr == DATA_CNTR_MODULE)
                cntr = 0;
            if (wrd_err != LTR_OK) {
                if (err == LTR_OK)
                    err = wrd_err;
            }
        }
        *size = dest ? (INT)(pdest - dest) : 0;

        if (!(flags & LTR12_PROC_FLAG_NONCONT_DATA)) {
            internal->cntr = cntr;
            internal->cntr_lost = cntr_lost;
        }
    }

    return err;
}

LTR12API_DllExport(INT) LTR12_GetFrame(TLTR12 *hnd, DWORD *buf, DWORD timeout) {
    DWORD rd_buf[LTR12_MAX_LCHANNELS_CNT + 1];
    INT err = LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && !hnd->State.Configured)
        err = LTR_ERROR_MODULE_NOT_CONFIGURED;
    if ((err == LTR_OK) && hnd->State.Run)
        err = LTR_ERROR_MODULE_STARTED;


    if (err == LTR_OK) {
        err = f_acq_start(hnd, 1);
        if (err == LTR_OK) {
            err = ltr_module_recv_cmd_resp_tout(&hnd->Channel, rd_buf, hnd->Cfg.LChCnt + 1, timeout);
            if (err == LTR_OK) {
                unsigned a;
                /* проверка окончания сбора кадра (по приходу подтверждения) */
                a = LTR_MODULE_CMD_GET_CMDCODE(rd_buf[hnd->Cfg.LChCnt]);

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

LTR12API_DllExport(INT) LTR12_SearchFirstFrame(TLTR12 *hnd, const DWORD *data, DWORD size,
                                               DWORD *frame_idx) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR :
        ((data == NULL) || (size == 0) || (frame_idx == NULL)) ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (err == LTR_OK) {
        INT i;
        INT idx_first = -1;
        INT first_ch_swmode = f_fill_mcu_lch(&hnd->Cfg.LChTbl[0]) & DATA_WRD_SWSTATE;


        for (i = 0; ((i < (INT)size) && (idx_first < 0)); i++, data++) {
            INT rcv_ch_sw  = *data & DATA_WRD_SWSTATE;
            if (rcv_ch_sw == first_ch_swmode)
                idx_first = i;
        }

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

LTR12API_DllExport(LPCSTR) LTR12_GetErrorString(INT err) {
    size_t i;
    for (i = 0; (i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0])); i++) {
        if (f_err_tbl[i].code == err)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(err);
}



static INT f_flash_write(TLTR12 *hnd, WORD addr, const BYTE *data, WORD size) {
    WORD crc;
    DWORD cmds[MCU_MAX_PARAM_ARR_SIZE/2];
    DWORD *cmd_ptr = cmds;
    WORD pkt_size = 6 + size;
    DWORD i;
    DWORD ack = CMD_RESP_FLASH_WRITE;
    INT err;
    memset(cmds, 0, sizeof(cmds));

    crc = eval_crc16(0, (const unsigned char*)&pkt_size, 2);
    crc = eval_crc16(crc, (const unsigned char*)&addr, 2);
    crc = eval_crc16(crc, data, size);

    *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_FLASH_WRITE | 0x01, pkt_size);
    *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_FLASH_WRITE, addr);
    for (i = 0; i < (DWORD)size/2; i++) {
        WORD wrd = *data++;
        wrd |= (*data++) << 8;
        *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_FLASH_WRITE, wrd);
    }
    *cmd_ptr++ = ltr_module_fill_cmd_parity(CMD_FLASH_WRITE, crc);

    err = ltr_module_send_with_single_resp(&hnd->Channel, cmds, pkt_size/2, &ack);

    return err;
}


static int f_flash_read(TLTR12 *hnd, WORD addr, BYTE *data, WORD size) {
    INT err = LTR_OK;

    if (err == LTR_OK) {
        DWORD cmds[2];
        cmds[0] = ltr_module_fill_cmd_parity(CMD_FLASH_SET_RDSIZE, size);
        cmds[1] = ltr_module_fill_cmd_parity(CMD_FLASH_READ, addr);
        err = ltr_module_send_cmd(&hnd->Channel, cmds, 2);
        if (err == LTR_OK) {
            DWORD ack_cnt = 4 * size  + 1;
            DWORD *ack_ptr, *acks;
            ack_ptr = acks = malloc(ack_cnt * sizeof(ack_ptr[0]));
            if (ack_ptr == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                err = ltr_module_recv_cmd_resp_tout(&hnd->Channel, ack_ptr, ack_cnt,
                                                    LTR_MODULE_CMD_RECV_TIMEOUT);
            }

            if (err == LTR_OK) {
                if (LTR_MODULE_CMD_GET_CMDCODE(*ack_ptr) != CMD_RESP_SET_RDSIZE_ACK) {
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                } else {
                    ack_ptr++;
                }
            }

            if (err == LTR_OK) {
                if (CMD_RESPWDATA_GET_CMDCODE(*ack_ptr) != CMD_RESP_FLASH_DATA_FIRST) {
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                } else {
                    WORD ack_pos;
                    *ack_ptr &= ~0x10UL;

                    for (ack_pos = 0; (ack_pos < size) && (err == LTR_OK); ack_pos++) {
                        int byte_pos_idx;
                        *data = 0;
                        for (byte_pos_idx = 6; ((byte_pos_idx >= 0) && (err == LTR_OK)); byte_pos_idx-= 2) {
                            if (CMD_RESPWDATA_GET_CMDCODE(*ack_ptr) != CMD_RESP_FLASH_DATA) {
                                err = LTR_ERROR_INVALID_CMD_RESPONSE;
                            } else {
                                *data |= CMD_RESPWDATA_GET_DATA(*ack_ptr) << byte_pos_idx;
                            }
                            ack_ptr++;
                        }
                        data++;
                    }
                }
            }

            free(acks);
        }
    }
    return err;
}





LTR12API_DllExport(INT) LTR12_WriteInfo(TLTR12 *hnd) {
    INT err = LTR12_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD ch_idx;
        t_mcu_module_info_page pg;
        memset(&pg, 0, sizeof(pg));
        pg.signature = MCU_MODULE_INFO_SIGNATURE;
        pg.fmt_ver = MCU_MODULE_INFO_FMT_VERSION;
        pg.info_size = sizeof(pg.info);
        pg.info.firmware_ver = hnd->ModuleInfo.FirmwareVersion;
        memcpy(pg.info.firmware_date, hnd->ModuleInfo.FirmwareDate, sizeof(pg.info.firmware_date));
        memcpy(pg.info.module_name, hnd->ModuleInfo.Name, sizeof(pg.info.module_name));
        memcpy(pg.info.serial_num, hnd->ModuleInfo.Serial, sizeof(pg.info.serial_num));
        pg.info.adc_calibr.offset = hnd->ModuleInfo.Cbr.Offset;
        for (ch_idx = 0; ch_idx < LTR12_CHANNELS_CNT; ch_idx++) {
            pg.info.adc_calibr.k[ch_idx] = hnd->ModuleInfo.Cbr.ChScale[ch_idx];
        }
        pg.crc = eval_crc16(0, (const unsigned char *)&pg, sizeof(pg) - 2);


        if (err == LTR_OK) {
            err = f_flash_write(hnd, MCU_MODULE_INFO_PAGE_ADDR, (const BYTE *)&pg, sizeof(pg));
        }
    }
    return err;
}

LTR12API_DllExport(INT) LTR12_FlashUserDataRead(TLTR12 *hnd, DWORD addr, BYTE *data, DWORD size) {
    INT err = ((addr + size) > LTR12_FLASH_USERDATA_SIZE) ? LTR_ERROR_PARAMETERS : LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        err = f_flash_read(hnd, (MCU_FLASH_USERDATA_PAGE_ADDR + addr) & 0xFFFF, data, size & 0xFFFF);
    }
    return err;
}


LTR12API_DllExport(INT) LTR12_FlashUserDataWrite(TLTR12 *hnd, DWORD addr, const BYTE *data, DWORD size) {
    INT err = ((addr + size) > LTR12_FLASH_USERDATA_SIZE) ? LTR_ERROR_PARAMETERS : LTR12_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Run) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        WORD flash_wr_addr = (MCU_FLASH_USERDATA_PAGE_ADDR + addr) & 0xFFFF;
        WORD flash_wr_size = size & 0xFFFF;

        /* проверка выравненности адреса на начало страницы */
        WORD addr_page_offs = flash_wr_addr  & (MCU_FLASHPAGE_SIZE-1);
        if (addr_page_offs != 0) {
            /* если не выровнен, то считываем страницу и подменяем перезаписываемую часть данных
               (тут же обрабатываем случай, когда вся запись - кусок в середине страницы) */
            BYTE page[MCU_FLASHPAGE_SIZE];
            err = f_flash_read(hnd, flash_wr_addr - addr_page_offs,  page, MCU_FLASHPAGE_SIZE);
            if (err == LTR_OK) {
                WORD page_cpy_size = MCU_FLASHPAGE_SIZE - addr_page_offs;
                if (page_cpy_size > flash_wr_size)
                    page_cpy_size = flash_wr_size;
                memcpy(&page[addr_page_offs], data, page_cpy_size);
                err = f_flash_write(hnd, flash_wr_addr - addr_page_offs, page, MCU_FLASHPAGE_SIZE);
                if (err == LTR_OK) {
                    flash_wr_addr += page_cpy_size;
                    flash_wr_size -= page_cpy_size;
                    data += page_cpy_size;
                }
            }
        }


        /* запись целого числа страниц без изменений */
        if ((err == LTR_OK) && (flash_wr_size >0 )) {
            WORD page_wr_size = flash_wr_size & ~(MCU_FLASHPAGE_SIZE-1);
            if (page_wr_size > 0) {
                err = f_flash_write(hnd, flash_wr_addr, data, page_wr_size);
                if (err == LTR_OK) {
                    flash_wr_addr += page_wr_size;
                    flash_wr_size -= page_wr_size;
                    data += page_wr_size;
                }
            }
        }

        /* елси остался хвост не кратный размеру страницы, то снова вычиываем
         * дальшейшие данные и перезаписываем всю странцу */
        if ((err == LTR_OK) && (flash_wr_size > 0)) {
            BYTE page[MCU_FLASHPAGE_SIZE];
            err = f_flash_read(hnd, flash_wr_addr + flash_wr_size,  &page[flash_wr_size],
                               MCU_FLASHPAGE_SIZE - flash_wr_size);
            if (err == LTR_OK) {
                memcpy(page, data, flash_wr_size);
                err = f_flash_write(hnd, flash_wr_addr, page, MCU_FLASHPAGE_SIZE);
            }
        }
    }
    return err;
}
