Using analogRead() along with ADC free-run mode?

I'm new to the whole free-run mode concept and have hit a problem.

I'm trying to build a sound visualization module and started with this Adafruit code base for a microphone unit from there (piccolo/Piccolo.pde at master · adafruit/piccolo · GitHub). For the most part it works as is and I have it running LEDs, however I wanted to add a couple of potentiometer knobs to it to adjust its behavior.

It seems like the ADC free-run stuff this code uses isn't immediately compatible with the standard method of reading an analog pin. If I include a 'pinMode(A4, INPUT)' and use analogRead(A4) (piccolo/Piccolo.pde at master · adafruit/piccolo · GitHub) then the sound sensing seems to stop working.

Does anyone know how to get around this? It seems odd that using this method of sensing the input on A0 would prevent any of the other analog pins from being useful, so I feel there must be some method to do this. Either by flipping some bits or reading an additional pin in the free-run stuff, however as I said I'm not familiar with the free-run mode stuff (or really the ADC in general beyond using analogRead()) and I'm not finding any good tutorials or overviews for it online.

(This is running on a Arduino Nano currently, may move to a custom board with an ATMega328P later once I have things working)

If I include a 'pinMode(A4, INPUT)' and use analogRead(A4)

Analog pins are INPUT only. Using pinMode() sets the nature of the digital pin that shares the same physical location on the board, which is pointless when it is the analog pin at that location that you are using.

It seems odd that using this method of sensing the input on A0 would prevent any of the other analog pins from being useful

What method is that? What do you need to do to get the pin in "free running mode"? Maybe you need to do that again after using analogRead().

I believe you cannot use analogRead() in free-run mode, you have to actually check one of the ATmega328p's registers to check if the conversion is complete. Im too lazy too look for the correct register, but let me show you what I would use in the Arduino Due just to get an idea

while(!(ADC->ADC_ISR & 0x80)); // wait for conversion

It seems odd that using this method of sensing the input on A0 would prevent any of the other analog pins from being useful...

An AVR processor has a single shared analog-to-digital converter (ADC). When you call analogRead(A4) the ADC is reconfigured to take one reading from A4. Afterwards you will have to reconfigure the ADC to resume free-running reads on A0.

1 Like

I looked at the ATmega328P's datasheet and here is what I found starting at page 249

24.9 Register Description
24.9.1 ADMUX – ADC Multiplexer Selection Register
Bits 3:0 – MUX[3:0]: Analog Channel Selection Bits
The value of these bits selects which analog inputs are connected to the ADC.
0000 ADC0
0001 ADC1
0010 ADC2
0011 ADC3
0100 ADC4
0101 ADC5
0110 ADC6
0111 ADC7
1000 ADC8(1) <--Temperature Sensor

24.9.2 ADCSRA – ADC Control and Status Register A
Bit 7 – ADEN: ADC Enable
Writing this bit to one enables the ADC. By writing it to zero, the ADC is turned off.
Bit 6 – ADSC: ADC Start Conversion
In Free Running mode, write this bit to one to start the first conversion.
Bit 5 – ADATE: ADC Auto Trigger Enable //IMPORTANT, SET ME FOR ADCSRB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
When this bit is written to one, Auto Triggering of the ADC is enabled.
Bit 4 – ADIF: ADC Interrupt Flag
This bit is set when an ADC conversion completes and the Data Registers are updated.

24.9.3 ADCL and ADCH – The ADC Data Register
When an ADC conversion is complete, the result is found in these two registers.
Btw, if you change the ADLAR bit in the ADMUX register, it is left adjusted, otherwise its fine.
ADCL is the low byte of the conversion, ie: bits 0-7
ADCH is the high byte of the conversion, ie: bits 8-9
ADCL must be read first, then ADCH.

24.9.4 ADCSRB – ADC Control and Status Register B
Bit 2:0 – ADTS[2:0]: ADC Auto Trigger Source
If ADATE in ADCSRA is written to one, the value of these bits selects which source will trigger an ADC
conversion. If ADATE is cleared, the ADTS[2:0] settings will have no effect.
0 0 0 Free Running mode

I believe you can just treat these registers as if they were variables, you should be able to read and write to then. Just be careful what you are doing!

As far as I know you can only use free running mode on a single analog input pin and if you want to change to a different analog pin you will have to stop free running, switch to the other pin, read the value, switch back to the first pin and restart fre running mode.

Needless to say this will almost certainly kill the advantage of using free running mode.

...R

Read the datasheet on page 249 for the ADMUX register, I believe it specifies what happens when you change it during a conversion. I dont think you need to stop and restart, although even if you did, it should still be faster than non-free-running mode.

Im at school so I dont wanna check

I have an example of using the ADC asynchronously (if that is what you mean by free-running):

Read the Analog-to-Digital converter asynchronously.

That lets you start a reading, do something else, and get an interrupt when the reading is ready.

As stated above there is only one ADC converter, so you can't expect to take other analog readings in the meantime.

I just called it free-running mode as that what it was referred to in the picollo code sample I linked above, but it appears fairly similar to your asynchronous read except that it is getting a series of rapid samples to feed into an FFT.

Ps991's pointer above I think has directed me to what I need to do, which is to switch the ADMUX bits after it completes its sample (ie here in the adafruit code: piccolo/Piccolo/Piccolo.pde at master · adafruit/piccolo · GitHub) to read the pin I'm using for the potentiometer, and then switch back once it has a reading for that. I'll play around with this stuff tonight and see how it works out.

I'm going to suggest (having read through the discussion here) that you'll have to use the digital pins to get your input.

I'm in a similar quandary myself — I think I will try using the digital inputs for "up" and "down" rather than the more convenient analog knob.

I suppose if a knob is absolutely required you could put the potentiometer off on another chip with it's own ADC and get the value serially over one of the digital pins. A little more complicated, but no doubt there are a number of chips that would be up to the task.

Using an additional chip was something I'm considering as well, but would prefer not to do as I've already built the module and would need to crack it back open and do some more work to do that.

Does anyone know if I2C works when doing this free-run stuff? I do see I2C analog chips. Or does anyone has a good suggestion for a chip for reading analog values?

The atmega328p is very capable of this task...

If you need to detect a change in a potentiometer, you could create an Interrupt Service Routine to periodically sample the ADC. Although, since the ADC is pretty slow (upwards of 100uS per call) you can change its clock prescaler to something like 16 or 32(32 is best) without too much loss in accuracy but huge gains in speed (32 gives 30uS read).

After reading up on sources pointed to above, I've got a modified version of the adafruit code running which will switch between pins for the ADC samples. Basically when each sample is complete you need to turn off the sampler interrupt and set the pin: "ADCSRA &= ~_BV(ADIE); ADMUX = pin;"

Full example here:

volatile byte current_pin = SOUND_PIN;
volatile int ready_pin = -1;

/*
 * Setup ADC free-run mode
 */
void setupFreeRun() {
  // Init ADC free-run mode; f = ( 16MHz/prescaler ) / 13 cycles/conversion
  ADMUX  = current_pin; // Channel sel, right-adj, use AREF pin
  ADCSRA = _BV(ADEN)  | // ADC enable
           _BV(ADSC)  | // ADC start
           _BV(ADATE) | // Auto trigger
           _BV(ADIE)  | // Interrupt enable
           _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
  ADCSRB = 0;                // Free run mode, no high MUX bit
  DIDR0  = 1 << SOUND_PIN // Turn off digital input for ADC pin
         | 1 << LIGHT_PIN 
         | 1 << KNOB_PIN;
  TIMSK0 = 0;                // Timer0 off

  sei(); // Enable interrupts
}

/*
 * Sampling interupt
 *   - Cycles through the sensor pins, taking FFT_N audio samples and a single
 *     sample of all others.
 */
ISR(ADC_vect) {
  int16_t sample = ADC; // 0-1023
  boolean done = false;

  if (current_pin == SOUND_PIN) {
    static const int16_t noiseThreshold = 4;

    capture[samplePos] =
      ((sample > (512 - noiseThreshold)) &&
       (sample < (512 + noiseThreshold))) ? 0 :
      sample - 512; // Sign-convert for FFT; -512 to +511

    if (++samplePos >= FFT_N) done = true;
  } else if (current_pin == LIGHT_PIN) {
    light_level = sample; // Does there need to be input pullup or pull down resistor?
    done = true;
  } else if (current_pin == KNOB_PIN) {
    knob_level = sample;
    done = true;
  }

  if (done) {
    // Record which pin is done sampling
    ready_pin = current_pin;

    // Turn off interrupts to report back and switch pins
    ADCSRA &= ~_BV(ADIE);
    
    // Switch pins
    switch (current_pin) {
      case SOUND_PIN: current_pin = KNOB_PIN; break;
      case KNOB_PIN: current_pin = LIGHT_PIN; break;
      case LIGHT_PIN: current_pin = SOUND_PIN; break;
    }
    ADMUX  = current_pin;
  }
}

I think that looks great, although because you are using FFT, which requires fast sampling rates for most applications, I would highly recommend changing the pre-scaler to something lower than 128, which takes upward of 100uS per call.

Your call

I'm using the FFT for sound visualization with LEDs, and thus far it seems reasonably reactive at the 128 prescalar. I'll play around with lowering it, thats easy enough to try out.

Do you mind me asking to see your code for the fft, or the project in general, I have worked with fft just a little and am curious if there is more than 1 way to do it.

You can post here or just pm me, please :smiley:

Sure. I'm actually just using the FFT library from the Adafruit piccolo code (piccolo/ffft at master · adafruit/piccolo · GitHub)

I personally haven't done much with it yet other than refactor the sound sampling and transform stuff from the Picollo project into my own code base (see ObjectLights/Sound.cpp at master · aphelps/ObjectLights · GitHub). Now that I have this working I'll need to start tweaking it a good bit more, I haven't done much work with FFTs since grad school (ie something like 15 years ago now)

I used this library, it seems to run in about half the time as the one you are using,
http://wiki.openmusiclabs.com/wiki/ArduinoFFT

And its customizable with windows, not the OS

However it is limited to 256 sample size

Hi there;

I came across this thread in searching for something similar I'm trying to do: namely, I'm using an Arduino Mega to do something like the Piccolo example, but I also have an onboard thermometer from which I'd like to sample.

The partial code from random_vamp is almost-perfectly-helpful, but I'm making an assumption about how he's defining KNOB_PIN, LIGHT_PIN, etc (I assume they're just set to something like either, say, A5 or 5), but this assumption seems incorrect.

Basically what I'm wondering is: If I want to sample a signal from a different analog pin while running the mega in free-run mode, how might I do that? Random_vamp, if you're still reading this, could I persuade you to post your code in full so I can learn from it?

I see several responses here saying "read the datasheet", but the datasheet for doing this freerun mode is a bit over my head; I need a layperson's translation of the datasheet to build my understanding of how sampling these pins in freerun mode actually works.

Yeah, I'm still reading it. The full code I have is in the github repo I linked to:

The definitions for the pins is over here, but they are just set to 0, 1, and 4:

Note that I've only tried this out fully on an ATMega328 (basically a Nano v3), so there may be bits that are different on the mega that I don't know about, probably with the various registers that are being directly accessed. This isn't an area I've bothered to research much beyond just getting my particular devices working so I probably can't be much direct help, but if its not working on the Arduino Mega (ie ATMega2560) I'd suggest checking the data sheets for the register definitions to see if they match what is being used in here. I know that chip has twice the analog pins as the ATMega328, so its possible the pin numbering and the register naming doesn't match up. Most likely you could start looking up the registers that are being mucked with here: