# Most efficient way to read analog signals in c++

I am new to AVR programming, and I am trying to learn the basic I/O with C++.

I am trying to find the simplest, most efficient way to read analog signals using the same function for different devices, just like the analogRead() function in the Wiring language (Arduino code). But I have not found anything very simplistic that I could use for AVR programming.

Any suggestions?

The nice thing about AVR is code ports very easily from processor to processor. I have a set of functions on file for setting up the peripherals.

Here's my ADC initialization function:

void init_adc(void)
{
sei();
}


The sei() function serves as a global interrupt enable, and only needs to be called once if multiple interrupts are being used.

The ADMUX register has the ADC set up with the internal 2.56V reference, the conversion result left adjusted so the most significant bits are in ADCH, and converting ADC0, single ended.

The ADSRA register has the ADC enabled, in interrupt mode, and sets the prescaler to $\frac{f_{osc}}{16}$. This register also houses the conversion complete interrupt flag.

ADCSRA = ADCSRA | (1<< ADSC);


This line starts the conversion. It's important to note that the ADC is not free running. A new conversion must be started after each conversion.

My bare bones interrupt service routine code:

ISR(ADC_vect)
{
}


The most significant part of the result is in ADCH. ADCL contains the least significant bits. Read from ADCH as necessary. If you have multiple channels you're converting single ended, this is a good place to handle the multiplexing. For example:

if (n == 3)
{
n = 0;
}
else
{
n++;
}


I strongly recommend reading the datasheet to understand the operation of the ADC before you start writing code. Atmel's datasheets are pretty good, and offer good explanations and some sample code.

I don't use Arduino, so I don't know what is happening in those routines, but there are few common ways of taking an ADC measurement with AVR. One of the great things about AVR chips in the ATtiny and ATmega lines is that a lot of peripheral registers have the same (or very similar) names in different chips. I'm going to be using the ATtinyx4 (datasheet) in this example.

Also of note is that these chips are more commonly programmed in C (or assembly), not C++, although Arduino does use a version of C++.

The ADC first has to be set up. This typically involves setting the reference voltage, initializing the ADC multiplexer, setting the ADC clock prescaler, setting the mode, and enabling the ADC interrupt.

For example (in C):

  // _BV(BIT) is defined as (1<<(BIT))
ADMUX = _BV(REFS1);   // Use internal 1.1V reference voltage, multiplexer = 0
_BV(ADPS1) |      // Prescaler = 4: F_ADC = F_cpu / prescaler
ADCSRB = _BV(ADLAR);  // Left Adjust Result for 8 bit resolution


To take a measurement, you would do this:

  ADCSRA |= ADSC;               // Start an ADC conversion
while(ADCSRA & _BV(ADSC));    // Wait until conversion is complete


Notice that I only read the high register because I am only using 8 bits resolution. I could have easily read the entire ADC value instead.

If instead you wanted the code to automatically handle the ADC result, you could enable the ADC conversion complete interrupt.

  ADCSRA |= _BV(ADIE);     // Enable ADC Interrupt


And handle the ADC value in the ADC interrupt service routine. This might be more time efficient, but it will use more code space.

There is also a "free running mode" which means the ADC will continuously take a reading while it is enabled for the selected channel. All of this information is in the datasheet of AVR chips with ADC functionality under the "Analog to Digital Converter" chapter.

If you wanted to create your own function to do this, if might look like this:

uint8_t ADC_read(uint8_t channel)
{
ADMUX &= (_BV(REFS1) | _BV(REFS0));    // Clear the ADC Multiplexer
ADMUX |= channel;                      // Set the ADC multiplexer
while(ADCSRA & _BV(ADSC));    // Wait until conversion is complete
return (ADCH);                // Return the ADC high register value
}


Other things to consider: the first reading after you enable the ADC is typically junk, so if you aren't going to leave it enabled (uses more power this way) you will need to take two consecutive measurements, discarding the first one. Of course, the pins being used should be set as inputs with the digital input buffers disabled (DIDRx).

My above examples used the macro _BV() to set a particular bit in a byte, but this is just a different way of left shifting a 1 so many places. The _BV() macro is typically defined in one of the AVR header files along with the definitions for chip specific register names that are included at the top of a program, but I don't know how that is handled in Arduino.

If the _BV() macro does not work, you can define it yourself as:

#define _BV(BIT)    (1<<(BIT))


Since a byte is 8 bits, it can be thought of as this: [bit7, bit6, ... bit1, bit0]. Setting an individual bit high by left shifting a 1 so many bits over means you can make more readable (and changeable) code then just setting a byte to a specific value. For example, this code:

ADCSRA =
_BV(ADPS1) |      // Prescaler = 4: F_ADC = F_cpu / prescaler


makes a lot more sense than this code:

ADCSRA = 0x8A;          // 0b10001010


in addition to being more portable and easier to edit.

Some people do not like using (or seeing) this macro, so they prefer to just use the left shift operand. Either way is fine and uses the same amount of code space/clock cycles to operate. Forgoing the _BV() macro, my examples would look like this:

// Setting up the ADC
ADMUX = (1<<REFS1);     // Use internal 1.1V reference voltage, multiplexer = 0
(1<<ADPS1) |        // Prescaler = 4: F_ADC = F_cpu / prescaler
ADCSRB = (1<<ADLAR);    // Left Adjust Result for 8 bit resolution

// Taking a measurement
while(ADCSRA & (1<<ADSC));    // Wait until conversion is complete

// Enabling the ISR

//Function to read an ADC Channel (once the ADC is setup)
{
ADMUX &= (1<<REFS1) | (1<<REFS0));     // Clear the ADC Multiplexer
ADMUX |= channel;                      // Set the ADC multiplexer

• Ok, I will be putting those functions in a header file, in your example you say "// _BV(BIT) is defined as (1<<(BIT))" - does that mean I have to do this: #define _BV ...? – user151324 May 19 '13 at 18:55
• Could you include that in your example instead of _BV() please? – user151324 May 19 '13 at 20:52