Title:     Battery charger (German: Akku Lader)
Author:    (c) 2006-2008 by Malte Marwedel
Date:      2008-05-07
Version:   Final 1.0
Purpose:   Charging of NIMH batteries, Source of constant current
Software:  avr-gcc: Older versions were tested with 3.4.3
                    This version was tested with 4.1.0 and 4.2.2
           avr-libc: 1.4.5 and 1.4.7
Hardware:  ATMEGA168 with 8MHZ
Mail:      m.marwedel <AT> onlinehome.de
           I am always like to hear if someone has interests in my projects.
Homepage:  www.marwedels.de/malte
License:   GNU General Public License Version 2
           see gpl-license.txt

===================== Overview =================================================
This project implements a charger for small and medium sized NIMH batteries. The
charger has the ability to discharge batteries wile measuring the capacity
as well. The charging is done with simple constant current. The charger has the
ability to charge single cells or battery packs with up to 8 cells in series.
A LCD displays the charging parameters. Moreover the program transmits the
current and voltage over the RS232 connector to make it able to plot the values
as graph.

===================== Technical data ===========================================
Maximum input voltage: 19.5V
Minimum input voltage: 7V
Maximum charging current: 2.4A theoretically, tested up to 700mA
Minimum charging current: 5mA
Annoying-do-not-know-where-it-comes-from charging current offset: ~2.1mA
Relative measurement resolution: +-2mV, +-2mA
Absolute measurement resolution: +-20mV, +-20mA (theoretical)
                                 < 5% if calibrated (practical)
Accuracy of setted charging/discharging current: +-10% (depending on resistor
                                                        accuracy)
Calibration: Values for some of the resistors may be stored in the EEPROM

===================== The charging process =====================================
This charger is intended for NIMH batteries, however NICD batteries have
similar characteristics and will most likely work too, but were never tested.
First there is a choice between slow (14 hours), medium (7 hours) and fast
(3.3 hours) charge.
If using the slow or medium charge speed, it can be chosen if the -deltaV
detection should be on or off. If the charging speed is fast, it is always on.
The charging works simply by applying a constant current to the battery.
Beside the possibility of detecting -deltaV, the charging stops always by the
following events:
1. The charging current is less the half value it should be. (This is the
   case if no battery is connected)
2. The predefined capacity (mAh) has been charged
3. The user presses the back key for aborting the charging.

If you only know that your battery is neither completely full nor completely
empty and discharging is not wanted, set the current full state to 50-60%
and enable the -deltaV detection. To be safe it is best if you observe the
voltage and battery heating from time to time.

WARNING: THE CURRENT SOFTWARE IS NOT ABLE TO CHARGE RECHARGEABLE LITHIUM ION OR
LITHIUM POLYMER BATTERIES IN ANY WAY. IF DONE, THIS WILL MOST LIKELY DAMAGE THE
CELL WHICH CAN RESULT IN BURNING OR EXPLODING. YOU FIND A LOT OF PEOPLE ON THE
INTERNET TO WHICH THIS HAPPENED. The reason is that those type of batteries
have a upper voltage limit which may never ever be crossed.

===================== The charging process -deltaV detection====================
Getting a usable -deltaV detection was not easy, and in the end it does not
always work properly. This is why it can be switched of for slow and medium
speed.
Especially some very old batteries (10 years in my case) had a very strange
developing of the voltage during charging, where the voltage decreases at the
beginning of the charging and did not rise later. This will result in an
immediately charging stop if the -deltaV detection is used.
Moreover, batteries where the last charge was some months ago had a very
fluctuating voltage making a -deltaV detection useless too.
Third the charging process is not interrupted for a short time while measuring
the voltage and the measurement takes place within the charger and not directly
at the batteries contacts. As result there is a voltage drop in the used
wires and connectors because of the resistance. Moving the wires
while the charging is in process often results in small changes of those
resistance.
Because the current stays constant, the voltage on the charger output changes
instead. This can be enough to trigger a -deltaV detection as well.
Fourth, even touching the batteries while charging can be seen in the output
voltage, this could trigger the -deltaV too.

In general the testing of the -deltaV detection were made mostly with the
PC program an recorded charging graphs. In the end the implemented -deltaV
detection consists of four slightly different detectors. Some of them were
only enabled if the battery should be already at least 60 or 70% percent full.
Checking for a -deltaV detection takes place every 30 (fast charge) or
45 seconds (normal and slow charge). In the time between, the voltage is
measured four times per second and the average voltage is calculated.
After the charging had stared, the first 30 seconds are completely ignored
to let the current rise to the wished value.

Please note that the values which were found suitable for a -deltaV detection
were primary tested by charging one or two Mignon cells in series with medium
or fast charge. I hope that the detection works with more cells and higher or
lower capacity as well.
My experience in practice were that the -deltaV detection works in most cases
correctly but on some cells the software always stops the charging much too
early.

Please let me know your experience.

===================== The discharging process ==================================
The maximum discharging current is half the capacity value. Example: a 2Ah
battery will be discharged with a maximum of 1A.
The minimum discharging current is somewhere around 1/12th the capacity value.
The discharging starts with 1/8th the capacity value.
Every second the battery voltage is measured, if the value is below
CAL_CELL_VERYEMPTY_V per cell, the current is decreased by 20%. If the new
discharging current is below the minimum discharging current, the discharging
stops.
If the voltage per cell is above CAL_CELL_LOAD_V, the current is increased by
20% (until the maximum discharging current is reached).
In the case 20% of the discharging current is less than 1mA, the values are
increased or decreased at least by 1mA.

The default values defined in the file calibration_config.h are:
CAL_CELL_VERYEMPTY_V: 900mV
CAL_CELL_LOAD_V: 1050mV

===================== The RS232 interface ======================================
The RS232 interface is used for uploading calibration values and read out the
voltage and current on the output. Moreover the interface shows some debugging
messages. The interface speed is 9600 baud, 8bit, one stop bit.
A message beginning with 'E' are debugging messages:
"E: B   ": The device was reset by the brown out detector. This happens if
           the operating voltage was too low. If this happen if the device was
           powered on, do not worry. If this happen while the charger was
           running, you should check your power supply.
"E: J xy": An invalid jump was done, usually this indicates a programming error.
           xy is a Number which represents the last running task (the one who
           most likely was responsible for the error).
"E: W xy": The watchdog was triggered. This usually indicates a programming
           error like a loop which did not terminate. For xy, see description
           of the previous error message.
"E: S xy": A stack corruption has been detected. xy represents the task which
           has the corrupted stack. Usually this means the PREVIOUS task was
           responsible for this. Note: not every stack corruption can be caught
           or detected, it can happen that the wrong stack results in a invalid
           jump before the stack checking routine got active again. Second,
           if a function allocates stack, but does not initialise all of it,
           this may stay undetected.
           "E: S 00" has the special meaning, that the stack of the thread with
           the lowest stack address had left the field which were initialised
           with the detection pattern, meaning that the stack could run into the
           data (global variables etc).

If the output is active, the output mode, the voltage and the current will be
reported four times per second. The format is:
"I:X;YYYYY;ZZZZ
X: The mode of operation. 1: Probing, 2: Charging, 3: Discharging
Y: The voltage in mV
Z: The current in mA
On probing, Y and Z are always reported as zero.
While starting and stopping a charging or discharging some more statistics are
sent:

On starting: B:cc;SSSSS;CCCCC;PP
B: Begins
cc: Cells: how many the battery(pack) has
SSSSS: Speed: the charging current or started discharging current in mA.
CCCCC: Capacity of the battery in mAh
PP: Charged percent, how full the battery already is at the beginning.

On stopping: T:R;MM;HH;CCCCC;PPP
T: Termination
R: Reason for termination:
    A: Abort by user
    C: charging current was too low (charging only, like battery disconnected)
    P: battery is full (100% percent, charging only)
    D: -dV detection (charging only, deprecated)
    W: -dV detection: weak -deltaV peak (charging only)
    S: -dV detection: strong -deltaV peak (charging only)
    R: -dV detection: Voltage does not rise (charging only)
    L: -dV detection: Voltage is lower than it was (charging only)
    E: Empty (discharging only)
MM: Minutes the process needed
HH: Hours the process needed
CCCCC: How much was charged/discharged in mAh
PPP: Percent the battery is full (charging only, will always reported as 0 after
    a discharge)
While the output is inactive, sending an 'R' to the device causes a reset.

===================== Calibration ==============================================
As long as the output is inactive, sending a 'G' (for get) to the serial port
will return a dump encoded hexadecimal of the current configuration in the RAM.
Sending a 'P' (for put) followed by a 'd' (for data) , the device will wait for
a dump of hexadecimal data which will be programmed into the EEPROM. Each two
received hexadecimal values (representing one byte) will be acknowledgement by
the controller with an 'A'.
For simplicity only capital letters are allowed for the hexadecimal values.
The values will be loaded from the EEPROM to the RAM only after the next reset.
Every data byte is stored two times in the EEPROM. The first half of the dump
are the normal values, the second half the bitwise inverted form of the first
one.
On the start-up the controller uses this to check if the EEPROM may be corrupt
or not.
The calibration values can be read and written with the "Charge Control"
PC application.
BEFORE CHANGING THE VALUES PLEASE NOTE THAT WRONG VALUES MAY RESULT IN A
CHARGER MALFUNCTION AND COULD OVERLOAD THE CHARGER, CAUSING A DAMAGE AND/OR
OVERLOAD THE BATTERIES, CAUSING IN A DAMAGE FOR THE BATTERIES TOO.
See section 'Using the hard & software' below for a way how to restore the
default values without a PC program.

Determine the right values for calibration:
Equipment: A multimeter, a resistor 1 ... 25 Ohm >= with some watts heat
dissipation, a full (two or more cells) rechargeable battery.
If the charger is in the main menu holding the left key starts a simple
calibration interface. The right key brings the charger back to the main menu.
There are two voltages displayed, called the UpperVolage (Uup) and the
LowerVoltage (Ulo).
See the schematic to find out what they measure ;-)
Holding the up key, lets the charger try to deliver/charge 160mA.
Holding the up key and left key at the same time, lets the charger try to
   deliver/charge 700mA.
Holding the down key, lets the charger try to consume/discharge with 160mA.
(Why 160mA? Many multimeters can select between 0.2, 2, 20, ... measurement
ranges)
Please note: The calibration does NOT take many small side effects into account:
Changing of the resistance because of heating.
Changing of the 5V output voltage because of a different input voltage.
Some side effects the oversampling of the A/D converter produces. (high
test currents->a high voltage drop on the resistors->high A/D values
may minimise those effects)
Resistors within the cables and pcb tracks.
Cheap multimeters may not be that good as reference.

WARNING: If you have connected a multimeter in series with the output
measuring the current (eg 200mA range) disconnect it before performing a
reset (to load the new configuration) otherwise you will blow the fuse
of the multimeter on the power up because of the performed self-test.
(I had that two times...).

1. The 5V Voltage of the L7805:
The Output of the voltage regulator can be measured directly, or can be
determined as following: While the output delivers some current (connect a
proper resistor as load to the output), measure the voltage
difference between the negative output and GND (UMeasured), write down at
the same time the shown LowerVolage.
Using only 160mA has shown to get unsatisfied results, it is better to use
the 700mA for this measurement.
If they are equal -> good, nothing has to be changed here.
Otherwise the real L7805 voltage can be calculated as following:
U5VrefNew = UMeasured*U5VrefOld/LowerVoltage;
U5VrefOld is the currently used voltage in the calibration, default: 4950mV
U5VrefNew is the voltage which should be used here next time.
Now adapt all calibrated values to the U5VrefNew value (see step 5, note that
U5VrefNew is part of the Upper Sum factor and Lower Sum factor- use the value
'4' for fNew), write them to the charger and perform a reset.
(They should be used for the next steps).
2. Determine R2
Connect a resistor to the output in series with a multimeter, measuring the
current. Set the output to deliver some current.
Write down the measured mA (named Im) of the multimeter and the LowerVoltage.
The real value for R2 is now: R2=LowerVoltage/Im.
3. Determine R4, R7 voltage division factor
The individual values for R4 and R7 can not be determined here, only the
factor R7/(R4+R7) can be measured.
Here only the factor f=1/(R7*(R4+R7))=(R4+R7)/R7 is needed.
The default values are: R7= 10000Ohm, R4= 30000Ohm => fOld=4.00.
Connect a resistor (good is one where UpperVoltage has at least 4V)
to the output and a multimeter, measuring the voltage between
the positive output and GND. Set the output to deliver some current.
Write down the measured voltage (named Um) of the multimeter and the
UpperVoltage.
To calculate fNew: fNew = Um*fOld/UpperVoltage.
4. Determine R3
Connect a battery to the output in series with a multimeter, measuring the
current. Set the output to consume some current (discharge).
Write down the measured mA (named I2m) of the multimeter and the LowerVoltage.
The real value for R3 is now: R3=LowerVoltage/I2m.
5. Set fNew, R2 and R3 into the charger with the Java GUI
Upper Sum factor = (1/1023)*U5VrefNew*fNew*1000/64
Lower Sum factor = (1/1023)*U5VrefNew*1000/64
Setting R2 and R3 in the GUI should be clear, U5Vref has been setted already
after the first step.

===================== Internals of the AVR software ============================
The software uses a cooperative multitasking scheduler. All calculations are
done without floating points. While using the ATMEGA8, I reached the limits of
the flash memory. I did some optimisations to save flash, however some of them
had unfortunately impacts of the code quality and may not needed with ATMEGA168
any longer.
The internal analogue to digital converter has a 10 bit resolution, where the
LSB represents nearly 20mV. To measure voltages differences lower than this,
64 samples are taken and the average value is calculated. To get high resolution
voltages while charging, the average of up to 2048 values is used.

===================== Compiling the software ===================================
For the AVR software:
go into the 'avr' directory and run make.
If you did not change the source and use the same software versions
than I do, your resulting .hex file
may have the md5sums:
with gcc 4.1.0: avr-libc 1.4.5: f91e6496e1b3b04810ad473d1dfbe0e2
with gcc 4.2.2, avr-libc 1.4.7: a523e677d7b20e1270015cc413b416b9

On an ATMEGA168 there is enough space left for a boot loader, which will
make later software updates a lot easier. Note that if the boot loader
clears the RAM on start-up, this may alter the reported error messages after
a reset due to a watchdog hit or stack overflow.

For the PC Java software:
This software is not required for using the charger, however a comfortable way
to record a charging if wished.
Compiling: Go into 'ChargeControl/src' and run
javac -cp .:../lib/RXTXcomm.jar control/Main.java -d ../bin
Executing Compile.sh runs the same command.

On some operating systems, you may replace '/' in the paths by a '\' and ':' by
';' for all commands above.

===================== Using the AVR hard & software ============================
By general, I hope the menu structure and the use of the up/down/left/right keys
are self-explaining. The up key increases values, the down key decreases values,
the left key goes back and the right key goes to the next screen...
However the keys may sometimes have functions which are not so obviously:
While charging or discharging, the left key aborts this process and with the
up and down key someone can switch the display between showing the current
current (mA) or the current capacity charged/discharged (mAh).
Pressing the up and down keys at the same time while power-up restores the
default calibration values for the EEPROM.
There are five modes which are useful for a battery:
Charging:     Can charge a battery from a selectable percent of charging up to
              100%.
Discharging:  Discharges a battery.
Dis & Charge: First performs a discharge, then a full charge
Measure mAh:  First discharge, then a full charge, followed by a discharge for
              the measurement and the a charge. The results of the last
              discharge and charge can be seen afterwards under the menu point
              battery info (see below).
Battery info: If the last charging or discharging results in one mAh or more
              charged or discharged, these values can be re-viewed here
              before performing a battery test, otherwise the test starts
              immediately. The test tries to determine the internal resistance
              by measuring off-load voltage first and then slowly increase the
              load until the voltage is 10% below the off-load voltage or the
              test current is over 768mA.
The sixth mode provides a simple constant current with a software watched (slow)
maximum output voltage.
If your battery back has more cells than you can select, your input voltage is
not high enough to handle them. Please increase your input voltage.

Please connect the battery only after circuit is powered up and disconnect
before power off. This is needed for two reasons: First on power up, the
software checks input voltage, for doing this an unconnected output is needed.
The software will warn you if the output is connected to something and waits
until you disconnect the output. Second: I discovered that a connected battery
starts powering the device by some unknown way. I don't know if this may result
in a circuit failure.

===================== Charger Control PC software ==============================
The PC software is written in Java. So it is platform independent.
For the serial communication, a library from http://www.rxtx.org/
is used, supporting a lot of platforms.
The software provides a way to record multiple charging and discharging graphs
and save them in an compressed text format.
Moreover the calibration values can be read out and written back.
I hope, using the software is simple. Starting the Software:
On a console change into 'ChargeControl'.
Then run:
java -cp ./bin:lib/RXTXcomm.jar -Djava.library.path=lib control/Main
Under Linux, the ChargeControl.sh and under Win the ChargeControl.bat script
can be used for running.
Then open the right serial port and start charging as usual.
The PC software will automatically open a window with the measured values.

The software can be imported into Eclipse for compiling and running too.
On some operating systems, you may replace '/' in the paths by a '\' and ':' by
';' for all commands above.

A .crf file can be opened by giving the path as first parameter. After the .crf
files have been associated with the program, a charge record file can be opened
simply by clicking on them.

===================== Hardware limits ==========================================
With the the right resistors, the circuit could handle charging currents up to
4.4A. But I did not plan to use the circuit for more than 2.4A.
The measurable input voltage is divided by resistors to fit within the 5V of
the microcontroller. The division factor is 4. As result voltages up to 20V are
measured correctly, above that a diode will prevent the AVR to become damaged,
and the AVR will still measure 20V. The circuit could withstand input voltages
of up to 32V without damage.

===================== Known AVR software limits ================================
I hope that all limits are high enough to be not a problem in practice, since
the hardware is more limited than the software. The software has some
additional easy to change pre-set limits, to prevent overloading the hardware.

Most of the calculation is done with 16 bit unsigned variables. Since volts and
amps are handled in a resolution of milliamps and millivolts, the program could
handle values up to 65.535 volts and 65.535 amps. Batteries may have a capacity
of up to 65.535 Ah. The Ri measurement is calculated with a resolution of
0.01Ohm resulting in a maximum displayed resistor of 655.35 Ohm. The maximum
heat dissipation may be set up to 65.535 watts.

As addition, the displaying of the current is limited to four digits.
Everything above 9.999A is cut off.

The maximum charging/discharging time could be calculated up to 255 hours,
59 minutes and 59 seconds. Displaying the hours is limited to two digits
(99 hours).

===================== Known AVR software bugs ==================================
The Program will crash after running more than 49 days without reset. The reason
for this is that a 32Bit variable is used for timing, which is incremented every
millisecond and after 49 days and some hours the variable will overflow. Meaning
that some timers will never get released (hanging threads) and some other will
be released immediately (thread will run too fast for a short time).
WORKAROUND: Since I do not recommend running the circuit without a person
watching anyway, I do not think such a long uptime will happen in practice.
SIMPLE FIXING: Let the program reset itself after 49 days.
BETTER FIXING: Someone may increase the counter variables in the program from 32
to 64 bit, however, this will make the program a lot larger and it may not fit
within the controller any longer.

If the maximum current the hardware can deliver is lower, than the charge speed
selection calculated to be needed, the battery may not be charged fully.
Imagine the following example:
You battery read:
Capacity: 10Ah. Common charge: 14h @ 1Ah. Fast charge: 1h @ 10Ah
The charger can deliver only 1A but you select fast charge: The charger will
charge 1A (because the hardware can not deliver more) for 10 hours for getting
the selected 10Ah. However for this current 14 hours (ending up with 14Ah)
would have been more appropriate.
WORKAROUND: Watch if the selected charging current is higher than the
current the charger can deliver and select a slower charging speed if it is.

If the charging current is very low the software seems not to detect it if the
battery is disconnected and tries to continue with charging.

================= Possible improvements ========================================
Software is never really finished. So there are things which could be
implemented:
* Charging of Lithium Batteries. However this is dangerous if something goes
  wrong.
* Implement more conditions where charging is stopped
* Actively correct the measured current to the current which is intended by
  software.
* Make the RS232 data parsing of the PC software more robust.

===================== Changelog ================================================
See version-info.txt

--------------------- End of file-----------------------------------------------