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.
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.
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.
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?
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.
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.
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 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: