6 Monitoring the Voltage Level with the Internal ADC

The Internal Analog to Digital Converter (ADC) makes it possible to monitor and measure the output voltage level of the voltage follower. To do this, it is necessary to connect the output pin of the voltage follower to the ADC, as shown in Figure 6-1.

The ADC is initialized to perform this functionality with the following steps:
  1. Set the voltage reference of the ADC to 4.096V.
  2. Set the input of the ADC to the output pin of the OPAMP.
  3. Set some oversampling to get a more accurate and averaged result.
  4. Enable the ADC.
The code below sets up the ADC for use in this manner.
void adc_init()
{
	VREF.ADC0REF = VREF_REFSEL_4V096_gc;
	ADC0.MUXPOS = ADC_MUXPOS_AIN2_gc;
	ADC0.CTRLB= ADC_SAMPNUM_ACC16_gc;
	ADC0.CTRLA= ADC_ENABLE_bm;
}

To sample, we need to start an ADC conversion and read it when it is done. The following function gives this functionality.

uint16_t adc_sample()
{
	uint16_t res;
	ADC0.COMMAND = ADC_STCONV_bm;
	while(!(ADC0.INTFLAGS & ADC_RESRDY_bm));
	/*Right shift result by 4 due to 16 over samples*/
	res=ADC0.RES>>4;
	return res;
}
Figure 6-1. Monitoring the Voltage Level with the Internal ADC

The code below is based on example Using the Internal Resistor Ladder, with added ADC functionality.

#define F_CPU 4000000ul
#define CLK_PER 4000000ul
#define OPAMP_MAX_SETTLE_TIME 0x7F
#include <avr/io.h>
#include <math.h>


uint16_t adc_sample();
void adc_init();
void op_amp_init();
void op_amp_setup_int_resistors(uint8_t MUXWIP_gc);

int main(void)
{
	adc_init();
	op_amp_init();
	op_amp_setup_int_resistors(OPAMP_OP0RESMUX_MUXWIP_WIP4_gc);
    while (1) 
    {
		/*In this example we have output voltage of ~1.88V, check that we are within 0.1V of that, LSB of result is 1mV*/
		uint16_t OP_out = adc_sample();
		if ((1780<OP_out) & (OP_out<1980))
		{
			/*Your code goes here*/
		}
    }
}


uint16_t adc_sample()
{
	uint16_t res;
	ADC0.COMMAND = ADC_STCONV_bm;
	while(!(ADC0.INTFLAGS & ADC_RESRDY_bm));
	/*Right shift result by 4 due to 16 over samples*/
	res=ADC0.RES>>4;
	return res;
}

void adc_init()
{
	VREF.ADC0REF = VREF_REFSEL_4V096_gc;
	ADC0.MUXPOS = ADC_MUXPOS_AIN2_gc;
	ADC0.CTRLB= ADC_SAMPNUM_ACC16_gc;
	ADC0.CTRLA= ADC_ENABLE_bm;
}

void op_amp_init()
{
	/*Disable input on op amp output pin*/
	PORTD.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
	
	/*Set up op amp*/
	OPAMP.CTRLA = OPAMP_ENABLE_bm;
	OPAMP.TIMEBASE = (uint8_t) ceil(CLK_PER*0.000001)-1; /*Number of peripheral clock cycles that amounts to 1us*/
	OPAMP.OP0CTRLA = OPAMP_RUNSTBY_bm | OPAMP_ALWAYSON_bm | OPAMP_OP0CTRLA_OUTMODE_NORMAL_gc;
    OPAMP.OP0SETTLE = OPAMP_MAX_SETTLE_TIME; //As the settle time is unknown, the maximums should be set
}

void op_amp_setup_int_resistors(OPAMP_OP0RESMUX_MUXWIP_t MUXWIP_gc)
{
	OPAMP.OP0INMUX = OPAMP_OP0INMUX_MUXNEG_OUT_gc | OPAMP_OP0INMUX_MUXPOS_WIP_gc;
	/* MUXWIP is the bit field that decides the output voltage of the op amp
	   With VDD at 5V you will get: 
	   OPAMP_OP0RESMUX_MUXWIP_WIP4_gc -> ~1.88V
	   OPAMP_OP0RESMUX_MUXWIP_WIP3_gc -> ~2.5V
	   OPAMP_OP0RESMUX_MUXWIP_WIP2_gc -> ~3.75V */
	OPAMP.OP0RESMUX = OPAMP_OP0RESMUX_MUXTOP_VDD_gc|OPAMP_OP0RESMUX_MUXBOT_GND_gc|MUXWIP_gc;
}

The code for this example is available in the monitoring-OPAMP-output-with-ADC folder in these github repositories: