21.06.2009

Как измерить Vcc?

Недавно наткнулся на интересную статью о том, как измерить собственное напряжение питания в Arduino.

Как известно, ATmega может питаться от широкого диапазона напряжений, поэтому может оставаться "в строю" даже в случае постепенного разряда батареи, выражающегося в уменьшении напряжения. Ситуация эта в робототехнике более чем стандартная, за примером далеко ходить не надо: возьмем проект Voyager. Это робот-космический аппарат, запущенный за пределы солнечной системы и потому лишенный способности питаться от солнечных батарей. На него установлены радиоизотопные термические генераторы (ядерные батарейки), которые на момент старта выдавали 30В / 470Вт, но каждый год они теряют 0.78% своей мощности. Соответственно, в настоящий момент осталось около 60% от первоначальной, и приходится включать исследовательские подсистемы поочередно, чтобы не перегрузить генераторы.

В Arduino нельзя просто взять и подключить Vcc к аналоговому пину напрямую - по умолчанию AREF связан с Vcc и вы всегда будете получать максимальное значение 1023, от какого бы напряжения вы не питались. Спасает подключение к AREF источника напряжения с заранее известным, стабильным напряжением, но это - лишний элемент в схеме.

Еще можно соединить Vcc с AREF через диод: падение напряжение на диоде заранее известно, поэтому вычислить Vcc не составит труда. Однако, при такой схеме через диод постоянно протекает ток, сокращая жизнь батареи, что тоже не очень удачно.

Как же выкрутиться из положения, не добавляя новых элементов в схему и не сокращая время работы батареи? Оказывается, выход - есть, и поможет нам внутренний источник опорного напряжения 1.1В в ATmega (в документации он проходит как bandgap reference), которое не зависит от Vcc. Получается такая формула:

V_BAT=(1.1*1024)/analogRead(14);

где V_BAT - это напряжение Vcc в вольтах, а analogRead(14) - результат прямого чтения АЦП из канала 14.

В Arduino разрешены чтения только из каналов 0-7 (не удивляйтесь, если нашли на своем Diecimila или Duemilanove только 0-5, возьмите, к примеру, Seeeduino ;)

Чтобы появилась возможность отправлять данные в другие каналы, в том числе в 14, надо изменить маску в библиотеке-ядре Arduino. Для этого откройте файл hardware\cores\arduino\wiring_analog.c и найдите там строку:

ADMUX = (analog_reference << 6) | (pin & 0x07);

замените ее на:

ADMUX = (analog_reference << 6) | (pin & 0x0f);

После этого можно написать вот такой скетч:
 
uint16_t raw_bandgap = 0; // значение внутреннего bandgap
float volt_battery = 0.0;

void setup(){
Serial.begin(57600);
}

void loop(){
// Чтение напряжения батареи
analogReference(DEFAULT); // использовать Vcc как AREF
raw_bandgap = analogRead(14); // холостое чтение после смены AREF (см. 23.5.2 в руководстве)
raw_bandgap = analogRead(14); // измерить значение внутреннего bandgap
volt_battery = (1.1 * 1024) / raw_bandgap; // вычислить Vcc
Serial.print(volt_battery);
Serial.println(" v_bat");
delay(1000);
}



Тем, кто хочет знать напряжение на батарее, стоит иметь ввиду, что при наличии регулятора (типа L7805CV), мы будем измерять напряжение Vout регулятора, а в случае наличия входного диода - надо учесть напряжение, которое на нем падает.

Вот так выглядят вычисляемые значения в Seeeduino, в момент переключения с питания 5В на 3,3В:



... при этом мой вольтметр показывает меньшие значения. Но, тем не менее - скетч работает ;)

UPD: Нашел примеры, в которых вместо константы 1.1 используется 1.05. Результат получается гораздо ближе к показаниям вольтметра, ищу теоретическую базу, способную объяснить этот факт...

11 комментариев:

  1. добрый день уважаемый!подскажи пожалуйста
    как вычислять среднее значение,мне пришлось вот че нагородить,я правда не уверен что он это считает,но если раз в секунду мерить сильнее разброс,чем так
    (это термостат на лм335з,работает кстати :)
    а float-я думал покажет мне температуру с десятыми долями градуса :D




    float fgh1= 0;
    float fgh = 0;
    float a=0;
    float b=0;
    float c=0;
    float d=0;
    float e=0;
    float f=0;
    float g=0;
    float h=0;
    float i=0;
    float k=0;
    float L=0;
    float a1=0;
    float b2=0;
    float c3=0;
    float d4=0;
    float e5=0;
    float f6=0;
    float g7=0;
    float h8=0;
    float i9=0;
    float k10=0;
    float L11=0;
    float cnst = 271;

    void setup()
    {
    pinMode(7, OUTPUT);
    Serial.begin(9600);
    pinMode(8, OUTPUT);
    }
    void loop()
    {
    digitalWrite(8, HIGH);
    a= analogRead(0);
    delay(100);
    b= analogRead(0);
    delay(100);
    c= analogRead(0);
    delay(100);
    d= analogRead(0);
    delay(100);
    e= analogRead(0);
    delay(100);
    f= analogRead(0);
    delay(100);
    g= analogRead(0);
    delay(100);
    h= analogRead(0);
    delay(100);
    i= analogRead(0);
    delay(100);
    k= analogRead(0);
    delay(100);
    L= analogRead(0);
    delay(100);
    a1= analogRead(0);
    delay(100);
    b2= analogRead(0);
    delay(100);
    c3= analogRead(0);
    delay(100);
    d4= analogRead(0);
    delay(100);
    e5= analogRead(0);
    delay(100);
    f6= analogRead(0);
    delay(100);
    g7= analogRead(0);
    delay(100);
    h8= analogRead(0);
    delay(100);
    i9= analogRead(0);
    delay(100);
    k10= analogRead(0);
    delay(100);
    L11= analogRead(0);
    delay(100);
    fgh=(a+b+c+d+e+g+h+i+k+f+L+a1+b2+c3+d4+e5+g7+h8+i9+k10+f6+L11)/22*0.48828125;
    if (fgh<=20+cnst){
    digitalWrite(7, 0);
    delay(10000);
    }
    if (fgh>21+cnst){
    digitalWrite(7, 1);
    }
    Serial.println(fgh-cnst,DEC);
    }

    ОтветитьУдалить
  2. Среднее можно здесь считать так:

    unsigned int step = 1;
    float average = analogRead(0);

    for (int i=0;i<22;i++) {
    delay(100);
    step++;
    average = (step-1)/step*average + analogRead(0)/step;
    }

    ОтветитьУдалить
  3. благодарю!буду разбираться:)
    на тот случай если кто сюда забредет,я выше какой то кривой скетч выложил,вот рабочий:

    float fgh2 = 0;
    float fgh = 0;
    float a=0;
    float b=0;
    float c=0;
    float d=0;
    float e=0;
    float f=0;
    float g=0;
    float h=0;
    float i=0;
    float k=0;

    float cnst = 271;

    void setup()
    {
    pinMode(13, OUTPUT);
    Serial.begin(9600);
    }
    void loop()
    {
    a= analogRead(0);
    delay(10);
    b= analogRead(0);
    delay(10);
    c= analogRead(0);
    delay(10);
    d= analogRead(0);
    delay(10);
    e= analogRead(0);
    delay(10);
    f= analogRead(0);
    delay(10);
    g= analogRead(0);
    delay(10);
    h= analogRead(0);
    delay(10);
    i= analogRead(0);
    delay(10);
    k= analogRead(0);
    delay(10);

    fgh2=(a+b+c+d+e+f+g+h+i+k)/10;

    fgh =fgh2*0.48828125-cnst;

    Serial.println(fgh,DEC);

    if (fgh <28){

    digitalWrite(13, 1);

    delay(1000);

    ОтветитьУдалить
  4. Позволю себе несколько замечаний... возможно они окажутся полезными.

    1. analogRead() возвращает целое значение, следовательно хранить его можно в uint16_t (word).

    2. для хранения однотипных данных можно использовать массив, например word measurents[22];

    3. для обработки массивов можно использовать цикл, например так:

    for (int i=0;i<22;i++) {
    measurements[i] = analogRead(0);
    }

    4. Для вычисления среднего вообще не обязательно запоминать все измерения - этот способ я описал в моем предыдущем комментарии. Для 10-20 значений это несущественно, но когда их 100-200... можно получить проблемы с памятью, особенно в атмеге.

    ОтветитьУдалить
  5. Ребят, у меня по рандому выдает результат:

    1.10 v_bat
    1.10 v_bat
    1.71 v_bat
    4.64 v_bat
    375.47 v_bat
    0.00 v_bat
    0.00 v_bat
    16.81 v_bat
    2.07 v_bat

    Все сделал как написано, в чем дело? Не надо разве цеплять вход с Vcc?

    ОтветитьУдалить
    Ответы
    1. Наличие встроенного bandgap reference, сколько на нем падает и на каком канале АЦП он висит - зависит от конкретной модели МК.

      У Вас какая ATmega?

      Удалить
  6. Здравствуйте, все сделал как в примере, но значения выводятся какие попало. Использую Iteaduino V1.1 (ATmega 328). Если плата работает в режиме 5V то показывает 2,8-3,2V, а если переключаю в режим 3,3V, то вообще котовасия: от 8V до 15V, питается от USB. Подскажите, пожалуйста в чем может быть причина?

    ОтветитьУдалить
  7. Доброго времени суток!
    Есть необходимость мониторить ардуинку запитаную от 3-х элементов АА(4,5 В) по питанию и при падении напряжения до 3-х и меньше Вольт подавать сигнал(пищалку включать или мигать светиком). Наткнулся на Вашу статейку, с ходу получаю такие результаты:
    3.79 v_bat
    3.78 v_bat
    3.74 v_bat
    3.73 v_bat
    3.73 v_bat
    3.73 v_bat
    3.73 v_bat
    3.74 v_bat
    3.77 v_bat
    3.79 v_bat
    3.79 v_bat
    3.77 v_bat
    3.74 v_bat
    3.73 v_bat
    Питание в данный момент от USB. Мультиметр показывает 5.02 В на ножках питания ATMega328P-PU.
    Еще непонятен вопрос относительно замера:
    V_BAT=(1.1*1024)/analogRead(14);
    и
    analogReference(DEFAULT);
    raw_bandgap = analogRead(14);
    analogReference(DEFAULT) - говорит использовать питание Vcc как опорное http://arduino.ru/Reference/AnalogReference.
    Далее , поправьте если не прав, снимаем показания с 14 канала который подключен к Vcc внутри самого контроллера?
    Каким образом тогда тут притянуто 1.1 В ?
    Прошу знатоков помочь в вопросе!
    Заранее благодарен.

    ОтветитьУдалить
    Ответы
    1. Дело в том, что автор вместо использования analogReference(INTERNAL) правит библиотеку-ядро ардуино, перенаправляя дефолтное референсное напряжение на источник 1,1В. Честно говоря, не совсем понимаю, почему нельзя использовать именно analogReference(INTERNAL)

      Удалить
  8. здравсвуйте, вольтметр показывает каждый раз при измерении значение 12.1
    12.2
    12.1
    12.3
    12.5
    и т.д. т.е. быстро меныется как можно програмно осуществить плавную смену показаний на дисплее?

    ОтветитьУдалить
    Ответы
    1. Смотря чего хочется от дисплея: можно загрубить (отбросить младшую цифру), можно реже считывать и выводить значения, а можно вообще усреднять последние N значений - т.е. выводить только это среднее значение.

      Удалить