Rikki - 2013-02-11

Having recently begun to code for the  Arduino Mega Funduino it became clear that the ADC read routines had not been
completed.  The Atmel datasheet helped with configuring the necessary registers. All extra code is commented for
clear explanation.

Support has been added to access all 16 ADC pins on the AVR mega 2560.

'    Analog to Digital conversion routines for Great Cow BASIC
'    Copyright (C) 2006 - 2010 Hugh Considine, Kent Schafer
'    Modified 02/2013 by Richard White to correctly configure
'   AVR Atmel 2560 as used on the Arduino Mega Funduino
'    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
'********************************************************************************
'IMPORTANT:
'THIS FILE IS ESSENTIAL FOR SOME OF THE COMMANDS IN GCBASIC. DO NOT ALTER THIS FILE
'UNLESS YOU KNOW WHAT YOU ARE DOING. CHANGING THIS FILE COULD RENDER SOME GCBASIC
'COMMANDS UNUSABLE!
'********************************************************************************
'Original code by Hugh Considine
'18Fxx31 code by Kent Schafer
'Commands:
'var = ReadAD(port) Reads port, and returns value.
'ADFormat(type)     Choose Left or Right justified
'ADOff          Set A/D converter off. Use if trouble is experienced when
'           attempting to use ports in digital mode
DIM Work_ADC as word        
#define Format_Left 0
#define Format_Right 255
'Acquisition time. Can be reduced in some circumstances - see PIC manual for details
#define AD_Delay 2 10us
'Optimisation
#define ADSpeed MediumSpeed
#define HighSpeed 255
#define MediumSpeed 128
#define LowSpeed 0
#define InternalClock 192
'Port names
'PIC style
#define AN0 0
#define AN1 1
#define AN2 2
#define AN3 3
#define AN4 4
#define AN5 5
#define AN6 6
#define AN7 7
#define AN8 8
#define AN9 9
#define AN10 10
#define AN11 11
#define AN12 12
#define AN13 13
'AVR style
#define ADC0 0
#define ADC1 1
#define ADC2 2
#define ADC3 3
#define ADC4 4
#define ADC5 5
#define ADC6 6
#define ADC7 7
#define ADC8 8
#define ADC9 9
#define ADC10 10
#define ADC11 11
#define ADC12 12
#define ADC13 13
#define ADC14 14
#define ADC15 15
macro LLReadAD
    #IFDEF PIC

        'Set up A/D
        'Make necessary ports analog
        'Code for PICs with older A/D (No ANSEL register)
        #IFDEF NoVar(ANSEL)
            #IFDEF NoVar(ANSEL0)
                #IFDEF NoVar(ANSELA)
                    #IFDEF NoVar(ANSELB)

                        #IFDEF NoBit(PCFG4)
                            #IFDEF NoVar(ADCON2)
                                #IFDEF NoBit(ANS0)
                                    'Example: 16F877A
                                    #IFDEF Bit(PCFG3)
                                        SET PCFG3 OFF
                                    #ENDIF
                                    SET PCFG2 OFF
                                    SET PCFG1 OFF
                                    SET PCFG0 OFF
                                #ENDIF
                                'Example: 10F220
                                #IFDEF Bit(ANS0)
                                    SET ANS0 OFF
                                    SET ANS1 OFF
                                #ENDIF
                            #ENDIF

                            #IFDEF Var(ADCON2)
                                'Example: 18F4620
                                #IFDEF BIT(PCFG3)
                                    SET PCFG3 OFF
                                    SET PCFG2 OFF
                                    SET PCFG1 OFF
                                    SET PCFG0 OFF
                                #ENDIF
                            #ENDIF
                        #ENDIF

                        'PICs with PCFG4 and higher use ADCON1 as an ANSEL type register
                        'Example: 18F1320
                        #IFDEF Bit(PCFG4)
                            'Some 18F8xxxx chips have error in chip definition
                            'They claim to have PCFG4, but actually don't, can spot them by presence of ADCON2
                            Dim AllANSEL As Byte Alias ADCON1
                            AllANSEL = 0
                            ADTemp = ADReadPort + 1
                            Set C On
                            Do
                                Rotate AllANSEL Left
                                decfsz ADTemp,F
                            Loop
                        #ENDIF

                    'ANSELB/A
                    #ENDIF
                #ENDIF

                'Code for 16F193x chips (and others?) with ANSELA/ANSELB/ANSELE registers
                #IFDEF Var(ANSELA)
                    Select Case ADReadPort
                        #IFDEF OneOf(CHIP_16F1826, CHIP_16F1827)
                            Case 0: Set ANSELA.0 On
                            Case 1: Set ANSELA.1 On
                            Case 2: Set ANSELA.2 On
                            Case 3: Set ANSELA.3 On
                            Case 4: Set ANSELA.4 On

                            Case 11: Set ANSELB.1 On
                            Case 10: Set ANSELB.2 On
                            Case 9: Set ANSELB.3 On
                            Case 8: Set ANSELB.4 On
                            Case 7: Set ANSELB.5 On
                            Case 5: Set ANSELB.6 On
                            Case 6: Set ANSELB.7 On
                        #ENDIF

                        #IFDEF OneOf(CHIP_16F1933, CHIP_16F1934, CHIP_16F1936, CHIP_16F1937, CHIP_16F1938, CHIP_16F1939)
                            Case 0: Set ANSELA.0 On
                            Case 1: Set ANSELA.1 On
                            Case 2: Set ANSELA.2 On
                            Case 3: Set ANSELA.3 On
                            Case 4: Set ANSELA.5 On

                            #IFDEF Var(ANSELB)
                                Case 12: Set ANSELB.0 On
                                Case 10: Set ANSELB.1 On
                                Case 8: Set ANSELB.2 On
                                Case 9: Set ANSELB.3 On
                                Case 11: Set ANSELB.4 On
                                Case 13: Set ANSELB.5 On
                            #ENDIF

                            #IFDEF Var(ANSELE)
                                Case 5: Set ANSELE.0 On
                                Case 6: Set ANSELE.1 On
                                Case 7: Set ANSELE.2 On
                            #ENDIF
                        #ENDIF

                    End Select
                #ENDIF

                'ANSEL0/ANSEL
            #ENDIF
        #ENDIF

        'Code for PICs with newer A/D (with ANSEL register)
        #IFDEF Var(ANSEL)
            #IFDEF Var(ANSELH)
                Dim AllANSEL As Word Alias ANSELH, ANSEL
            #ENDIF
            #IFDEF NoVar(ANSELH)
                Dim AllANSEL As Byte Alias ANSEL
            #ENDIF
            AllANSEL = 0
            ADTemp = ADReadPort + 1
            Set C On
            Do
                Rotate AllANSEL Left
                decfsz ADTemp,F
            Loop

        #ENDIF
        'Code for 18F4431, uses ANSEL0 and ANSEL1
        #IFDEF Var(ANSEL0)
            #IFDEF Var(ANSEL1)
                Dim AllANSEL As Word Alias ANSEL1, ANSEL0
            #ENDIF
            #IFDEF NoVar(ANSEL1)
                Dim AllANSEL As Byte Alias ANSEL0
            #ENDIF
            AllANSEL = 0
            ADTemp = ADReadPort + 1
            Set C On
            Do
                Rotate AllANSEL Left
                decfsz ADTemp,F
            Loop

        #ENDIF

        'Set Auto or Single Convert Mode
        #IFDEF Bit(ACONV)
            SET ACONV OFF  'Single shot mode 
            SET ACSCH OFF  'Single channel CONVERSION
            'GroupA
            IF ADReadPort = 0 OR ADReadPort = 4 OR ADReadPort = 8 Then
                SET ACMOD1 OFF
                SET ACMOD0 OFF
            END IF
            'GroupB
            IF ADReadPort = 1 OR ADReadPort = 5 Then
                SET ACMOD1 OFF
                SET ACMOD0 ON
            END IF
            'GroupC
            IF ADReadPort = 2 OR ADReadPort = 6 Then
                SET ACMOD1 ON
                SET ACMOD0 OFF
            END IF
            'GroupD
            IF ADReadPort = 3 OR ADReadPort = 7 Then
                SET ACMOD1 ON
                SET ACMOD0 ON
            END IF

        #ENDIF

        'Set conversion clock
        #IFDEF Bit(ADCS0)
            #IFDEF ADSpeed HighSpeed
                SET ADCS1 OFF
                SET ADCS0 OFF
            #ENDIF
            #IFDEF ADSpeed MediumSpeed
                SET ADCS1 OFF
                SET ADCS0 ON
            #ENDIF
            #IFDEF ADSpeed LowSpeed
                SET ADCS1 ON
                SET ADCS0 ON
            #ENDIF
            #IFDEF ADSpeed InternalClock
                SET ADCS1 ON
                SET ADCS0 ON
            #ENDIF
        #ENDIF

        'Choose port
        #IFDEF Bit(CHS0)
            SET ADCON0.CHS0 OFF
            SET ADCON0.CHS1 OFF
            #IFDEF Bit(CHS2)
                SET ADCON0.CHS2 OFF
                #IFDEF Bit(CHS3)
                    SET ADCON0.CHS3 OFF
                #ENDIF
            #ENDIF

            IF ADReadPort.0 On Then Set ADCON0.CHS0 On
            IF ADReadPort.1 On Then Set ADCON0.CHS1 On
            #IFDEF Bit(CHS2)
                IF ADReadPort.2 On Then Set ADCON0.CHS2 On
                #IFDEF Bit(CHS3)
                    If ADReadPort.3 On Then Set ADCON0.CHS3 On
                #ENDIF
            #ENDIF
        #ENDIF
        #IFDEF BIT(GASEL0)
            'GROUP A SELECT BITS
            IF ADReadPort = 0 THEN
                SET GASEL1 OFF
                SET GASEL0 OFF
            END IF
            IF ADReadPort = 4 THEN
                SET GASEL1 OFF
                SET GASEL0 ON
            END IF
            IF ADReadPort = 8 THEN
                SET GASEL1 ON
                SET GASEL0 OFF
            END IF
            'GROUP C SELECT BITS        
            IF ADReadPort = 2 THEN
                SET GCSEL1 OFF
                SET GCSEL0 OFF
            END IF
            IF ADReadPort = 6 THEN
                SET GCSEL1 OFF
                SET GCSEL0 ON
            END IF
            'GROUP B SELECT BITS
            IF ADReadPort = 1 THEN
                SET GBSEL1 OFF
                SET GBSEL0 OFF
            END IF
            IF ADReadPort = 5 THEN
                SET GBSEL1 OFF
                SET GBSEL0 ON
            END IF
            'GROUP D SELECT BITS
            IF ADReadPort = 3 THEN
                SET GDSEL1 OFF
                SET GDSEL0 OFF
            END IF
            IF ADReadPort = 7 THEN
                SET GDSEL1 OFF
                SET GDSEL0 ON
            END IF
        #ENDIF

        'Enable A/D
        SET ADCON0.ADON ON
        'Acquisition Delay
        Wait AD_Delay

        'Read A/D
        #IFDEF Bit(GO_DONE)
            SET ADCON0.GO_DONE ON
            Wait While ADCON0.GO_DONE ON
        #ENDIF
        #IFNDEF Bit(GO_DONE)
            #IFDEF Bit(GO)
                SET ADCON0.GO ON
                Wait While ADCON0.GO ON
            #ENDIF
        #ENDIF
        'Switch off A/D
        #IFDEF Var(ADCON0)
            SET ADCON0.ADON OFF
            #IFDEF NoVar(ANSEL)
                #IFDEF NoVar(ANSELA)

                    #IFDEF NoBit(PCFG4)
                        #IFDEF NoVar(ADCON2)
                            #IFDEF NoBit(ANS0)
                                #IFDEF Bit(PCFG3)
                                    SET PCFG3 OFF
                                #ENDIF
                                SET PCFG2 ON
                                SET PCFG1 ON
                                SET PCFG0 OFF
                            #ENDIF
                            #IFDEF Bit(ANS0)
                                SET ANS0 OFF
                                SET ANS1 OFF
                            #ENDIF
                        #ENDIF

                        #IFDEF Var(ADCON2)
                            #IFDEF BIT(PCFG3)
                                SET PCFG3 ON
                                SET PCFG2 ON
                                SET PCFG1 ON
                                SET PCFG0 ON
                            #ENDIF
                        #ENDIF
                    #ENDIF

                    'For 18F1320, which uses ADCON1 as an ANSEL register
                    #IFDEF Bit(PCFG4)
                        ADCON1 = 0
                    #ENDIF
                #ENDIF
            #ENDIF
        #ENDIF

        'Clear whatever ANSEL variants the chip has
        #IFDEF Var(ANSEL)
            ANSEL = 0
        #ENDIF
        #IFDEF Var(ANSELH)
            ANSELH = 0
        #ENDIF
        #IFDEF Var(ANSEL0)
            ANSEL0 = 0
        #ENDIF
        #IFDEF Var(ANSEL1)
            ANSEL1 = 0
        #ENDIF
        #IFDEF Var(ANSELA)
            ANSELA = 0
        #ENDIF
        #IFDEF Var(ANSELB)
            ANSELB = 0
        #ENDIF
        #IFDEF Var(ANSELE)
            ANSELE = 0
        #ENDIF

    #ENDIF
    #IFDEF AVR

        'Select channel   ** modified for atmel Mega 2560

            'Select Voltage reference source as VCC (5V) '**new need to modify these bits after writing to ADMUX register.
        set REFS1 off '**new                           because all bits are cleared when writing ADreadport to ADMUX
        set REFS0 on  '**new

        if chipMHZ > 15 then
            SET ADPS2 On   'for prescaler
            SET ADPS1 On
            SET ADPS0 On   '**new  divide by 128 if chipspeed > 15 Mhz. Runs ADC conversio clock slower
            end if

            if ADReadPort >= 8 then     
            set MUX5 on                 '**NEW for mega 2560. selects 3rd bit in ADCSRB register for ADC8 thru ADC15
            ADMUX.0 = ADReadPort.0       'Dont write bits 3 & 4  to MUX as it selects diferential ADC conversion    
            ADMUX.1 = ADReadPort.1       'manually copy individual bits 
            ADMUX.2 = ADReadPort.2

            else
            ADMUX.0 = ADReadPort.0       'Dont write bits 3 & 4  to MUX as it selects diferential ADC conversion    
            ADMUX.1 = ADReadPort.1       'manually copy individual bits 
            ADMUX.2 = ADReadPort.2
            set MUX5 off                 'MUX5 is for selecting ADC8-ADC15  
            end if

            ADMUX.3 = 0      'CLEAR bits 3,4,5  ** experimental
            ADMUX.4 = 0      'manually copy individual bits 
            ADMUX.5 = 0

        'Set conversion clock
        #IFDEF Bit(ADPS2)

                    'disabled this section experimentally
                    '#IFDEF ADSpeed HighSpeed
                    '   SET ADPS2 Off
                    '   SET ADPS1 Off
                    '   SET ADPS0 Off   '**new
                    '#ENDIF
                    '#IFDEF ADSpeed MediumSpeed
                    '   SET ADPS2 On
                    '   SET ADPS1 Off
                    '   SET ADPS0 Off   '**new
                    '#ENDIF
                    '#IFDEF ADSpeed LowSpeed
                    '   SET ADPS2 On
                    '   SET ADPS1 On
                    '   SET ADPS0 On   '**new  divide by 128
                    '#ENDIF
        #ENDIF

        'Acquisition Delay
        Wait AD_Delay

        'Take reading
        Set ADEN On
        Set ADSC On
        Wait While ADSC On
        Set ADEN Off

    #ENDIF

end macro
function ReadAD(ADReadPort)
'Set up for 8 bit
#IFDEF AVR
    #IFDEF Bit(ADLAR)
        Set ADReadPort.ADLAR Off
    #endif
#ENDIF
'Perform conversion
LLReadAD
'Write output
#IFDEF PIC
    #IFDEF Var(ADRESH)
        ReadAD = ADRESH
    #ENDIF
    #IFDEF NoVar(ADRESH)
        ReadAD = ADRES
    #ENDIF
#ENDIF
#IFDEF AVR
    Work_ADC=peek(0x78)   '**new. need to read low register first. use reserved working variable
    Work_ADC_H=peek(0x79)  'read high register as a dummy to clear DATA register. Otherwise next ADC conversion cannot be read...

    Work_ADC=Work_ADC/[word]4   'shift right twice to remove 2 LSB
    READAD=Work_ADC             'copy result to READAD so that function can return value
#ENDIF
end function
'Large ReadAD
function ReadAD10(ADReadPort) As Word
#IFDEF PIC
    'Set up A/D format
    #IFDEF Bit(ADFM)
        SET ADFM ON
    #ENDIF
#ENDIF
#IFDEF AVR
    Dim LLADResult As Word Alias ADCH, ADCL
    #IFDEF Bit(ADLAR)
        Set ADReadPort.ADLAR Off
    #EndIf
#ENDIF
'Do conversion
LLReadAD
#IFDEF PIC
    'Write output
    #IFDEF NoVar(ADRESL)
        ReadAD10 = ADRES
    #ENDIF
    #IFDEF Var(ADRESL)
        ReadAD10 = ADRESL
    #ENDIF
    #IFDEF Var(ADRESH)
        ReadAD10_H = ADRESH
    #ENDIF

    'Put A/D format back to normal
    #IFDEF Bit(ADFM)
        SET ADFM OFF
    #ENDIF
#ENDIF
#IFDEF AVR
    ReadAD10 = peek(0x78)  '**new. need to read low register first
    ReadAD10_H=peek(0x79)
#ENDIF
end function
'This sub is deprecated
sub ADFormat(ADReadFormat)
 SET ADFM OFF
 IF ADReadFormat.1 ON THEN SET ADFM ON  
end sub
'This sub is deprecated
sub ADOff
'Disable the A/D converter, and set all ports to digital.
'This sub is deprecated, InitSys automatically turns off A/D 
 SET ADCON0.ADON OFF
#IFDEF NoBit(PCFG4)
 #IFDEF NoVar(ANSEL)
  #IFDEF NoVar(ADCON2)
   #IFDEF Bit(PCFG3)
    SET PCFG3 OFF
   #ENDIF
   SET PCFG2 ON
   SET PCFG1 ON
   SET PCFG0 OFF
  #ENDIF
  #IFDEF Var(ADCON2)
   SET PCFG3 ON
   SET PCFG2 ON
   SET PCFG1 ON
   SET PCFG0 ON
  #ENDIF
 #ENDIF
#ENDIF
 #IFDEF Bit(PCFG4)
  #IFDEF Bit(PCFG6)
   SET PCFG6 ON
  #ENDIF
  #IFDEF Bit(PCFG5)
   SET PCFG5 ON
  #ENDIF
  SET PCFG4 ON
  SET PCFG3 ON
  SET PCFG2 ON
  SET PCFG1 ON
  SET PCFG0 ON
 #ENDIF
 #IFDEF Var(ANSEL)
  ANSEL = 0
 #ENDIF
 #IFDEF Var(ANSELH)
  ANSELH = 0
 #ENDIF
end sub