1.5 Getting Started with ADCC

1.5 Introduction

Author: Ioan Pop, Microchip Technology Inc.

The Analog-to-Digital Converter with Computation (ADCC) peripheral converts an analog voltage value into a 10-bit numerical value and offers different computational actions that can be performed on the result such as averaging or low-pass filtering. The code examples in this technical brief also apply to other PIC18 families of devices that feature a 12-bit ADCC.

For each use case, there are two different implementations which have the same functionalities: one code generated with MPLAB® Code Configurator (MCC) and one bare metal code. The MCC-generated code offers hardware abstraction layers that ease the use of the code across different devices from the same family. The bare metal code is easier to follow, allowing a fast ramp-up on the use case associated code.

This technical brief describes the application area, the modes of operation and the hardware and software requirements of the Analog-to-Digital Converter with Computation. It covers the following use cases:

Note: The examples in this technical brief have been developed using the PIC18F47Q10 Curiosity Nano development board. The PIC18F47Q10 pin package present on the board is QFN.

1.5.1 Peripheral Overview

The Analog-to-Digital Converter with Computation (ADCC) is a peripheral that reads an analog voltage, transforms it into a digital value and performs various computations on the results. The ADCC input can be selected from several internal channels (Fixed Voltage Reference (FVR), temperature sensor or ground reference) or from an external pin. The ADC Positive Channel Selection (ADPCH) register selects the channel.

The ADCC can have different positive and negative references. The positive reference can be chosen by writing in the ADC Positive Voltage Reference Selection (ADPREF) bits of the ADC Reference Selection (ADREF) register. The available sources are the FVR output, the external VREF+ pin or the supply voltage (VDD). The negative reference can be selected from the ground reference (VSS) or external VREF- pin by writing in the ADC Negative Voltage Reference Selection (ADNREF) bit in the same register as before.

Figure 1-32. ADCC Block Diagram

The ADCC has two clock sources. They are selected by the value of the Clock Selection (ADCS) bit in the Control 0 (ADCON0) register. The value ‘0’ selects the main microcontroller clock prescaled by a value determined by the ADCLK register. The value ‘1’ of ADCS selects the dedicated fixed-frequency ADCC clock called FRC.

The ADCC can provide right- or left-justified results depending on the setting of the results Format/Alignment Selection (ADFM) bit in the Control 0 (ADCON0) register in the following way: ‘1’ for right-justified results and ‘0’ for left-justified. The result is found in the 16-bit ADC Result (ADRES) register.

The ADCC can be configured to start conversions on a trigger signal. The trigger can come from an external pin or from other peripherals. The timing requirements for a conversion still need to be followed (e.g. if a conversion takes 100 us, then a trigger that comes faster than 100 us will not produce a new conversion).

The ADCC is enabled by setting the ADC Enable (ADON) bit in the Control 0 (ADCON0) register. There are then three ways to start a conversion. The first is by setting the ADC Conversion Status (ADGO) bit in the ADCON0 register. The second is by receiving an auto-conversion trigger that is configured for this purpose. The third is automatic, if the ADC Continuous Operation Enable (ADCONT) bit in the ADCON0 register is set. A conversion will start immediately after the end of the previous one.

The ADCC module is equipped with post-conversion computation features that can be configured through the ADC Control 2 (ADCON2) register. There are five available computation modes:

  • Basic: The normal conversion mode. A single or double conversion is done and then the result is stored in the ADRES register.
  • Accumulate: With each trigger, the result is right-shifted by a number of bits equal to the value in the ADC Accumulated Calculation Right Shift Select bits in the ADCON2 register and then added to the accumulator, a threshold test is performed and ADC Repeat Counter (ADCNT) register is incremented.
  • Average: With each trigger, the result is right-shifted by a number of bits equal to the value in the ADCRS bits in the ADCON2 register and then added to the accumulator. When a number of conversions equal to the ADC Repeat Setting (ADRPT) is completed, the value in the accumulator is divided by the number of samples and a threshold test is performed and the accumulator is cleared on the next trigger.
  • Burst average: It is similar to the Average mode, but it re-triggers the conversion until ADCNT becomes equal to ADRPT even if continuous mode is not enabled.
  • Low-pass filter: It works the same as the Average mode except it performs a low-pass filtering operation on all the samples and reduces the effect of high-frequency noise on the average.

At the end of each computation, the results are latched and held stable. The error is then calculated by a formula selected by the ADC Error Calculation Mode Select (ADCALC) bits in the ADC Control 3 (ADCON3) register. The error is stored in the ADC Setpoint Error (ADERR) register.

The error is then compared to the upper and lower thresholds depending on the setting of the Threshold Interrupt Mode Select (ADTMD) bits in the ADC Control 3 (ADCON3) register and if it is between the required bounds an interrupt will be triggered.

1.5.2 ADCC Single Conversion

This example shows how to configure the ADCC module to give one conversion of an analog value read from a pin. The ADCC module is configured to use its dedicated clock (FRC) with results right-justified. First, the sample capacitor will be discharged by connecting it to the ground potential of the microcontroller and then a conversion will be started on the analog channel.

The value can then be read from the debugger’s variable watch menu.

To achieve the functionality described by the use case, the following actions will be performed:

  • System clock initialization
  • Port initialization
  • ADCC initialization
  • Discharge sample capacitor
  • Starting the conversion

1.5.2.1 MCC Generated Code

To generate this project using MPLAB Code Configurator (MCC), follow the next steps:

  1. Create a new MPLAB X IDE project for PIC18F47Q10.
  2. Open MCC from the toolbar (more information about how to install the MCC plug-in can

    be found here.

  3. Go to Project Resources → System → System Module and do the following configuration:
    • Oscillator Select: HFINTOSC
    • HF Internal Clock: 1 MHz
    • Clock Divider: 1
    • In the Watchdog Timer Enable field in WWDT tab, make sure “WDT Disabled” is selected
    • In the Programming tab, make sure Low-Voltage Programming Enable is checked.
  4. From the Device Resources window, add ADCC, then do the following configuration:
    • Enable ADC: Check
    • Operating: Basic Mode
    • Clock Source: FRC
    • Result Alignment: Right
    • Positive Reference: VDD
    • Negative Reference: VSS
  5. Go to Pin Manager → Grid View and select the RA0 pin as ANx input for the ADCC.
    Figure 1-33. Pin Mapping
  6. Go to the Pin Module in the Project Resources tab and set the RA0 pin as analog and as input (check to see if output is not checked).
  7. Press the Generate button. The generated code can now be found in the project folder.
  8. Add the following code to the main function.

    Add this before the main function. This is the variable that stores the value and can be read with the debugger.

    uint16_t volatile adcVal;
    Add this code inside the body of the main() function. This discharges the sample capacitor and adds a function that starts the conversion on the analog channel and returns the value.
    ADCC_DischargeSampleCapacitor();
    adcVal = ADCC_GetSingleConversion(channel_ANA0);
    
    while (1)
    {
       ;
    }

1.5.2.2 Bare Metal Code

The necessary code and functions to implement the presented example are analyzed in this section. It has five functions: three of them configure the ADCC, the main oscillator and the pin, while the fourth one discharges the sample capacitor and the last one initiates the conversion and returns the result.

The first step will be to configure the microcontroller to disable the Watchdog Timer (WDT) and to enable low-voltage programming.

/*disable Watchdog*/
#pragma config WDTE = OFF
/* Low voltage programming enabled, RE3 pin is MCLR */
#pragma config LVP = ON 
The CLK_Init function selects HFINTOSC as the main oscillator and sets the frequency to 1 MHz.
static void CLK_Init(void)
{
    /* set HFINTOSC Oscillator */
    OSCCON1bits.NOSC = 6;
    /* set HFFRQ to 1 MHz */
    OSCFRQbits.HFFRQ = 0;
}

The PORT_Init function sets the RA0 pin as analog input.

static void PORT_Init(void)
{
    /*set pin RA0 as analog*/
    ANSELAbits.ANSELA0 = 1;
    /*set pin RA0 as input*/
    TRISAbits.TRISA0 = 1;  
}

The ADCC_Init function enables the ADCC, configures it to give right-justified results and use its dedicated clock.

static void ADCC_Init(void)
{
    /* Enable the ADCC module */
    ADCON0bits.ADON = 1; 
    /* Select FRC clock */
    ADCON0bits.ADCS = 1;
    /* result right justified */
    ADCON0bits.ADFM = 1;
}

The ADCC_DischargeSampleCap function connects the ADCC to VSS and thus discharges the capacitor to provide an accurate reading.

static void ADCC_DischargeSampleCap(void)
{
    /*channel number that connects to VSS*/
    ADPCH = 0x3C;
}

The ADCC_ReadValue function selects the channel, starts the conversion, waits for it to end and then returns the result. This result is saved into a variable that can be watched with the debugger. The channel that connects to pin RA0 is 0x00.

static uint16_t ADCC_ReadValue(uint8_t channel)
{   
    ADPCH = channel;
    /*start conversion*/
    ADCON0bits.ADGO = 1;
    while (ADCON0bits.ADGO)
    {
        ;
    }
        
    return ((uint16_t)((ADRESH << 8) + ADRESL));
}

1.5.3 ADCC Temperature Measurement

This example shows how to read the internal temperature sensor of the microcontroller. To achieve that, the ADCC should be enabled and configured to use its dedicated clock (FRC) with results right-justified.

The microcontroller is equipped with a temperature circuit designed to measure the operating temperature of the silicon die (internal temperature). It is integrated into the Fixed Voltage Reference (FVR) module and provides a voltage proportional with the die temperature.

The temperature sense circuit is enabled by setting the Temperature Indicator Enable (TSEN) bit in the Fixed Voltage Reference Control (FVRCON) register. It provides two selectable output ranges: the high range provides more resolution over temperature range and a higher operating range (>3.6V), while the lower range is provided for low-voltage operation. The range is selected by the Temperature Indicator Range Selection (TSRNG) bit in the FVRCON register

First, the sample capacitor will be discharged and then a conversion will be started on the temperature channel.

Two macros are provided for transforming the received ADCC value into a Celsius or Fahrenheit value.

To achieve the functionality described by the use case, the following actions will be performed:

  • System clock initialization
  • Port initialization
  • ADCC initialization
  • Discharge sample capacitor
  • Starting the conversion

1.5.3.1 MCC Generated Code

To generate this project using MPLAB Code Configurator (MCC), follow the next steps:

  1. Create a new MPLAB X IDE project for PIC18F47Q10.
  2. Open MCC from the toolbar (more information about how to install the MCC plug-in can

    be found here.

  3. Go to Project Resources → System → System Module and do the following configuration:
    • Oscillator Select: HFINTOSC
    • HF Internal Clock: 1 MHz
    • Clock Divider: 1
    • In the Watchdog Timer Enable field in WWDT tab, make sure “WDT Disabled” is selected
    • In the Programming tab, make sure Low-Voltage Programming Enable is checked.
  4. The peripherals can be added to the project from the Device Resources window. Add ADCC and FVR. The peripherals are now available for configuration in the Project Resources window under Peripherals.
  5. ADCC configuration:
    • Enable ADC: Check
    • Operating: Basic Mode
    • Clock Source: FRC
    • Result Alignment: Right
    • Positive Reference: VDD
    • Negative Reference: VSS
  6. FVR configuration:
    • Enable FVR: Check
    • Enable Temperature Sensor: Check
    • Voltage Range Selection: Lo_Range
  7. Press the Generate button. The generated code can now be found in the project folder.
  8. Add the following code to the main.c file. Add the following code before the main() function. It provides two macros that will convert the value read from the ADCC to a value in Celsius or Fahrenheit.
    #define VDD                                 3.3
    #define ADC_TO_CELSIUS(adcVal)              (int16_t) \
                    ((1241.4967 - VDD * (1024 - (adcVal))) / 2.70336)
    #define ADC_TO_FAHRENHEIT(adcVal)           (int16_t) \
                    ((((1241.4967 - VDD * (1024 - (adcVal))) / 2.70336) * 1.8) + 32)
    
    uint16_t volatile adcVal;
    int16_t volatile celsiusValue;
    int16_t volatile fahrenheitValue; 
    Add the following code to the main() function. It will discharge the sampling capacitor and start a conversion on the temperature channel and return the value in a variable.
    ADCC_DischargeSampleCapacitor();
    adcVal = ADCC_GetSingleConversion(channel_Temp);
            
    celsiusValue = ADC_TO_CELSIUS(adcVal); 
    fahrenheitValue = ADC_TO_FAHRENHEIT(adcVal);

1.5.3.2 Bare Metal Code

The necessary code and functions to implement the presented example are analyzed in this section. It has five functions: three of them configure the ADCC, FVR and the main oscillator and one discharges the sample capacitor. The last one initiates the conversion on the temperature channel.

The first step will be to configure the microcontroller to disable the Watchdog Timer (WDT) and to enable low-voltage programming.

/*disable Watchdog*/
#pragma config WDTE = OFF
/* Low voltage programming enabled, RE3 pin is MCLR */
#pragma config LVP = ON 
The CLK_Init function selects HFINTOSC as the main osciilator and sets the frequency to 1 MHz.
static void CLK_Init(void)
{
    /* set HFINTOSC Oscillator */
    OSCCON1bits.NOSC = 6;
    /* set HFFRQ to 1 MHz */
    OSCFRQbits.HFFRQ = 0;
}

The FVR_Init function enables the Fixed Voltage Reference (FVR) and the temperature sensor. The sensor is configured to work on the low range as the high range requires an operating voltage above 3.6V.

static void FVR_Init(void)
{
    /*Enable temperature sensor*/
    FVRCONbits.TSEN = 1;
    /*Enable FVR*/
    FVRCONbits.FVREN = 1;
}
The ADCC_Init function configures the peripheral to use its FRC clock and give the results right justified.
static void ADCC_Init(void)
{
    /* Enable the ADCC module */
    ADCON0bits.ADON = 1; 
    /* Select FRC clock */
    ADCON0bits.ADCS = 1;
    /* result right justified */
    ADCON0bits.ADFM = 1;
}
The ADCC_DischargeSampleCap function connects the ADCC channel to VSS to discharge the sampling capacitor and provide accurate results.
static void ADCC_DischargeSampleCap(void)
{
    /*channel number that connects to VSS*/
    ADPCH = 0x3C;
}
The ADCC_ReadValue function initiates the conversion on the temperature channel, waits for it to end and returns the value. The channel number to connect to the temperature sensor is 0x3C.
static uint16_t ADCC_ReadValue(uint8_t channel)
{   
    ADPCH = channel;
    /*start conversion*/
    ADCON0bits.ADGO = 1;
    while (ADCON0bits.ADGO)
    {
        ;
    }
        
    return ((uint16_t)((ADRESH << 8) + ADRESL));
}

The value from the ADC is then converted to Celsius and Fahrenheit values that give the temperature of the device. The variables can be checked with the debugger.

#define VDD                                 3.3
#define ADC_TO_CELSIUS(adcVal)              (int16_t) \
                ((1241.4967 - VDD * (1024 - (adcVal))) / 2.70336)
#define ADC_TO_FAHRENHEIT(adcVal)           (int16_t) \
                ((((1241.4967 - VDD * (1024 - (adcVal))) / 2.70336) * 1.8) + 32)

1.5.4 ADCC Low-Pass Filtering of a Conversion

This example shows how to set the ADCC in the Low-pass Filter mode, read several values and give an average result with the high-frequency noise removed by the filter.

The mode of the ADCC is set through the ADCON2 register. This example sets the mode to low-pass filter (the ADMD bit field is equal to 0x4) and sets the time constant of the filter equal to 16 (from the ADCRS bit field; the time constant is equal to 2 to the power of this bit field).

The ADCC will do a number of conversions equal to the value set in ADRPT, 100 in this example.

The ADCC is configured with right-justified results, running on its dedicated FRC clock.

RA0 is configured as analog input and the main oscillator is HFINTOSC with 1 MHz frequency.

The code will discharge the sample capacitor, initiate the conversion on the analog channel and return the result in a variable that can be checked with the debugger.

To achieve the functionality described by the use case, the following actions will have to be performed:

  • System clock initialization
  • Port initialization
  • ADCC initialization
  • Discharge sample capacitor
  • Starting the conversion

1.5.4.1 MCC Generated Code

To generate this project using MPLAB Code Configurator (MCC), follow the next steps:

  1. Create a new MPLAB X IDE project for PIC18F47Q10.
  2. Open MCC from the toolbar (more information about how to install the MCC plug-in can

    be found here.

  3. Go to Project Resources → System → System Module and do the following configuration:
    • Oscillator Select: HFINTOSC
    • HF Internal Clock: 1 MHz
    • Clock Divider: 1
    • In the Watchdog Timer Enable field in WWDT tab, make sure “WDT Disabled” is selected
    • In the Programming tab, make sure Low-Voltage Programming Enable is checked.
  4. From the Device Resources window, add ADCC. Then do the following configuration:
    • Enable ADC: Check
    • Operating: Low_pass_filter_mode
    • Clock Source: FRC
    • Result Alignment: Right
    • Positive Reference: VDD
    • Negative Reference: VSS
    • In Computation Features tab:
      • Repeat: 100
      • Arc Right Shift: 4
  5. Go to Pin Manager → Grid View and select the RA0 pin as ANx input for the ADCC:
    Figure 1-34 1-35. Pin Mapping
  6. Go to the Pin Module in the Project Resources tab and set the RA0 pin as analog and as input (check to see if Output is not checked).
  7. Press the Generate button. The generated code can now be found in the project folder.
  8. Add to the main.c file before the main() function the following line of code.
    uint16_t volatile adcVal;
    In the main() function add the code that discharges the sample capacitor, starts the conversion on the analog channel and returns the value in the variable that can be read with the debugger.
    ADCC_DischargeSampleCapacitor();
    adcVal = ADCC_GetSingleConversion(channel_ANA0);
    
    while (1)
    {
       ;
    }

1.5.4.2 Bare Metal Code

The necessary code and functions to implement the presented example are analyzed in this section.

The first step will be to configure the microcontroller to disable the Watchdog Timer (WDT) and to enable low-voltage programming.

/*disable Watchdog*/
#pragma config WDTE = OFF
/* Low voltage programming enabled, RE3 pin is MCLR */
#pragma config LVP = ON
The CLK_Init function selects HFINTOSC as the main oscillator and sets its frequency to 1 MHz.
static void CLK_Init(void)
{
    /* set HFINTOSC Oscillator */
    OSCCON1bits.NOSC = 6;
    /* set HFFRQ to 1 MHz */
    OSCFRQbits.HFFRQ = 0;
}
The PORT_Init function sets the RA0 pin as analog input.
static void PORT_Init(void)
{
    /*set pin RA0 as analog*/
    ANSELAbits.ANSELA0 = 1;
    /*set pin RA0 as input*/
    TRISAbits.TRISA0 = 1;  
}

The ADCC_Init function configures the mode and time constant in ADCON2 and the number of repetitions in ADRPT as well as selecting the FRC dedicated clock and setting the results right-justified.

static void ADCC_Init(void)
{
    /* Enable the ADCC module */
    ADCON0bits.ADON = 1; 
    /* Select FRC clock */
    ADCON0bits.ADCS = 1;
    /* result right justified */
    ADCON0bits.ADFM = 1;  
     /* Low pass filter mode */
    ADCON2bits.ADMD = 4;  
    /* lpf time constant = 16 */
    ADCON2bits.ADCRS = 4; 

    ADRPT = 100;
}
The ADCC_DischargeSampleCap function connects the ADCC channel to VSS in order to discharge the sampling capacitor.
static void ADCC_DischargeSampleCap(void)
{
    /*channel number that connects to VSS*/
    ADPCH = 0x3C;
}
The ADCC_ReadValue function initiates a conversion on the analog channel and returns the result. The channel number that connects to pin RA0 is 0x00. The variable can then be checked with the debugger.
static uint16_t ADCC_ReadValue(uint8_t channel)
{   
    ADPCH = channel;
    /*start conversion*/
    ADCON0bits.ADGO = 1;
    while (ADCON0bits.ADGO)
    {
        ;
    }
        
    return ((uint16_t)((ADRESH << 8) + ADRESL));
}

1.5.5 ADCC Spike Detection

This example presents a solution that will generate an interrupt when a spike is detected in the value that is being read. A sharp increase or decrease is necessary to generate the interrupt, a slow increase or decrease will not trigger it. The code was tested with a potentiometer that can be turned quickly between its maximum and minimum values.

The main clock is configured to run from HFINTOSC with 1 MHz with a prescaler of 4 for a clock frequency of 250 kHz. The RA0 pin is configured as analog input.

The ADCC is enabled, Continuous mode is enabled, and the result is right-justified. The clock is given by the main clock of the microcontroller, divided by two times the value found in the ADC Clock Selection (ADCLK) register plus one, 128 in this case. This gives a sampling frequency of 169 Hz for the ADCC which is small enough that the interrupt can be triggered by turning the potentiometer quickly.

The ADCC is configured to run in the Average mode with 16 samples per conversion and a right shift of 16, selected by setting the ADC Accumulated Calculation Right Shift Select (ADCRS) bits in the ADCON2 register to 4. In order to ensure a correct average, it is recommended to have the same number of samples as the bits shifted to the right. The A/D Accumulator Clear Command (ADACLR) bit in the ADCON2 register is set. This Command bit clears the Overflow Status bit, the accumulator and the count register after each conversion and is necessary for the correct triggering of the threshold interrupt. When the accumulator overflows and the Overflow Status bit is set, a threshold interrupt is generated. This condition can be checked in the interrupt handler or the ADACLR bit can be set to prevent triggers from overflows.

The error calculation is configured as the first derivative of a single measurement (e.g. the error is the difference between this conversion and the last conversion) and the threshold interrupt is configured to trigger if the error is higher than the upper threshold of 35 or smaller than the lower threshold of -35.

Finally, the ADCC threshold interrupt, peripheral and global interrupts are enabled.

As in the previous examples, the sample capacitor is discharged by connecting it to Ground and then the conversion is started.

The interrupt handler returns the value of the error which is found in the ADERRH and ADERRL registers and this can be checked with the debugger.

To achieve the functionality described by the use case, the following actions will have to be performed:

  • System clock initialization
  • Port initialization
  • ADCC initialization
  • Interrupt initialization
  • Discharge sample capacitor
  • Starting the conversion
  • ADCC threshold interrupt handling

1.5.5.1 MCC Generated Code

To generate this project using MPLAB Code Configurator (MCC), follow the next steps:

  1. Create a new MPLAB X IDE project for PIC18F47Q10.
  2. Open MCC from the toolbar (more information about how to install the MCC plug-in can be found here.
  3. Go to Project Resources → System → System Module and do the following configuration:
    • Oscillator Select: HFINTOSC
    • HF Internal Clock: 1 MHz
    • Clock Divider: 4
    • In the Watchdog Timer Enable field in WWDT tab, make sure “WDT Disabled” is selected
    • In the Programming tab, make sure Low-Voltage Programming Enable is checked.
  4. From the Device Resources window, add ADCC, then do the following configuration:
    • Enable ADC: Check
    • Operating: Average_mode
    • Clock Source: FOSC/ADCLK
    • Clock: FOSC/128
    • Result Alignment: Right
    • Positive Reference: VDD
    • Negative Reference: VSS
    • Enable Continuous Operation: Check
    • In Computation Features tab:
      • Error Calculation: First Derivative of Single measurement
      • Threshold Interrupt: ADERR < ADLTH or ADERR > ADUTH
      • Lower Threshold: -35
      • Upper Threshold: 35
      • Repeat: 16
      • Arc Right Shift: 4
    • In CVD Features:
      • Enable ADC Threshold Interrupt: Check
  5. Go to Pin Manager → Grid View and select the RA0 pin as ANx input for the ADCC:
    Figure 1-34 1-35. Pin Mapping
  6. Go to the Pin Module in the Project Resources tab and set the RA0 pin as analog and as input (check to see if Output is not checked).
  7. Press the Generate button. The generated code can now be found in the project folder.
  8. Add the following code to the main.c file. The code sets the Interrupt Service Routine (ISR) for the ADCC threshold interrupt to the ThresholdISR() function that returns the value of the error when the interrupt is triggered and clears the Interrupt flag. The rest of the code enables the global and peripheral interrupts as well as discharging the sampling capacitor and starting the conversion on the analog channel.
    uint16_t volatile errVal;
    
    void main(void)
    {
    
        SYSTEM_Initialize();
    
        INTERRUPT_GlobalInterruptEnable();
    
        INTERRUPT_PeripheralInterruptEnable();
        ADCC_SetADTIInterruptHandler(ThresholdISR);
        ADCC_DischargeSampleCapacitor();
        ADCC_StartConversion(channel_ANA0);
        
    
        while (1)
        {
           ;
        }
    }
    
    void ThresholdISR(void)
    {
        errVal = ADCC_GetErrorCalculation();
        PIR1bits.ADTIF = 0;
        
    }

1.5.5.2 Bare Metal Code

The necessary code and functions to implement the presented example are analyzed in this section. The code contains seven functions and the main function that implements the previously described functionality.

The first step will be to configure the microcontroller to disable the Watchdog Timer (WDT) and to enable low-voltage programming.
/*disable Watchdog*/
#pragma config WDTE = OFF
/* Low voltage programming enabled, RE3 pin is MCLR */
#pragma config LVP = ON

The CLK_Init function selects HFINTOSC as the main oscillator, sets its frequency to 1 MHz and sets the prescaler to 4 for a operating frequency of 250 kHz.

static void CLK_Init(void)
{
    /* set HFINTOSC Oscillator */
    OSCCON1bits.NOSC = 6;
    /* clk divided by 4 */            
    OSCCON1bits.NDIV = 4;
    /* set HFFRQ to 1 MHz */
    OSCFRQbits.HFFRQ = 0;
}

The PORT_Init function configures the RA0 pin as analog input.

static void PORT_Init(void)
{
    /*set pin RA0 as analog*/
    ANSELAbits.ANSELA0 = 1;
    /*set pin RA0 as input*/
    TRISAbits.TRISA0 = 1;  
}

The function ADCC_Init configures the ADCC: it enables it, activates Continuous Conversion mode, sets the result right-justified, selects the main clock divided by 128 as the ADCC clock.

static void ADCC_Init(void) 
{
    /* Enable the ADCC module */
    ADCON0bits.ADON = 1; 
    /* Enable continuous operation*/
    ADCON0bits.ADCONT = 1;
    /* result right justified */
    ADCON0bits.ADFM = 1;
    /*FOSC divided by 128*/
    ADCLKbits.ADCS = 63;

It sets the number of repetitions to 16, sets the ADACLR bit, selects the Average mode and sets the number of bits that will be right-shifted at the end of a conversion to 16.

    ADRPT = 16;
    
    /*clear status bits on overflow enabled (this setting prevents overflow 
    interrupts that  trigger the same interrupt as the threshold interrupt)*/
    ADCON2bits.ADACLR = 1;  
    /* Average mode */
    ADCON2bits.ADMD = 2;
    /*result is right shifted by 16*/
    ADCON2bits.ADCRS = 4; 

It then selects the Error Calculation mode as the first derivative of a single measurement, the Threshold Comparison mode as the error being higher than the upper threshold or smaller than the lower threshold and sets the upper threshold to 35 and the lower threshold to -35.

    /* mode: error bigger than upper threshold    
    or lower than lower threshold*/
    ADCON3bits.ADTMD = 4;  
    /*error calculation method:      
    difference between the last result and the current result*/    
    ADCON3bits.ADCALC = 0;
    /*upper threshold*/
    ADUTH = 35;
    /*lower threshold*/    
    ADLTH = -35;

Finally, it clears the Threshold Interrupt bit and enables the Threshold interrupt.

    /* Clear the ADC Threshold interrupt flag */   
    PIR1bits.ADTIF = 0;  
    /* Enable ADCC threshold interrupt*/
    PIE1bits.ADTIE = 1;    
}

The ADCC_DischargeSampleCap function connects the ADCC channel to VSS in order to discharge the sampling capacitor.

static void ADCC_DischargeSampleCap(void)
{
    /*channel number that connects to VSS*/
    ADPCH = 0x3C;
}

The INTERRUPT_Init function enables the global and peripheral interrupts.

static void INTERRUPT_Init(void)
{
    /* Enable global interrupts */
    INTCONbits.GIE = 1;
    /* Enable peripheral interrupts */    
    INTCONbits.PEIE = 1;   
}

The ADCC_StartConversion function selects the RA0 analog channel for the ADCC and starts the conversion by setting the ADGO bit in the ADCON0 register. The channel number to connect to pin RA0 is 0x00.

static void ADCC_StartConversion(uint8_t channel) 
{
    ADPCH = channel;
    /* Start the conversion */
    ADCON0bits.ADGO = 1;  
}

The INTERRUPT_InterruptManager function is called at every interrupt. It checks to see if the peripheral interrupts are enabled and then checks to see if the ADCC threshold interrupt is enabled and triggered. It then calls ADCC_ThresholdISR to store the value of the error into the errVal variable.

void __interrupt() INTERRUPT_InterruptManager(void) 
{
    if (INTCONbits.PEIE) 
    {
        if ((PIE1bits.ADTIE) && (PIR1bits.ADTIF))
            
        {
            ADCC_ThresholdISR();
        }
    }
}
static void ADCC_ThresholdISR(void)
{
    /*read the error*/
    errVal =  ((ADERRH << 8) + ADERRL);
    /*clear interrupt flag*/
    PIR1bits.ADTIF = 0;
}

The main function calls the previously described functions in order and then waits for the interrupt. The variable can be checked with a debugger after an interrupt has been triggered,

void main(void)
{
    CLK_Init();
    PORT_Init();   
    ADCC_Init();
    ADCC_DischargeSampleCap();
    INTERRUPT_Init();
    
    /*channel number that connects to RA0*/    
    ADCC_StartConversion(0x00);
    while (1) 
    {
        ;
    }
}