/*
 * VU Meter für ATTiny25 (45/85) für 12 LEDs (z.B. 9 grün, 2 gelb, 1 rot)
 * 15.3.17, HildeK
 * Anregung von https://www.mikrocontroller.net/topic/419372 - dort ist die HW mit einem Tiny13 beschrieben
 *
 * Taktfrequenz 8MHz intern RC-Oszillator
 * Autotrigger des ADC durch TIMER0_OVL --> Samplefrequenz 31.25 kHz,  
 * LED MUX  Charlie-Plexing
 * Peak-Hold Anzeige für ca. 500ms
 * ADC input an PB4 (ADC2) mit ca. 0.55V Offset durch Spannungsteiler herstellen - ohne Signal auf "alle LEDs AUS" abgleichen
 * interne ADC Referenz 1.1V
 * Effektivwertberechnung über 1024 Samples: ca. 32ms = Intervall zum LED update
 *
 * Für die Gesamtbearbeitung reichte die Zeit mit 32 µs nicht aus, da aber die Auswertung (log) nur jedes 1024. Mal 
 * interessiert, wird da eine AD Konvertierung verloren gehen. Wenig tragisch :-). 
 * Das wurde behoben durch einige Optimierungen, wie:
 * - löschen von TOV0 mittels EMPTY_INTERRUPT(TIMER0_OVF_vect); 
 * - Verwendung von ISR(ADC_vect, ISR_NAKED), spart die ISR Registersicherung, die hier nicht notwendig ist (leere Endlosschleife in main() )
 * - Verwendung von i=(i>12)?0:i; statt i %= 13;, generell: Modulo-Operator weglassen.
 * 
 * Erweiterungen in der ADC-ISR können dazu führen, dass die Zeit für die Berechnung von eff_power innerhalb der Abtastzeit nicht reicht. 
 *  Das ist aber nicht problematisch, es entsteht nur eine einzelne Lücke alle 1024 Samples ...
 *
 * Eine weitere Alternativen ist dann, den Systemtakt mittels PLL auf 16MHz zu bringen und z.B. 
 *  mit dem CTC-Mode den Timer IRQ für die ADC Triggerung einzustellen. Die Berechnung sind dann doppelt so schnell.
 *
 * Übersetzen mit DEBUG ermöglicht es, an PB0 das Timing zu messen. Die LEDs zeigen dann allerdings Mist an!
 */

#include <avr/io.h>
#include <avr/interrupt.h> 
#include <avr/pgmspace.h>

#define PEAK_HOLD_TIME 15  // Parameter for peak LED hold time (t=(PEAK_HOLD_TIME+1)*32.7ms), actually about 0.5s

//#define DEBUG

#ifdef DEBUG
const uint8_t display[] = {1,0x89,0x45,0x23,0x8B,0x47,0x03,0x8D,0x27,0x05,0x4D,0x2B,0x09};  // Debug: PB0 shows ISR entry and leave for measurements, but then uncorrect LED display
#else
const uint8_t display[] = {0,0x89,0x45,0x23,0x8A,0x46,0x13,0x8C,0x26,0x15,0x4C,0x2A,0x19};  // Contains DDRB (LS nibble) and PORTB (MS nibble) values for LED Charlie plexing
#endif


const uint16_t square_number[] PROGMEM =
	{ 
		16384,16129,15876,15625,15376,15129,14884,14641,14400,14161,13924,13689,13456,13225,12996,12769,\
		12544,12321,12100,11881,11664,11449,11236,11025,10816,10609,10404,10201,10000,9801,9604,9409,\
		9216,9025,8836,8649,8464,8281,8100,7921,7744,7569,7396,7225,7056,6889,6724,6561,\
		6400,6241,6084,5929,5776,5625,5476,5329,5184,5041,4900,4761,4624,4489,4356,4225,\
		4096,3969,3844,3721,3600,3481,3364,3249,3136,3025,2916,2809,2704,2601,2500,2401,\
		2304,2209,2116,2025,1936,1849,1764,1681,1600,1521,1444,1369,1296,1225,1156,1089,\
		1024,961,900,841,784,729,676,625,576,529,484,441,400,361,324,289,\
		256,225,196,169,144,121,100,81,64,49,36,25,16,9,4,1,\
		0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,\
		256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961,\
		1024,1089,1156,1225,1296,1369,1444,1521,1600,1681,1764,1849,1936,2025,2116,2209,\
		2304,2401,2500,2601,2704,2809,2916,3025,3136,3249,3364,3481,3600,3721,3844,3969,\
		4096,4225,4356,4489,4624,4761,4900,5041,5184,5329,5476,5625,5776,5929,6084,6241,\
		6400,6561,6724,6889,7056,7225,7396,7569,7744,7921,8100,8281,8464,8649,8836,9025,\
		9216,9409,9604,9801,10000,10201,10404,10609,10816,11025,11236,11449,11664,11881,12100,12321,\
		12544,12769,12996,13225,13456,13689,13924,14161,14400,14641,14884,15129,15376,15625,15876,16129
	};


EMPTY_INTERRUPT(TIMER0_OVF_vect);  // Vorschlag von Peter Danneger

ISR(ADC_vect, ISR_NAKED)   // autotrigger mit 32kHz; 
// Attention: ISR_NAKED omitts prolog and epilog, reti() at the end necessary!
// so: no registers are saved at ISR entry!
{ 
	static uint16_t sample_counter;
	static uint32_t adc_square_sum;
	static uint8_t leds, i=0;
	static uint8_t peak_hold_counter=0, peakled=0;

#ifdef DEBUG
	PORTB &= ~(1<<PB0);  // PB0 is LOW during ISR work - for debug purposes
#endif

	sample_counter++;	

	// sum the sqares of ADC values into a 32Bit integer (max. 24 bit used)
	adc_square_sum += pgm_read_word (&square_number[ADCH]);	// only 8 bit ADC values used

#ifdef DEBUG
	if (sample_counter > 63) 		// for measuring timing influence of the calculation part (oscilloscope display)
#else
	if (sample_counter > 1023)  	// calculate dB and LED count
#endif
	{	// every 1024 Samples --> f= 30.5Hz or T = 32.7ms
		uint16_t eff_power;

		eff_power = (uint16_t)(adc_square_sum / 512); // read squares from table, divider 512 empirical optimized
		leds = 0; 
											  // suggested scaling, thresholds calculated: 0dB_value * 10^(att[dB]/10)-1 		
		if      (eff_power > 5113) leds = 12; // +2dB  --> red
		else if (eff_power > 3226) leds = 11; //  0dB  --> yellow
		else if (eff_power > 2035) leds = 10; // -2dB  --> yellow
		else if (eff_power > 1284) leds = 9;  // -4dB  --> all others green
		else if (eff_power > 810 ) leds = 8;  // -6dB 
		else if (eff_power > 405 ) leds = 7;  // -9dB
		else if (eff_power > 203 ) leds = 6;  // -12dB
		else if (eff_power > 101 ) leds = 5;  // -15dB
		else if (eff_power > 50  ) leds = 4;  // -18dB
		else if (eff_power > 25  ) leds = 3;  // -21dB
		else if (eff_power > 12  ) leds = 2;  // -24dB
		else if (eff_power > 5   ) leds = 1;  // -27dB  

		if (leds >= peakled)   // '>=' to reset peak_hold_counter if actual peak is again reached
		{
			peakled = leds; 
			peak_hold_counter = 0; 
		}
		peak_hold_counter ++;
		peak_hold_counter = (peak_hold_counter > PEAK_HOLD_TIME) ? 0 : peak_hold_counter;
		if (!peak_hold_counter) peakled = 0;

		adc_square_sum = 0; 
		sample_counter = 0;
	}  // samplecounter
	
	// Do LED multiplexing, 13 cycles @31.25kHz  --> Cycle = 416µs, 
	// 12 LEDs + OFF
	i = (i > 12 ) ? 0 : i;  
	PORTB = 0;							// all LEDs off
	DDRB = display[i] & 0x0F;			// set DDR 
	if (leds>=i || peakled==i) PORTB = display[i]>>4; // and PORT acc. to LEDs which should glow 
	i++;

#ifdef DEBUG
	PORTB|= (1<<PB0);
#endif
	reti();  // necessary when using ISR_NAKED, must be removed for standard ISR
} // ADC ISR



int main()
{
	// Init
#ifdef DEBUG
	PORTB = 0xF;
	DDRB = 1;
#endif
	// set to 8 MHz, regardless of fuse CKDIV8, may be omitted if CKDIV8 is cleared by other way
	CLKPR = 0x80;
	CLKPR = 00;    

	// disable all pullups
 	MCUCR = 0x40;  

	// ADC init
	// Set timer for ADC trigger
	// 8MHz / 256 : 31.25kHz sampling rate
	TCCR0A = 0;
	TCCR0B = (1<<CS00); 	// Prescaling 1
	TIMSK |= (1<<TOIE0);  	// activate timer interrupt 
 
	ADMUX = (1<<REFS1) | (1<<MUX1) | (1<<ADLAR);  	// use internal reference 1.1V, ADC channel 2, left aligned

	// ADC-Enable, ADC start conversion, ADC auto trigger, ADC interrupt enable, 500kHz ADC clock
	ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS2) ; 
	ADCSRB = (1<<ADTS2);  							// ADC auto trigger on Timer/Counter0 Overflow

	DIDR0 = (1<<ADC2D);  // disable digital input buffer for ADC2

	sei();

  	// all stuff in ISR
	// Attention: omit code in while(1) because ADC_ISR is ISR_NAKED, e.g. no registers are saved
	// otherwise you must 
	while(1);  
  	
}
