//#######################################################################################################################
//#######################################################################################################################
// включаемые файлы
#define F_CPU 9600000
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
//eeprom_read_byte(), eeprom_write_byte(), eeprom_read_word(), eeprom_write_word()

//дефайны
#define BtnPort PINB            /*порт подключения кнопки */
#define BtnPin (1<<1)           /*пин подключения кнопки */
#define ReceivePort PINB        /*порт подключения выхода приемника 433 */
#define ReceivePin (1<<3)       /*пин подключения выхода приемника 433 */
#define LedPort PORTB           /*порт подключения светодиода */
#define LedPin (1<<2)           /*пин подключения светодиода */
#define RelayPort PORTB         /*порт подключения реле */
#define RelayPin (1<<4)         /*пин подключения реле */

#define INLINE   inline __attribute__((__always_inline__))
#define NOINLINE __attribute__((__noinline__))

//прототипы функций     
static void Receive(void);             //ф-ция обработки данных радиоканала в прерывании с частотой 20000Гц
static void Int100Hz(void);            //обработка медленных функций с частотой 100Гц

//глобальные переменные
enum { DW_B0 = 0, DW_B1 = 1, DW_B2 = 2, DW_B3 = 3 };  // LITTLE ENDIAN CPU
// enum { DW_B0 = 3, DW_B1 = 2, DW_B2 = 1, DW_B3 = 0 }; // BIG ENDIAN CPU
union {
    uint32_t dw;
    uint8_t  b[4];
} ReceiveData;      //4 байта для чтения кода с радиоканала

unsigned char RelayOnCoun = 0;  //счетчик времени включения реле 
unsigned char LedCoun;          //счетчик для мигания светика с частотой 1Гц

#define EEDataLen 60            /*длина буфера данных в EEPROM */
struct {
    unsigned char Data[EEDataLen];  //массив данных в EEPROM
    unsigned char Pnt;         //указатель на байт массива в EEPROM (0-59)
} EE EEMEM;

static
NOINLINE
void delay(uint8_t ticks10ms)
{
	while(ticks10ms--) _delay_ms(10);
}

static 
NOINLINE
void EEWrite(uint8_t addr, uint8_t data)
{
    while (EECR & (1<<EEPE)) ;
    EECR = (0 << EEPM1) | (0 << EEPM0);
    EEARL = addr;
    EEDR  = data;
    EECR |= (1 << EEMPE);
    EECR |= (1 << EEPE);    
} 

static
NOINLINE
uint8_t EERead(uint8_t addr)
{
    while (EECR & (1<<EEPE)) ;
    EEARL = addr;
    EECR |= (1 << EERE);
    return EEDR;
} 

//#######################################################################################################################
//#######################################################################################################################
//Главная функция
__attribute__((OS_main))
int main(void)
{
    DDRB = (LedPin | RelayPin); //включение линий порта на вывод
    PORTB = BtnPin;             //включит подтяжку на кнопке

    TCCR0A = 0x02;              //режим таймера - CTC (0x02 сброс при совпадении), выводы контроллера не исп-ся
    TCCR0B = 0x02;              //инициализация режима предделителя таймера (0x02 деление на 8)
    TIMSK0 = 0x04;              //инициализация таймера\счетчика по совпадению с OCR0A
    OCR0A = 59;                 //настройка регистра сравнения 59 (f прерывания =20khz)

    delay(20);
    if (!(BtnPort & BtnPin))    //если кнопка нажата при старте - стереть EEPROM
    {
        uint8_t from = (unsigned)&EE;
        uint8_t to   = (unsigned)&EE + sizeof(EE);
        do  { EEWrite(from, 0); ++from; } while(from < to); //цикл стирания данных EEPROM

        for (unsigned char i = 0; i < 30; i++) {        //индикация стирания (частое мигание в течение 3 сек)
            LedPort |= LedPin;  //мигаем с периодом 100мс
            delay(5); //_delay_ms(50);
            LedPort &= ~LedPin;
            delay(5); //_delay_ms(50);
        }
    }

    sei();                      //разрешаем глобально прерывания
    while (1)                   //бесконечный цикл
    {
    }
}

//#######################################################################################################################
//#######################################################################################################################
//обработка прерывания (частота вызова 20 кГц)
ISR(TIM0_COMPA_vect)
{
    Receive();                  //функция обработки данных линии радиоканала

    static unsigned char Div100 = 0;    //счетчик, для деления частоты прохода с 20000 Гц до 100 Гц
    if (++Div100 >= 200) {
        Div100 = 0;
        Int100Hz();             //функции с частотой вызова 100Гц
    }
}

//-----------------------------------------------------------------------------------------------------------------------
//часть функций с частотой вызова 100Гц

void Int100Hz(void)
{
    sei();                      //разрешить прерывания в медленной части (для выполнения прерываний с частотой 10000 Гц) 

    if (BtnPort & BtnPin) {     //если кнопка отпущена - мигать в режиме StBy
        if (++LedCoun >= 100)
            LedCoun = 0;
        if (LedCoun >= 50)
            LedPort &= (~LedPin);
        else
            LedPort |= LedPin;
    } else                      //если кнопка нажата - погасить (ожидаем сигнала с брелка, для занесения в память)
        LedPort &= (~LedPin);


    if (RelayOnCoun)            //обработка включения реле
    {
        RelayOnCoun--;
        RelayPort |= RelayPin;  //включить реле
    } else
        RelayPort &= ~RelayPin; //выключить реле
}

//-----------------------------------------------------------------------------------------------------------------------
//ф-ция приема данных радиоканала 
void Receive(void)
{
    static unsigned char Step = 0,      //переменная шагов алгоритма
        BitHiPartCoun = 0,      //счетчик времени приема высокой части полу бита
        BitLoPartCoun = 0,      //счетчик времени приема низкой части полу бита
        BitCounter = 0;         //счетчик принятых бит

    //Step==0 инициализация приема серии
    if (Step == 0)
        BitCounter = 0;

    //Step==1 начало основного цикла приема бита
    if (Step < 2) {
        BitHiPartCoun = 0;
        BitLoPartCoun = 0;
    }
    //Step==2 обработка высокого уровня полубита
    if (Step < 3) {
        if (ReceivePort & ReceivePin) {
            Step = 2;
            if (BitHiPartCoun < 50)     //Подсчитываем длительность высокого уровня сигнала в линии
                BitHiPartCoun++;
            return;
        }
    }
    //Step==3 обработка низкого уровня полубита
    if (ReceivePort & ReceivePin) {
        //завершено чтение бита, анализ временных параметров
        Step = 0;
        unsigned char temp = (BitLoPartCoun + BitHiPartCoun);
        if ((temp < 8) || (temp >= 15))
            return;             //временные параметры бита не соответствуют, инициализация

        Step = 1;

        //сдвиг буфера и запись в младший разряд принятого бита
        unsigned long ultemp = ReceiveData.dw << 1;
        if (BitLoPartCoun < BitHiPartCoun) ultemp |= 1;
        ReceiveData.dw = ultemp;

        if (BitCounter < 24)    //считаем скока бит приняли
            BitCounter++;
        return;
    } else {
        Step = 3;
        if (++BitLoPartCoun < 50)       //если длительность низкого уровня сигнала в линии больше 50мс - значит серия принята
            return;
        Step = 0;
        if (BitCounter < 24)    //Если принято менее 24 бит - ошибка, начать прием заново
            return;
        //ReceiveData &= 0x00ffffff;      //очистить лишние старшие 8 бит

        //идентификация принятых данных
        unsigned char EEPntTmp = EERead((unsigned)&EE.Pnt);
        if (!(BtnPort & BtnPin)) {      //кнопка нажата - запомнить принятый код
            if (EEPntTmp >= EEDataLen)
                return;         //память заполнена, выход

            EEWrite((unsigned)&EE.Pnt, EEPntTmp+3);
            uint8_t ee_addr = (unsigned)&EE.Data[EEPntTmp];
            EEWrite(ee_addr,     ReceiveData.b[DW_B0]);
            EEWrite(ee_addr+1,   ReceiveData.b[DW_B1]);
            EEWrite(ee_addr+2,   ReceiveData.b[DW_B2]);

            LedPort |= LedPin;  //индикация выполненного программирования (включ LED на 2 сек)
            delay(200);
            return;
        } else {                //кнопка не нажата - проверить на соответствие кодам в памяти
            while (EEPntTmp >= 3)       //есть ли данные для сравнения
            {
                EEPntTmp -= 3;
                uint8_t ee_addr = (unsigned)&EE.Data[EEPntTmp];

                if ((EERead(ee_addr) == ReceiveData.b[DW_B0])
                    && (EERead(ee_addr+1) == ReceiveData.b[DW_B1])
                    && (EERead(ee_addr+2) == ReceiveData.b[DW_B2])
                ) {
                    RelayOnCoun = 50;   //включить реле на 500мс.
                    return;
                }
            }
        }
    }
}

//#######################################################################################################################
//#######################################################################################################################

