В портативном устройстве, работающем от аккумулятора, почти обязательным «удобством» является индикатор уровня его заряда. Казалось бы, если оно собрано на основе любого современного микроконтроллера и имеет графический дисплей, ничего сложного в этом нет: нужно лишь регулярно измерять напряжение батарейки с помощью встроенного АЦП и выводить его в виде традиционной батарейки🔋, степень заполнения которой зеленой краской зависит от напряжения. Но если так сделать в лоб, есть риск, что индикатор будет вести себя, как в известном перле «она металась, как стрелка осциллографа». В лучшем случае, он будет время от времени раздражающе подергиваться туда-сюда на один-два пикселя.
В статье описывается простая реализация индикатора разряда, лишенная этого недостатка.
Проблема «дергающейся батарейки»
Причин такой нестабильности показаний индикатора несколько. Для начала, нужно отметить, что напряжение почти полностью заряженного литий-ионного аккумулятора – 4,0 В, а почти полностью разряженного -- 3,4-3,5 В. Соответственно, перепад от 0 до 100% соответствует всего 0,5-0,6 В, то есть индикация заряда с шагом 10% требует точности измерения напряжения не хуже 1%. При этом метрологические характеристики «вольтметра», встроенного в устройство, чаще всего достаточно скверные, потому что всерьез к проектированию этого узла относятся достаточно редко. Да и само напряжение, поступающее на устройство, потребление тока которым постоянно меняется в интервале от нескольких до 150-200 миллиампер, с учетом его подключения через невысокого качества китайский разъем типа JST – тоже непостоянно. При непостоянном токе потребления, зависимость разрядной характеристики аккумулятора от тока разряда – самое главное препятствие для точного определения заряженности по напряжению. Поэтому в смартфонах и ноутбуках для этого чаще применяют другой подход – специализированный контроллер подсчитывает кулоны, пошедшие на зарядку батареи и затраченные затем при разряде, а напряжение при этом играет вспомогательную роль.
Но мы не будем забираться в такие дебри. Способ этот дает прекрасные результаты, но он не так прост в реализации: такие контроллеры сложно достать в розницу, трудно паять вручную, и вдобавок их нужно прошивать на требуемые параметры аккумулятора с помощью платной программы и не менее платного программатора. Тем более, что простыми средствами тоже можно достичь неплохих результатов, пусть и не таких точных.
Решение
Предлагаемая идея состоит в том, что раз потребление тока устройством меняется и наибольшая просадка напряжения происходит в моменты наибольшего потребления, нужно фиксировать напряжение именно в такие моменты. Это логично, так разряженный аккумулятор еще может долгое время «тянуть» устройство, пока оно находится в малопотребляющем режиме, но быстро просядет ниже минимально допустимого напряжения, когда потребление подскочит, например, при включении дисплея. При этом очевидно, что когда аккумулятор разряжается, степень его заряженности может только снижаться, но никак не увеличиваться. И наоборот, когда аккумулятор заряжается – степень его заряженности только возрастает, несмотря на то, что измеренное значение напряжения может в какие-то моменты падать из-за помех и т.п. Поэтому давайте будем во время разряда игнорировать поступающие данные об изменениях напряжения, если оно растет, считать этот рост артефактом. Делается это элементарно – путем сравнения каждого следующего значения измеренного напряжения с ранее зафиксированным минимальным, которое обновляется каждый раз, когда измеренное значение окажется ниже него. Во время заряда мы поступим аналогично, но фиксировать будем не минимумы, а максимумы.
Разумеется, нам здесь понадобится некий сигнал от зарядного устройства, информирующий о том, в каком состоянии (заряд или разряд) находится аккумулятор. Обычно контроллеры заряда литиевых аккумуляторов имеют выход на светодиод или пару светодиодов, который несложно завести на GPIO контроллера.
Тут нужно учесть еще и то, что кривые разряда и заряда существенно различаются. Поэтому по смене статуса зарядного контроллера нам нужно сменить не только направление работы индикатора, но и формулу расчета процентов заряженности от напряжения. А также то, что на протяжении этапа CV, на который приходится примерно 25-30% емкости батареи и половина времени заряда, индикатор будет показывать 100%, если мы будем принимать во внимание только напряжение. Можно так и оставить (сделав внятную индикацию, что зарядка еще не окончена), а можно заморочиться и вычислять на этом этапе проценты заряженности, как линейную (или более сложную) функцию от времени.
Код
Нижеприведенный код на Си реализует самый простой вариант описанного алгоритма. Здесь мы считаем, что полностью разряженная батарейка при разряде дает 3,4 В. Чем это обусловлено? Во-первых, тем, что примерно с этого напряжения начинается быстрый спад напряжения, и дальнейший разряд не дает существенно большего времени работы. Во-вторых, если питать МК от аккумулятора через LDO на 3,3 В, при снижении напряжения ниже этого значения начинает падать и напряжение питания МК. В некоторых случаях это не очень желательно, и в частности, в данной задаче пришлось бы задействовать встроенный источник опорного напряжения, чтобы измерить напряжение батареи в 3,3В и ниже. Та же полностью разряженная батарея при включении заряда сразу увеличивает напряжение до 3,65 В, я же взял 3,6 В, так как тогда при том же коэффициенте наклона автоматически выходит нужное напряжение на 100% заряженном аккумуляторе 4,2 В.
// Глобальные переменные и типы данных:
// Состояние зарядного устройства
typedef enum {
NOCHG,
CHG,
CHGEND
} tChgState;
tChgState oldChargeStatus = NOCHG // Переменная для хранения предыдущего состояния
// зарядного устройства между вызовами функции
uint8_t minBatPercent = 100; // Минимальное и максимальное значения
uint8_t maxBatPercent = 0; // уровня заряда батареи
// Код следующих двух функций я не привожу, так как он привязан
// к реализации конкретного устройства в железе.
tChgState getChargeState(void)
{
// Здесь мы определяем состояние зарядного устройства
.
.
.
}
uint16_t getBatVoltage()
{
// А здесь запрашиваем АЦП и вычисляем значение напряжения на батарее в милливольтах
.
.
.
}
uint8_t batPercent(uint16_t voltage)
{
tChgState chargeStatus = getChargeState();
uint16_t emptyBatVoltage = 3400;// Напряжение, соответствующее полностью
// разряженной батарее
uint8_t slope = 6; // 6 мВ/%
if(chargeStatus == CHG) // При заряде напряжение возрастает, учитываем это
emptyBatVoltage = 3600;
int8_t result = (voltage - emptyBatVoltage) / slope;
if(result < 0) result = 0; // Уровень заряда не может оказаться меньше нуля
if(result > 100) result = 100; // и больше 100%.
// Ищем минимум и максимум и сохраняем их в глобальных переменных для
// использования при следующем вызове
if(minBatPercent > result) minBatPercent = result;
if(maxBatPercent < result) maxBatPercent = result;
if(chargeStatus != oldChargeStatus) // При изменении состояния зарядного устройства
{ // начинаем заново с чистого листа.
minBatPercent = result;
maxBatPercent = result;
}
if(maxBatPercent - minBatPercent > 20) // Защита от особо сильных помех
{
minBatPercent = result;
maxBatPercent = result;
}
oldChargeStatus = chargeStatus; // Перед окончанием сохраняем текущее состояние ЗУ
// И, наконец, возвращаем максимальное значение, если идет заряд
// или минимальное -- если идет разряд.
if(chargeStatus == CHG)
{
return maxBatPercent;
}
else
{
return minBatPercent;
}
}
Далее мы в удобном месте вызываем функцию batPercent, скажем, раз в секунду, и то, что она вернула, передаем в код, рисующий батарейку.
* * *
Вот и все. Теперь никаких ненужных колебаний и шевелений, индикатор аккумулятора стоит, как вкопанный, не забывая, впрочем, убавляться по мере разряда. Данный способ, конечно, не претендует на точность измерения остатка заряда, но это обычно и не требуется. При необходимости, конечно, можно усложнить код, добавив в него учет температуры, использовав вместо линейной интерполяции более сложную и точную.
Автор:
jar_ohty