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();
}
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 02, 2009 at 03:27 AM 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 10, 2009 at 02:03 AM PDT #