Electronic – ADC on a SAMD21 always gives zero values, except when using breakpoints in its interrupt

adccortex-m0

I started experimenting with the ADC on a SAMD21, and strangely, the result I get is always 0.

I initialized it like this (using the temperature sensor to get some values without external hardware):

SYSCTRL->VREF.bit.TSEN = 1;  // enable temperature sensor

GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID_ADC;  // base clock GCLK2 = 1MHz
PM->APBCMASK.bit.ADC_ = 1; // Enable bus clock

ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INT1V_Val;   // internal 1V reference
ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_4_Val; // average from 4 samples
ADC->SAMPCTRL.bit.SAMPLEN = 49; // for approx. 100 us/sample with a 4x prescaler on the 1MHz clock
ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV4_Val;
ADC->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_16BIT_Val;     // to support averaging
ADC->CTRLB.bit.FREERUN = 1;

ADC->INPUTCTRL.bit.MUXPOS = 0x18;  // temperature sensor

ADC->INTENSET.bit.RESRDY = 1;           // result ready interrupt

ADC->CTRLA.bit.ENABLE = 1;

NVIC_EnableIRQ( ADC_IRQn );

I checked that the clocks are working as they should, and made sure the ADC interrupts get regularly triggered, by toggling a portpin in the ADC interrupt.

After that, I tried reading some values.

volatile int debug_adc[201];
int debug_adc_c = 0;

void ADC_Handler()
{
    volatile int result = ADC->RESULT.bit.RESULT;

    debug_adc[debug_adc_c++] = result;
    if (debug_adc_c >= 200) 
    {
        debug_adc_c = 0;
    }
}

I let it run for a while, and when I stop it, the array is full of zeroes, and the RESULT register in the I/O view is also zero.

However, if I place a breakpoint inside the interrupt handler, I see non-zero values, both in the array in the Watch screen and in the I/O view.

I don't touch the ADC anywhere else in my code. I also tried it without averaging (ADC->AVGCTRL.bit.SAMPLENUM = 0), nothing changed.

The datasheet recommends performing one manual conversion at the beginning, even if I use the freerunnig mode: ADC->SWTRIG.bit.START = 1;. I did it, but it didn't change anything.

Did I miss something obvious?

Best Answer

The ADC has both negative and positive inputs, but it seems it is not just merely an optional extra feature. I have to specify a negative input, even if I only use the positive one. (the datasheet is a little unclear here, it only mentions both inputs being used in the calculation if I choose differential input in CTRLB.DIFFMODE, which wasn't the case here)

After setting the negative input to the internal ground,

ADC->INPUTCTRL.bit.MUXNEG = 0x18;

everything works as expected.

I still don't understand why it was working when a breakpoint was set, but at least the code works as it should.