Low-level ADC control: ADMUX
While discussing the 3 phase sketch implementation here: http://openenergymonitor.org/emon/node/467 by Pcunha which makes use of the AVR analog read commands rather than the arduino analog_read function which is a wrapping for these commands JBecker suggested that the analog reference voltage was not being selected correctly, so I had to do some reading up on this on ADMUX and bitwise operators.
So for anyone interested, this is how it works first ADMUX is an 8-bit byte that holds the settings for the analog reference voltage and the analog pin to select:
ADMUX is 8-bit:
7 bit | 6 bit | 5 bit | 4 bit | 3 bit | 2 bit | 1 bit | 0 bit | |
ADMUX | REFS1 | REFS0 | ADLAR | - | MUX3 | MUX2 | MUX1 | MUX0 |
REFS1 | REFS0 | Voltage Reference Selection |
0 | 0 | AREF, Internal Vref turned off |
0 | 1 | AVcc with external capacitor on AREF pin |
1 | 0 | Reserved |
1 | 1 | Internal 1.1V (ATmega168/328) or 2.56V on (ATmega8) |
MUX 3...0 | Single Ended Input |
0000 | ADC0 |
0001 | ADC1 |
0010 | ADC2 |
0011 | ADC3 |
0100 | ADC4 |
0101 | ADC5 |
So if we want to use AVcc with external capacitor on AREF pin as our voltage reference (which is the default arduino voltage reference) and we want to select ADC2 for example, we would need to set ADMUX to:
7 bit | 6 bit | 5 bit | 4 bit | 3 bit | 2 bit | 1 bit | 0 bit | |
ADMUX | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
We can use bitwise operations to get there:
This is how its done in wiring_analog.c found in Arduino-1.0/hardware/arduino/cores/arduino
ADMUX = (analog_reference << 6) | (pin & 0x07);
Starting with (analog_reference << 6). The variable analog_reference is set to 1 as default for the atmega328 so 1 << 6 means bitshift 1 left 6 places where 1 in binary is 00000001 and shifting the bits left 6 places moves the 1 to 01000000. The desirable result for setting the 6th bit REFS0 to 1.
If you dont need to select different analog_reference values then we can get 01000000 directly by specifying its hexadecimal: 0x40.
Next looking at the (pin & 0x07) part.
The last part is the | bitwise operator: OR
(analog_reference << 6) | (pin & 0x07) becomes in our example here 01000000 OR 00000010. Which is equall to: 01000010 the result we where looking for above.
If you would like to read more on the AVR ADC commands see these helpful articles:
https://sites.google.com/site/qeewiki/books/avr-guide/analog-input
http://www.protostack.com/blog/2011/02/analogue-to-digital-conversion-on-an-atmega168/
ADMUX = (analog_reference << 6) | (pin & 0x07);
Starting with (analog_reference << 6). The variable analog_reference is set to 1 as default for the atmega328 so 1 << 6 means bitshift 1 left 6 places where 1 in binary is 00000001 and shifting the bits left 6 places moves the 1 to 01000000. The desirable result for setting the 6th bit REFS0 to 1.
If you dont need to select different analog_reference values then we can get 01000000 directly by specifying its hexadecimal: 0x40.
Next looking at the (pin & 0x07) part.
- The & 0x07 is actually just for validation, it essentially says only allow selection of ADC0 to ADC5.
- The & bitwise operator is the AND operation.
- 0x07 is hexadecimal whose binary representation is 00000111
- Selecting ADC2 which is pin = 2 or in binary 00000010
- The result of 2 & 0x07 = 00000010 AND 00000111 = 00000010.
The last part is the | bitwise operator: OR
(analog_reference << 6) | (pin & 0x07) becomes in our example here 01000000 OR 00000010. Which is equall to: 01000010 the result we where looking for above.
If you would like to read more on the AVR ADC commands see these helpful articles:
https://sites.google.com/site/qeewiki/books/avr-guide/analog-input
http://www.protostack.com/blog/2011/02/analogue-to-digital-conversion-on-an-atmega168/