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

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

Резистор подключается к шинке питания, на которой измеряется напряжение. Если микроконтроллер запитан от батарейки через стабилизатор — резистор нужно подключить до стабилизатора.
Через резистор заряжается конденсатор. По времени заряда конденсатора до определённого опорного напряжения можно вычислить и входное напряжение. Встроенный источник опорного напряжения 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
Понятно, что от температуры ползёт, даже после запуска контроллера несколько минут немножко ползёт, видимо по мере нагрева кристаллика. Но при комнатной температуре показывает на удивление точно. В общем случае можно рассчитывать на ±10% по даташиту.
Забавно. Похоже от резистора и кондёра зависит куда сильнее чем от температуры кристаллика. Межку подогревал и охлаждал — ползёт процента на 3, стоит же дунуть феном на RC — показания резко уползают до максимума.
конденсатор с типом диэлектрика NPO может помочь.
[чешет репу] хотя, для 10нФ — еще не факт, что НПО найдешь:) можно попробовать пленочный поставить. или (что, имхо, получше) увеличить сопротивление резистора, пропорционально уменьшив кондей.
[чешет репу] хотя, для 10нФ — еще не факт, что НПО найдешь:) можно попробовать пленочный поставить. или (что, имхо, получше) увеличить сопротивление резистора, пропорционально уменьшив кондей.
- podkassetnik
- 29 ноября 2012, 23:49
- ↑
- ↓
там не только от нагрева кристалла зависит. от емкости конденсатора — вообще охереть как:) даже NPO не спасает, блин. и от резистора зависит тоже. в свое время изгалялись на работе — делали 4-х канальный АЦП на меге8515, основой которого была схема типа такой же:

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

мучились с полмесяца, а потОм плюнули и тайно от начальства поставили мегу32:) но у нас надо было реально точно измерять значения сигналов, для измерения же напряжения питания камня (где достаточно точности «плюс/минус лапоть»), имхо, предложенный вариант схемы вполне сгодится.
- podkassetnik
- 29 ноября 2012, 21:54
- ↑
- ↓
для измерения же напряжения питания камня (где достаточно точности «плюс/минус лапоть»)
Ну, не всегда так. Некоторые типы аккумуляторов имеют достаточно узкий диапазон по рабочему напряжению. Например, LiFe имеют оптимальный диапазон 3.3 – 3.0 В. Все что выше/ниже приводит к преждевременному старению батареи. А диапазон в 0.3 В – это не такой уж и «лапоть» :)
че-та я туплю сегодня:) имел в виду (напрочь похерив в памяти начало статьи) напряжение питания кирпича вообще:) с аккумуляторами, безусловно, надо быть намного «аккуратнее».
- podkassetnik
- 29 ноября 2012, 23:53
- ↑
- ↓
Можно включить резистор с порта на землю. В случае необходимости измерить напряжение питания, подключаем PULL-UP резистор к порту и на образованном делителе меряем АЦП.
Итог — один резистор, один порт, минимум потребления, высокая точность
Итог — один резистор, один порт, минимум потребления, высокая точность
внешний делитель плох тем, что он постоянно потребляет энергию. В батарейных устройствах, это расточительно.
Для этого есть другой фокус — ставим опорным напряжение питания и меряем стабильную опору. Микропотребляющие МК вроде MSP430 и STM8L так умеют.
Еще один вариант — подключить нижний резистор делителя через пин или полевик и включать его только при необходимости измерения.
Еще один вариант — подключить нижний резистор делителя через пин или полевик и включать его только при необходимости измерения.
Хороший повод сравнить методы измерения на реальном железе. Через время заряда конденсатора и сигма-дельта.
Сигма-дельта АЦП
Сигма-дельта АЦП
Никакого геморроя. Посидел пару часов, размял моск. Я планирую девайсик на межке 328 — ADC полностью выделен под другую задачу.
Неточно. Поскольку выходное напряжение на ножке контроллера вовсе не 0 и питание, и зависит от разных факторов. Здесь же точность зависит от точности встроенной опоры — 10% по даташиту, около 1% — фактически. Ну и от термостабильности деталек (впрочем, от этого зависит и точность любого другого способа включая ADC).
у меня на гарнитуре прикольно сделано! раньше нигде такого не встречал.
как я понял" при разряде, когда гарнитура уже отрубается — она выставляет бит и при попытке вклюдчения — проверяет его. сбрасывается он при подключении зарядки)) т.е. если гарнитура вырубилать от разряда то потом ее включить до зарядки уже не получится.
как я понял" при разряде, когда гарнитура уже отрубается — она выставляет бит и при попытке вклюдчения — проверяет его. сбрасывается он при подключении зарядки)) т.е. если гарнитура вырубилать от разряда то потом ее включить до зарядки уже не получится.
Бит ли? Похоже на стандартное поведение контроллера аккума — если вырубился от переразряда, то пока не подашь снаружи напряжение зарядки — на выходе будет ноль. Так что возможно гарнитура просто полностью отлучается от питания контроллером банки.
Комментарии (56)
RSS свернуть / развернуть