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

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

#define LTR34CMD_IO_ROM                                     (LTR010CMD|0x60)
#define LTR34CMD_CONTROL                                    (LTR010CMD_INSTR | 0x20)
#define LTR34CMD_START                                      (LTR010CMD_INSTR | 0x00)




/* Адреса информации внутри FLASH-памяти */
#define LTR34_FLASH_ADDR_SERIAL                             (0)
#define LTR34_FLASH_ADDR_NAME                               (24)
#define LTR34_FLASH_ADDR_FPGA_VER                           (36)
#define LTR34_FLASH_ADDR_CALIBR_VER                         (48)
#define LTR34_FLASH_ADDR_CH_QNT                             (56)
#define LTR34_FLASH_ADDR_CALIBR                             (100)
#define LTR34_FLASH_ADDR_CRC                                (2024)

#define LTR34_SERIAL_SIZE                                    24
#define LTR34_MODULE_NAME_SIZE                               16
#define LTR34_FPGA_VER_SIZE                                  8


#define WORKING_FLASH_SIZE                                  (2000)

#define LTR34_FLASH_WT_RDY_TOUT        1000 /* таймаут в мс на ожидание завершения
                                               зписи во FLash */


#define LTR34_FLASH_WRITE_CODE  (53637)
#define LTR34_MODULE_NAME       "LTR34"

#define LTR34_FLASH_WRITE_DUMMY_WRDS 100


static const LPCSTR err_str[] = {
    "LTR34: Невозможно отправить данные в модуль!",
    "LTR34: Модуль не отвечает",
    "LTR34: Невозможно выполнить аппаратный сброс модуля!",
    "LTR34: Модуль не является LTR34!",
    "LTR34: Переполнение буфера в крейте!",
    "LTR34: Ошибка четности!",
    "LTR34: Переполнение буфера в модуле!",
    "LTR34: Неправильный индекс!",
    "LTR34: Ошибка модуля LTR34",
    "LTR34: Ошибка обмена данными",
    "LTR34: Неправильный формат данных",
    "LTR34: Неправильные параметры",
    "LTR34: Неправильный ответ",
    "LTR34: Неверная контрольная сумма ППЗУ!",
    "LTR34: Невозможно выполнить запись в ППЗУ!",
    "LTR34: Невозможно выполнить чтение из ППЗУ!",
    "LTR34: Невозможно записать серийный номер или он имеет неверный формат",
    "LTR34: Невозможно прочитать серийный номер модуля из ППЗУ",
    "LTR34: Невозможно записать версию прошивки ПЛИС или она имеет неверный формат",
    "LTR34: Невозможно прочитать версию прошивки ПЛИС из ППЗУ",
    "LTR34: Невозможно записать версию калибровки или она имеет неверный формат",
    "LTR34: Невозможно прочитать версию калибровки из ППЗУ",
    "LTR34: Невозможно остановить генерацию данных!",
    "LTR34: Невозможно отправить команду в модуль!",
    "LTR34: Невозможно записать имя модуля в ППЗУ!",
    "LTR34: Невозможно записать максимальное количество каналов в ППЗУ!",
    "LTR34: Не открыт интерфейсный канал связи с сервером",
    "LTR34: Неверная конфигурация логических каналов!"
};

static INT LTR34_WriteFlash(TLTR34 *module, const BYTE *data, WORD size, WORD Address);
static INT f_eval_flash_crc(TLTR34 *module, WORD *Flash_CRC);
static INT f_write_flash_crc(TLTR34 *module);

#ifdef __cplusplus
extern "C"  {
#endif
    LTR34API_DllExport(INT) LTR34_WriteSerialNumber(TLTR34 *module, const CHAR *sn, DWORD Code);
    LTR34API_DllExport(INT) LTR34_ReadSerialNumber(TLTR34 *module, CHAR *sn);
    LTR34API_DllExport(INT) LTR34_WriteModuleName(TLTR34 *module, const CHAR *ModuleName, DWORD Code);
    LTR34API_DllExport(INT) LTR34_ReadModuleName(TLTR34 *module, CHAR *name);
    LTR34API_DllExport(INT) LTR34_WriteFPGA_Ver(TLTR34 *module, const CHAR *FPGA_Ver, DWORD Code);
    LTR34API_DllExport(INT) LTR34_Read_FPGA_Ver(TLTR34 *module, CHAR *FPGA_Ver);

    LTR34API_DllExport(INT) LTR34_WriteCalibrVer(TLTR34 *module, CHAR *CalibrVer, DWORD Code);
    LTR34API_DllExport(INT) LTR34_ReadCalibrVer(TLTR34 *module, CHAR *CalibrVer);

    LTR34API_DllExport(INT) LTR34_WriteChQnt(TLTR34 *module, BYTE ChQnt, int Code);
    LTR34API_DllExport(INT) LTR34_ReadChQnt(TLTR34 *module, BYTE *ChQnt);

#ifdef __cplusplus
}
#endif


#ifdef _WIN32
BOOL APIENTRY DllMain( HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved
                      ) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}  


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

static double f_calc_dac_freq(BYTE devisor, BYTE ChannelQnt) {
    return (2*1000000)/(float)((64-devisor)*ChannelQnt);
}

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

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

    for (ch = 0; ch < 2*LTR34_DAC_CHANNEL_CNT; ++ch) {
        hnd->DacCalibration.FactoryCalibrOffset[ch] = 0.F;
        hnd->DacCalibration.FactoryCalibrScale[ch] = 1.F;
    }

    hnd->BufferFull = FALSE;       // флаг "буфер переполнен" обнуляется
    hnd->BufferEmpty = FALSE;       // флаг "буфер пуст" обнуляется
    hnd->DACRunning = FALSE;       // Генерация не запущена
}

/*******************************************************************************
    Функция инициализации интерфейсного канала связи с модулем
*******************************************************************************/
LTR34API_DllExport(INT)  LTR34_Init(TLTR34 *module) {
    INT res = LTR_OK;
    if(module != NULL) {
        memset(module,0,sizeof(TLTR34));

        res = LTR_Init(&module->Channel);
        if(res == LTR_OK) {

            module->ChannelQnt = 1;     // Задействован только один канал
            module->AcknowledgeType = FALSE; //  тип подтверждения - TRUE - высылать состояние буфера каждые 100 мс
            module->ExternalStart = FALSE;  //  внешний старт отключен, используется внутренний старт
            module->RingMode = FALSE;        // режим кольца отключен, используется потоковый режим
            module->FrequencyDAC = 500000.0;

            module->LChTbl[0] = LTR34_CreateLChannel(1,0); // Создаем один логический канал, соотв. физическому каналу 1, выход 1:1
            module->size = sizeof(TLTR34);

            f_info_init(module);
        }
    } else {
        res = LTR_ERROR_INVALID_MODULE_DESCR;
    }
    return res;
}

/*******************************************************************************
    Функция открытия интерфейсного канала связи с модулем
*******************************************************************************/
LTR34API_DllExport (INT)  LTR34_Open(TLTR34 *module, DWORD net_addr,
                                     WORD net_port, const CHAR *crate_sn, WORD slot_num) {
    DWORD open_flags = 0;
    INT warning, res = module == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (res == LTR_OK) {
        if (LTR34_IsOpened(module) == LTR_OK)
            LTR34_Close(module);
    }

    if (res == LTR_OK) {
        DWORD ack;
        res = ltr_module_open(&module->Channel, net_addr, net_port, crate_sn, slot_num,
                         LTR_MID_LTR34, &open_flags, &ack, &warning);
        if (res == LTR_OK) {
            f_info_init(module);
            /* в ответе на RESET приходит версия прошивки ПЛИС */
            sprintf(module->ModuleInfo.FPGA_Version, "%d", ack&0x3F);
        }
    }

    if ((res == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        /* Считываем серийный номер */
        res = LTR34_ReadSerialNumber(module, module->ModuleInfo.Serial);
        if (res == LTR_OK) {
            /* Считываем имя модуля */
            res = LTR34_ReadModuleName(module, module->ModuleInfo.Name);
        }
        if (res == LTR_OK) {
            /* Считываем версию калибровки */
            res = LTR34_ReadCalibrVer(module, module->ModuleInfo.CalibrVersion);
        }
        if (res == LTR_OK) {
            /* Считываем макс. кол-во каналов: 4 или 8 */
            res = LTR34_ReadChQnt(module, &module->ModuleInfo.MaxChannelQnt);
        }
        if (res == LTR_OK) {
            /* Считываем калибровочные коэффициенты */
            res = LTR34_GetCalibrCoeffs(module);
        }
    }

    if(res != LTR_OK)
        LTR_Close(&module->Channel);

    return res == LTR_OK ? warning : res;
}


/*******************************************************************************
        Функция закрытия интерфейсного канала связи с модулем
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_Close(TLTR34 *module) {
    INT res = (module==NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        INT closeres;
        if (module->DACRunning)
            res = LTR34_DACStop(module);
        closeres = LTR_Close(&module->Channel);
        if (res == LTR_OK)
            res = closeres;
    }
    return res;
}

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

/*******************************************************************************
        Функция конфигурирования модуля
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_Config(TLTR34 *module) {
    BYTE i;
    INT res = LTR34_IsOpened(module);

    /* Проверяем конфигурацию логических каналов */
    if((res == LTR_OK) && (module->ChannelQnt > module->ModuleInfo.MaxChannelQnt))
        res = LTR34_ERROR_WRONG_LCH_CONF;

    for(i = 0; (i < module->ChannelQnt) && (res == LTR_OK); i++) {
        BYTE PhysChannel = (BYTE)((module->LChTbl[i]) & 0xFF);
        int j;

        if((PhysChannel < 1) || (PhysChannel > module->ModuleInfo.MaxChannelQnt))
            res = LTR34_ERROR_WRONG_LCH_CONF;

        /* анализируем повторы в физ. каналах */
        for(j = i + 1; (j < module->ChannelQnt) && (res == LTR_OK); j++) {
            if(PhysChannel == ((module->LChTbl[j]) & 0xFF))
                res = LTR34_ERROR_WRONG_LCH_CONF;
        }
    }

    if ((res == LTR_OK) && ((module->ChannelQnt > LTR34_DAC_CHANNEL_CNT)
                            || (module->FrequencyDivisor > LTR34_FREQ_DIV_MAX))) {
        res = LTR_ERROR_PARAMETRS;
    }

    if ((res == LTR_OK) && ((module->ChannelQnt == 0)
        || (module->ChannelQnt == 3)
        || (module->ChannelQnt == 5)
        || (module->ChannelQnt == 7))) {
        res = LTR_ERROR_PARAMETRS;
    }

    if (res == LTR_OK)
        res=LTR34_IsOpened(module);

    if(res == LTR_OK) {
        BYTE NumOfChFactor;
        WORD  config;
        DWORD cmd;

        if(module->ChannelQnt == 1)
            NumOfChFactor = 0;
        if(module->ChannelQnt == 2)
            NumOfChFactor = 1;
        if(module->ChannelQnt == 4)
            NumOfChFactor = 2;
        if(module->ChannelQnt == 8)
            NumOfChFactor = 3;

        config = ((NumOfChFactor & 0x3) << 14) |
            ((~(module->AcknowledgeType) & 1) << 10) |
            ((module->RingMode & 1) << 9) |
            ((module->ExternalStart & 1) << 8) |
            ((module->FrequencyDivisor & 0xff) << 0) |
            (1 << 11);

        cmd = (config << 16) | LTR34CMD_CONTROL;

        res = ltr_module_send_cmd(&module->Channel, &cmd, 1);
        if (res == LTR_OK) {
            module->FrequencyDAC=(float)f_calc_dac_freq(module->FrequencyDivisor, module->ChannelQnt);
        }
    }
    return res;
}


/*******************************************************************************
        Функция создания логического канала
*******************************************************************************/
LTR34API_DllExport (DWORD) LTR34_CreateLChannel(BYTE PhysChannel, BOOLEAN ScaleFlag) {
    /* Формирует запись в Таблице Логических Каналов
       Младший байт - номер физического канала (начиная с единицы)
       Биты [8..15] - флаг применения выхода 1:1 или 1:10 */
    return ((DWORD)ScaleFlag << 8) | PhysChannel;
}

/*******************************************************************************
    Функция старта генерации
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_DACStart(TLTR34 *module) {
    INT res = LTR34_IsOpened(module);
    if(res == LTR_OK) {
        DWORD cmd = LTR34CMD_START;
        res = ltr_module_send_cmd(&module->Channel, &cmd, 1);
        if (res == LTR_OK)
            module->DACRunning = TRUE;
    }
    return res;
}


/*******************************************************************************
    Функция аппаратного сброса модуля
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_Reset(TLTR34 *module) {
    INT res = LTR34_IsOpened(module);
    if(res == LTR_OK) {
        DWORD sbuf[3] = {LTR010CMD_STOP, LTR010CMD_RESET, LTR010CMD_STOP};
        INT fpga_ver = atoi(module->ModuleInfo.FPGA_Version);
        res = ltr_module_stop(&module->Channel, sbuf, sizeof(sbuf)/sizeof(sbuf[0]),
                LTR010CMD_RESET | fpga_ver, 0, 0, NULL);
        if (res==LTR_OK)
            module->DACRunning = FALSE;
    }
    return res;
}

/*******************************************************************************
        Функция остановки генерации
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_DACStop(TLTR34 *module) {
    INT res = LTR34_DACStopRequest(module);
    if(res == LTR_OK) {
        res = LTR34_DACStopResponseWait(module, LTR_MODULE_STOP_TIMEOUT);
    }
    return res;
}


LTR34API_DllExport (INT) LTR34_DACStopRequest(TLTR34 *module) {
    INT res = LTR34_IsOpened(module);
    if (res == LTR_OK) {
        DWORD cmd[] = { LTR010CMD_STOP, LTR34CMD_IO_ROM } ;
        res = ltr_module_send_cmd(&module->Channel, cmd, sizeof(cmd)/sizeof(cmd[0]));
    }
    return res;
}

LTR34API_DllExport (INT) LTR34_DACStopResponseWait(TLTR34 *module, DWORD tout) {
    INT res = LTR34_IsOpened(module);
    if (res == LTR_OK) {
        res = ltr_module_recv_fnd_ack(&module->Channel, LTR34CMD_IO_ROM, 0, 0, tout, NULL);
    }
    if (res == LTR_OK)
        module->DACRunning = FALSE;
    return res;
}

/*******************************************************************************
        Функция получения команд или данных из модуля
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_Recv(TLTR34 *module, DWORD *data, DWORD *tstamp,
                                    DWORD size, DWORD timeout) {
    INT res = LTR34_IsOpened(module);
    if (res == LTR_OK) {
        module->BufferFull = FALSE;
        module->BufferEmpty = FALSE;

        res = LTR_Recv(&module->Channel, data, tstamp, size, timeout);
        if (res > 0) {
            INT i;
            /** @note В предыдущей версии при приеме данных меньше,
                чем требуется, возвращалась всегда ошибка, хотя в документации
                это не так. Теперь обрабатываем реально принятое кол-во данных,
                аналогично остальным модулям. */
            for (i = 0; i < res; i++) {
                /* Анализируем, чему равны флаги переполнения и опустошения FIFO */
                if (data[i] & 0x11) {
                    if (data[i] & 0x1)
                        module->BufferFull = TRUE;
                    if (data[i] & 0x10)
                        module->BufferEmpty = TRUE;
                }
            }
        }
    }
    return res;
}


/******************************************************************************
    Функция отправки команд или данных в модуль
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_Send(TLTR34 *module, const DWORD *data,
                                    DWORD size, DWORD timeout) {
    INT res = LTR34_IsOpened(module);
    if (res == LTR_OK)
        res = LTR_Send(&module->Channel, data, size, timeout);
    return res;
}


/*******************************************************************************
    Функция формирования массива для отправки данных в ЦАП
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_ProcessData(TLTR34 *module, const double *src,
                                           DWORD *dest, DWORD size, BOOLEAN volt) {
    INT res = LTR34_IsOpened(module);
    if (res == LTR_OK) {
        DWORD i, ChannelCounter;
        DWORD NumChannels = module->ChannelQnt;

        for (i = 0, ChannelCounter = 0; i < size; i++) {
            double TempValue;
            SHORT DataValue;
            BYTE PhysChannel= (BYTE)((module->LChTbl[ChannelCounter] & 0xF) - 1);
            BYTE OutScaleFlag= (BYTE)(((module->LChTbl[ChannelCounter] >> 8) & 0xF));

            if(!volt) {
                /* Если входной массив задан в кодах, то можно его не изменять */
                TempValue = src[i];
            } else {
                /* Если входной массив задан в вольтах, то переводим вольты
                   в коды в соответствии с установленным диапазоном */
                if(!OutScaleFlag) {
                    TempValue = src[i] * 65535 / 20.0; /* Если используем выход 1:1 */
                } else {
                    TempValue = src[i] * 65535 / 2.0;  /* Если используем выход 1:10 */
                }
            }

            if (module->UseClb) {
                /* применяем калибровочные коэффициенты для данного канала и
                   данного диапазона */
                int CalibrIndex = OutScaleFlag ? 2*PhysChannel+1 : 2*PhysChannel;

                TempValue = (TempValue*module->DacCalibration.FactoryCalibrScale[CalibrIndex])
                     + module->DacCalibration.FactoryCalibrOffset[CalibrIndex];
            }

            /* переводим в 16-битное целое значение с округлением до ближайшего
               целого */
            DataValue = (TempValue>=0.0) ? (SHORT)(TempValue + 0.5) :
                                           (SHORT) (TempValue - 0.5);

            dest[i] = (DataValue << 16) | ((PhysChannel & 0x7) << 1);

            if (++ChannelCounter == NumChannels)
                ChannelCounter=0;
        }
    }
    return res;
}


/*******************************************************************************
    Функция записи в ППЗУ
*******************************************************************************/
static INT LTR34_WriteFlash(TLTR34 *module, const BYTE *data, WORD size, WORD Address) {    
    INT res= LTR34_IsOpened(module);
    if (res == LTR_OK) {
        if (module->DACRunning)
            res = LTR34_DACStop(module);
    }
    if (res == LTR_OK) {
        res = ltr_at93c86a_write(&module->Channel, Address, data, size);
    }
    return res;
}


/*******************************************************************************
    Функция чтения из ППЗУ
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_ReadFlash(TLTR34 *module, BYTE *data,
                                         WORD size, WORD Address) {
    INT res = LTR34_IsOpened(module);

    if (res == LTR_OK) {
        if (module->DACRunning)
            res = LTR34_DACStop(module);
    }

    if (res == LTR_OK) {
        res = ltr_at93c86a_read(&module->Channel, Address, data, size);
    }
    return res;
}


/*******************************************************************************
    Считывает из ППЗУ заводские калибровочные коэффициенты
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_GetCalibrCoeffs(TLTR34 *module) {
    INT res;
    res = LTR34_ReadFlash(module, (BYTE*)&module->DacCalibration.FactoryCalibrOffset,
                          sizeof(module->DacCalibration.FactoryCalibrOffset),
                          LTR34_FLASH_ADDR_CALIBR);
    if (res == LTR_OK) {
        res=LTR34_ReadFlash(module, (BYTE *)&module->DacCalibration.FactoryCalibrScale,
                            sizeof(module->DacCalibration.FactoryCalibrScale),
                            LTR34_FLASH_ADDR_CALIBR+
                                sizeof(module->DacCalibration.FactoryCalibrOffset));
    }
    return res;
}

/*******************************************************************************
        Функция записи калибровочных коэффициентов в ППЗУ
*******************************************************************************/
LTR34API_DllExport (INT) LTR34_WriteCalibrCoeffs(TLTR34 *module) {
    int res=LTR_OK;
    /* Записываем базовые коэфициенты смещения */
    res = LTR34_WriteFlash(module,(BYTE *)&module->DacCalibration.FactoryCalibrOffset,
                           sizeof(module->DacCalibration.FactoryCalibrOffset),
                           LTR34_FLASH_ADDR_CALIBR);
    if (res == LTR_OK) {
        // Записываем базовые коэффициенты усиления
        res = LTR34_WriteFlash(module, (BYTE *)&module->DacCalibration.FactoryCalibrScale,
                               sizeof(module->DacCalibration.FactoryCalibrScale),
                               LTR34_FLASH_ADDR_CALIBR +
                                    sizeof(module->DacCalibration.FactoryCalibrOffset));
    }

    if (res == LTR_OK) {
        res = f_write_flash_crc(module);
    }
    return res;
}

/*******************************************************************************
    Получение строки, описывающей ошибку, соответствующую коду ErrorCode
*******************************************************************************/
LTR34API_DllExport (LPCSTR) LTR34_GetErrorString(INT ErrorCode) {
    const int err_qnt = sizeof err_str / sizeof err_str[0];
    const int min_err_id=3000;
    LPCSTR    ret_val = NULL;

    if (ErrorCode > -min_err_id || ErrorCode < -(min_err_id + err_qnt)) {
        ret_val = LTR_GetErrorString(ErrorCode);
    } else {
        ret_val = err_str[-ErrorCode - min_err_id-1];
    }

    return ret_val;
}


/*******************************************************************************
    Функция проверки контрольной суммы ППЗУ
*******************************************************************************/


LTR34API_DllExport (int) LTR34_TestEEPROM(TLTR34 *module) {
    WORD crc_calc;
    BYTE crc_flash_bytes[2];

    INT res = f_eval_flash_crc(module, &crc_calc);
    if (res == LTR_OK) {
        res = LTR34_ReadFlash(module, crc_flash_bytes, 2, LTR34_FLASH_ADDR_CRC); //Считываем КС по адр. 2001
    }

    if (res == LTR_OK) {
        WORD crc = ((WORD)crc_flash_bytes[1] << 8) | crc_flash_bytes[0];
        if (crc != crc_calc)
            res = LTR34_ERROR_WRONG_FLASH_CRC;
    }
    return res;
}

/*******************************************************************************
    Функция расчета контрольной суммы ППЗУ
*******************************************************************************/
static INT f_eval_flash_crc(TLTR34 *module, WORD *Flash_CRC) {
    BYTE data[WORKING_FLASH_SIZE];
    INT res;
    res = LTR34_ReadFlash(module, data, WORKING_FLASH_SIZE, 0);
    if (res == LTR_OK) {
        WORD crc = eval_crc16(0, data, WORKING_FLASH_SIZE);
        if (Flash_CRC)
            *Flash_CRC = crc;
    }
    return res;
}

/*******************************************************************************
    Функция записи контрольной суммы в ППЗУ
*******************************************************************************/
static INT f_write_flash_crc(TLTR34 *module) {
    WORD crc;
    INT res = f_eval_flash_crc(module, &crc);
    if (res == LTR_OK) {
        BYTE CRC_Bytes[2];
        CRC_Bytes[0] = crc & 0xFF;
        CRC_Bytes[1] = (crc >> 8) & 0xFF;
        res = LTR34_WriteFlash(module, CRC_Bytes, 2, LTR34_FLASH_ADDR_CRC);
    }
    return res;
}

/*******************************************************************************
    Функция чтения серийного номера из ППЗУ
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_ReadSerialNumber(TLTR34 *module, CHAR *sn) {
    INT res = LTR34_ReadFlash(module, (BYTE *) sn, LTR34_SERIAL_SIZE,
                              LTR34_FLASH_ADDR_SERIAL);
    if (res == LTR_OK) {
        /* если нет завершающего нуля в прочитанной строке - то серийный не
           записан и устанавливаем в качестве него пустую строку */
        if (strnlen(sn, LTR34_SERIAL_SIZE) == LTR34_SERIAL_SIZE)
            sn[0]='\0';
    }
    return res;
}

/*******************************************************************************
    Функция чтения имени модуля из ППЗУ
******************************************************************************/
LTR34API_DllExport(INT) LTR34_ReadModuleName(TLTR34 *module, CHAR *name) {
    INT res = LTR34_ReadFlash(module, (BYTE *) name, 7, LTR34_FLASH_ADDR_NAME);
    if (res == LTR_OK) {
        /** @warning Реально имя модуля записывается во flash без финального
            нуля! поэтому приходится добавлять этот ноль вручную...
            При этом имя всегда 7 байт... */
        name[7] = '\0';
    }
    return res;
}

/*******************************************************************************
    Функция чтения версии прошивки ПЛИС из ППЗУ
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_Read_FPGA_Ver(TLTR34 *module, CHAR* FPGA_Ver) {
    INT res = LTR34_ReadFlash(module, (BYTE*)FPGA_Ver, LTR34_FPGA_VER_SIZE,
                              LTR34_FLASH_ADDR_FPGA_VER);
    if (res == LTR_OK) {
        if (strnlen(FPGA_Ver, LTR34_FPGA_VER_SIZE) == LTR34_FPGA_VER_SIZE)
            FPGA_Ver[0] = '\0';
    }
    return res;
}

/******************************************************************************
    Функция чтения версии калибровки из ППЗУ
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_ReadCalibrVer(TLTR34 *module, CHAR *CalibrVer) {
    INT res=LTR34_ReadFlash(module, (BYTE*)CalibrVer, LTR34_CALIBR_VER_SIZE,
                            LTR34_FLASH_ADDR_CALIBR_VER);
    if (res == LTR_OK) {
        if (strnlen(CalibrVer, LTR34_CALIBR_VER_SIZE) == LTR34_CALIBR_VER_SIZE) {
            CalibrVer[0]='1';
            CalibrVer[1]='\0';
        }
    }
    return res;
}

/*******************************************************************************
    Функция чтения максимального числа каналов  (4 или 8)
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_ReadChQnt(TLTR34 *module, BYTE *ChQnt) {
    return LTR34_ReadFlash(module, ChQnt, 1, LTR34_FLASH_ADDR_CH_QNT);
}





static INT f_write_flash_string(TLTR34 *module, DWORD Code, WORD addr,
                                const CHAR *str, INT max_len) {
    INT res = str == NULL ? LTR_ERROR_PARAMETERS : Code != LTR34_FLASH_WRITE_CODE ?
                LTR_ERROR_PARAMETERS : LTR_OK;
    if (res == LTR_OK) {
        INT len = (INT)strnlen(str, max_len);
        if (len == max_len) {
            res = LTR_ERROR_PARAMETERS;
        } else {
            res = LTR34_WriteFlash(module, (BYTE *)str, len+1, addr);
        }
    }
    return res;
}


/*******************************************************************************
    Функция записи серийного номера в ППЗУ
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_WriteSerialNumber(TLTR34 *module,
                                                const CHAR *sn, DWORD Code) {
    INT res = f_write_flash_string(module, Code, LTR34_FLASH_ADDR_SERIAL, sn,
                                   LTR34_SERIAL_SIZE);
    if (res == LTR_OK) {
        res = f_write_flash_string(module, Code, LTR34_FLASH_ADDR_NAME,
                                   LTR34_MODULE_NAME, LTR34_MODULE_NAME_SIZE);
    }
    if (res == LTR_OK)
        res = f_write_flash_crc(module);
    return res;
}


/*******************************************************************************
        Функция записи имени модуля в ППЗУ
*******************************************************************************/
LTR34API_DllExport(INT) LTR34_WriteModuleName(TLTR34 *module,
                                             const CHAR *ModuleName, DWORD Code) {
    INT res = f_write_flash_string(module, Code, LTR34_FLASH_ADDR_NAME, ModuleName,
                                LTR34_MODULE_NAME_SIZE);
    if (res == LTR_OK)
        res = f_write_flash_crc(module);
    return res;
}



/*******************************************************************************
    Функция записи версии прошивки ПЛИС в ППЗУ
*******************************************************************************/   
LTR34API_DllExport(INT) LTR34_WriteFPGA_Ver(TLTR34 *module,
                                            const CHAR *FPGA_Ver, DWORD Code) {
    INT res = f_write_flash_string(module, Code, LTR34_FLASH_ADDR_FPGA_VER,
                                   FPGA_Ver, LTR34_FPGA_VER_SIZE);
    if (res == LTR_OK)
        res = f_write_flash_crc(module);
    return res;
}

/*******************************************************************************
    Функция записи версии калибровки в ППЗУ
*******************************************************************************/   
LTR34API_DllExport(INT) LTR34_WriteCalibrVer(TLTR34 *module, CHAR *CalibrVer,
                                             DWORD Code) {
    INT res = f_write_flash_string(module, Code, LTR34_FLASH_ADDR_CALIBR_VER,
                                   CalibrVer, LTR34_CALIBR_VER_SIZE);
    if (res == LTR_OK)
        res = f_write_flash_crc(module);
    return res;
}

/*******************************************************************************
    Функция записи максимального числа каналов (4 или 8) в ППЗУ
*******************************************************************************/   
LTR34API_DllExport(INT) LTR34_WriteChQnt(TLTR34 *module, BYTE ChQnt, int Code) {

    INT res = Code != LTR34_FLASH_WRITE_CODE ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (res == LTR_OK) {
        res=LTR34_WriteFlash(module, &ChQnt, 1, LTR34_FLASH_ADDR_CH_QNT);
    }
    if (res == LTR_OK)
        res = f_write_flash_crc(module);
    return res;
}

LTR34API_DllExport(INT) LTR34_FindDacFreqDivisor(double DacFreq, BYTE ChannelQnt, BYTE *devisor, double *resultDacFreq) {
    INT err = (ChannelQnt != 1) && (ChannelQnt != 2) && (ChannelQnt != 4) && (ChannelQnt != 8) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        BYTE fnd_divisor;
        double fnd_f;
        double fnd_df;
        BYTE div;
        /** @todo можно сделать вариант обратного рассчета, но т.к. значений
         * всего 61, то проход по всем значениям также быстро выполняется */
        for (div = 0; div <= LTR34_FREQ_DIV_MAX; div++) {
            double f = f_calc_dac_freq(div, ChannelQnt);
            double df = fabs(f - DacFreq);
            if ((div == 0) || (df < fnd_df)) {
                fnd_df = df;
                fnd_f = f;
                fnd_divisor = div;
            }
        }

        if (devisor != NULL)
            *devisor = fnd_divisor;
        if (resultDacFreq != NULL)
            *resultDacFreq = fnd_f;
    }
    return err;
}
