2

I am trying to build an oscilloscope using an Arduino Due and an LCD. I have the LCD running perfectly and started working on using the ADC peripheral to sample signals. Here's what I have so far:

#define SAMPLE_BUFFER_SIZE 800
volatile int sample_buffer[SAMPLE_BUFFER_SIZE];
volatile bool sample_buffer_full = 0;
volatile int sample_buffer_idx = 0;

void adcm_init() {
    pmc_enable_periph_clk(ID_ADC); // To use peripheral, we must enable clock distributon to it
    adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST); // initialize, set maximum posibble speed
    adc_disable_interrupt(ADC, 0xFFFFFFFF);
    adc_set_resolution(ADC, ADC_12_BITS);
    adc_configure_power_save(ADC, 0, 0); // Disable sleep
    adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1); // Set timings - standard values
    adc_set_bias_current(ADC, 1); // Bias current - maximum performance over current consumption
    adc_stop_sequencer(ADC); // not using it
    adc_disable_tag(ADC); // it has to do with sequencer, not using it
    adc_disable_ts(ADC); // deisable temperature sensor
    adc_disable_channel_differential_input(ADC, ADC_CHANNEL_7);
    adc_configure_trigger(ADC, ADC_TRIG_SW, 1); // triggering from software, freerunning mode
    adc_disable_all_channel(ADC);
    adc_enable_channel(ADC, ADC_CHANNEL_7); // just one channel enabled
    adc_enable_interrupt(ADC, ADC_IER_DRDY);            // Data Ready Interrupt Enable
    NVIC_EnableIRQ(ADC_IRQn);
    adc_start(ADC);
}

void ADC_Handler(void)  {
    if (!sample_buffer_full) {

        if ((adc_get_status(ADC) & ADC_ISR_DRDY) == ADC_ISR_DRDY) {

            sample_buffer[sample_buffer_idx] = adc_get_latest_value(ADC);
            sample_buffer_idx++;

            if (sample_buffer_idx == SAMPLE_BUFFER_SIZE) {
                sample_buffer_full = 1;
                sample_buffer_idx = 0;
            }
        }
    }
}

void setup()
{
    adcm_init();
    Serial.begin(115200);
}

void loop() {
    Serial.print("Loop\n");
    if (sample_buffer_full) {
        for (int i = 0; i < SAMPLE_BUFFER_SIZE; i++) {
            Serial.print(sample_buffer[i]);
            Serial.print(" ");
        }
        Serial.print("\n");
        sample_buffer_full = 0;
    }
}

This was copied and slightly modified from somewhere on the internet (can't remember where, though). I have also studied the docs and this code seems to make perfect sense and it should work.

The idea is for the sampling to be interrupt-based. However, if I set the ADC to freerunning mode and enable interrupts, it just gives up and stops working.

What I mean by that is: on my serial monitor, after pressing reset, all I see are 2 lines saying "Loop" and it just stops there. Normally it should print "Loop" indefinitely, and also print ADC values from time to time.

If I disable interrupts, "Loop" is printed indefinitely, but of course no values are shown. If I disable free-running mode, "Loop" is printed and no values are shown unless the buffer size is 1, in which case the same value is printed over and over (as expected, in that case the interrupt runs only once, right at the beginning).

The CPU clock freq. is 84Mhz, I'm going for the fastest I can get out of the ADC, so a sample is taken (and an interrupt is fired) every 1Mhz. That means 84 clock cycles per interrupt. Don't know for sure if that is enough for the rest of the program to work, but my gut tells me that's not a problem. Besides, I tried with a lower ADC clock value and it still didn't work.

Any ideas? I'm really at a loss here...

  • You only want two samples? – Nick Gammon Jan 24 '16 at 20:30
  • @NickGammon No, of course not. I'd like hundreds of them. Sorry, that was just me testing the code (edited question and changed sample number to 800). – Alex Jan 24 '16 at 20:31
  • I'm no Due expert, but where do you link ADC_Handler as being the interrupt handler for those interrupts? – Nick Gammon Jan 24 '16 at 20:37
  • I've spotted a few threads that mention that handler but they have it inside extern "C" declarations. – Nick Gammon Jan 24 '16 at 20:41
  • @NickGammon It's defined in the ATMEL source code as void ADC_Handler ( void ) __attribute__ ((weak, alias("Dummy_Handler"))); – Alex Jan 24 '16 at 20:42
3

In your interrupt handler, ADC_Handler(), you set sample_buffer_full when the buffer is full. I don't see anywhere in the code where sample_buffer_full ever gets reset (cleared) once it's been set.

While sample_buffer_full is set, it disables the body of the interrupt handler – ie, the handler's structure is

if (!sample_buffer_full) { ...body of handler... }

– and in particular, prevents adc_get_latest_value(ADC); from occurring. The source code of adc_get_latest_value(); is like

uint32_t adc_get_latest_value(const Adc *p_adc) {
  return p_adc->ADC_LCDR;
}

which accesses the ADC data register. According to section 43.6.4 Conversion Results on page 1322 of the data sheet, Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf,

The channel EOC bit in the Status Register (ADC_SR) is set and the DRDY is set. In the case of a connected PDC channel, DRDY rising triggers a data transfer request. In any case, either EOC and DRDY can trigger an interrupt.

Reading one of the ADC_CDR registers clears the corresponding EOC bit. Reading ADC_LCDR clears the DRDY bit and EOC bit corresponding to the last converted channel.

It appears to me that your code enables interrupts, fills a buffer, sets sample_buffer_full, then spins in ADC_Handler(). That is, repeatedly executes ADC_Handler() without clearing the EOC bit.

Note, for oscilloscope applications with a Due, I suggest using DMA buffers instead of one interrupt per conversion. See thread #137635, speed of analogRead, in forum.arduino.cc for DMA code to read one or two analog channels on a Due at one sample per microsecond. Also see edaboard.com/thread283843 and forum.arduino.cc #186169

Edit note: Although sample_buffer_full does get reset, that reset happens after a time-consuming for-loop. Whether it takes some hundreds of microseconds or a few milliseconds, the for-loop is enough of a delay that at least one more conversion and interrupt will occur before sample_buffer_full gets cleared. One unhandled conversion is enough to lock up the interrupt handler. Here are some alternative approaches to consider:

• Use a circular buffer, and never stop filling it. This is ok if you are only interested in the most recent data, rather than a complete waveform record.

• Set or reset sample_buffer_full as now; and remove the initial if test from the handler. Instead, put an if before sample_buffer_idx++;, to not increment if the buffer is full.

• Turn off ADC's freerun mode. This is a reasonable thing to do when not taking data.

• Turn off ADC's interrupt when not taking data. Also reasonable.

• Use multiple buffers. Reasonable, but by itself doesn't avoid the problem.

• Read the latest value every time the handler executes [for example, say int datum = adc_get_latest_value(ADC); as the first statement in the handler] and then only store that value in certain cases. An easy and effective way to avoid problem.

  • Sorry, I copied my code wrong. There should be a line sample_buffer_full = 0; inside my loop code (I've edited my question). The code with that line does not work. – Alex Jan 24 '16 at 19:16
  • Also, many thanks for the DMA buffers suggestion. I'd already seen some code examples use that, but I was trying to get it to work this way first before moving on to other more advanced options. – Alex Jan 24 '16 at 19:21
  • Ohh! I see! Well caught, thank you very much. That might just explain the problem, yes... – Alex Jan 24 '16 at 20:43
  • Yes, I tried your last suggestion and it works perfectly, which means your answer was spot on. You certainly deserve an upvote and an accepted answer. Thank you very much! – Alex Jan 24 '16 at 20:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.