Voltage Measurement

With this experiment I want to implement a somwehat simple volt meter with the Blinkenlighty. This experiment will NOT work 1:1 with the Blinkenlight Shield as it exploits one of the two additional ADC inputs of the Blinkenlighty. Unlike the standard Arduinos the Blinkenlighty has 8 ADC inputs instead of 6. Thus I can use all 20 digital IO Pins to drive LEDs and still process analog input.

In case you want to follow this experiment without a Blinkenlighty you might want to set the constants analog_in to 5 and debug to true.

Measuring a voltage with the Arduino is trivial, right? Just call analogRead() and multiply by 5V / 1024 in order to get the voltage at the pin. At least this is what most people do. However after my supply voltage measurement experiment it is clear that the reference voltage is never exactly 5V. In some cases I have seen the supply voltage drop to even below 4.5V. In such cases the reference voltage would be 10% to low. It follows that the computed values would then be 10% to high.

Since I will use 20 LEDs my display resolution is 1/20 = 5%. So I did not want to ignore this effect. So how to resolve it? Easy – just like in the supply voltage measurement experiment I measure the internal precision voltage reference vs. the supply voltage. Then I measure the analog input vs. the supply voltage. After this I divide both values and the supply voltage cancels out. Presto more precise results.

So much for the theory, here is the code.

//
//  www.blinkenlight.net
//
//  Copyright 2014 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/


const uint8_t analog_in = 7;
const bool debug = false;

void trigger_adc() {
     // trigger conversion     
    ADCSRA |= 1<<ADSC;
    
    // wait for conversion to finish
    while (ADCSRA & (1<<ADSC)) {};
}
  
uint16_t read_adc() {  
    // dummy conversion to ensure adc settles before reading first value
    delay(5);
    trigger_adc();
    
    // the "real" adc conversion
    trigger_adc();
    
    // read result
    uint16_t adc;
    adc = ADCL;
    adc |= ADCH<<8;
  
    return adc;
}

uint16_t read_ref() {
    // data sheet 24.9.1 ADC Multiplexer Selection Register
    ADMUX = (1<<REFS0) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1);
  
    return read_adc();
}

uint16_t read_analog_in() {
    // data sheet 24.9.1 ADC Multiplexer Selection Register
    ADMUX = (1<<REFS0) | ((analog_in << MUX0) & (1<<MUX3 | 1<<MUX2 | 1<<MUX1 | 1<<MUX0));

    return read_adc();
}


// Our measurements will only resolve some mV.
// However we compute in uV in order to remove
// the impact of rounding differences.

uint32_t ref_to_vin_uV(const uint16_t ref) {
     // data sheet 24.7 ADC Conversion Result
    // ADC = vref * 1024 / vin
    // notice that vin and vref have exchanged roles because we 
    // measure the reference voltage against vin
    // ==> vin [V] = vref [V] * 1024 / adc
    // ==> vin [uV] = 1.1 * 1000000 * 1024 / adc
    return 1126400000UL / ref;  

}

uint32_t analog_in_to_uV(const uint16_t ref, const uint16_t analog_in) {
    return ((uint32_t)analog_in * 1100000UL) / ref;
}


void display(const uint8_t n) {
    for (uint8_t led= debug? 2:0; led<20; ++led) {
        // display nothing for n == 0
        // light led 19 for n == 20
        const uint8_t m = min(n, 20);
        digitalWrite(led, led == m-1);
    }
}

void setup_display() {
      for (uint8_t led= debug? 2:0; led<20; ++led) {
        pinMode(led, OUTPUT);
    }
}

void setup_debug() {
    if (debug) {
        Serial.begin(115200);
    }
}

void setup() {  
    setup_display();
    setup_debug();
}


void loop() {
    const uint16_t ref       = read_ref();
    const uint16_t analog_in = read_analog_in();
  
    const uint32_t measured_uV = analog_in_to_uV(ref, analog_in);
    
    // We have 20 ticks and want to light highest LED
    // if we are within 1/2 display resolution of 5V
    // display resolution = 5V/20 = 250 000 uV.
    const uint8_t tick = (measured_uV + 125000)/ 250000;
   
    if (debug) {
        Serial.print(F("ref [1]: "));
        Serial.println(ref);
        Serial.print(F("ref [uV]: "));
        Serial.println(ref_to_vin_uV(ref));
        Serial.print(F("analog_input [1]: "));
        Serial.println(analog_in);
        Serial.print(F("measured_uV: "));
        Serial.println(measured_uV);
        Serial.print(F("tick: "));
        Serial.println(tick);
    }
    
    display(tick);
}
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.