How to enable ADC for pin A0 to A7 on a Mega2560

Hi everyone,

I'm actually making a motion control system : 8 stepper motors controlled with 4 joysticks.
So far, everything is fine, and my sketch is running fine... but really too slow. Digging a bit, I found that using AnalogRead is a slow solution, so i'm now trying to use the free running mode and ADC stuff.
I found a piece of code to enable the free running mode on ADC7 (pin AO) on a DUE which looks like this
:

ADC->ADC_MR |= 0x80; //set free running mode on ADC 7 (Pin A0)
ADC->ADC_CR=2; // starts adc conversion, not sure if this is neccessary
ADC->ADC_CHER = 0x80; //enable ADC on pin A0

I can't find how to enable free running mode on A0 to A7 on a Mega2560. If anyone can help or knows a tutorial that explain what is this weird code, it would be fantastic : i'd like to understand what are these ADC_MR and ADC_CHER things, and what the 0x80 means (hexa for 128 ? but what is the link with PinA0/ADC7 - nothing found on the mega2560 datasheet).
Thanks for your answers

On an ATMega328p I used an Interrupt service routine, it is NOT how Wiring does things so is outside normal Arduino. In this case, four channels are read into an array (adc[]), it happens one at a time as fast as the ADC is able. I don't think it is the same on a ATMega2560, but a datasheet should tell what needs to change.

#define ADC_CHANNELS 4
volatile uint16_t adc[ADC_CHANNELS];
volatile uint8_t adc_channel;

// Interrupt service routine for when the ADC completes
ISR(ADC_vect){  
  // ADCL contain lower 8 bits, ADCH upper (two bits)
  // Must read ADCL first
  adc[adc_channel] = ADCL | (ADCH << 8);
  
  ++adc_channel;
  if (adc_channel >= ADC_CHANNELS) {
    adc_channel = 0;
  }
  ADMUX &= ~(1<<MUX3) & ~(1<<MUX2) & ~(1<<MUX1) & ~(1<<MUX0);
  ADMUX = (((ADMUX & ~(1<<REFS1)) | (1<<REFS0)) & ~(1<<ADLAR)) + adc_channel;
 
  // set ADSC in ADCSRA, ADC Start Conversion
  // next adc interrupt will be in about 1664 mcu clocks
  ADCSRA |= (1<<ADSC);
}

To initialize the ADC hardware I used this function.

void initAnalog(void) {
  adc_channel = 0;
  for(uint8_t i=0; i<ADC_CHANNELS; i++) {
    adc[i] = 0;
  }

  // See ADMUX Register Description 24.9 in ATMega328 datasheet
  // MUX[3:0] default to zero which sellects ADC0
  // ADC_VREF_AREF ((ADMUX & ~(1<<REFS1)) & ~(1<<REFS0)) & ~(1<<ADLAR)
  // ADC_VREF_VCC ((ADMUX & ~(1<<REFS1)) | (1<<REFS0)) & ~(1<<ADLAR)
  // ADC_VREF_BG ((ADMUX | (1<<REFS1)) & ~(1<<REFS0)) & ~(1<<ADLAR)
  // ADC_VREF_INT ((ADMUX | (1<<REFS1)) | (1<<REFS0)) & ~(1<<ADLAR)
  ADMUX = ((ADMUX & ~(1<<REFS1)) | (1<<REFS0)) & ~(1<<ADLAR);
 
  // in ADCSRA:
  // set ADEN, Enable ADC
  // clear ADATE, not Auto Trigger Enable
  // set ADIE, Interrupt Enable
  // set ADPS[2:0], Prescaler division factor (128, thus 16M/128 = 125kHz ADC clock)
  // Note, the first instruction takes 25 ADC clocks to execute, next takes 13 clocks
  ADCSRA = (ADCSRA | (1<<ADEN)) & ~(1<<ADATE) | (1 << ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
 
  // Enable global interrupts
  sei();
 
  // set ADSC in ADCSRA, ADC Start Conversion
  ADCSRA |= (1<<ADSC);
}

Thanks for helping me. I read your code... and i think that actually, it is above my programming skills. I need some upgrade to follow those ADC and ISR things - if you know any good site about it, i'm interested !
Thanks again for helping

The ISR() function is from avr-libc.

http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Thanks for the link, very interesting indeed. Digging around i found a excellent (really excellent) tutorial for learning direct port access which can be found here : How to start using AvrStudio, C code and Arduino | HeKilledMyWire
It implies to move from the Arduino IDE to the atmel but it opens new perspectives... well, for me at least !

Atmel studio is an option and it gets regular updates, though is a little confusing. I am personally trying to figure out the minimal possible toolchain which on Ubuntu is:

sudo apt-get install gcc-avr
gcc-avr also installs binutils-avr
sudo apt-get install gdb-avr
sudo apt-get install avr-libc
sudo apt-get install avrdude

I have problems with how the Arduino IDE builds things. It mashes the ino files into a cpp file, and I can't figure out how to order functions so that the calls work. I'm very rusty at this stuff, but recall things worked fine back when I used plain C with header files that had function definitions. I just realized how not sensor related this is.

Hello Arduino-Team,

maybe it it a little surprising for you, that this thread gets another reply after at least two years.
I reviewed a couple of threads in the forum and found this one to be closest to my issue, and I hope, you might be able to give me a kick forward.
To my person:

  • I am an electronics engineer with quite some expereince in hard real-time programming with C.
  • This is my first post in this forum.

My project aims to sequentially read four analog sensors A0 ...A3, with an UNO
and display the result using a Nextion display.
I selected the "single conversion"-mode of the UNO with an interrupt-controlled readout of the conversion result.
The ISR shall select the next AD-channel and start the next conversion.
Finally I ended up in almost exactly the same code sequence, that Ron Sutherland issued above in this thread.

Still I get the same issue: ADMUX does NOT forward my selection to the multiplexer, no matter, what I set in either initialisation of the ADC or during the ISR, only A1 will converted repeatedly.

I used the Nextion-display for doing some debugging, so I ensured that loop() and ISR are executed, but ADMUX alway read as 0x41, which means only channel A1 is selected.

My question with regard to the ATMega328 datasheet:
is ther any sequence between ADMUX and ADCSRA.ADSC, that I need to obey?

I really apreciate your recommendation with some impatience.

kr, sepp2gl

@OP

1. This is the block diagram that contains the hardware resources (at conceptual level) of the ADC of Arduino MEGA Board.


Figure-1: Internal structure of the ADC of Arduino MEGA Board

Brief Description:
An analog channel (say, ADC0) is selected first; wait for a while for the signal to stabilize in the internal hold capacitor.

Issue a START command to the ADC for converting the input analog signal into digital form. The maximum value of the input analog signal will depend on the selection of the Reference Voltage for the VREF-pin of the ADC. The START command is implemented by putting LH at the ADSC-bit of the ADCSRA Register. The ADSC-bit remains at LH-state as long as the conversion is going ON. At the end-of-conversion the ADSC-bit assumes LL-state.

The ADC takes about 13us - 260 us time to convert a sample depending on the clkADC. The recommended clkADC is 125 kHz.

After the conversion, the 10-bit digital value enters into a 10-bit ADC (ADCH, ADCL). At the same time, the ADIF-bit of ADCSRA Register assumes LH-state to indicate the end-of-conversion.

A user can determine the end-of-conversion state by continuous polling of the ADSC-bit or the ADIF-bit of the ADCSRA Register.

If interrupt logic bits are kept at enabled states (ADIE-bit of ADCSRA Register is at LH; the I-bit of SREG is at LH), the ADIF-flag/bit can interrupt the MCU to convey the message of the end-of-conversion.

2. Functional Test of Ch-0 using Arduino Instructions
Upload the following sketch in the MEGA Board, Short A0-pin to 3.3V point of the MEGA. Check that the Serial Monitor shows about 675 (1023/5*3.3 ~= 675). Connect the A0-pin to GND; check that the Serial Moroni shows 0.

void setup()
{
  Serial.begin(9600);
  analogReference(DEFAULT);  //5V for Vref-pin of ADC
}

void loop()
{
  //select Ch-0; (clkADC=125 kHz by Arduino); start conversion; check end-of-conversion; read ADC //data into variable x
  int x = analogRead(A0); 
  Serial.println(x, DEC); //3.3V point is shorted to A0-pin
  delay(2000);
}

sm22.png

3. To write Register Level Codes for the codes of Step-2, we have to deal with bit manipulation of the following registers.

sm22.png

Hi GolamMostafa,

thanks for your kind and detailed reply.

In the meantime I double-checked the only difference between my code and Ron Sutherland's code:
He defined his "adc_channel" as global variable "volatile unsigned char ", whereas I defined my respective variable as "unsigned char" locally in the ISR and copied the value of the ADMUX&&0x07 into it, incremented it, and so on...
After I modified my code defining this variable like Ron did, the whole thing worked as intended.
I am still not sure, why this change is that significant. But it matters obviously.

Nevertheless now I am happy to see the intended four readings on Nextion-display.
So this thread can be considered to be closed.

Thanks again, sepp2gl