/*
 * Created on: 04.07.2010
 *
 * Copyright (c) 2010 by Oleg Trifonov <otrifonow@gmail.com>
 * http://trol.0fees.net
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */


#include "config.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/iom8.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <avr/wdt.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <util/delay.h>

#include "stddef.h"
#include "debug.h"

#include "ds18b20.h"


#define KATODE_1		_BV(2)
#define KATODE_2		_BV(3)

#define KEY_PLUS		_BV(3)
#define KEY_MINUS		_BV(2)


#define MIN_PROGRAMM	0
#define MAX_PROGRAMM	13

#define MAX_PROGRAMM_VIEW_DELAY		240		// как долго отображать текущую программу (x10мс)
#define TIMER_TICKS_PER_T2			255		// сколько тиков таймера попадут на полупериод синусоиды


static PROGMEM const uint8_t _display_digits_codes[10] = {
	0b00111111, // 0
	0b00000110, // 1
	0b01011011, // 2
	0b01001111, // 3
	0b01100110, // 4
	0b01101101, // 5
	0b01111101, // 6
	0b00000111, // 7
	0b01111111, // 8
	0b01101111, // 9
};

#define MAX_STEP		8
PROGMEM uint8_t stepping[MAX_STEP] = {170, 160, 150, 140, 100, 50, 10, 1};

#define MAX_STEP2		5
PROGMEM uint8_t stepping2[MAX_STEP2] = {170, 160, 150, 140, 100};

PROGMEM uint8_t powers[10] = {170, 160, 150, 140, 120, 100, 50, 25, 10, 1};


uint16_t temperature;
uint8_t display_data[2];
uint8_t display_index;

uint16_t timer_delta_count;		// через сколько тиков таймера будет срабатывать прерывание

uint8_t keyRepeated[2];			// тут хранится длительность удержания клавишь в условных единицах (10мс)

uint8_t program = 9;			// номер режима

uint8_t programViewDelay;		// если 0, показывать температуру, иначе - длительность отображения режима смены программы (10мс)

volatile uint8_t phase = 0;

volatile uint16_t phaseStep = 0;	// счетчик шагов по 100 мс

/**
 * Возвращает значение фазы для текущего режима и текущего шага
 *
 * 0..9 - [С0..С9] скорости
 * 10   - [БР] бриз, плавное нарастание и резкий спад
 * 11   - [БС] случайный бриз
 * 12   - [с] вращение случайным образом на малых оборотах
 * 13   - [С] вращение случайным образом
 *
 */
static uint8_t getPhase() {
	static uint8_t step = 0;
	static uint8_t time = 100;
	static uint8_t value = 100;
	uint8_t result;

	if ( program <= 9 ) {
		return pgm_read_byte(&powers[program]);
	} else if ( program == 10 ) {	// бриз, плавное наростание и резкий спад
		if ( step >= MAX_STEP*2 )
			step = 0;
		if ( step < MAX_STEP ) {
			result = pgm_read_byte(&stepping[step]);
			if ( phaseStep >= 100 ) {
				phaseStep = 0;
				step++;
			}
		} else {
			result = pgm_read_byte(&stepping[2*MAX_STEP-step-1]);
			if ( phaseStep >= 10 ) {
				phaseStep = 0;
				step++;
			}
		}
		return result;
	} else if ( program == 11 ) {		// случайный бриз
		if ( step >= MAX_STEP*2 )
			step = 0;
		if ( step < MAX_STEP ) {
			result = pgm_read_byte(&stepping[step]);
		} else {
			result = pgm_read_byte(&stepping[2*MAX_STEP-step-1]);
		}
		if ( phaseStep >= time ) {
			phaseStep = 0;
			time = rand() % 250;
			step++;
		}
		return result;
	} else if ( program == 12 ) {		// вращение случайным образом на малых оборотах
		if ( phaseStep >= time ) {
			phaseStep = 0;
			time = rand() % 250;
			value = pgm_read_byte(&stepping2[rand() % MAX_STEP2]);
		}
		return value;
	} else {//if ( program == 13 ) {
		if ( phaseStep >= time ) {
			phaseStep = 0;
			time = rand() % 250;
			value = pgm_read_byte(&stepping[rand() % MAX_STEP]);
		}
		return value;
	}
}

/**
 * Возвращает семисегментный код цифры, digit in [0..9]
 */
uint8_t getDigitCode(uint8_t digit) {
	return pgm_read_byte(&_display_digits_codes[digit]);
}

/**
 * Измеряет температуру
 */
static bool meassureTemperature() {
	uint8_t subzero;
	uint8_t cel;
	uint8_t cel_frac_bits;
	uint8_t result = DS18B20_read_meas_single(&subzero, &cel, &cel_frac_bits);
	DS18B20_start_single_meas_parasite();
	if ( result == DS18X20_OK ) {
		uint16_t t = cel*10 + cel_frac_bits/10;
		if ( t == 850 )
			return false;
		temperature = t;
//		temperature_negative = subzero != 0;

		return true;
	}
	return false;
}


/**
 * Обновляет экран
 */
static void updateDisplay() {
	PORTB &= ~(KATODE_1 | KATODE_2);
	uint8_t data = display_data[display_index];
	PORTC = data & 0b00111111;
	PORTB &= 0b11111100;
	PORTB |= (data >> 6) & 0b00000011;
	switch ( display_index ) {
		case 0:
			PORTB |= KATODE_1;
			display_index = 1;
			break;
		default:
			PORTB |= KATODE_2;
			display_index = 0;
	}
}

/**
 * Записывает в видеопамять двузначное число val
 */
static void displayOutNumber(uint8_t val) {
	if ( val >= 100 ) {
		display_data[0] = 0b01000000;
		display_data[1] = 0b01000000;
	} else {
		uint8_t hi = val / 10;
		display_data[0] = hi != 0 ? getDigitCode(hi) : 0;
		display_data[1] = getDigitCode(val % 10);
	}
}



/**
 *
 */
static void init() {
	DDRC = 0xFF;
	DDRB = 0xFF;
	DDRD = 0b00010000;
	temperature = 0xffff;
	display_index = 0;
	keyRepeated[0] = 0;
	keyRepeated[1] = 0;
	programViewDelay = 0;
//	program = MIN_PROGRAMM;
	uart_init();

	// иннициализация компаратора
	ACSR = 0b00001000;

	timer_delta_count = F_CPU/(TIMER_TICKS_PER_T2*100);	// прерывание будет возникать TIMER_TICKS_PER_T2*100 раз в секунду

	// конфигурируем T/C1
	TCCR1A = 0;//0b00000000;	// ШИМ отключен, пины отключены
	TCCR1B = _BV(CS10); // на вход таймера подается TCK/1
	OCR1A = timer_delta_count;
	TIMSK = 0x10;			// включить прерывание по совпадению для OCR1A
	TCNT1H = 0;				// сброс счетчика таймера, именно в этой последовательности
	TCNT1L = 0;

	sei();
}

/**
 * В зависимости от режима обновляет значение, выводимое на дисплей - температура или номер режима
 */
static void updateScreen() {
	if ( programViewDelay == 0 ) {
		displayOutNumber(temperature/10);
	} else {
		if ( program <= 9 ) {
			display_data[0] = 0b00111001;
			display_data[1] = getDigitCode(program);
		} else if ( program == 10 ) {
			display_data[0] = 0b01111101;
			display_data[1] = 0b01110011;
		} else if ( program == 11 ) {
			display_data[0] = 0b01111101;
			display_data[1] = 0b00111001;
		} else if ( program == 12 ) {
			display_data[0] = 0b01011000;
			display_data[1] = 0;
		} else if ( program == 13 ) {
			display_data[0] = 0b00111001;
			display_data[1] = 0;
		}
	}
}


/**
 * Опрос состояния клавиатуры
 */
static void scanKeyboard() {
	if ( PIND & KEY_PLUS ) {
		if ( keyRepeated[0] != 0xFF )
			keyRepeated[0]++;
	} else if ( keyRepeated[0] > 10 ) {
		keyRepeated[0] = 0;
		// нажат "+"
		if ( program < MAX_PROGRAMM && programViewDelay != 0 )
			program++;
		programViewDelay = MAX_PROGRAMM_VIEW_DELAY;
		updateScreen();
	}
	if ( PIND & KEY_MINUS ) {
		if ( keyRepeated[1] != 0xFF )
			keyRepeated[1]++;
	} else if ( keyRepeated[1] > 10 ) {
		keyRepeated[1] = 0;
		// нажат "-"
		if ( program != MIN_PROGRAMM && programViewDelay != 0 )
			program--;
		programViewDelay = MAX_PROGRAMM_VIEW_DELAY;
		updateScreen();
	}

}


// прерывание сравнение таймера, вызывается 255*100 раз в секунду
ISR(TIMER1_COMPA_vect) {
	OCR1A += timer_delta_count;

	phase++;
	if ( phase > getPhase() )
		PORTD |= _BV(4);		// включаем выход
	else
		PORTD &= ~_BV(4);		// выключаем выход

	static uint8_t countT = 0;		// счетчик считает тики внутри полупериода синусойды
	static uint8_t count100 = 0;	// счетчик инкрементируется каждые 10мС
	if ( countT++ == TIMER_TICKS_PER_T2 ) {
		scanKeyboard();
		if ( programViewDelay != 0 ) {
			programViewDelay--;
		if ( programViewDelay == 0 )
			updateScreen();
		}

		if ( count100 % 10 == 0 )
			phaseStep++;			// инкремент каждые 100мс

		if ( count100 == 200 ) {	// примерно раз в две секунды измеряем температуру
			if ( meassureTemperature() ) {
				updateScreen();
			}
			count100 = 0;
		}

		count100++;
		countT = 0;
	}
}

// прерывание аналогового компаратора
ISR(ANA_COMP_vect) {
	phase = 0;
}


int __attribute__((OS_main)) main(void) {
	init();

	meassureTemperature();
	updateScreen();
//	display_data[0] = 0xff;
//	display_data[1] = 0xff;
	uint16_t count = 0;
	while ( 1 ) {
		// для режима отображения номера программы число будет мерцать. температура - без мерцания
		if ( programViewDelay == 0 ) {
			updateDisplay();
		} else {
			if ( count % 10 == 0 ) {
				updateScreen();
				updateDisplay();
				_delay_ms(1);
				updateDisplay();
			} else {
				display_data[0] = 0;
				display_data[1] = 0;
				updateDisplay();
				_delay_ms(1);
				updateDisplay();
			}
		}
		_delay_ms(1);
		count++;
	}
}
