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) 
    {
        ;
    }
}