// ----------------------------------------------------------------------------------------
//
//  avr/device/adc.cpp
//    
//  Bob Cook Development, Robotics Library
//  http://www.bobcookdev.com/rl/
//    
//  This file provides a basic Analog to Digitial Conversion (ADC) library.
//
//  Copyright (C) 2007 Bob Cook
//
//      This program is free software; you can redistribute it and/or modify it
//      under the terms of the GNU General Public License as published by the
//      Free Software Foundation; either version 2 of the License, or (at your
//      option) any later version.
//    
//      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.  See the GNU General Public License 
//      for more details.
//    
//      You should have received a copy of the GNU General Public License along
//      with this program; if not, write to the Free Software Foundation, Inc., 
//      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// ----------------------------------------------------------------------------------------


#include "adc.h"

#include <avr/io.h>

#include "packages/avr/device/spinwait.h"

#include "hwdefs.h"
#include "project_defs.h"


// ----------------------------------------------------------------------------------------
//  Project-specific definitions
//

#if defined( PRJ_ADC_ENABLED )

#if !defined( HW_HAS_ADC )
#error Target HW has no ADC!
#endif

#if !defined( PRJ_ADC_CLOCK_FACTOR_DIV_2 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_4 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_8 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_16 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_32 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_64 ) \
    && !defined( PRJ_ADC_CLOCK_FACTOR_DIV_128 )
#error Must define an ADC clock prescalar value!
#endif

#endif // #if defined( PRJ_ADC_ENABLED )


// ----------------------------------------------------------------------------------------

#if defined( PRJ_ADC_ENABLED )

void adc_init()
{
    //--    Set the pins for each requested channel as input.

    HW_ADC_DDR &= ~( 0

#if defined( PRJ_ADC_USE_CHANNEL_6 )

        | ( 1 << PC6 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_5 )

        | ( 1 << PC5 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_4 )

        | ( 1 << PC4 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_3 )

        | ( 1 << PC3 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_2 )

        | ( 1 << PC2 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_1 )

        | ( 1 << PC1 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_0 )

        | ( 1 << PC0 )

#endif

        );

    //--    Disable the internal pull-ups on the specified input pins.

    HW_ADC_PORT &= ~( 0

#if defined( PRJ_ADC_USE_CHANNEL_6 )

        | ( 1 << PC6 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_5 )

        | ( 1 << PC5 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_4 )

        | ( 1 << PC4 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_3 )

        | ( 1 << PC3 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_2 )

        | ( 1 << PC2 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_1 )

        | ( 1 << PC1 )

#endif

#if defined( PRJ_ADC_USE_CHANNEL_0 )

        | ( 1 << PC0 )

#endif

        );

    //--    Enable the ADC with the specified parameters. We start the first conversion
    //      immediately and block until it finishes.

    HW_ADC_REG_ADCSR = 0
        
        | ( 1 << ADEN ) 
        | ( 1 << ADSC )

#if defined( HW_ADC_REG_ADCSR_ADATE )

        | ( 0 << HW_ADC_REG_ADCSR_ADATE )

#elif defined( HW_ADC_REG_ADCSR_ADFR )

        | ( 0 << HW_ADC_REG_ADCSR_ADFR )

#endif

        | ( 0 << ADIF )
        | ( 0 << ADIE )

#if defined( PRJ_ADC_CLOCK_FACTOR_DIV_2 )
    
        | ( 0 << ADPS2 ) | ( 0 << ADPS1 ) | ( 0 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_4 )

        | ( 0 << ADPS2 ) | ( 1 << ADPS1 ) | ( 0 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_8 )

        | ( 0 << ADPS2 ) | ( 1 << ADPS1 ) | ( 1 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_16 )

        | ( 1 << ADPS2 ) | ( 0 << ADPS1 ) | ( 0 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_32 )

        | ( 1 << ADPS2 ) | ( 0 << ADPS1 ) | ( 1 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_64 )

        | ( 1 << ADPS2 ) | ( 1 << ADPS1 ) | ( 0 << ADPS0 )

#elif defined( PRJ_ADC_CLOCK_FACTOR_DIV_128 )

        | ( 1 << ADPS2 ) | ( 1 << ADPS1 ) | ( 1 << ADPS0 )

#endif

        ;

    //--    Wait for the first conversion to complete before returning.

    while ( ( HW_ADC_REG_ADCSR & ( 1 << ADSC ) ) != 0 ) ;

}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_ADC_ENABLED )

void adc_stop()
{
    HW_ADC_REG_ADCSR = 0;
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_ADC_ENABLED )

uint16_t adc_read( uint8_t channel )
{
    HW_ADC_REG_ADMUX = channel;
    HW_ADC_REG_ADCSR |= ( 1 << ADSC );

    while ( ( HW_ADC_REG_ADCSR & ( 1 << ADSC ) ) != 0 ) ; // wait for the conversion

    uint16_t result = ADCL;
    result |= ( ADCH << 8 );
    return result;
}

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_ADC_ENABLED ) && defined( PRJ_ADC_EXTENDED_READ )

void adc_read
    (
        uint8_t   channel,
        uint16_t* data,
        uint8_t   count,
        uint8_t   delay_ms
    )
{
    HW_ADC_REG_ADMUX = channel;

    for ( uint8_t i = 0; i < count; i++ )
    {
        HW_ADC_REG_ADCSR |= ( 1 << ADSC );

        while ( ( HW_ADC_REG_ADCSR & ( 1 << ADSC ) ) != 0 ) ; // wait for the conversion

        uint16_t result = ADCL;
        result |= ( ADCH << 8 );

        *data = result;
        data++;

        spinwait_delay_ms( delay_ms );
    }
}

#endif


// ----------------------------------------------------------------------------------------
//  end of adc.cpp
// ----------------------------------------------------------------------------------------

