/*
  schip_analog.c - interrupt driven analog input with multiple options
  Based on Arduino wiring_analog.c v1.0.5r2 - http://www.arduino.cc/
   Copyright (c) 2005-2006 David A. Mellis
   Modified 28 September 2010 by Mark Sproul

  Copyright (c) 2015 M.Schippling

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA

  $Id: schip_analog.c,v 1.1 2015/10/28 19:51:26 schip Exp $
*/

// our external interface and options, note Arduino.h included in below
#include "schip_analog.h"

// also need these internal header files for defines
#include "wiring_private.h"
#include "pins_arduino.h"

// convenient way to make two bytes into one word w/o all that shiftlessness
typedef union
{
	uint16_t word;
	struct
	{
		uint8_t low;
		uint8_t high;
	};
} Uword;

uint8_t		_ADCNum = 0xFF;	// current conversion channel
uint16_t	_ADCValues[NUM_ADCCHANNEL];	// immediate result storage

#ifdef SCHIPAVG	// to average 64 samples on each ADC channel
#define NUM_ADCAVG	64	// number of values to average
				//  special magic value....do not change...
uint8_t		_ADCAvgCnt;	// number of readings currently accumulated
uint16_t	_ADCAccum[NUM_ADCCHANNEL];	// result accumulator for averages
uint8_t		_ADCAverage[NUM_ADCCHANNEL];	// result storage for averages

// calculate ADC averages, set return vals, clear accumulators & counter
void doADCAverages()
{
	uint8_t i;

	for( i=0; i<NUM_ADCCHANNEL; ++i )
	{
		// note special magic of using only high byte
		//  of 64x 10-bit values to get /64 into a byte to return...
		//  this is why 64 is the only choice for NUM_ADCAVG
		_ADCAverage[i] = ((Uword)_ADCAccum[i]).high;
		_ADCAccum[i] = 0;
	}
	_ADCAvgCnt = 0;		// start counting all over again

// if we have included some version of the schip scheduler, use it
#if defined ( SCHEDULER_H ) | defined ( SCHEDULERLIB_H )
	// Post a schip task to look at the stuff.
	// For some reason posting with a 0 time
	//  makes the instantaneous readings fail...
	postTask( 1, ADCTask, NUM_ADCCHANNEL );
#endif // SCHIPSCHED
}
#endif // SCHIPAVG

// defined and initialized to value in wiring_analog.c
//  the "DEFAULT" define is kinda ambiguious...kids these days....
//  I think the relevant value is DEFAULT==1,
//   which makes an anal_ref of 0100 0000b when shifted by 6
//   and finally set in the register 
#ifdef __cplusplus
extern "C"{
#endif
extern uint8_t analog_reference; // = DEFAULT
#ifdef __cplusplus
} // extern "C"
#endif

/** this starts a conversion on the current _ADCNum channel number
 *  results are collected by the interrupt routine which
 *  in turn re-calls this function to start the next channel
 **/
void doAnalogRead()
{
	uint8_t pin = _ADCNum;

// if we don't have an ADC don't use it...
#if defined(ADCSRA) && defined(ADCL)

	// deal with special case 2 level MUX on some chips -- probably NOT
#if defined(__AVR_ATmega32U4__)
	pin = analogPinToChannel(pin);
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#elif defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 -- collect all 10 bits in two registers (the default).
#if defined(ADMUX)
	// note that we want a 0100 0000b for the anal_ref value...
	// We have already shifted reference setting into place so just use it
	ADMUX = (analog_reference | (pin & 0x07));
	//ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

	// without a delay, we seem to read from the wrong channel
	//delay(1);	// jeez, I hope not...
	//
	// SCHIP NOTE: the ADCSRA PRESCALER and ADEN are in wiring.c::init() 
	//  ADPS bits2:0 and ADEN bit7 are all set to 1,
	//  giving /128 prescale or approx 8us/ADC clock (I think...)
	// We _could_ reset them here or in the analogRead() startup
	//  if we _wanted_ to have a faster sample rate...

	// enable interrupt
	sbi(ADCSRA, ADIE);
	// start the conversion
	sbi(ADCSRA, ADSC);

#endif // we actually have an ADC....otherwise....Why Bother(TM)


	return;
}

#ifdef SCHIPBUFFER	// to buffer a block of data then post to application
			// currently just for ADC0, but reader may extend
// This will buffer SBUFSIZE samples and then set the address of the
// buffer in "SchipBUF0" for the application to poke at.
// The app should wait on that variable, then NULL it before copying data.
// The interrupt will alternate filling the two buffers so the app has
// some time to do stuff with the data. Maybe the timing will work out...
//
word _bufdata0a[SBUFSIZE];	// buffer A for ADC0
word _bufdata0b[SBUFSIZE];	// buffer B for ADC0
byte _bufcnt0 = SBUFSIZE;	// sample counter for ADC0 (down counter)
byte _bufnum0 = 0;	// buffer being used A,B
word *_bufptr0 = _bufdata0a;	// current store position in active buffer
word *SchipBUF0 = NULL;	// semaphore to signal app that data is ready
#endif // SCHIPBUFFER

/** the ADC interrupt handler
 *  note NOT "ADCF_vect" !!?? (I dunno...)
 **/
ISR(ADC_vect)
{
#if 1	// go ahead and execute the interrupt code for real
	Uword data; // result collection

#ifdef SCHIP_ISRLED
     // trace ADC ISR execution period and time --
     //  toggle LED on "pin 13" ... nominally ON here
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)	// Mega ??
     PORTB ^= (1<<PINB7);
#elif defined(__AVR_ATmega32U4__)	// Leonardo
     PORTC ^= (1<<PINC7);
#else					// "Normal" Arduino -- atmega 328
     PORTB ^= (1<<PINB5);
#endif // normal atmega 328
#endif // SCHIP_ISRLED

	// ADSC is cleared and ADIF is set when the conversion finishes
	//  but ADIF didn't work for me, so we use the old way
//	if( bit_is_set(ADCSRA, ADIF) )
	if( !bit_is_set(ADCSRA, ADSC) )
	{
		// we have to read ADCL first; doing so locks both ADCL
		// and ADCH until ADCH is read.  reading ADCL second would
		// cause the results of each conversion to be discarded,
		// as ADCL and ADCH would be locked when it completed.
		data.low  = ADCL;
		data.high = ADCH;

		// combine the two bytes and set result array
		_ADCValues[_ADCNum] = data.word;

#ifdef SCHIPAVG // if averaging
		// add value to our accumulator
		_ADCAccum[_ADCNum] += data.word;
#endif // SCHIPAVG

#ifdef SCHIPBUFFER	// if buffering
		// buffer a block of data then post to application
		// currently just for ADC0...
		if( _ADCNum == 0 )	// only buffer ADC0 data
		{
			*_bufptr0++ = data.word;	// save data
			if( --_bufcnt0 == 0 ) // check for end of buffer
			{
				// signal the app and reset the buffers
				if( _bufnum0 == 0 )
				{
					SchipBUF0 = _bufdata0a;
					_bufptr0 = _bufdata0b;
					_bufnum0 = 1;
				}
				else
				{
					SchipBUF0 = _bufdata0b;
					_bufptr0 = _bufdata0a;
					_bufnum0 = 0;
				}

				_bufcnt0 = SBUFSIZE;
			}
		}
#endif // SCHIPBUFFER

		// increment channel
		if( ++_ADCNum >= NUM_ADCCHANNEL )
		{
#ifdef SCHIP_ADC0PERIOD
			// trace ADC0 sample period -- toggle LED on "pin 13"
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)	// Mega ??
			 PORTB ^= (1<<PINB7);
#elif defined(__AVR_ATmega32U4__)     // Leonardo
			 PORTC ^= (1<<PINC7);
#else								// "Normal" Arduino -- atmega 328
			 PORTB ^= (1<<PINB5);
#endif // normal atmega 328
#endif // SCHIP_ADC0PERIOD

			// wrap around to start over
			_ADCNum = 0;
#ifdef SCHIPAVG
			// if averaging, increment number of samples
			//  then do averages when we hit our limit
			//  note that doADCAverages resets the accumulators
			if( ++_ADCAvgCnt >= NUM_ADCAVG )
				doADCAverages();
#endif // SCHIPAVG
		}
#ifdef SCHIPSKIPI2C
		else if( (_ADCNum > 3) && (_ADCNum < 6) )
		{
			// skip channels 4&5 to avoid I2C overlap
			//  note: in theory x>3<6 executes one less compare 1/2 time
			_ADCNum = 6;
		}
#endif // SCHIPSKIPI2C

		// start the next conversion
		doAnalogRead();
	}

#ifdef SCHIP_ISRLED
	// trace me -- toggle LED on pin 13...nominally OFF
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)	// Mega ??
     PORTB ^= (1<<PINB7);
#elif defined(__AVR_ATmega32U4__)     // Leonardo
     PORTC ^= (1<<PINC7);
#else								// "Normal" Arduino -- atmega 328
     PORTB ^= (1<<PINB5);
#endif // normal atmega 328
#endif // SCHIP_ISRLED

#endif // execute interrupt code
}

/**** our external interfaces *****/

#ifdef SCHIPAVG
/** Return the specified ADC's averaged 8 bit value 
 **  note that someone should have gotten the ball rolling with
 **  a call to analogReadMS() when initializing...
 **/
byte analogReadAvg( byte pin )
{
	// note: convert a bunch of crap to channel numbers 0-whatever...
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#elif defined(analogPinToChannel) && (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__))
	pin = analogPinToChannel(pin);
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

	return _ADCAverage[pin];
}
#endif // SCHIPAVG

/** analogReadMS() like analogRead() but just returns the stored results
 *  !!!! DO NOT INTERLEAVE with the old analogRead() !!!!
 *   all the conversion is done in the ADC interrupt loop
 *   ...except...
 *    on the first call this just starts conversions and returns 0
 * note that the header file #defines this to analogRead() so it
 *  "should" be transpartent when using this library
 **/
int analogReadMS( byte pin )
{
	uint8_t oldSREG;
	int val;

	// if ADCNum has not been initialized we need to get the ball rolling
	//  by starting the interrupt cycle
	if( _ADCNum == 0xFF )
	{
#ifdef SCHIPAVG
		// if averaging, use this to init the accumulators and stuff
		doADCAverages();
#endif // SCHIPAVG

		// preemptively adjust ref bit value once instead of on each call....
		// just a schip optimization
		//  rather than shifting analog_reference on each use
		//  adjust this once when the interrupt code initializes
		//  and presume that we are never changing it anyway...
		analog_reference <<= 6;

		// start ADC ball rolling at channel 0
		_ADCNum == 0;
		doAnalogRead();
	}

	// now get the actual value...

	// note: convert a bunch of crap to channel numbers 0-whatever...
	//  this is from the original code, hopefully it works right
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644P__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#elif defined(analogPinToChannel) && (defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__))
	pin = analogPinToChannel(pin);
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

	// shut off interrupts to get last sampled value
	oldSREG = SREG;	// save interrupt settings
	cli();
	val = _ADCValues[pin];
	SREG = oldSREG; // restore settings

	return val;
}
