//----------------------------------------------------------------------------

//DFS-90
//Модуль реализации меню

//----------------------------------------------------------------------------

#include "Main.h"
#include "Menu.h"
#include "Display.h"
#include "Sound.h"
#include "Encoder.h"
#include "Control.h"

//--------------------------- Опции компиляции: ------------------------------

//#define INITIAL_FAN_ON     //включение фена по включению питания

//----------------------------- Константы: -----------------------------------

#define INIT_TM       240  //задержка инициализации, мс
#define START_TM     1000  //задержка включения, мс
#define ONOFF_TM      200  //задержка включения-выключения, мс
#define WAIT_TM        60  //задержка перехода в STB, с
#define STB_TM        300  //задержка перехода в OFF, с
#define BLDEL_TM      640  //задержка начала мигания дисплея, мс
#define BLINK_TM      160  //полупериод мигания дисплея, мс
#define STDEL_TM     1280  //время индикации установленных значений, мс
#define EXITS_TM     5600  //задержка выхода из меню редактирования, мс
#define EXITP_TM     5600  //задержка выхода из меню пресетов, мс
#define READY_TM     2000  //время удержания температуры для готовности, мс
#define BBEEP_TM      120  //период двойного beep, мс

#define AVG             8  //количество усреднений для выводимой температуры

#define T_SET_MIN       0  //минимальная  задаваемая температура, x0.1°C
#define T_SET_NOM    3000  //номинальная  задаваемая температура, x0.1°C
#define T_SET_MAX    4800  //максимальная задаваемая температура, x0.1°C
#define T_STB_NOM    1000  //номинальная температура для Standby, x0.1°C
#define T_OFF_NOM     500  //номинальная температура остужения, x0.1°C
#define T_STEP         50  //шаг регулировки температуры, x0.1°C
#define DT_READY        4  //отклонение температуры для готовности, x0.1°C
#define DT_UNREADY     20  //отклонение температуры для неготовности, x0.1°C

#define V_SET_MIN    1000  //минимальная  задаваемая скорость, rpm
#define V_SET_NOM    8200  //номинальная  задаваемая скорость, rpm
#define V_SET_MAX    9900  //максимальная задаваемая скорость, rpm
#define V_STB_NOM    5000  //номинальная скорость для Standby, rpm
#define V_OFF_NOM    9000  //номинальная скорость для остужения, rpm
#define V_STEP         50  //шаг регулировки скорости, rpm

#define PRES            1  //количество пресетов
#define PR_OFF          0  //код пресета Off
#define PR_STB          1  //код пресета Standby
#define PR_ON           2  //код пресета On

//коды состояний:
enum
{
  ST_NO,       //код неопределенного состояния
  ST_START,    //код состояния начального старта
  ST_ON,       //код рабочего состояния
  ST_WAIT,     //код ожидания перехода в ждущий режим
  ST_STB,      //код ждущего режима
  ST_COOL,     //код режима охлаждения
  ST_OFF,      //код выключенного режима
  ST_ERROR     //код режима ошибки
};

//коды меню:
enum
{
  MNU_NO,      //код отсутствия меню
  MNU_INIT,    //код меню начальной инициализации
  MNU_OFF,     //код меню OFF
  MNU_STB,     //код меню STB
  MNU_MAIN,    //код основного меню
  MNU_SETT,    //код меню установки температуры
  MNU_SETV,    //код меню установки скорости вентилятора
  MNU_ERROR    //код меню индикации аварии
};

//коды готовности:
enum
{
  RDY_OFF,     //проверка готовности выключена
  RDY_NO,      //температура не достигнута
  RDY_YES,     //температура достигнута
  RDY_STAB,    //температура стабилизировалась
  RDY_OK       //сигнализация готовности произведена
};

//----------------------------- Переменные: ----------------------------------

static char Menu;        //индекс требуемого меню
static char DispMenu;    //индекс текущего меню
static char SubMenu;     //индекс подменю
static char RetMenu;     //индекс меню для возврата из индикации
static char Message;     //код управляющего сообщения
static bool Display;     //признак включения дисплея
static bool Update;      //признак обновления дисплея
static bool Changed;     //признак редактирования величины
static int  BlinkTimer;  //таймер мигания дисплея
static int  MenuTimer;   //таймер возврата из меню

static char State;       //текущее состояние
static char NextState;   //следующее состояние
static long StateTimer;  //таймер машины состояний

static char Error;       //код управляющего сообщения
static char Preset;      //номер текущего пресета
static bool Pc;          //признак управления от компьютера
static char Ready;       //код готовности
static int  ReadyTimer;  //таймер готовности
static bool UpdateT;     //признак обновления температуры
static int  T_Get;       //измеренная температура
static int  T_Set;       //установленная температура
//static bool UpdateV;     //признак обновления скорости DEBUG
//static int  V_Get;       //измеренная скорость DEBUG
static int  V_Set;       //установленная скорость

typedef struct //тип данных для представления пресета
{
  int t;       //температура
  int v;       //скорость
} preset;

__no_init __eeprom preset EPresets[PRES + 1]; //пресеты в EEPROM

//-------------------------- Прототипы функций: ------------------------------

void Mnu_Init(bool ini);  //меню начальной инициализации
void Mnu_Off(bool ini);   //меню OFF
void Mnu_Stb(bool ini);   //меню STB
void Mnu_Main(bool ini);  //основное меню
void Mnu_SetT(bool ini);  //меню установки температуры
void Mnu_SetV(bool ini);  //меню установки скорости вентилятора
void Mnu_Error(bool ini); //меню индикации аварии

void DisplayT(int t);     //вывод значения температуры
void DisplayV(int v);     //вывод значения скорости
bool ChangeTset(int s);   //регулировка температуры
bool ChangeVset(int s);   //регулировка скорости

//---------------------- Инициализация системы меню: -------------------------

void Menu_Init(void)
{
  Pc = Pin_RXD;                    //проверка подключения порта PC
  Display_Init(!Pc, '-');          //инициализация дисплея
  Encoder_Init();                  //инициализация модуля энкодера
  Error = HE_NON;                  //ошибок нет
  State = ST_NO;                   //текущее состояние не определено
  Ready = RDY_NO;                  //температура не достигнута
  DispMenu = MNU_NO;               //никакое меню пока не отображается
  Preset = PR_OFF;                 //пресет OFF
  Menu_ReadP(Preset);              //нулевая температура и скорость
  if(Pc)
  {
    NextState = ST_NO;             //машина состояний не запускается
    Menu = MNU_NO;                 //запрос меню инициализации
  }
  else
  {
    NextState = ST_START;          //запуск машины состояний
    Menu = MNU_INIT;               //запрос меню инициализации
  }
}

//---------------------------- Обработка меню: -------------------------------

void Menu_Exe(bool t)
{
  Encoder_Exe(t);                  //опрос энкодера
  Sound_Exe(t);                    //формирование звукового сигнала

  //обработка программных таймеров:
  if(t)
  {
    if(BlinkTimer) BlinkTimer--;   //обработка таймера мигания
    if(MenuTimer)  MenuTimer--;    //обработка таймера возврата
    if(ReadyTimer) ReadyTimer--;   //обработка таймера готовности
    if(StateTimer) StateTimer--;   //обработка таймера состояний
  }

  //обновление температуры:
  UpdateT = Control_GetTupd();     //признак обновления температуры
  if(UpdateT)
  {
    static char Avg = 0;
    static long Sum = 0;
    Sum += Control_GetTc();        //чтение температуры и суммирование
    if(++Avg == AVG)
    {
      T_Get = Sum / AVG;           //вычисление среднего значения
      Sum = Avg = 0;               //обнуление суммы и счетчика
    }
    //обработка ошибок:
    Error = Control_GetError();    //чтение кода ошибки
    /*
    if(Error && (State > ST_START))
    { 
      NextState = ST_ERROR;        //состояние ошибки
    }
    */
  }

  /*
  //обновление скорости: DEBUG
  UpdateV = Control_GetVupd();     //признак обновления скорости
  if(UpdateV)
    V_Get = Control_GetVc();       //чтение скорости
  */

  //звуковая сигнализация готовности:
  switch(Ready)
  {
  case RDY_NO:                     //температура не достигнута
    if(!UpdateT) break;
    if((T_Get > T_Set - DT_READY) &&
       (T_Get < T_Set + DT_READY))
    {
      ReadyTimer = ms2sys(READY_TM); //запуск таймера готовности 
      Ready = RDY_YES;             //температура попала в вилку
    }
    break;
  case RDY_YES:                    //температура достигнута
    if(!UpdateT) break;
    if((T_Get < T_Set - DT_READY) ||
       (T_Get > T_Set + DT_READY))
    {
      Ready = RDY_NO;              //температура вышла из вилки
      break;
    }
    if(!ReadyTimer)
    {
      Sound_Start(D_SHRT);         //beep 1
      ReadyTimer = ms2sys(BBEEP_TM); //задержка двойного beep
      Ready = RDY_STAB;            //температура стабилизировалась
    }
    break;
  case RDY_STAB:                   //температура стабилизировалась
    if(!ReadyTimer)
    {
      Sound_Start(D_SHRT);         //beep 2
      Ready = RDY_OK;              //сигнализация произведена
    }
    break;
  case RDY_OK:                     //сигнализация готовности произведена
    if(!UpdateT) break;
    if((T_Get < T_Set - DT_UNREADY) ||
       (T_Get > T_Set + DT_UNREADY))
    {
      //Sound_Start(D_BELL);         //сигнал ошибки - нужен ли???
      Ready = RDY_NO;              //температура вышла из широкой вилки
    }
    break;
  }

  //чтение сигнала включения сети:
  bool On = Control_MainsOn();     //чтение текущего состояния выключателя
#ifdef INITIAL_FAN_ON    
  static bool Off = 0;             //признак выключенной сети
#endif        
  
  //фильтрация сигнала геркона:
  static char ReedFlt = 0;         //фильтр сигнала геркона
  bool Oper = Pin_REED;            //чтение текущего состояния геркона
  static bool Idle = 0;            //признак фена на подставке
  if(t)
  {
    if(Oper  && (ReedFlt < 0xFF)) ReedFlt++;
    if(!Oper && (ReedFlt > 0x00)) ReedFlt--;
  }
  Oper = ReedFlt & 0x80;           //отфильтрованное состояние геркона
  
  //перезапуск таймеров:
  Message = Encoder_Msg();         //чтение кода управления
  if(Message != MSG_NOP)           //перезапуск таймера
  {
    if(State == ST_WAIT) StateTimer = s2sys(WAIT_TM);
    if(State == ST_STB)  StateTimer = s2sys(STB_TM);
  }

  //машина состояний - обработка текущего состояния:
  switch(State)
  {
  case ST_START:                   //начальный старт:
    {
      if(StateTimer)               //если длится задержка старта,
        break;                     //выход
#ifdef INITIAL_FAN_ON    
      if(On && Oper)               //если сеть включена и фен поднят
        NextState = ST_ON;         //включение
      else
#endif        
        NextState = ST_OFF;        //иначе выключение
      break;
    }
  case ST_ON:                      //работа:
    {
      if(StateTimer)               //если длится задержка,
        break;                     //выход
      if(!Oper)                    //если фен положен,
        NextState = ST_WAIT;       //ожидание перехода в STB
      if(!On)                      //если сеть выключена,
        NextState = ST_OFF;        //выключение
      break;
    }
  case ST_WAIT:                    //ожидание перехода в STB:
    {
      if(!StateTimer)              //если задержка перехода в STB истекла,
        NextState = ST_STB;        //переход в STB
      if(Idle && Oper)             //если фен подняли,
          NextState = ST_ON;       //включение
      if(!On)                      //если сеть выключена,
        NextState = ST_OFF;        //выключение
      break;
    }
  case ST_STB:                     //STB:
    {
      if(!StateTimer)              //если задержка отключения истекла,
        NextState = ST_OFF;        //выключение
      if(Idle && Oper)             //если фен подняли,
        NextState = ST_ON;         //включение
      if(!On)                      //если сеть выключена,
        NextState = ST_OFF;        //выключение
      break;
    }
  case ST_COOL:                    //охлаждение:
    {
      if(T_Get < T_OFF_NOM)        //если охлаждение закончено,
        NextState = ST_OFF;        //выключение
      if(On && Idle && Oper)       //если фен подняли,
        NextState = ST_ON;         //включение
#ifdef INITIAL_FAN_ON    
      if(Off && On && Oper)        //если сеть включили и фен поднят,
        NextState = ST_ON;         //включение
#endif        
      break;
    }
  case ST_OFF:                     //OFF:
    {
      if(On && Idle && Oper)       //если фен подняли,
        NextState = ST_ON;         //включение
      break;
    }
  /*  
  //выкинуть вообще это состояние!!!
  case ST_ERROR:
    {
      if(Error == HE_NON) // делать это в меню ошибки!!!
      {
        //NextState = ???;
        Menu = RetMenu;              //возврат в меню
      }
      break;
    }
  */
  }

  //машина состояний - обработка перехода в следующее состояние:
  if(State != NextState)
  {
    switch(NextState)
    {
    case ST_START:                 //переход на начальный старт
      {
        StateTimer = ms2sys(START_TM); //запуск таймера старта
        break;
      }
    case ST_ON:                    //переход на включение
      {
        StateTimer = ms2sys(ONOFF_TM); //задержка включения-выключения
        Port_TRIAC2_0;             //включение триака блокировки выключателя
        Preset = PR_ON;            //текущий пресет
        Menu_ReadP(Preset);        //чтение пресета
        Control_SetT(T_Set);       //установка температуры    
        Control_SetV(V_Set);       //установка скорости    
        Ready = RDY_NO;            //включение контроля температуры
        Menu = MNU_MAIN;           //переход в основное меню
        break;
      }
    case ST_WAIT:                  //переход на ожидание STB
      {
        StateTimer = s2sys(WAIT_TM);
        break;
      }
    case ST_STB:                   //переход в STB
      {
        StateTimer = s2sys(STB_TM);
        Preset = PR_STB;           //текущий пресет
        Menu_ReadP(Preset);        //чтение пресета
        Control_SetT(T_Set);       //установка температуры    
        Control_SetV(V_Set);       //установка скорости    
        Ready = RDY_OFF;           //выключение контроля температуры
        Menu = MNU_STB;            //переход в меню STB
        break;
      }
    case ST_OFF:                   //переход в состояние OFF
      {
        Control_SetT(0);           //выключение нагрева
        Control_SetV(0);           //выключение вентилятора
        Ready = RDY_OFF;           //выключение контроля температуры
        Preset = PR_ON;            //текущий пресет
        Menu_ReadP(Preset);        //чтение рабочего пресета
        Menu = MNU_OFF;            //переход в меню OFF
        if(T_Get < T_OFF_NOM)      //если температура ниже порога,
        {
          Port_TRIAC2_1;           //выключение триака блокировки выключателя
          break;
        }
        Port_TRIAC2_0;             //включение триака блокировки выключателя
        NextState = ST_COOL;       //иначе переход к охлаждению
      }
    case ST_COOL:                  //переход к охлаждению
      {
        Control_SetT(0);           //выключение нагрева
        Control_SetV(V_OFF_NOM);   //включение вентилятора для охлаждения
        Ready = RDY_OFF;           //выключение контроля температуры
        Menu = MNU_OFF;            //переход в меню OFF
        break;
      }
    /*  
    case ST_ERROR:
      {
        RetMenu = DispMenu;            //сохранение меню возврата
        Menu = MNU_ERROR;              //переход в меню ошибки
        break;
      }
    */
    }
    State = NextState;             //сохранение нового состояния
  }
  Idle = !Oper;                    //сохранение положения фена
#ifdef INITIAL_FAN_ON    
  Off = !On;                       //сохранение состояния выключателя
#endif        
  
  //обработка меню:
  bool MnuIni = Menu != DispMenu;  //признак необходимости прорисовки
  switch(Menu)                     //выполнение обработчиков меню
  {
  case MNU_INIT:  Mnu_Init(MnuIni);  break; //меню начальной инициализации
  case MNU_OFF:   Mnu_Off(MnuIni);   break; //меню OFF
  case MNU_STB:   Mnu_Stb(MnuIni);   break; //меню STB
  case MNU_MAIN:  Mnu_Main(MnuIni);  break; //основное меню
  case MNU_SETT:  Mnu_SetT(MnuIni);  break; //меню установки температуры
  case MNU_SETV:  Mnu_SetV(MnuIni);  break; //меню установки скорости
  case MNU_ERROR: Mnu_Error(MnuIni); break; //меню индикации ошибки
  }
}

//----------------------------------------------------------------------------
//---------------------------- Реализация меню: ------------------------------
//----------------------------------------------------------------------------

//---------------------- Меню начальной инициализации: -----------------------

void Mnu_Init(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    Update = 0;                    //обновление дисплея не требуется
    MenuTimer = ms2sys(INIT_TM);   //загрузка интервала инициализации
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //проверка таймера:
  if(!MenuTimer)                   //если интервал истек, то
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = MNU_OFF;                //переход в меню OFF
  }
}

//----------------------------- Меню OFF: ------------------------------------

void Mnu_Off(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    Display = 1;                   //дисплей включен
    Display_Char(0, 'O');
    Display_Char(1, 'F');
    Display_Char(2, 'F');
    RetMenu = Menu;                //код меню для возврата
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //вращение энкодера вправо:
  if(Message == MSG_INC)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    NextState = ST_STB;            //переход OFF -> STB
  }
  //вращение энкодера влево:
  if(Message == MSG_DEC)
  {
    Sound_Start(D_TICK);           //звуковой сигнал ошибки
  }
  //нажатие кнопки энкодера:
  if(Message == MSG_KDN)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = MNU_SETT;               //переход в меню установки температуры
  }
}

//----------------------------- Меню STB: ------------------------------------

void Mnu_Stb(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    Display = 1;                   //дисплей включен
    Display_Char(0, 'S');
    Display_Char(1, 't');
    Display_Char(2, 'b');
    RetMenu = Menu;                //код меню для возврата
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //вращение энкодера вправо:
  if(Message == MSG_INC)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    NextState = ST_ON;             //переход STB -> ON
  }
  //вращение энкодера влево:
  if(Message == MSG_DEC)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    NextState = ST_OFF;            //переход STB -> OFF
  }
  //нажатие кнопки энкодера:
  if(Message == MSG_KDN)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = MNU_SETT;               //переход в меню установки температуры
  }
}

//--------------------------- Основное меню: ---------------------------------

void Mnu_Main(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    Display = 1;                   //дисплей включен
    DisplayT(T_Set);               //вывод установленной температуры
    SubMenu = 0;
    BlinkTimer = ms2sys(STDEL_TM); //загрузка времени индикации
    RetMenu = Menu;                //код меню для возврата
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //гашение дисплея и
  //применение пресета:
  if(!BlinkTimer && !SubMenu)      //если время индикации истекло,
  {
    Display = 0;                   //дисплей выключен
    DisplayT(T_Set);               //гашение дисплея
    SubMenu = 1;
    BlinkTimer = ms2sys(BLINK_TM); //загрузка таймера мигания
  }
  //обновление дисплея:
  if(UpdateT && !BlinkTimer)
  {
    Display = 1;                   //дисплей включен
    DisplayT(T_Get);               //вывод значения температуры
  }
  //вращение энкодера вправо:
  if(Message == MSG_INC)
  {
    Sound_Start(D_TICK);           //звуковой сигнал ошибки
  }
  //вращение энкодера влево:
  if(Message == MSG_DEC)
  {
    NextState = ST_STB;            //переход ON -> STB
  }
  //нажатие кнопки энкодера:
  if(Message == MSG_KDN)
  {
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = MNU_SETT;               //переход в меню установки температуры
  }
}

//---------------------- Меню установки температуры: -------------------------

void Mnu_SetT(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    MenuTimer = ms2sys(EXITS_TM);  //загрузка интервала возврата
    BlinkTimer = 0;                //обнуление таймера мигания
    Display = 1;                   //дисплей будет выключен
    Changed = 0;                   //значение не редактировалось
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //мигание дисплея:
  if(!BlinkTimer)                  //проверка таймера мигания
  {
    BlinkTimer = ms2sys(BLINK_TM); //перезагрузка таймера мигания
    Display = !Display;            //инвертирование признака влючения дисплея
    DisplayT(T_Set);               //обновление дисплея
  }
  //обновление дисплея:
  if(Update)
  {
    MenuTimer = ms2sys(EXITS_TM);  //загрузка интервала возврата
    BlinkTimer = ms2sys(BLDEL_TM); //загрузка задержки мигания
    Display = 1;                   //дисплей включен
    DisplayT(T_Set);               //индикация установленной температуры
    Update = 0;                    //очистка флага обновления
  }
  //вращение энкодера вправо:
  if(Message == MSG_INC)
  {
    if(ChangeTset(T_STEP))         //шаг вверх
    {
      if(RetMenu != MNU_OFF)
        Control_SetT(T_Set);       //установка температуры
      Changed = 1;                 //значение изменено
    }
    else Sound_Start(D_TICK);      //звуковой сигнал ошибки
    Update = 1;                    //требование перерисовки
  }
  //вращение энкодера влево:
  if(Message == MSG_DEC)
  {
    if(ChangeTset(-T_STEP))        //шаг вниз
    {
      if(RetMenu != MNU_OFF)
        Control_SetT(T_Set);       //установка температуры
      Changed = 1;                 //значение изменено
    }
    else Sound_Start(D_TICK);      //звуковой сигнал ошибки
    Update = 1;                    //требование перерисовки
  }
  //нажатие кнопки энкодера:
  if(Message == MSG_KDN)
  {
    Menu_SaveP(Preset);            //сохранение пресета
    Sound_Start(D_BEEP);           //звуковой сигнал
    if(Changed)
      Menu = RetMenu;              //возврат
        else Menu = MNU_SETV;      //переход в меню установки скорости
  }
  //проверка таймера:
  if(!MenuTimer)                   //если интервал истек, то
  {
    Menu_SaveP(Preset);            //сохранение пресета
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = RetMenu;                //возврат
  }
}

//------------------ Меню установки скорости вентилятора: --------------------

void Mnu_SetV(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
    MenuTimer = ms2sys(EXITS_TM);  //загрузка интервала возврата
    BlinkTimer = 0;                //обнуление таймера мигания
    Display = 1;                   //дисплей будет выключен
    DispMenu = Menu;               //снятие требования прорисовки
  }
  //мигание дисплея:
  if(!BlinkTimer)                  //проверка таймера мигания
  {
    BlinkTimer = ms2sys(BLINK_TM); //перезагрузка таймера мигания
    Display = !Display;            //инвертирование признака влючения дисплея
    DisplayV(V_Set);               //обновление дисплея
  }
  //обновление дисплея:
  if(Update)
  {
    MenuTimer = ms2sys(EXITS_TM);  //загрузка интервала возврата
    BlinkTimer = ms2sys(BLDEL_TM); //загрузка задержки мигания
    Display = 1;                   //дисплей включен
    DisplayV(V_Set);               //индикация установленной скорости
    Update = 0;                    //очистка флага обновления
  }
  //вращение энкодера вправо:
  if(Message == MSG_INC)
  {
    if(ChangeVset(V_STEP))         //шаг вверх
    {
      if(RetMenu != MNU_OFF)
        Control_SetV(V_Set);       //установка скорости
    }
    else Sound_Start(D_TICK);      //звуковой сигнал ошибки
    Update = 1;                    //требование перерисовки
  }
  //вращение энкодера влево:
  if(Message == MSG_DEC)
  {
    if(ChangeVset(-V_STEP))        //шаг вниз
    {
      if(RetMenu != MNU_OFF)
        Control_SetV(V_Set);       //установка скорости
    }
    else Sound_Start(D_TICK);      //звуковой сигнал ошибки
    Update = 1;                    //требование перерисовки
  }
  //нажатие кнопки энкодера:
  if(Message == MSG_KDN)
  {
    Menu_SaveP(Preset);            //сохранение пресета
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = RetMenu;                //возврат
  }
  //проверка таймера:
  if(!MenuTimer)                   //если интервал истек, то
  {
    Menu_SaveP(Preset);            //сохранение пресета
    Sound_Start(D_BEEP);           //звуковой сигнал
    Menu = RetMenu;                //возврат
  }
}

//------------------------- Меню индикации ошибки: ---------------------------

void Mnu_Error(bool ini)
{
  //прорисовка меню:
  if(ini)                          //если требуется прорисовка, то
  {
//    Control_SetT(0);               //выключение нагрева
//    if()                           //если перегрев,
//      Control_SetV(V_SET_MAX);     //включение вентилятора на максимум
//        else
//          Control_SetV(0);         //включение вентилятора на максимум
    Sound_Start(D_BELL);           //звуковой сигнал ошибки
    Display_Char(0, 'E');          //вывод E
    Display_Char(1, '-');          //вывод -
    Display_Char(2, Error);        //вывод номера ошибки
    DispMenu = Menu;               //снятие требования прорисовки
  }
  if(UpdateT)
  {
    if(!Error)
      Menu = RetMenu;              //возврат
  }
}

//----------------------------------------------------------------------------
//------------------------- Вспомогательные функции: -------------------------
//----------------------------------------------------------------------------

//----------------------- Вывод значения температуры: ------------------------

void DisplayT(int t)
{
  t = (t + 5) / 10;                     //деление с округлением
  if(t < 0) t = 0;
  if(t > 999) t = 999;
  if(Display)                           //если дисплей включен, то
  {
    char a = t / 100;
    char r = t % 100;
    Display_Char(0, a? a : ' ');        //выводим сотни
    char b = r / 10; r = r % 10;
    Display_Char(1, (a || b)? b : ' '); //выводим десятки 
    Display_Char(2, r);                 //выводим единицы
  }
  else Display_Fill(' ');
}

//------------------------ Вывод значения скорости: --------------------------

void DisplayV(int v)
{
  if(v < 0) v = 0;
  if(v > V_SET_MAX) v = V_SET_MAX;
  v = (v + 5) / 10;                     //деление с округлением
  if(Display)                           //если дисплей включен, то
  {
    char r = v % 100;
    Display_Char(0, v / 100 + POINT);   //выводим тысячи rpm
    Display_Char(1, r / 10);            //выводим сотни rpm
    Display_Char(2, r % 10);            //выводим единицы
  }
  else Display_Fill(' ');
}

//------------------------- Регулировка температуры: -------------------------

bool ChangeTset(int s)
{
  if(T_Set < T_SET_MIN) return(0);
  int t = (T_Set / s + 1) * s;
  if(t < T_SET_MIN) t = T_SET_MIN;
  if(t > T_SET_MAX) t = T_SET_MAX;
  if(T_Set == t) return(0);
  T_Set = t;
  return(1);
}

//-------------------------- Регулировка скорости: ---------------------------

bool ChangeVset(int s)
{
  if(V_Set < V_SET_MIN) return(0);
  int v = (V_Set / s + 1) * s;
  if(v < V_SET_MIN) v = V_SET_MIN;
  if(v > V_SET_MAX) v = V_SET_MAX;
  if(V_Set == v) return(0);
  V_Set = v;
  return(1);
}

//---------------------------- Чтение пресета: -------------------------------

void Menu_ReadP(char p)
{
  if(p > PR_STB + PRES) return;
  if(p == PR_OFF) { T_Set = 0; V_Set = 0; return; }
  int te = EPresets[p - PR_STB].t; //чтение T из EEPROM
  if((te >= T_SET_MIN) && (te <= T_SET_MAX))
    T_Set = te;
      else T_Set = (p == PR_STB)? T_STB_NOM : T_SET_NOM;
  int ve = EPresets[p - PR_STB].v; //чтение V из EEPROM
  if((ve >= V_SET_MIN) && (ve <= V_SET_MAX))
    V_Set = ve;
      else V_Set = (p == PR_STB)? V_STB_NOM : V_SET_NOM;
}

//-------------------------- Сохранение пресета: -----------------------------

void Menu_SaveP(char p)
{
  if(p < PR_STB || p > PR_STB + PRES) return;
  if(T_Set < T_SET_MIN) T_Set = T_SET_MIN;
  if(T_Set > T_SET_MAX) T_Set = T_SET_MAX;
  if(EPresets[p - PR_STB].t != T_Set)
    EPresets[p - PR_STB].t = T_Set; //запись T в EEPROM
  if(V_Set < V_SET_MIN) V_Set = V_SET_MIN;
  if(V_Set > V_SET_MAX) V_Set = V_SET_MAX;
  if(EPresets[p - PR_STB].v != V_Set)
    EPresets[p - PR_STB].v = V_Set; //запись T в EEPROM
}

//----------------------------------------------------------------------------
//------------------ Функции для управления от компьютера: -------------------
//----------------------------------------------------------------------------

//-------------------- Задание температуры нагревателя: ----------------------

bool Menu_SetT(int t)
{
  if(t >= 0 && t <= T_SET_MAX)
  {
    T_Set = t;
    return(1);
  }
  return(0);
}

//------------------------- Включение нагревателя: ---------------------------

void Menu_Heat(void)
{
  Control_SetT(T_Set);
  Sound_Start(D_BEEP);
}

//---------------------- Задание скорости вентилятора: -----------------------

bool Menu_SetV(int v)
{
  if(v >= 0 && v <= V_SET_MAX)
  {
    V_Set = v;
    return(1);
  }
  return(0);
}

//------------------------- Включение вентилятора: ---------------------------

void Menu_Fan(void)
{
  Control_SetV(V_Set);
  Sound_Start(D_BEEP);
}

//----------------------------------------------------------------------------


