/* ==========================================================================
 * 
 *  Voluminizer 2.x
 *  3-channel analog audio volume controller 
 * 
 * Target:  ATMega 48/88/168 at 20MHz
 * Author:  Arvid Staub <arvid.staub@innoc.at> 
 *
 * License: GPL v2
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Changelog:
 *     2011-01-29 added life sign, analog input offset compensation 
 *     2011-01-28 fixed pin definitions, added T[CS->CLK] delay, ADC code added
 *     2011-01-27 first draft
 *
 * ========================================================================== */

#include <avr/io.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/power.h>

#define F_CPU 20000000UL

#include <util/delay.h>

/* binary data word for 1:1 amplification (zero db) */
#define ZERODB	192

#define CYCLE_DELAY 1
#define RAMP_DT		1

/* LEDs */
#define LED_BTN1_PINNR	1
#define LED_BTN2_PINNR	0
#define LED_IR_PINNR		5
#define LED_SENSOR_PINNR	2

#define LED_BTN1_PORT	PORTD
#define LED_BTN2_PORT	PORTD
#define LED_IR_PORT		PORTD
#define LED_SENSOR_PORT	PORTC

#define LED_BTN1_DDR	DDRD
#define LED_BTN2_DDR	DDRD
#define LED_IR_DDR		DDRD
#define LED_SENSOR_DDR	DDRC

#define SELECT_DDR		DDRB
#define SELECT_PORT		PORTB
#define SELECT_PINNR		1

#define UNMUTE_DDR		DDRD
#define UNMUTE_PORT		PORTD
#define UNMUTE_PINNR		6

#define BTN1_DDR		DDRD
#define BTN1_PORT		PORTD
#define BTN1_PINNR		2
#define BTN1_PIN		PIND

#define BTN2_DDR		DDRD
#define BTN2_PORT		PORTD
#define BTN2_PINNR		3
#define BTN2_PIN		PIND

#define SPICLK_DDR		DDRB
#define SPICLK_PINNR	5
#define SPICLK_PORT		PORTB

#define SPIMOSI_DDR			DDRB
#define SPIMOSI_PINNR		3
#define SPIMOSI_PORT		PORTB

#define SPISS_DDR		DDRB
#define SPISS_PINNR		2
#define SPISS_PORT		PORTB


#define pin_make_output(x) do{ x##_DDR |= _BV( x##_PINNR ); }while(0)
#define pin_set(x)	do{ x##_PORT |= _BV( x##_PINNR ); }while(0)
#define pin_clear(x) do{ x##_PORT &= ~_BV( x##_PINNR); }while(0)

#define pin_value(x) (x##_PIN & _BV(x##_PINNR))

#define led_on(x) pin_set(x)
#define led_off(x) pin_clear(x)


static void spi_send(uint8_t data) 
{
	SPDR = data;
	/* wait until the current transmission (if any) is complete */
	while(!(SPSR & _BV(SPIF)));
}


int main(void)
{
	
	uint8_t vol, volreg;
	uint8_t adch;
	uint8_t cycle;
	float scalingfactor;
	
	
	uint8_t sensor_raw, sensor_offset, sensor_max;
	
	pin_make_output(LED_BTN1);
	pin_make_output(LED_BTN2);
	pin_make_output(LED_IR);
	pin_make_output(LED_SENSOR);

	pin_make_output(SPICLK);
	pin_make_output(SPIMOSI);
	
	/* this pin must be HIGH or OUTPUT to keep the SPI module in master mode
	 * just do both to be sure */
	pin_make_output(SPISS);
	pin_set(SPISS);
	
	pin_make_output(LED_SENSOR);

	pin_set(SELECT);				
	pin_set(UNMUTE);

	pin_make_output(SELECT);
	pin_make_output(UNMUTE);
	

	/* activate pull-ups */
	pin_set(BTN1);
	pin_set(BTN2);
	
	
	/* configure and enable SPI */
	SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPR1) | _BV(SPR0);
	
	/* configure analog input */
	adch = 3;	
	ADMUX = _BV(REFS0)  | _BV(ADLAR) | adch;
	ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
	DIDR0 = _BV(ADC3D);
	
	/* unused for now */
	sleep_enable();
	
	cycle = 0;
	sensor_offset = 0;
	sensor_max = 200;
	scalingfactor = 1.7;
	
	volreg = 127;
	
	for(;;) {
		/*
		led_on(LED_BTN1);
		_delay_ms(BLINKDELAY);
		led_off(LED_BTN1);
		_delay_ms(BLINKDELAY);
		
		led_on(LED_BTN2);
		_delay_ms(BLINKDELAY);
		led_off(LED_BTN2);
		_delay_ms(BLINKDELAY);
		
		led_on(LED_IR);
		_delay_ms(BLINKDELAY);
		led_off(LED_IR);
		_delay_ms(BLINKDELAY);
		
		led_on(LED_SENSOR);
		_delay_ms(BLINKDELAY);
		led_off(LED_SENSOR);
		_delay_ms(BLINKDELAY);
*/		
		//sleep_cpu();
		
		_delay_ms(CYCLE_DELAY);
		
		/* life sign  */
		if( (cycle & _BV(7)) && 0 == (cycle & 0x78)) led_on(LED_IR);
		else led_off(LED_IR);
		
		
		/* perform an AD conversion to read the IR distance sensor */
		ADCSRA |= _BV(ADSC);
		while(0 != (ADCSRA & _BV(ADSC)));
		
		sensor_raw = ADCH;
		
		if( sensor_raw > sensor_offset) led_on(LED_SENSOR);
		else led_off(LED_SENSOR);
		
		/* crunch, crunch! this should probably be more elaborate */
		vol = 110 + scalingfactor*(sensor_raw-sensor_offset);
		
		/* don't allow amplification > 0db for now */
		if(vol > ZERODB) 
			vol = ZERODB;
		
		/* don't set the new volume immediately. use a ramp to avoid audible 
		 * clicks with large changes
		 */
		if(vol > volreg) volreg+=RAMP_DT;
		if(vol < volreg) volreg-=RAMP_DT;
		
		/* update the analog amplifier */
		pin_clear(SELECT);
		_delay_us(3);
		
		spi_send(volreg);
		spi_send(volreg);
		spi_send(volreg); /* channel 3 is not connected */
		spi_send(volreg);
		_delay_us(3);
		
		pin_set(SELECT);

		
		/* BTNx/LEDx placement is swapped in the layout. damn! */
		if( 0 == pin_value(BTN2)) {
			if(sensor_raw > sensor_offset) 
				sensor_offset  = sensor_raw;
			
			//scalingfactor = (float)ZERODB / (sensor_max - sensor_offset);
			led_on(LED_BTN1);
		}
		
		
		while( 0 == pin_value(BTN1)) {
			sensor_offset  = 0;
			led_on(LED_BTN2);
		}
		
		led_off(LED_BTN1);
		led_off(LED_BTN2);
		
		cycle++; 
	}
	
	
	return 0;
}

