/*****************************************************
Project : STH0024
Company : Smartmodule

Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 8,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>
#include <1wire.h>
#include <ds18b20.h>
#include <delay.h>


/* Максимальное количество датчиков DS18B20 подключенных к шине 1 Wire */
#define MAX_DEVICES 1
unsigned char rom_code[MAX_DEVICES][9];

eeprom float ee_temp_norm, ee_temp_diff; //Сохраненные настройки нормальной температуры и разницы
eeprom unsigned char ee_init; //Признак первого запуска 0 - первый запуск
eeprom unsigned char ee_brightness; //Сохраненное значение яркости
eeprom unsigned char ee_t_mode; //Сохраненное значение режима работы 1 - нагрев / 0 - охлаждение

#define HEAT_MODE 1 //Режим нагрева
#define COOLING_MODE 0 //Режим охлаждения

#define RELAY_ON PORTD = (0b00100000 | PORTD) //Команда включения реле
#define RELAY_OFF PORTD = (0b11011111 & PORTD) //Команда отключения реле
#define relay (0b00100000 & PORTD) //Состояние реле
#define MAX_TEMP 125.000 //Максимальная измеряемая температура
#define MIN_TEMP -55.000 //Минимальная измеряемая температура 
#define MAX_BRIGHTNESS 5 //Максимальное значение яркости
#define DEFAULT_TEMP_NORM 22.0 //Значение нормальной температуры по-умолчанию
#define DEFAULT_TEMP_DIFF 0.5 //Значение гистерезиса по-умолчанию
float temp_norm; //Установленная нормальная температура термостата
float temp_diff; //Установленная разница температур термостата
unsigned char brightness=MAX_BRIGHTNESS; //Яркость (0..MAX_BRIGHTNESS) 
unsigned char t_mode=HEAT_MODE; //Режим работы (нагрев/охлаждение) 

unsigned char symbols[18]={    // массив символов для индикатора
215, // 0
17,  // 1
103, // 2
117, // 3
177, // 4
244, // 5
246, // 6
81,  // 7
247, // 8
245, // 9
8,   // 10 - "."
32, // 11 - "-"
178, // 12 - "h"
2,   // 13 - "i"
130, // 14 - "l"
54,  // 15 - "o"
182, // 16 - "b"
34   // 17 - "r"
};

unsigned char digits[4]={255,255,255,255}; //Отображаемые в текущий момент сегменты на индикаторе (с права налево)
unsigned char digit=0; //Отображаемый в текущий момент динамической индикации разряд индикатора (0..3) 
unsigned int dscounter = 0; //Счетчик для опроса датчика ds18b20, прибавляется в таймере индикации
#define DS_COUNTER_MAX 1500 //Значение счетчика dscounter при котором происходит опрос датчика и сброс счетчика
unsigned char dscall = 0; //Флаг опроса датчика ds18b20, устанавливается в 1 при dscounter=DS_COUNTER_MAX и сбрасывается после обработки 
unsigned char devices=0; //Количество датчиков
float temp=0; //Текущая температура

#define KEY_PLUS 1 //Код кнопки 1 "+"
#define KEY_MINUS 2 //Код кнопки 3 "-"
#define KEY_OK 4 //Код кнопки 2 "OK/Menu"

#define SET_DIFF 1 //Режим установки разницы температур
#define SET_BRIGHTNESS_MODE 2 //Режим установки яркости
#define SET_MODE 3 //Выбор режима работы (нагрев/охлаждение)
#define SET_NORM 4 //Режим установки нормальной температуры

unsigned char key = 0;  //Нажатая кнопка

unsigned char runner=0; //Вспомогательная переменная для регулировки яркости

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
  // Динамическая индикация
  PORTB=0;
  // Отображаем в разряд digit (нумерация справа налево 0..3), символ digits[digit]
    switch (digit){
        case 3: PORTD=(0b11111011 & PORTD) | 0b00001011; if (runner++ >= MAX_BRIGHTNESS) runner = 0; break;
        case 2: PORTD=(0b11111101 & PORTD) | 0b00001101; break;
        case 1: PORTD=(0b11111110 & PORTD) | 0b00001110; break;
        case 0: PORTD=(0b11110111 & PORTD) | 0b00000111; break;
    }
    if (runner <= brightness) PORTB=digits[digit]; //Включаем индикатор
    else PORTB=0; //Если пробежка больше уровня яркости, то не включаем индикатор
    digit++;
    if (digit > 3) digit = 0;
  // Конец динамической индикации 
  
    if (++dscounter > DS_COUNTER_MAX) {
      dscounter=0; //Сброс счетчика
      dscall = 1; //Установка флага опроса датчика
    }
 #asm("wdr") //Сброс сторожевого таймера
}


void save_params(void){ //Сохранить настройки в постоянной памяти
    ee_temp_norm = temp_norm;
    ee_temp_diff = temp_diff;
    ee_brightness = brightness;
    ee_t_mode = t_mode;
    ee_init = 1;
}

void set_relay() //Установка состояния реле
{
    if (relay) {
        if ((temp > (temp_norm+temp_diff)) && t_mode || (temp < (temp_norm-temp_diff)) && !t_mode) {RELAY_OFF;}
    } else 
        if ((temp < (temp_norm-temp_diff)) && t_mode || (temp > (temp_norm+temp_diff)) && !t_mode) {RELAY_ON;}
}


//Присвоение переменным, отвечающим за показания индикатора, значения в параметрах
void show(unsigned char d3,unsigned char d2,unsigned char d1,unsigned char d0){
    digits[3]=d3;
    digits[2]=d2;
    digits[1]=d1;
    digits[0]=d0;
}
void show_float(float _temp){ //Отображение температуры на индикаторе 

    unsigned char int_temp, dec_temp;
    unsigned char i3,i2,i1,d1,d2;
    int sign=1;
    if (_temp < 0) sign=-1;
    _temp = _temp + sign*0.005;
    int_temp = (int) (_temp*sign); //целая часть температуры 
    dec_temp = (int) (100.0 * (_temp*sign)) % 100; //дробная часть температуры
    i3=int_temp/100;
    int_temp =  int_temp%100;
    i2=int_temp/10;
    i1=int_temp%10;
    d1=dec_temp/10;
    d2=dec_temp%10;
    if (_temp >= 100) { 
        show(symbols[i3],symbols[i2],symbols[i1] + symbols[10],symbols[d1]);        
    } else if (_temp >= 10) {
        show(symbols[i2],symbols[i1] + symbols[10],symbols[d1],symbols[d2]);        
    } else if (_temp >= 0) {
        show(0,symbols[i1] + symbols[10],symbols[d1],symbols[d2]);        
    } else if (_temp < -10) {
        show(symbols[11],symbols[i2],symbols[i1] + symbols[10],symbols[d1]);        
    } else if (_temp < 0) {
        show(symbols[11],symbols[i1] + symbols[10],symbols[d1],symbols[d2]);        
    }
}

//Отображение ошибки
void error(unsigned char errnum){
    switch(errnum){
            case 0: //Не указан код ошибки
                show(32,32,32,32); 
                break;
            case 1: //Температура полученная от термодатчика ниже минималной
                show(4,4,4,4); 
                break;
            case 2: //Температура полученная от термодатчика выше максимальной
                show(64,64,64,64); 
                break;
            default:
                show(230,34,34,symbols[errnum]);
                break;
    }
}

//Инициализация датчика, отключение реле
void init_sensors(unsigned char errnum)
{
    unsigned char err=1;
    RELAY_OFF;
    while (err) {
        error(errnum);
        delay_ms(250);
        devices=w1_search(0xf0,rom_code);
        if (devices){
            if (ds18b20_init(&rom_code[0][0], temp_norm-temp_diff, temp_norm+temp_diff, DS18B20_12BIT_RES))
                err = 0;
        } else errnum = 0;
    }
}
void show_char(unsigned char int_temp){ //Отображение целого числа на индикаторе
    unsigned char i3,i2,i1;
    i3 = int_temp/100;
    int_temp =  int_temp%100;
    i2 = int_temp/10;
    i1 = int_temp%10;
    if (int_temp >= 100) { 
        show(0,symbols[i3],symbols[i2],symbols[i1]);        
    } else if (int_temp >= 10) {
        show(0,0,symbols[i2],symbols[i1]);        
    } else if (int_temp >= 0) {
        show(0,0,0,symbols[i1]);        
    }
}

void first_start(void){ //Присвоение настройкам значений по-умолчанию
        temp_norm = DEFAULT_TEMP_NORM;
        temp_diff = DEFAULT_TEMP_DIFF;
        brightness = MAX_BRIGHTNESS;
        t_mode = HEAT_MODE;
}
void main(void)
{
    unsigned char save = 0; //Флаг сохранения параметров
    float dt = 0.1; //Шаг перебора температуры порогов
    unsigned char click=0; //Вспомогательный флаг для изменения значения настройки 
    unsigned char mode=0, prevmode=0; /*текущий режим*/ 
    unsigned char tmpkey=0, prevkey=0; unsigned int keycount=0; //временные переменны для обработки кнопок
    unsigned char i; //переменная для циклов

    PORTB=0x00; DDRB=0xFF;
    PORTC=0x07; DDRC=0x00;
    PORTD=0x00; DDRD=0b10101111;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
TCCR0=0x02;
TCNT0=0x00;
// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
MCUCR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x01;

    // 1 Wire Bus initialization
    // 1 Wire Data port: PORTD
    // 1 Wire Data bit: 7
    // Note: 1 Wire port settings must be specified in the
    // Project|Configure|C Compiler|Libraries|1 Wire IDE menu.
    w1_init();

// Watchdog Timer initialization
// Watchdog Timer Prescaler: OSC/2048k

#pragma optsize-
WDTCR=0x1F;
WDTCR=0x0F;
#ifdef _OPTIMIZE_SIZE_
#pragma optsize+
#endif

    // Global enable interrupts
    #asm("sei")

    show(255,255,255,255);
    delay_ms(250);


    //Первичная инициализация параметров при первом запуске и на случай если сохраненные настройки некорректные    
    if ((ee_init != 1) || ((ee_temp_norm + ee_temp_diff) > MAX_TEMP) || ((ee_temp_norm - ee_temp_diff) < MIN_TEMP)){ //Первый запуск или некорректные значения
        first_start();
        save_params();
    } else {
        temp_norm = ee_temp_norm;
        temp_diff = ee_temp_diff;
        brightness = ee_brightness; 
        t_mode = ee_t_mode; 
    }
    

    init_sensors(0);
    delay_ms(250);
    while (1)
    {
        tmpkey = (~PINC & 0b00000111); //Проверяем состояние кнопок
        delay_ms(10);
        if (tmpkey == (~PINC & 0b00000111)) // Повторно проверяем состояние кнопок для избежание дребезга
        {
            key = tmpkey;
        } //Если был дребезг, то состояние нажатой кнопки не меняем
        if (key == prevkey) {
            if (keycount < 65000) keycount++;
            if (keycount >= 5) click=1;
            if (keycount > 105) dt = 0.5; 
            else dt = 0.1;
        } else {
            keycount=0;
            if (key == 0) click=1; //Короткий клик
            if (key == KEY_OK) if (mode++ >= 3) mode = 0;         
        }
        if (((key == KEY_PLUS) || (key == KEY_MINUS)) && (mode == 0)) {
            show_float(temp_norm);
            mode = SET_NORM;
            delay_ms(1000);
            click=0;
            key=0;
            prevkey=0;
            keycount=0;
        }
        if (click){
            switch(prevkey){
                case KEY_PLUS: //Нажата кнопка "+"
                    switch(mode){
                        case SET_NORM: temp_norm += dt; if ((temp_norm+temp_diff) > MAX_TEMP) temp_norm=MAX_TEMP-temp_diff; save=1;  break;
                        case SET_DIFF: temp_diff += dt; if (((temp_norm+temp_diff) > MAX_TEMP) || ((temp_norm-temp_diff) < MIN_TEMP)) temp_diff -= dt; save=1; break;
                        case SET_BRIGHTNESS_MODE: if (brightness < MAX_BRIGHTNESS) {brightness++; save=1;} break;
                        case SET_MODE: t_mode = ~t_mode & 1; save=1; break;
                    }
                    break;
                case KEY_MINUS: //Нажата кнопка "-"
                    switch(mode){
                        case SET_NORM: temp_norm -= dt; if ((temp_norm-temp_diff) < MIN_TEMP) temp_norm=MIN_TEMP+temp_diff; save=1; break;
                        case SET_DIFF: temp_diff -= dt; if (temp_diff <= 0) temp_diff += dt; save=1; break;
                        case SET_BRIGHTNESS_MODE: if (brightness > 0) {brightness--; save=1;} break;
                        case SET_MODE: t_mode = ~t_mode & 1; save=1; break;
                    }
                    break;
                case (KEY_PLUS+KEY_MINUS+KEY_OK): //Сброс до заводских настроек
                    first_start();
                    mode=0;
                    save=1;
                    break;
            }
            click = 0;
            delay_ms(30);
        } else delay_ms(100);
        prevkey = key;
        if ((keycount > 100) && (key == 0) && (mode != 0)) { //Если долго не нажимаются кнопки в режимах настроек
            mode = 0; //Переходим в дежурный режим
        }
        switch(mode){
            case SET_NORM: //Установка temp_norm
                show_float(temp_norm); 
                break;
            case SET_DIFF: //Установка temp_diff
                if (prevmode != mode) {
                    for (i=0; i<2;i++){
                        show(63,0,0,0); delay_ms(300);
                        show(0,0,0,0); delay_ms(200);
                    }
                }
                show_float(temp_diff); 
                break;
            case SET_BRIGHTNESS_MODE: //Установка brightness
                if (prevmode != mode) {
                    for (i=0; i<2;i++){
                        show(symbols[16],symbols[17]+symbols[10],0,0); delay_ms(300);
                        show(0,0,0,0); delay_ms(200);
                    }
                }
                show_char(brightness); 
                break;
            case SET_MODE: //Установка режима термостатировани
                if (t_mode == HEAT_MODE) show(179,230,243,166);
                else  show(198,215,215,134);
                break;
            default: //Дежурный режим контроля температуры
              if (dscall) {
                if (devices){
                    while (dscall) {
                        temp=ds18b20_temperature(&rom_code[0][0]);    // Чтение температуры
                        if (temp < MIN_TEMP) init_sensors(1); //Ошибка! Температура ниже минимальной
                        else if (temp > MAX_TEMP) init_sensors(2); //Ошибка! Температура выше максимальной 
                        else dscall=0; 
                    } 
                } else init_sensors(3);
              }
              set_relay();
              if (save == 1) {
                save_params(); // Сохранить установленные параметры если были изменения
                for (i=0; i<3;i++){
                    show_float(temp); delay_ms(300);
                    show(0,0,0,0); delay_ms(200);
                }
                save=0;
              }
              show_float(temp);
              break;
        } 
        prevmode=mode;
    }
}
