/*****************************************************
CodeWizardAVR V1.24.8b Professional

Chip type           : ATmega8
Program type        : Application
Clock frequency     : 5,000000 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 256
*****************************************************/

#include <mega8.h>
// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x12 ;PORTD
#endasm
#include <lcd.h>

#define SOLENOID_PORT PORTB.0 // вывод, к которому подключён светодиод 
#define PULSE_TIME 200 // длительность импульса соленоида в миллисекундах
#define TICKS_IN_SECOND 9 // столько тиков таймера 1 в одной секунде 

#define KEY_PORT PORTC // порт, к которому подключены кнопки
#define KEY_PINS PINC // выводы, к которым подключены кнопки
#define KEY_MASK    0b00111111 // выводы, к которым подключены кнопки, обозначены как 1
#define START       0b00000001 // вывод PC0, к которому подключена кнопка "старт"
#define STOP        0b00000010 // вывод PC1, к которому подключена кнопка "стоп"
#define MIN_UP      0b00000100 // вывод PC2, к которому подключена кнопка "минуты +"
#define MIN_DOWN    0b00001000 // вывод PC3, к которому подключена кнопка "минуты -"
#define HOUR_UP     0b00010000 // вывод PC4, к которому подключена кнопка "час +"
#define HOUR_DOWN   0b00100000 // вывод PC5, к которому подключена кнопка "час  -"

#define KEY_POLLING_TIME 1 // интервал опроса состояния кнопок 1 мс
#define KEY_COUNTER 10  // счётчик, сколько интервалов опроса надо продержать кнопку нажатой/отпущеной
#define KEY_PUSH_COUNTER 50 // счётчик, сколько интервалов опроса надо продержать кнопку нажатой,
                            // чтобы начался автоповтор
#define KEY_AUTO_TIME 100 // интервал автоповтора 100 миллисекунд

volatile unsigned char key_state = 0; // состояние кнопок 0 - ни одна кнопка
                                        // не нажата. 1 - нажата одна из кнопок                                         
volatile unsigned char key = 0;  // значение нажатой кнопки                                   
volatile unsigned char key_new = 0;  // значение вновь нажатой кнопки
volatile unsigned char key_number = 0;  // номер нажатой кнопки 
volatile unsigned char key_counter = KEY_COUNTER;  // счётчик, сколько прошло интервалов опроса,
                                                // пока состояние кнопки стабильно
volatile unsigned char key_push_counter = KEY_PUSH_COUNTER; // счётчик, сколько должно пройти
                                                        // интервалов опроса, чтобы начался автоповтор

// объявляем счетчик как  и его состояние как глобальные переменные
volatile unsigned int meter_on_time; // время включения секундомера
volatile unsigned int pulse_time; // длительность импульса соленоида
volatile unsigned int ms_counter; // счётчик миллисекунд
volatile enum // состояния секундомера
{
    off,     // выключен
    must_on, // надо включить
    on,      // включён
    must_off // надо выключить
}meter_status;

// переменные для хранения часов, минут, секунд тоже как глобальные
unsigned char hour = 0;
unsigned char min = 0;
unsigned char sec = 0;

// время измерения будем хранить в энергонезависимой памяти
eeprom unsigned int meter_on_time_eeprom = 0; // какое было прошлый раз

// объявляем прототипы наших функций и обработчиков прерывний.
// а их самих сунем в конец файла (можно и вообще в другой файл)
// чтоб глаза не мозолили.
interrupt [TIM1_COMPA] void timer1_compa_isr(void);
interrupt [TIM2_COMP] void timer2_comp_isr(void);
void initPeripherals(void); // сюда засунем всю инициализацию
void timeDelayMs(unsigned int time_delay); // самопальная временная задержка 
void statusOffExecute(void); // функция обработки состояния "выключено"
void statusOnExecute(void); // функция обработки состояния "включено"
void timeDisplay(unsigned int on_time_display); // выводит на дисплей время
void getKeyState(void); // получить состояние и номер кнопки
void keyExecute(void); // что-то делает, в зависимости от того, какая кнопка нажата. 

void main(void)
{

initPeripherals(); // инициализация нашего железа.

meter_on_time = meter_on_time_eeprom; // берём предыдущее значение из EEPROM
meter_status = off;
// Global enable interrupts
#asm("sei")


while (1)
    {
        //проверяем состояние нашей шарманки
        // в зависимости от этого вызываем соответствующую функцию
        switch(meter_status)
        {
            case off: // выключена
                statusOffExecute(); // вызываем функцию состояния "выключено"
                break;
            case on: // включена и тИкает...
                statusOnExecute(); // вызываем функцию состояния "выключено"
                break;

            default: // в прочих случаях ничего делать не надо.
                break;
         }
    };
}// конец главной программы

void keyExecute(void) // что-то делает, в зависимости от того, какая кнопка нажата. 
{
        switch(key_number)
        {
                case MIN_UP: // увеличить минуты
                    if(min < 59)
                    {
                        min++;
                    }
                    break;
                case MIN_DOWN: // уменьшить минуты
                    if(min > 0)
                    {
                        min--;
                    }
                    break;
                case HOUR_UP: // увеличить ы
                    if(hour < 17)
                    {
                        hour++;
                    }
                    break;
                case HOUR_DOWN: // уменьшить минуты
                    if(hour > 0)
                    {
                        hour--;
                    }
                    break;
                default: // в прочих случаях ничего делать не надо.
                    break;              
        } // конец switch(key_number)
        meter_on_time = 3600*(unsigned int)hour + 60*(unsigned int)min + (unsigned int)sec;            
}



void getKeyState(void) // получить состояние и номер кнопки
{
    key_counter = KEY_COUNTER; // устанавливаем счётчик интевалов стабильного
                                // состояния кнопки
    while(key_counter)
    {    
        key_state = 0; // состояние кнопок возможно будет меняться
        // считываем состояние кнопок
        key_new = (~KEY_PINS)&KEY_MASK;
        // состояние кнопок изменилось?
        if(key_new != key)
        {
            // изменилось
            key = key_new; // запоминаем новое значение клавиши
            key_counter = KEY_COUNTER; // заново устанавливаем счётчик интевалов
                                        // стабильного состояния кнопки
        }
        else
        {
            // состояние кнопок не изменилось
            // проверяем, продержалась ли кнопка в стабильном состоянии
            // заданное количество интервалов KEY_COUNTER
            if(key_counter)
            {
                // нет, не прдержалась.
                key_counter--; // уменьшаем счётчик времени на единичку
                timeDelayMs(KEY_POLLING_TIME); // выдерживаем интервал перед следующей
                                        // проверкой состояния клавиатуры              
            }
            if(key_counter == 0)
            {
                // продержалась! - состояние кнопок стабильно в течение KEY_COUNTER
                key_number = key; // запоминаем номер кнопки
                key_state = 1; // состояние кнопок стабильно
            }
        }
    } // конец while(key_time)    
}// конец void getKeyState(void)  получить состояние и номер кнопки

  
void timeDisplay(unsigned int on_time_display) // выводит на дисплей время 
{
hour = on_time_display/3600;
min = (on_time_display - (3600*(unsigned int)hour))/60;
sec = on_time_display - 3600*(unsigned int)hour - 60*(unsigned int)min;
// дальше вывести hour, min, sec на дисплей
lcd_gotoxy(0,0);
        lcd_putsf("Timer: ");
        lcd_putchar(hour/10+0x30);
        lcd_putchar(hour%10+0x30);        
        lcd_putchar(':');
        lcd_putchar(min/10+0x30);
        lcd_putchar(min%10+0x30);
        lcd_putchar(':');
        lcd_putchar(sec/10+0x30);
        lcd_putchar(sec%10+0x30);
}



void statusOffExecute(void) // функция обработки состояния "выключено"
{
// индикация установленного времени
timeDisplay(meter_on_time); // выводит на дисплей время

// ждём, какую кнопку нажмут

// ждём, пока нажмут кнопку
    while(!key_number)
    {
        getKeyState(); // считываем состояние кнопок
        key_push_counter = KEY_PUSH_COUNTER; // сбрасываем счётчик автоповтора        
    }
    // кнопка нажата, проверяем, какая именно
    // это, случАем, не "старт" ?
    if(key_number == START)
    {
        // да, это "старт"
        // запустим секундомер только если заданное время больше нуля
        if(meter_on_time)
        {
            // запись установленного времени в EEPROM
            meter_on_time_eeprom = meter_on_time;
            #asm("cli") // запрещаем все прерывания
            // запуск секундомера
            meter_status = must_on;
            #asm("sei") // разрешаем прерывания
        }
        else
        {
            // иначе - фиг!
            meter_status = off;
        }              
    }
    else
    {    
        // установка времени
        keyExecute(); // выполнили то, что должна делать кнопка
        timeDisplay(meter_on_time); // выводим на дисплей время
        
        // ждём, пока кнопку отпустят
        while(key_number)
        {
            getKeyState(); // считываем состояние кнопок
            // проверяем, как долго держат кнопку нажатой        
            if(key_push_counter)
            {
                // кнопку продержали мало 
                key_push_counter--; // уменьшаем счётчик автоповтора на единичку
            }    
            if(key_push_counter == 0)
            {
                // кнопку продержали достаточно долго, чтобы начать автоповтор
                keyExecute();
                timeDisplay(meter_on_time); // выводим на дисплей время
                timeDelayMs(KEY_AUTO_TIME); // выдерживаем интервал автоповтора   
            }
        }
    }    
}

void statusOnExecute(void) // функция обработки состояния "включено"
{
// Показываем оставшееся время, пока не нажали кнопку "стоп"
timeDisplay(meter_on_time); // выводим на дисплей время
getKeyState(); // считываем состояние кнопок
// "стоп" случАем не нажали?
if(key_number == STOP)
{
    // Нажали! Выключаем нах!
    #asm("cli") // запрещаем все прерывания
    SOLENOID_PORT = 1;
    pulse_time = PULSE_TIME; // задаём длительность импульса соленоида
    // и изменям статус на выключен
    meter_status = off;
    meter_on_time = 0; // сбрасываем время в ноль
    #asm("sei") // разрешаем прерывания
}
}

// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
static unsigned int tmp_counter = 0; // временный счётчик тиков
// сюда попадаем каждые 100 миллисекунд
if(tmp_counter < TICKS_IN_SECOND)
{
    // не досчитал до секунды.
    tmp_counter++; // увеличиваем временный счётчик тиков на 1
}
else
{
    // Досчитал до секунды.
    // сюда попадаем каждую секунду
    tmp_counter = 0; // сбрасываем временный счётчик тиков
 


    // проверяем, была ли дана команда на включение?
    if(meter_status == must_on)
    {
        // да, была. включаем
        SOLENOID_PORT = 1;
        pulse_time = PULSE_TIME; // задаём длительность импульса соленоида
        // и изменям статус на включён
        meter_status = on;
    }

    if(meter_status == on)
    {
        // время вышло?
        if(meter_on_time)
        {
            // нет, не вышло
            meter_on_time--; // уменьшили на секунду
        }
        else
        {
            // вышло! Выключаем нах!
            SOLENOID_PORT = 1;
            pulse_time = PULSE_TIME; // задаём длительность импульса соленоида
            // и изменям статус на выключен
            meter_status = off;
        }
    }
}
}//interrupt [TIM1_COMPA] void timer1_compa_isr(void)


// Timer 2 output compare interrupt service routine
interrupt [TIM2_COMP] void timer2_comp_isr(void)
{
// сюда попадаем каждую миллисекунду
// проверяем, время импульса вышло ?
if(pulse_time)
{
    // нет, не вышло
    pulse_time--; // уменьшили на миллисекунду
}
else
{
    // вышло! Выключаем !
    SOLENOID_PORT = 0;
}
// проверяем счётчик для функции временной задержки 
if(ms_counter)
    ms_counter--; // 
}//interrupt [TIM2_COMP] void timer2_comp_isr(void)


void initPeripherals(void)
{
// Input/Output Ports initialization
// Port B initialization
// PB0, PB1, PB2, PB3 - на вывод 
PORTB=0x00;
DDRB=0x0F;

DDRC=0; // включаем порт PC на ввод
KEY_PORT = KEY_PINS | KEY_MASK; // включаем подтягивающие резисторы
                            // на выводах, к которым подключены кнопки.

// Timer/Counter 1 initialization
// Clock source: System Clock
// прескалер 8
// Clock value: 625,000 kHz
// Mode: CTC top=OCR1A
// OC1A output: Toggle
// Compare A Match Interrupt: On

TCCR1A=0x40;
TCCR1B=0b00001010;
//TCCR1B=0x0C;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1A=62499; // считать до 62499, чтобы получить прерывание каждые 100 мс
//OCR1AH=0x7A;
//OCR1AL=0x12;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// прескалер 32
// Clock value: 156,250 kHz
// Mode: CTC top=OCR2
// OC2 output: Toggle on compare match
ASSR=0x00;
TCCR2=0x1B;
TCNT2=0x00;
OCR2=155;
//OCR2=0xFA;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x90;

// LCD module initialization
lcd_init(16);

}

void timeDelayMs(unsigned int time_delay)  // самопальная временная задержка
{
    #asm("cli") // запрещаем все прерывания
    ms_counter = time_delay;
    #asm("sei") // разрешаем прерывания
    while(ms_counter);
}
