Измерение напряжения питания

AVR
В различных батарейных и аккумуляторных девайсах бывает не лишним отображать оставшийся заряд и/или иметь возможность вовремя отрубиться, чтобы не переразряжать аккумулятор. Для этого нужно измерять напряжение питания, что обычно делается с помощью встроенного ADC. Впрочем, ADC может отсутствовать (например, в тиньке 2313) или быть недоступен — заниматься более важным делом, либо в качестве опоры может использоваться то самое напряжение питания, etc.

В таком случае, напряжение питания несложно измерить с помощью встроенного аналогового компаратора, используя только одну ножку.

Схемка измерения напряжения

Резистор подключается к шинке питания, на которой измеряется напряжение. Если микроконтроллер запитан от батарейки через стабилизатор — резистор нужно подключить до стабилизатора.

Через резистор заряжается конденсатор. По времени заряда конденсатора до определённого опорного напряжения можно вычислить и входное напряжение. Встроенный источник опорного напряжения Vbg можно подключить к положительному входу компаратора, установив битик ACBG в регистре компаратора.

Перед измерением нужно разрядить конденсатор, прижав ножку AIN0 к земле.

Впрочем, у данного подхода есть недостаток — напряжение низкого уровня на ножке микроконтроллера не равно нулю. Конденсатор разрядится не полностью и измерение будет неточным. Для повышения точности можно добавить полевичок, разряжающий конденсатор.

Схемка с транзистором
Настроим компаратор. Выберем подключение встроенной опоры к положительному входу компаратора и тактирование захвата (input capture таймера) от выхода компаратора.

ACSR = 1<<ACBG | 1<<ACIC;

Переведём таймер 1 в режим непрерывного счёта, без делителя тактовой частоты. Включим прерывание по захвату.

TCCR1A = 0;
TCCR1B = 1<<CS10;
TIMSK |= 1<<TICIE1;
sei();

Также настроим ножку, управляющую полевичком на выход.

DDRD |= 1<<PD7;

Для измерения нам нужно разрядить конденсатор, запомнить время начала заряда и дать конденсатору заряжаться.

volatile uint16_t rc_adc_offset;
//...
PORTD |= 1<<PD7;
//задержка
PORTD &= ~(1<<PD7);
rc_adc_offset = TCNT1;
//...

Когда конденсатор зарядится до опорного напряжения, сработает прерывание захвата. В регистре захвата мы получим время, когда закончился заряд.

volatile uint16_t rc_adc_value;

ISR(TIMER1_CAPT_vect)
{
    rc_adc_value = ICR1 - rc_adc_offset;
}


Мы получили время заряда конденсатора до опорного напряжения в тактах микроконтроллера, перевести время в секунды можно разделив полученное значение на тактовую частоту. Измеряемое напряжение — функция от полученного нами времени.

Формула

Вычислять это напрямую — довольно долго. Куда быстрее это сделать таблично. Табличку сгенерим сриптом.

#!/usr/bin/perl
use strict;
use warnings;

# тактовая частота
my $Fcpu = 20e6;

# сопротивление и ёмкость
my $RC = 22e3 * 10e-9;

# опорное напряжение
my $Vref = 1.23;

# минимальное время в тактах
my $CntMin = 1024;

# максимальное время в тактах
my $CntMax = 3072;

# размер таблицы
my $TableSize = 64;

print "{";
for my $i (0 .. $TableSize)
{
    my $cnt = $CntMin + ($CntMax - $CntMin) * $i / $TableSize;
    my $t = $cnt / $Fcpu;
    my $v = $Vref / (1 - exp(-$t / $RC));
    
    print "\n\t" if($i % 8 == 0);
    #print "/*$cnt*/ ";
    printf "%3d, ", int($v * 100);
}
print "\n};";

1024 — минимальное время зарядки конденсатора в тактах, 3072 — максимальное. Значения подобраны так, чтобы шаг таблички был равен степени двойки (3072 — 1024) / 64 = 32. Какому напряжению они соответствуют мы увидим, сгенерив саму табличку.

#define RC_ADC_TBL_L    1024
#define RC_ADC_TBL_H          3072
#define RC_ADC_TBL_SIZE       64
#define RC_ADC_TBL_STEP       ((RC_ADC_TBL_H - RC_ADC_TBL_L) / RC_ADC_TBL_SIZE)

prog_uint16_t rc_adc_tbl[] = 
{
        592, 576, 561, 547, 533, 521, 509, 498,
        487, 477, 467, 458, 449, 440, 432, 424,
        417, 410, 403, 396, 390, 384, 378, 373,
        367, 362, 357, 352, 347, 343, 338, 334,
        330, 326, 322, 318, 315, 311, 308, 304,
        301, 298, 295, 292, 289, 286, 284, 281,
        278, 276, 273, 271, 269, 266, 264, 262,
        260, 258, 256, 254, 252, 250, 248, 246,
        244,
};

Как видим, с этой табличкой диапазон измерения — от 2.44 до 5.92 В.

Сама функция измерения.

uint16_t get_supply_voltage()
{
    uint16_t t, v = 0;
    uint8_t idx, frac;
    uint16_t a, b;

    // разряжаем коненсатор
    RC_ADC_PORT |= RC_ADC_DSC;

    // пока конденсатор разряжается, вычислим напряжение,
    // используя сохранённое время зарядки конденсатора
    t = rc_adc_value;
    if(t != 0) {

        // ограничиваем время диапазоном таблицы
        if(t < RC_ADC_TBL_L) t = RC_ADC_TBL_L;
        if(t >= RC_ADC_TBL_H) t = RC_ADC_TBL_H - 1;
        t -= RC_ADC_TBL_L;
        
        // вытаскиваем два ближайших узла из таблички
        idx = t / RC_ADC_TBL_STEP;

        a = pgm_read_word(rc_adc_tbl + idx);
        b = pgm_read_word(rc_adc_tbl + idx + 1);

        // получаем значение линейной интерполяцией
        frac = t % RC_ADC_TBL_STEP;
        v = a - (a - b) * frac / RC_ADC_TBL_STEP;
    }

    // начинаем новое измерение
    RC_ADC_PORT &= ~RC_ADC_DSC;
    rc_adc_offset = TCNT1;

    return v;
}


Как видим, функция вычисляет напряжение на основе предыдущего измерения, и запускает новое измерение. Так что время между вызовами функции не должно быть меньше времени измерения.

Пример использования
//...
v = get_supply_voltage();
if(v != 0) {
    if(v < 300) shutdown(); //3.00V
    display_voltage(v);
}
//...


Точность измерения ограничивается точностью источника встроенного опорного напряжения — 10%, чего в общем-то достаточно. Повысить точность можно с помощью внешнего опорника.

При практическом испытании (без калибровки) ошибка со схемкой без полевичка составила около 400 мВ, с полевичком — около 60 мВ.

Стенд

Такая мелкая заметочка.

UPD. Важно применять детальки с достаточной термостабильностью. Некоторые конденсаторы могут значительно изменять ёмкость при изменении температуры окружающей среды.
  • +13
  • 29 ноября 2012, 18:44
  • Lifelover
  • 1
Файлы в топике: rc_adc.zip

Комментарии (56)

RSS свернуть / развернуть
Советую попробовать ещё при разных температурах замеры провести.
0
Понятно, что от температуры ползёт, даже после запуска контроллера несколько минут немножко ползёт, видимо по мере нагрева кристаллика. Но при комнатной температуре показывает на удивление точно. В общем случае можно рассчитывать на ±10% по даташиту.
0
Нет, ты попробуй :)
Мне просто очень интересно, насколько попалзёт в морозильнике.
0
dcoder уже пробовал)
0
Забавно. Похоже от резистора и кондёра зависит куда сильнее чем от температуры кристаллика. Межку подогревал и охлаждал — ползёт процента на 3, стоит же дунуть феном на RC — показания резко уползают до максимума.
0
При нагреве кондёра до 70-80°C ёмкость уменьшается с 10 до 4 нф)
0
конденсатор с типом диэлектрика NPO может помочь.

[чешет репу] хотя, для 10нФ — еще не факт, что НПО найдешь:) можно попробовать пленочный поставить. или (что, имхо, получше) увеличить сопротивление резистора, пропорционально уменьшив кондей.
0
там не только от нагрева кристалла зависит. от емкости конденсатора — вообще охереть как:) даже NPO не спасает, блин. и от резистора зависит тоже. в свое время изгалялись на работе — делали 4-х канальный АЦП на меге8515, основой которого была схема типа такой же:



мучились с полмесяца, а потОм плюнули и тайно от начальства поставили мегу32:) но у нас надо было реально точно измерять значения сигналов, для измерения же напряжения питания камня (где достаточно точности «плюс/минус лапоть»), имхо, предложенный вариант схемы вполне сгодится.
0
для измерения же напряжения питания камня (где достаточно точности «плюс/минус лапоть»)

Ну, не всегда так. Некоторые типы аккумуляторов имеют достаточно узкий диапазон по рабочему напряжению. Например, LiFe имеют оптимальный диапазон 3.3 – 3.0 В. Все что выше/ниже приводит к преждевременному старению батареи. А диапазон в 0.3 В – это не такой уж и «лапоть» :)
0
Например, LiFe имеют оптимальный диапазон 3.3 – 3.0 В
3.0-3.6, впрочем их мона и до 2В разряжать.
0
Хм, по вики и правда 3,0-3,3. Но я таких не встречал. Обычно указывается (и используется) 2,0-3,6.
0
По крайней мере, до 3.3 они набирают небольшую часть ёмкости)

0
Обычно указывается (и используется) 2,0-3,6.

Да, часто диапазон напряжения указывают именно так. Я думаю, что производили «хитрят». Да и понятие «рабочий диапазон по напряжению» весьма условен. Аккумулятор, любом случае, стареет — вопрос только на сколько.
0
Ну, 3.3 — тоже явный перегиб. Заряди до 3.3 — он не наберёт большую часть ёмкости. Впрочем, у LiFe действительно узкий диапазон. Для LiPo отсечка и 2,7 и 3,3 вполне приемлема — не переразрядишь и не оставишь лишнего.
0
Для LiPo отсечка и 2,7 и 3,3 вполне приемлема — не переразрядишь и не оставишь лишнего.

Здесь полностью согласен. Я сам недавно перевел свой коптрер на LiFe. Преимущества есть, но пока я не готов делать выводы (слишком мало практики).
0
К тому же, ошибка в 10% — худший случай (и маловероятный). Для него достаточно проверить, что не вылезем за абсолютные допустимые пределы. Фактическая ошибка там куда меньше (на порядок).
0
А я вот не согласен с этими вашими 2.7-2.8В для LiPo. Че вы их так вгоняете? зачем?
Производители рекомендуют садить не ниже 3В (фомам), в таком режиме прослужит долше. Те же 2.7В это нижняя граница, пересекать которую просто нельзя.
+1
Да к тому же, судя по разрядным характеристикам, от 3,0 до 2,7 остается насколько мало энергии, что там не за чем гнаться особо.
0
2.7 — это абсолютная минимальная граница. Я и не предлагал до неё разряжать. Лучше выставить отсечку на 3В.
0
Упс, звиняюсь, с прямым углом перепутал :)
Вы правы, ниже трех вольт лучше не разряжать.
0
LiFe вообще непривередливые в отличие от LiPo, но и для них допустимое напряжение 2.7В. Выставить отсечку 3.0В — и будет с запасом.
0
че-та я туплю сегодня:) имел в виду (напрочь похерив в памяти начало статьи) напряжение питания кирпича вообще:) с аккумуляторами, безусловно, надо быть намного «аккуратнее».
0
Можно включить резистор с порта на землю. В случае необходимости измерить напряжение питания, подключаем PULL-UP резистор к порту и на образованном делителе меряем АЦП.
Итог — один резистор, один порт, минимум потребления, высокая точность
0
Это если есть АЦП
0
На сопротивление встроенной подтяжки нельзя полагаться — оно очень неточное и плавает.
0
калибровать прикажете?
0
один разок, а что такого?
0
Зачем, если можно один внешний резистор добавить и не напрягаться?
0
внешний делитель плох тем, что он постоянно потребляет энергию. В батарейных устройствах, это расточительно.
0
Что мешает его подключиь не напрямую к земле, а коммутировать ногой контроллера. Которая в неактивном состоянии — высокомный вход, а на время измерения силовой «0».
0
Для этого есть другой фокус — ставим опорным напряжение питания и меряем стабильную опору. Микропотребляющие МК вроде MSP430 и STM8L так умеют.
Еще один вариант — подключить нижний резистор делителя через пин или полевик и включать его только при необходимости измерения.
0
Я не в теме, но вроде в АВР нельзя свою опору мерять?
0
Ну, они в основном и к микропотребляющим не относятся) Но поставить питание в качестве опоры для АЦП ЕМНИП можно, а измерить можно и внешнюю опору. Тем паче внутренняя параметрами не блещет.
0
Питание конечно можно, либо приняв за опору внутри камня, либо подключив на референсную ногу.
0
Можно мерять в межках *8, в тиньках, которые для силовых схемок (те ещё датчик температуры имеют).
0
Ок, спасибо за информацию
0
Вот тут, например, посмотрите…
0
И вот тут
0
вот я про нижний резистор и писал, а подключать к нему pull-up резистор во время измерения
0
На pull-up резистор нельзя полагаться
0
Ну, если подключать внешний pull-up, а не внутренний — то можно и так, да.
0
Хороший повод сравнить методы измерения на реальном железе. Через время заряда конденсатора и сигма-дельта.
Сигма-дельта АЦП
0
а тке забыли?
берем всеми любимые к10-17в и смотрим зависимости ёмкости от температуры для группы н90 (которые обычно в цифру и ставят) и для группы мп0 (которые практически не берут из-за цены)
0
Столько геморроя только потому что у 2313 нет АЦП?
0
Никакого геморроя. Посидел пару часов, размял моск. Я планирую девайсик на межке 328 — ADC полностью выделен под другую задачу.
0
В любом случае, метод древний как говно мамонта и описан в аппноте AVR400: Low Cost A/D Converter.
0
Там описан другой метод, им нельзя питание померять.
0
Если в качетве входного напряжения использовать опорное стабильное, то, думаю, можно.
0
Неточно. Поскольку выходное напряжение на ножке контроллера вовсе не 0 и питание, и зависит от разных факторов. Здесь же точность зависит от точности встроенной опоры — 10% по даташиту, около 1% — фактически. Ну и от термостабильности деталек (впрочем, от этого зависит и точность любого другого способа включая ADC).
0
Точность нужна, поскольку я хочу аккумуляторный девайс делать.
0
у меня на гарнитуре прикольно сделано! раньше нигде такого не встречал.
как я понял" при разряде, когда гарнитура уже отрубается — она выставляет бит и при попытке вклюдчения — проверяет его. сбрасывается он при подключении зарядки)) т.е. если гарнитура вырубилать от разряда то потом ее включить до зарядки уже не получится.
0
Бит ли? Похоже на стандартное поведение контроллера аккума — если вырубился от переразряда, то пока не подашь снаружи напряжение зарядки — на выходе будет ноль. Так что возможно гарнитура просто полностью отлучается от питания контроллером банки.
+1
ну хз, я подумал что именно так)
на телефоне ж тоже есть контроллер аккума, но его-то можно включить не зарядив…
0
Телефон обычно сам глохнет раньше, чем сработает контроллер банки. Но глубоко разряженный не включается, пока зарядку не воткнешь.
0
да и гарнитура ж не просто взяла и отрубилась… она пищит что скоро разрядится. а когда уже хочет вырубиться — сначала пищит и только потом вырубается. т.е. не просто отрубает питание и все…
0
Не забивайте на значок АКБ на ЖКИ тестера. От этого показания тоже плывут)
+1
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.