Простой тестер ёмкости аккумуляторов на Arduino

в 9:37, , рубрики: arduino, diy или сделай сам, Li-Ion, аккумулятор, емкость, тестер

В последнее время я начал замечать, что мой смартфон стал разряжаться быстрее. Поиски программного «пожирателя» энергии плодов не принесли, поэтому стал задумываться, не пришло ли время заменить АКБ. Но абсолютной уверенности в том, что причина в батарее не было. Поэтому прежде чем заказывать новый аккумулятор решил попробовать измерить реальную емкость старого. Для этого было решено собрать простой измеритель емкости АКБ, тем более что идея эта вынашивалась уже давно – уж очень много батареек и аккумуляторов окружает нас в повседневной жизни, и было бы неплохо иметь возможность время от времени тестировать их.

Сама идея, лежащая в основе работы устройства, крайне проста: есть заряженный аккумулятор и нагрузка в виде резистора, нужно лишь измерять ток, напряжение и время в ходе разряда АКБ, и по полученным данным рассчитать его емкость. В принципе, можно обойтись вольтметром и амперметром, но сидеть за приборами несколько часов удовольствие сомнительное, поэтому намного проще и точнее можно сделать это используя регистратор данных. Я в качестве такого регистратора использовал платформу Arduino Uno.

1. Схема

С измерением напряжения и времени в Arduino проблем нет – есть АЦП, но чтобы измерить ток нужен шунт. У меня появилась идея использовать сам нагрузочный резистор в качестве шунта. То есть, зная на нем напряжение и предварительно измерив сопротивление, мы всегда можем рассчитать ток. Поэтому простейший вариант схемы будет состоять лишь из нагрузки и АКБ, с подключением к аналоговому входу Arduino. Но было бы неплохо предусмотреть отключение нагрузки по достижению порогового напряжение на батарее (для Li-Ion это обычно 2,5-3В). Поэтому я предусмотрел в схеме реле, управляемое цифровым пином 7 через транзистор. Конечный вариант схемы на рисунке ниже.

Простой тестер ёмкости аккумуляторов на Arduino - 1

Все элементы схемы я разместил на кусочке макетной платы, которая устанавливается прямо на Uno. В качестве нагрузки использовал спираль из нихромовой проволоки толщиной 0,5мм, имеющей сопротивление около 3 Ом. Это дает расчетное значение тока разряда 0,9-1,2А.

Простой тестер ёмкости аккумуляторов на Arduino - 2

2. Измерение тока

Как было сказано выше ток рассчитывается исходя из напряжения на спирали и её сопротивления. Но стоит учесть, что спираль нагревается, а сопротивление нихрома довольно сильно зависит от температуры. Чтобы компенсировать ошибку я просто снял вольт-амперную характеристику спирали, используя лабораторный блок питания и давая ей прогреться перед каждым измерением. Далее вывел в Excel уравнение линии тренда (график ниже), которое дает довольно точную зависимость i(u) с учетом нагрева. Видно, что линия не прямая.

Простой тестер ёмкости аккумуляторов на Arduino - 3

3. Измерение напряжения

Поскольку точность данного тестера напрямую зависит от точности измерения напряжения, я решил уделить этому особое внимание. В других статьях уже неоднократно упоминали метод, позволяющих наиболее точно измерять напряжение контроллерами Atmega. Повторю лишь вкратце – суть состоит в определении внутреннего опорного напряжения средствами самого контроллера. Я пользовался материалами данной статьи.

4. Программа

Код не представляет из себя ничего сложного:

Текст программы

#define A_PIN 1
#define NUM_READS 100
#define pinRelay 7

const float typVbg = 1.095; // 1.0 -- 1.2
float Voff = 2.5; // напряжение выключения
float I;
float cap = 0;
float V;
float Vcc;
float Wh = 0;
unsigned long prevMillis;
unsigned long testStart;

void setup() {
  Serial.begin(9600);
  pinMode(pinRelay, OUTPUT);
  Serial.println("Press any key to start the test...");
  while (Serial.available() == 0) {
  }
  Serial.println("Test is launched...");
  Serial.print("s");
  Serial.print(" ");
  Serial.print("V");
  Serial.print(" ");
  Serial.print("mA");
  Serial.print(" ");
  Serial.print("mAh");
  Serial.print(" ");
  Serial.print("Wh");
  Serial.print(" ");
  Serial.println("Vcc");
  digitalWrite(pinRelay, HIGH);
  testStart = millis();
  prevMillis = millis();
}

void loop() {
  Vcc = readVcc(); //считывание опорного напряжения
  V = (readAnalog(A_PIN) * Vcc) / 1023.000; //считывание напряжения АКБ
  if (V > 0.01) I = -13.1 * V * V + 344.3 * V + 23.2; //расчет тока по ВАХ спирали
  else I=0;
  cap += (I * (millis() - prevMillis) / 3600000); //расчет емкости АКБ в мАч
  Wh += I * V * (millis() - prevMillis) / 3600000000; //расчет емкости АКБ в ВтЧ
  prevMillis = millis();
  sendData(); // отправка данных в последовательный порт
  if (V < Voff) { //выключение нагрузки при достижении порогового напряжения
    digitalWrite(pinRelay, LOW);
    Serial.println("Test is done");
    while (2 > 1) {
    }
  }
}

void sendData() {
  Serial.print((millis() - testStart) / 1000);
  Serial.print(" ");
  Serial.print(V, 3);
  Serial.print(" ");
  Serial.print(I, 1);
  Serial.print(" ");
  Serial.print(cap, 0);
  Serial.print(" ");
  Serial.print(Wh, 2);
  Serial.print(" ");
  Serial.println(Vcc, 3);
}


float readAnalog(int pin) {
  // read multiple values and sort them to take the mode
  int sortedValues[NUM_READS];
  for (int i = 0; i < NUM_READS; i++) {
    delay(25);
    int value = analogRead(pin);
    int j;
    if (value < sortedValues[0] || i == 0) {
      j = 0; //insert at first position
    }
    else {
      for (j = 1; j < i; j++) {
        if (sortedValues[j - 1] <= value && sortedValues[j] >= value) {
          // j is insert position
          break;
        }
      }
    }
    for (int k = i; k > j; k--) {
      // move all values higher than current reading up one position
      sortedValues[k] = sortedValues[k - 1];
    }
    sortedValues[j] = value; //insert current reading
  }
  //return scaled mode of 10 values
  float returnval = 0;
  for (int i = NUM_READS / 2 - 5; i < (NUM_READS / 2 + 5); i++) {
    returnval += sortedValues[i];
  }
  return returnval / 10;
}


float readVcc() {
  // read multiple values and sort them to take the mode
  float sortedValues[NUM_READS];
  for (int i = 0; i < NUM_READS; i++) {
    float tmp = 0.0;
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    ADCSRA |= _BV(ADSC); // Start conversion
    delay(25);
    while (bit_is_set(ADCSRA, ADSC)); // measuring
    uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
    uint8_t high = ADCH; // unlocks both
    tmp = (high << 8) | low;
    float value = (typVbg * 1023.0) / tmp;
    int j;
    if (value < sortedValues[0] || i == 0) {
      j = 0; //insert at first position
    }
    else {
      for (j = 1; j < i; j++) {
        if (sortedValues[j - 1] <= value && sortedValues[j] >= value) {
          // j is insert position
          break;
        }
      }
    }
    for (int k = i; k > j; k--) {
      // move all values higher than current reading up one position
      sortedValues[k] = sortedValues[k - 1];
    }
    sortedValues[j] = value; //insert current reading
  }
  //return scaled mode of 10 values
  float returnval = 0;
  for (int i = NUM_READS / 2 - 5; i < (NUM_READS / 2 + 5); i++) {
    returnval += sortedValues[i];
  }
  return returnval / 10;
}

Каждые 5 секунд данные о времени, напряжении батареи, токе разряда, текущей емкости в мАч и ВтЧ, а также напряжении питания передаются в последовательный порт. Ток рассчитывается по полученной в п. 2 функции. По достижении порогового напряжения Voff тест прекращается.
Единственным, на мой взгляд, интересным моментом в коде я бы выделил использование цифрового фильтра. Дело в том, что при считывании напряжения значения неизбежно «пляшут» вверх-вниз. Сначала я пытался уменьшить этот эффект просто сделав 100 измерений за 5 секунд и взяв среднее. Но результат по-прежнему меня не удовлетворил. В ходе поисков я наткнулся на такой программный фильтр. Работает он похожим образом, но вместо усреднения он сортирует все 100 значений измерений по возрастанию, выбирает центральные 10 и высчитывает среднее из них. Результат меня впечатлил – флуктуации измерений полностью прекратились. Я решил использовать его и для измерения внутреннего опорного напряжения (функция readVcc в коде).

5. Результаты

Данные из монитора последовательного порта в несколько кликов импортируются в Excel и выглядят следующим образом:

Простой тестер ёмкости аккумуляторов на Arduino - 4

Далее легко построить график разряда АКБ:

Простой тестер ёмкости аккумуляторов на Arduino - 5

В случае с моим Nexus 5 заявленная ёмкость аккумулятора BL-T9 – 2300 мАч. Измеренная мной – 2040 мАч при разряде до 2,5 В. В реальности контроллер вряд ли позволяет сесть батарее до такого низкого напряжения, скорее всего пороговое значение 3В. Ёмкость в этом случае 1960 мАч. Полтора года службы телефона привели к просадке емкости примерно на 15%. С покупкой новой АКБ было решено повременить.
С помощью данного тестера было разряжено уже несколько других Li-Ion аккумуляторов. Результаты выглядят очень реалистично. Измеренная емкость новых АКБ совпадает с заявленной с отклонением менее 2%.
Данный тестер подойдет и для металл-гидридных пальчиковых аккумуляторов. Ток разряда в этом случае составит около 400 мА.

Автор: unnk2004

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js