Arduino上的简单电池容量测试仪

最近,我开始注意到我的智能手机开始放电更快。搜索软件“吞噬者”并没有带来成果,因此我开始怀疑是否该更换电池了。但是并没有绝对的确定,没有理由要使用电池。因此,在订购新电池之前,我决定尝试测量旧电池的实际容量。为此,决定组装一个简单的电池容量表,尤其是因为这个想法已经酝酿了很长时间-日常生活中有这么多的电池和蓄电池,能够不时进行测试很高兴。



设备操作的基本思想非常简单:有一个充电的电池和一个电阻形式的负载,您只需要测量电池放电期间的电流,电压和时间,然后根据获得的数据计算其容量即可。原则上,您可以使用电压表和电流表进行操作,但是在设备上呆几个小时是一种可疑的乐趣,因此使用数据记录器执行此操作会更轻松,更准确。我将Arduino Uno平台用作此类注册商。

1.方案

在Arduino中测量电压和时间没有问题-有一个ADC,但是您需要一个分流器来测量电流。我想到了将负载电阻本身用作分流器的想法。就是说,知道了上面的电压并事先测量了电阻,我们就可以总是计算出电流。因此,最简单的电路版本将仅由负载和电池组成,并连接至Arduino的模拟输入。但是,当达到电池上的阈值电压时,最好提供一个减载(对于锂离子电池,通常为2.5-3V)。因此,我在电路中提供了一个由数字引脚7通过晶体管控制的继电器。下图是电路的最终版本。



我将电路的所有元素放在一块面包板上,该面包板直接安装在Uno上。作为负载,我使用了0.5毫米厚的镍铬合金螺旋线,其电阻约为3欧姆。这样得出的放电电流值为0.9-1.2A。



2.电流测量

如上所述,电流是根据线圈上的电压及其电阻来计算的。但是值得考虑的是螺旋加热,并且镍铬合金的电阻在很大程度上取决于温度。为了补偿该误差,我仅使用实验室电源就消除了螺旋的伏安特性,并在每次测量前对其进行预热。然后,他在Excel中推导了趋势线的方程式(下图),考虑了加热,该方程式给出了i(u)的相当准确的依赖性。可以看出这条线不是直线。



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秒钟,将有关时间,电池电压,放电电流,以mAh和Wh为单位的电流容量以及电源电压的数据传输到串行端口。电流由第2段中获得的函数计算。当达到阈值电压Voff时,测试终止。
我认为代码中唯一有趣的一点是使用数字滤波器。事实是,当读取电压时,这些值不可避免地会上下跳动。首先,我试图通过在5秒钟内简单地进行100次测量并取平均值来减少这种影响。但是结果仍然令我不满意。在搜索过程中,我遇到了这样的软件过滤器。它以类似的方式工作,但不是取平均值,而是将所有100个测量值按升序排序,选择中心10个并计算它们的平均值。结果给我留下了深刻的印象-测量波动完全停止了。我决定用它来测量内部参考电压(代码中的readVcc函数)。

5.结果

只需单击几下,即可来自串行端口监视器数据导入Excel,如下所示:



接下来,可以很容易地建立电池放电图:



对于我的Nexus 5,声称的BL-T9电池容量为2300 mAh。由我测得-2040 mAh,放电最高可达2.5V。实际上,控制器几乎不允许电池坐到如此低的电压,极有可能是3V的阈值。在这种情况下,容量为1960 mAh。一年半的电话服务导致容量减少约15%。购买新电池后,决定推迟。
借助该测试仪,其他几节锂离子电池也已放电。结果看起来很现实。新电池的实测容量与声明的容量一致,偏差小于2%。
该测试仪也适用于金属氢化物手指电池。在这种情况下,放电电流约为400 mA。

Source: https://habr.com/ru/post/zh-CN389105/


All Articles