Measuring internal resistance on SPOT battery

In the battery blog I did, I mentioned using a java program to measure internal resistance of the battery. I updated the program and will post the snippets here.

The internal resistance of a battery increases with cold, age and power cycles. This limits its ability to provide high currents and eventually make the cell unusable. Most of the increase over age is from oxidation of the electrodes. The higher the internal resistance, the higher are both the charging and discharging losses of the cell.

There are two methods of measuring internal resistance. The first method places a small AC signal, about 1KHz, across the cell and measures the in-phase AC current. This yields Z = E/I, where E is the AC voltage amplitude and I is the measured AC current. The second method is a DC load test. Measure the battery voltage and discharge current under both light and heavy loads. The internal resistance is then R = DE/ DI. The AC measurement is usually an order of magnitude less than the DC load method and can provide more information of the condition of the battery. 

The program I wrote uses the DC load test and requires two SPOTs. One is the SPOT-under-test (SPOUT?) and a basestation that prints the results to a terminal emulator (or ant echo). It uses the 802.15.4 radio to communicate its results. Each SPOT is running a different application. IntresSensor.java goes on the SPOUT and intresHost.java goes on the basestation. I used the latest RED release; however, this should work for BLUE. SPOUT requires an eDemo board and battery.

The SPOUT goes to sleep for sixty seconds, wakes up and takes a low current reading. SPOUT cranks up the current by turning on the radio and set LEDs to full maximum for another sixty seconds. It takes a second reading and transmits the result to the basestation.

The basestation prints the current and voltage measured, calculates the internal resistance and prints it. The code is provided as a snippet, without imports, pauseApp() nor destroyApp() methods. Using a basic SPOT template will provide a skeleton and Netbeans or Eclipse have a means of finding the imports.

Radio Stuff

Most of the work I’ve done on the radio has been low-level stuff – real low-level, for testing and compliance. This was my chance to use some of the basic Radio libraries like RadioDatagram. I wanted to keep this simple, and didn’t need Lowpan or mesh stuff. I did a dirt simple discovery method. I didn’t put any retries, back off strategies, etc. and this may break with a lot of SPOTs around.

The basestation broadcast a message containing an arbitrary string, appName. When the SPOUT receives the broadcast, it sends all subsequent messages to that basestation address. The basestation stops broadcast once it starts receiving packets addressed to itself. More than one SPOT can send to the basestation and the address will be printed with the sensor data. If this is used in a classroom, each student could assign appName to a unique “username” to pair the SPOTs.

SPOUT Code

public class intresSensor extends MIDlet {
  private static final byte PORT = (byte) 93;
  private IBattery battery;
  private ITriColorLED[] leds;
  private static final String appName = "intres";
  private String dstAddress = null;
  private void sendSensor() {
    int sleep_discharge;
    int sleep_vbatt;
    int run_discharge;
    int run_vbatt;
    IPowerController pctrl = null;
    RadiogramConnection rc = null;
    Datagram dg = null;
    try {
      // open a direct connection between this SPOT and the basestation
      // dstAddress is the basestation discovered by lookForHost
      rc = (RadiogramConnection) Connector.open("radiogram://" + dstAddress + ":"+PORT);
      dg = rc.newDatagram(rc.getMaximumLength());
      rc.setRadioPolicy(RadioPolicy.AUTOMATIC);
    } catch (IOException e) {
      System.out.println("Could not Send");
      return;
    }
    pctrl = Spot.getInstance().getPowerController();
    leds = EDemoBoard.getInstance().getLEDs();
    battery = Spot.getInstance().getPowerController().getBattery();
    for(int i = 0; i < 8; i++) {
      leds[i].setColor(LEDColor.WHITE);
      leds[i].setOff();
    }
    while(true) {
      // can do this test only when discharging
      if (battery.getState() == IBattery.DISCHARGING) {
        // turn everything off
        Spot.getInstance().getGreenLed().setOff();
        Spot.getInstance().getRadioPolicyManager().setRxOn(false);
        for(int i = 0; i < 8; i++) {
          leds[i].setOff();
        }
        // this should go into deep sleep but it doesn't
        // there is a bug prob with radio stuff keeping it awake
        Utils.sleep(60000); // 60 sec
        sleep_discharge= pctrl.getIdischarge();
        sleep_vbatt = pctrl.getVbatt();
        //   turn everything on
        Spot.getInstance().getGreenLed().setOn();
        Spot.getInstance().getRadioPolicyManager().setRxOn(true);
        for(int i = 0; i < 8; i++) leds[i].setOn();
        for(int i = 0; i < 600; i++) Utils.sleep(100); // 60 secs
        run_discharge = pctrl.getIdischarge();
        run_vbatt = pctrl.getVbatt();
        try {
          // transmit our data
          dg.reset();
          dg.writeInt(sleep_discharge);
          dg.writeInt(run_discharge);
          dg.writeInt(sleep_vbatt);
          dg.writeInt(run_vbatt);
          dg.writeInt(battery.getBatteryLevel());
          rc.send(dg);
        } catch (IOException e) {
          System.out.println("Could not Send");
        }
      }
    }
  }
  private boolean lookForHost() {
    RadiogramConnection rc = null;
    Datagram dg = null;
    long adr;
    String name;
    dstAddress = null;
    // open a connection to listen for broadcast
    // PORT needs to match between sender and receiver
    try {
      rc = (RadiogramConnection) Connector.open("radiogram://:" + PORT);
      dg = rc.newDatagram(rc.getMaximumLength());
    } catch (IOException e) {
      System.out.println("Could not open receiver");
      return false;
    }
    while(true){
      try {
        dg.reset();
        rc.receive(dg);
        name = dg.readUTF(); // UTF is a java String
        // message sent by broadcast is an arbitrary string
        // appName could be changed to a students name to pair
        // SPOTs in a classroom
        if (name.equals(appName)) {
          dstAddress = dg.getAddress();
          System.out.println("Host found: " + dstAddress);
          return true;
        }
      } catch (IOException e) {
        System.out.println("Nothing received");
        return false;
      }
    }
  }

  protected void startApp() throws MIDletStateChangeException {
    // Listen for downloads/commands over USB connection
    new com.sun.spot.util.BootloaderListener().start();
    // Make sure this SPOT has an eDemo board
    if (Spot.getInstance().getExternalBoardMap().isEmpty()) {
      System.out.println("Requires eDemo board");
    } else {
      // do discovery and if successful, the main loop
      if (lookForHost()) sendSensor();
    }
  }

Host Code

public class intresHost extends MIDlet {
  private static final byte PORT = (byte) 93;
  private static final String appName = "intres";
  private IBattery battery;
  private boolean discovery;
  private ILed greenLite = Spot.getInstance().getGreenLed();
  // broadcast thread
  synchronized private void sendBroadcast() {
    new Thread() {
      public void run() {
        discovery = true;
        RadiogramConnection rc = null;
        Datagram dg = null;
        try {
          // open a broadcast connection
          rc = (RadiogramConnection) Connector.open("radiogram://broadcast:"+PORT);
          dg = rc.newDatagram(rc.getMaximumLength());
        } catch (IOException e) {
          System.out.println("Could not broadcast");
          return;
        }
        discovery = true;
        while(discovery){
          try {
            // Send appName (UTF encoded)
            dg.reset();
            dg.writeUTF(appName);
            rc.send(dg);
            // flash the LED slowly indicate broadcast
            // stops flashing when SPOTs are sending data back
            greenLite.setOn(!greenLite.isOn());
          } catch (IOException e) {
            System.out.println("Could not broadcast");
          }
          Utils.sleep(500);
        }
        greenLite.setOff();
      } // run
    }.start();
  }
  // receiver thread
  private void receiveSensor() {
    new Thread() {
      public void run() {
        double dv;
        double di;
        int ir;
        int sleep_discharge = 0;
        int sleep_vbatt = 0;
        int run_discharge = 0;
        int run_vbatt = 0;
        int batt_level = 0;
        RadiogramConnection rc = null;
        Datagram dg = null;
        try {
          // open port to receive
          rc = (RadiogramConnection)
           Connector.open("radiogram://:" + PORT);
          dg = rc.newDatagram(rc.getMaximumLength());
        } catch (IOException e) {
          System.out.println("Could not open receiver");
          e.printStackTrace();
          return;
        }
        while(true) {
          try {
            // receive the data packet
            dg.reset();
            rc.receive(dg);
            sleep_discharge = dg.readInt();
            run_discharge = dg.readInt();
            sleep_vbatt = dg.readInt();
            run_vbatt = dg.readInt();
            batt_level = dg.readInt();
            discovery = false;
          } catch (IOException e) {
            System.out.println("Nothing received");
          }
          // multiple SPOTs can send to this basestation
          // print the sensor SPOT address to identify
          System.out.println("Device: " + dg.getAddress());
          System.out.println("Battery Level: " + batt_level + "%");
          System.out.println("Low: " + sleep_discharge + "mA " + sleep_vbatt + "mV");
          System.out.println("High: " + run_discharge + "mA " + run_vbatt + "mV");
          // find the delta volts/delta current for internal resistance
          dv = ((double) sleep_vbatt - run_vbatt);
          di = ((double) run_discharge - sleep_discharge);
          if (di > 0 && dv > 0) {
          // internal resistance is dv/di
          // remove the sense resistance (0.1ohm) contribution
          // convert to milliohms and round (why? double to string isn't formatted)
            ir = (int) (((dv/di - 0.1)\*1000.0) + 0.5);
            System.out.println("Battery Resistance: " + ir + " milliohms");
          }
        } // while
      } // run()
    }.start();
  }

  protected void startApp() throws MIDletStateChangeException {
    // listen for USB
    new com.sun.spot.util.BootloaderListener().start();
    // start broadcast thread
    sendBroadcast();
    // start receive thread
    receiveSensor();
  }

Comments:

Hi,

Sorry, slightly off-topic but not too much ;-)

I have been studying the schematics and saw that the LTC3455 has V_ON2 and V_MODE connected to V_MAX.

According to the data sheet this would cause a leakage of 110uA if not switching. The manuals say the total leakage is 33uA (deep sleep). From reading the data sheets and schematics I estimated (so far):
oscillator ca. 1uA
current monitors 2x4uA
avr 9uA
LDO for avr 1.2uA+5uA
Switching voltage regulator: 2uA (lowest) or the 110uA??

What does the SunSPOT really need in deep sleep mode?

Thanks
Dominic

Posted by Dominic on July 01, 2009 at 08:27 PM PDT #

Hi, The spec for quiescent current with Von and Vmode high isn't leakage current, parts of this chip are still active. Go a few lines down and take a look at Shutdown. Vpwron is 0V and the quiescent current is 2uA to 4uA. During deep sleep, we deassert pwron reducing the current of this device to slightly more than 2uA. I think by far this is one of Linear Tech's best parts because of this and we are continuing to use it.

We have measured deep sleep in the lab with a calibrated Agilent 34410A at the battery to be 33uA. The manufacture test fixture is capable of measuring deep sleep and tests each of 25,000 SPOTs to be within 15% of 33uA.

Posted by Bob Alkire on July 09, 2009 at 07:03 PM PDT #

Vpwron is 0V and the quiescent current is 2uA to 4uA. During deep sleep, we deassert pwron reducing the current of this device to slightly more than 2uA. I think by far this is one of Linear Tech's best parts because of this and we are continuing to use it.

Posted by laptop batteries on April 01, 2010 at 01:10 PM PDT #

we deassert pwron reducing the current of this device to slightly more than 2uA. I think by far this is one of Linear Tech's best parts because of this and we are continuing to use it.

Posted by laptop batteries on April 05, 2010 at 06:38 PM PDT #

Go a few lines down and take a look at Shutdown. Vpwron is 0V and the quiescent current is 2uA to 4uA. During deep sleep, we deassert pwron reducing the current of this device to slightly more than 2uA.

Posted by laptop battery on April 05, 2010 at 06:39 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

user12611170

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today