/*
 * Copyright (c) 2009 by Oleg Trifonov <otrifonow@gmail.com>
 *
 * 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/iom8535.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <avr/wdt.h>

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

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

PROGMEM const uint8_t lcd_digits_codes[10] = {
	0b11101011,
	0b10001000,
	0b10110011,
	0b10111010,
	0b11011000,
	0b01111010,
	0b01111011,
	0b10101000,
	0b11111011,
	0b11111010
};


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


uint8_t  sym_lcd[3];	// тут коды, выводимые на экран температуры


#define MAX_LEVEL			64		// максимальное значение самодельного ШИМа, задающего яркость
#define TIMER_FREQ		80		// частота динамической индикации


uint8_t level_leds[2][8];	// яркости всех светодиодов

bool temp_meassured = false;	// флаг того, что температура была удачно измеряна хотя бы раз


// включает общий анод индикатора 0, 1, 2
inline void setLcdAnode(uint8_t index) {
	// пины: C1 C0 D7
	// выключаем все аноды
	PORTC |= 0b00000011;
	PORTD |= 0b10000000;
	switch ( index ) {
		case 0:
			PORTC &= 0b11111101;
			break;
		case 1:
			PORTC &= 0b11111110;
			break;
		case 2:
			PORTD &= 0b01111111;
			break;
	}
}

inline void setLedsAnode(uint8_t index) {
	// пины: C2, C3, C4, C5, C6, C7
	// выключаем все аноды
	PORTC |= 0b11111100;
	if ( index == 0 ) {
		PORTC &= 0b10101011;
	} else {
		PORTC &= 0b01010111;
	}
//		PORTC &= ~( 1 << (index+2) );
}


inline void setLcdValue(uint8_t val) {
	PORTB = ~val;
}

inline void setLedValue(uint8_t val) {
	PORTA = val;
}


/**
 * Возвращает семисегментный код цифры
 */
inline uint8_t getLcdDigitCode(uint8_t digit) {
	return pgm_read_byte(&lcd_digits_codes[digit]);
}

/**
 * Обновляет показания температуры, получает ее значение в десятых долях градуса
 */
void outTemperature(uint16_t val, bool negative) {
	if ( negative ) {
		sym_lcd[2] = 0b00010000;
		if ( val >= 100 ) {		// -12
			uint16_t r = val / 10;
			if ( val % 10 >= 5 )
				r++;
			sym_lcd[1] = getLcdDigitCode(r / 10);
			sym_lcd[0] = getLcdDigitCode(r % 10);
		} else {				// -1.2
			sym_lcd[1] = getLcdDigitCode(val / 10) | 0b00000100;
			sym_lcd[0] = getLcdDigitCode(val % 10);
		}
	} else {
		if ( val >= 100 ) {		// 12.3
			sym_lcd[2] = getLcdDigitCode(val / 100);
			val %= 100;
			sym_lcd[1] = getLcdDigitCode(val / 10) | 0b00000100;
			sym_lcd[0] = getLcdDigitCode(val % 10);
		} else {				// 1.2
			sym_lcd[2] = 0;
			sym_lcd[1] = getLcdDigitCode(val / 10) | 0b00000100;
			sym_lcd[0] = getLcdDigitCode(val % 10);
		}
	}
}


/**
 * Читает температуру и выводит ее значение в видеопамять
 */
void updateTemperature() {
	uint8_t subzero;
	uint8_t cel;
	uint8_t cel_frac_bits;
	bool 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 ) {
			outTemperature(t, subzero != 0);
			temp_meassured = true;
		}
	} else {
		sym_lcd[0] = 0b00010001;
		sym_lcd[1] = 0b00010001;
		sym_lcd[2] = 0b01110011;
	}
}



volatile uint8_t currentLine = 0;		// индекс текущей обновляемой линии
volatile uint8_t currentLcdAnode = 0;	// индекс текущего анода индикатора температуры
volatile uint8_t updateTick = 0;			// счетчик тиков самопального ШИМа-а, 0..MAX_LEVEL-1
volatile uint8_t updateTickT = 0;		// счетчик тиков самопального ШИМа-а для индикатора температуры, 0..10
volatile uint8_t ledsMasks[2][MAX_LEVEL];	// значения маски светодиодов на каждом тике


// прерывание сравнение таймера
ISR(TIMER1_COMPA_vect) {

	OCR1A += timer_delta_count;

	// обновление светодиодов
	if ( updateTick < MAX_LEVEL ) {	// если продолжается обновление текущей линии
		setLedValue(0);
		setLedsAnode(currentLine);
		setLedValue(ledsMasks[currentLine][updateTick]);
		updateTick++;
	} else {							// если переходим на следующую линию
		// переходим в к следующей линии
		if ( ++currentLine >= 2 ) {
			currentLine = 0;
		}
		updateTick = 0;
	}


	// обновление индикатора температуры
	if ( ++updateTickT < 10 ) {
		setLcdAnode(currentLcdAnode);
		setLcdValue(sym_lcd[currentLcdAnode]);
	} else {
		if ( ++currentLcdAnode >= 3 )
			currentLcdAnode = 0;
		updateTickT = 0;
	}
}

/**
 * Вычисляет маски светодиодов
 */
void updateMasks() {
	for ( uint8_t line = 0; line < 2; line++ ) {
		for ( uint8_t tick = 0; tick < MAX_LEVEL; tick++ ) {
			uint8_t mask = 0;
			for ( uint8_t i = 0; i < 8; i++ ) {
				if ( tick < level_leds[line][i] ) {
					mask |= (1<<i);
				}
			}
			ledsMasks[line][tick] = mask;
		} // tick
	} // line
}


/**
 * Перевод яркости в процентах в единицы яркости ШИМ
 */
PROGMEM const uint8_t level_percent_to_val[40] = {
		0,	0,
		5,	1,
		10,	2,
		15,	3,
		20,	4,
		25,	5,
		30,	6,
		35,	7,
		40,	8,
		45,	9,
		50,	10,
		55,	11,
		60, 12,
		65, 13,
		70, 16,
		75, 22,
		80, 27,
		85, 35,
		90, 45,
		95,	55
};



/**
 * Получает 2 уровня яркости в процентах и возвращает результат их произведения в единицах ШИМ
 */
uint8_t mul2Levels(uint8_t lev1, uint8_t lev2) {
	uint16_t lev = lev1;
	lev *= lev2;
	lev /= 100;	// 0..100
	uint8_t level = lev;
	for ( uint8_t i = 0; i < 20; i++ ) {
		uint8_t ind = i*2;
		if ( level <= pgm_read_byte(&level_percent_to_val[ind]) )
			return pgm_read_byte(&level_percent_to_val[ind + 1]);
	}
	return MAX_LEVEL;
}


/**
 * Яркости линий снежинки по тактам
 */
PROGMEM const uint8_t lines_levels[41] = {
	100, 95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5,
	0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100
};


/**
 * Яркости светодиодов снежинки по тактам
 */

PROGMEM const uint8_t leds_levels[32*8] = {
	30, 40, 50, 40, 30, 50, 70, 60, 50, 60, 70, 80, 90, 95, 80, 90,100, 90, 80, 85, 80, 70, 60, 65, 70, 60, 50, 55, 60, 40, 20, 25,
	50, 55, 60, 65, 70, 75, 80, 85, 90, 95,100, 95, 90, 95,100, 95, 90, 95,100, 95, 90, 95,100, 95, 90, 95,100, 95, 90, 95,100, 90,
	10,  5,  0, 10, 20,  5, 10, 20, 40, 20, 10, 30, 60, 30, 10, 30, 80, 40, 10, 50,100, 50, 20, 50,100, 50, 20, 50,100, 50, 20, 15,
	10,  5,  0,  5,  0, 10, 20, 10,  0, 20, 40, 20, 10, 30, 60, 30, 10, 40, 80, 40, 20, 50,100, 50, 20, 50,100, 50, 20, 50,100, 30,
	20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,100,100, 95, 90, 95,100, 95, 90, 95,100, 95, 90, 95,100, 95, 90,
	 0,  5, 10,  5,  0, 10, 20, 25, 30, 15, 10, 25, 40, 25, 10, 30, 50, 25, 15, 30, 60, 40, 20, 40, 80, 50, 30, 50,100, 70, 40, 30,
	 0,  5, 10,  5,  0, 10, 20, 25, 10, 15, 30, 25, 10, 25, 40, 30, 15, 25, 50, 30, 20, 40, 60, 40, 30, 50, 80, 50, 40, 70,100, 50,
	10, 15, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,100, 95,100, 95, 90, 95,100, 95, 90, 95,100, 70,
};

void main() {

	uart_init();

	// настройка портов
	DDRA = 0xFF;
	DDRB = 0xFF;
	DDRC = 0xFF;
	DDRD = 0b10000000;



	DS18B20_start_single_meas_parasite();


	timer_delta_count = F_CPU/8/MAX_LEVEL/TIMER_FREQ/2;


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

	sei();


	for ( uint32_t index = 0; true ; index++) {
		uint32_t indexLine = index % 41;
		uint32_t indexLed = (index/5) % (32*2);
		if ( indexLed >= 32 )
			indexLed = 32*2 - indexLed - 1;
		uint8_t lev1 = pgm_read_byte(&lines_levels[indexLine]);

		uint8_t ledsLevels[8];
		for ( uint8_t i = 0; i < 8; i++ )
			ledsLevels[i] = pgm_read_byte(&leds_levels[16*i+indexLed]);


		for ( uint8_t line = 0; line < 2; line++ ) {
			for ( uint8_t led = 0; led < 8; led++ ) {
				level_leds[line][led] =
						(line % 2 == 0) ?
						mul2Levels(lev1, ledsLevels[led]) : mul2Levels(110 - lev1, ledsLevels[led]);
			}
		}
		updateMasks();
		_delay_ms(50);

		if ( index % 1000 == 10 || (!temp_meassured && index % 100 == 10) ) {
			updateTemperature();
		}

	}


}
