Roughly around this time last year I first started experimenting with an Arduino based programmable current source, trying to regulate the current from an LM2576 switching regulator and failing miserably in my first attempts. Back then the programmable NiMh charger was just a concept I had come up with when finishing up one of my blogs on a similar subject, the device that I envisioned would be able to charge a single AA 1.2 V NiMh cell upto 12 AA NiMh cells in series and with a maximum regulated current output of 2.2 A and a minimum current output of 100 mA for slow timed charging needs.

There was no plan from the beginning and the venture started off as an experiment rather than a serious attempt at a complete prototype, the LM2576 decision has been criticized from the very start, as a MOSFET seemed like a suitable and simple option for current regulation as opposed to the messy controlling scheme for an LM2576, but like I said there was no plan and the part selection wasn’t planned either, I took one thing at a time and addressed issues as they came up, when the LM2576 scheme started to yield promising results I started concentrating all my efforts towards improving that design, I implemented a PID controller, bypassed the feedback mechanism of the LM2576 through software to get the desired result, it took a while before I got the PID steady state error within 1% tolerance of the set point, and I am pretty sure that even now with better component choice this can be reduced further.

The PID current controller is an integral part of the whole concept as the term “programmable” demands for a circuit that can regulate the output current once a set point is passed to the software, the rest of the building blocks include a means of interacting with the charger and a controller that implements a smart charge termination method using the negative delta V charge termination algorithm. Apart from this, as we’ll go along we’ll see the necessity and implementation of certain safety and self diagnostic features I built into the final prototype to help the user troubleshoot faults and determine the state of the charger and probably save the components involved from permanent malfunction.

The PID current controller and its evolution into it’s final state is covered in great detail in the following posts that I have published leading up to this,

Programmable Switching Current Source Using Arduino as a PID Controller

Making the PID Controlled Current Source Better

Improving and Porting the PID Controlled Current Source Design Over to an ATtiny85

The PID controller still remains an ATtiny85 based module as was last published, in fact I am using the same board for this final prototype since I was rushing to complete this project, not much has changed, the only addition has been in the software, lets discuss the individual segments of the NiMh programmable charger project to achieve a better understanding of whats lies ahead.

 

Changes In the ATtiny85 PID Controlled Programmable Current Source

To understand the small changes in the PID controller this time around lets just look at a crude paint drawn basic block diagram of the system we have on our hands.

Universal charger
Basic Block Diagram

The PID current source works largely independent of the master charge controller, once power is applied to the system the PID block initializes and goes into a constant state of waiting, the master controller knows of this state through the status signal which is either a logic LOW or HIGH coming from the PID controller to the master charge controller, this signal is also shared by the LM2676 regulator as an ON or OFF signal for switching to start, once we get to the actual circuit diagram things will become more easy to understand.

A logic LOW read by the master controller tells the master controller that the PID is currently waiting for a set point to be transferred to it, where as a logic HIGH tell the master controller that the PID has received a set point and is ready to start regulating the current once permitted by the master controller.

The communication between the master controller and the PID is a one wire simplex, with the master controller acting as the transmitter and PID as the receiver all the time.

I became interested in implementing a simple one wire simplex protocol for transferring of simple digits once I realized that I was forced to use an external ADC reference for the ATtiny85 PID block to attain a certain level of accuracy, and since the external reference and the SDA pin for I2C communication share a common physical pin on the ATtiny85 therefore I knew I had to look in a different direction, of course I could have used another controller for the PID block with more pins or could have thought of a way to multiplex the functionality where initially the pin would be used for I2C communication and once that is taken care of the pin can then be used for reading external ADC reference, the possibilities are endless with their respective pros and cons but I chose to go down this way and ended up writing up a simple “interrupt counting” protocol which in essence enables the ATtiny85 based PID block to count the number of interrupts on one of its input pins and calculate the time between two consecutive interrupts and based on conditions in the code determines if the received bit change was for a “count up” purpose or was an end of communication signal.

Another crude drawing will help to visualize the concept better,

int counter_1
Basic timing diagram for the interrupt counting protocol

So basically the above diagram implies that if the master is fed in with a current set point of 1.5A, the value will firstly be multiplied by 10, shifting the decimal place and converting the value into a whole number, the master controller will then fire 15 consecutive pulses at the PID pin which is configured to read interrupts, followed by a communication ending bit with a longer time value, the PID block on the other hand is programmed to treat every bit change after a particular time period as count up pulses that adds a one every time to the set point, when a pulse of considerably longer length, for which the limit is already defined in the software, is received the PID considers this as an end of communication signal and then goes about regulating the current from the LM2576, the state of the PID as discussed earlier is known by reading the state of another output pin of the ATtiny85 which is a shared signal for enabling the LM2576 and the status monitoring at the ATMega328P master controller board. Though the actual implementation and calculation in the codes for the master controller and the PID block might differ a little but this largely explains the basic idea behind how the interrupt counting is supposed to work.

Why Monitor The PID Status at The Master Controller?

I did not realize the need for this until the very last stage of the project, the above scheme seems simple and straight forward, and yes it does work rather well but unfortunately power supply noise is a very real phenomenon, specially when working with a noisy SMPS that I am using in this case, it is very likely that the PID receives false interrupts leading to premature activation of the unit resulting in no or very high current flow once the charging starts, since the communication between the PID and the master charge controller is a simplex therefore the only means to monitor the PID status is to monitor the output from the PID which enables the LM2576, in case of a premature activation the master controller can reset the PID once before transferring the set point to it, although the likelihood of a false activation has been greatly reduced by coupling the high frequency noise to ground via a 0.1uF ceramic capacitor.

The PID monitoring is done by the master controller before transferring the set point and during the charge cycle, in case of an ambiguity during the charge cycle the charging is terminated with a dynamic PID error. This is very essential since the two controllers are working largely independent of each other so implementing these checks in the master controller adds an extra degree of control over the process.

 

The ATMega328P based Master Charge Controller

The master controller is basically an off the board Arduino UNO with additional peripherals on the board to support its functionality, the master controller has a few important functionalities, some of which are,

1- Monitors the battery voltage for negative delta V charge termination.

2- Presents the LCD interface to the user for configuration of the charger at start up.

3- Monitoring of the entire process, processing of safety checks, keeping track of errors and  presenting the user with error messages on the LCD to serve as a diagnosis aid, also preventing the battery and charger electronics from damage.

Lets have a look at the simplified block diagram of the master charge controller.

master controller
Basic Block Diagram of The Master Controller & its Different Building Blocks

A few core elements of the master controller are highlighted above, the most important component other than the ATMega328P is the external 15 bit ADC, the ADS1115 which communicates to the micro-controller via the I2C bus plays in integral part in monitoring the battery voltage during charging allowing the master controller to detect a voltage dip of as low as 2mV and that too after having divided the voltage through a voltage divider resistor network. Lets look briefly at each one of these individual components and why these choices were made.

The Negative Delta V Charge Termination & The ADS1115 ADC Module

I have discussed charge termination methods in my very first blog, back then I was using delta T/delta t charge termination method which required a thermistor mounted onto the battery to monitor it’s temperature, however this time around it’s different since I will be charging a range of AA batteries from a single cell of 1.2 volts up to a maximum of 12, 1.2 volt series connected AA cells which counts up to 14.4 Volts. Therefore it was not practical to go with a temperature based charge termination since I would have surely faced issues with thermistor contact at the different range of batteries I wish to use.

The negative delta V is an established charge termination method for the NiMh and NiCad batteries and many commercially available fast chargers use this method of charge termination so I decided to go ahead and experiment with this method.

nickel_chargers_001
Charging Graph of The NiMh Cell

Taking a look back at this picture again helps understand this particular charge termination method, the battery is fed with a constant current of 1C and it’s terminal voltage rises and reaches a peak value, the controller keeps on updating the peak value until a point arrives where the terminal voltage starts to decrease, hence the negative change or the negative delta V occurs, the controller sees this as an end of charge signal and terminates the charging.

For NiMh batteries I selected a safe value of 2mV/cell drop in terminal voltage which will serve as an end of charge signal. However it isn’t that straight forward, the negative delta V charge termination method suffers from the problem of false peaking where an NiMh battery would false peak early on during the charge cycle causing the charge to terminate before the battery is fully charged, however this only seems to happen during the first 10 minute of a charge cycle and especially when the battery was fully drained before the charge cycle, to avoid this problem the charger has a built in function which if enabled will ignore the any peaks that occur during the first 7 minutes of the charging cycle, which is a recommended safe time when employing such peak ignoring features, however not all NiMh suffer from these problems and if your batteries are false peaking a lot which they tend to do near the end of their service life then it’s probably a good idea to replace those batteries.

Lets go back to our charger specs for a second and recall that we are planning to charger batteries from 1.2Volts up to 14.4 volts, and when charging a 14.4V battery pack the terminal voltage during charge can go as high as 18 to 20 volts, since the safe limit for many electronic devices or in this case the ADS1115 ADC is 5 volts therefore it’s understood that the battery voltage has to go to the ADS1115 through a resistor divider network to keep it under safe limits, but why use an external ADC in the first place and why not just work with the built in ADC of the Arduino UNO?

To answer this lets take a look at an example from the actual circuit we will be dealing with.

Capture1
A 1.2V NiMh cell connected across a resistor divider at the moment of having reached it’s peak charged value of 1.8V
Capture2
The terminal voltage falls by 2mV at the end of charge and goes down to 1.798V

The above two Multisim simulations demonstrates that a 2mV drop in the terminal voltage of the battery going through to the ADC (assuming you are using the built in ADC of the Arduini UNO) through the 10K and 2.2K resistor divider network only constitutes to a change of 0.361mV at the micro-controller’s analog input pin. Now for an Arduino UNO’s 10 bit built in ADC the lowest internal voltage reference is of around 1.1V, which means that you can basically read a minimum voltage of, 1.1 / 1024 = 1.074 mV, with which it won’t be possible to detect a voltage change of 0.361mV, of course we can change the resistor values but these values are chosen with the maximum battery voltage during charge in mind, now we can either reduce the reference voltage or increase the ADC bits, the ADS1115 does both for us very conveniently while offering a simple I2C interface for the micro-controller.

The ADS1115 is basically a 16 bit ADC (one bit reserved for voltage polarity, so effectively 15 bits when measuring positive voltage) that offers an I2C interface and has an inbuilt PGA that basically means that there are a number of reference voltages that can be selected depending on the kind of operation. Plus the ADS1115 module that I am using comes in a small package that has a very small footprint and requires no additional external components.

ADS1115
The ADS1115 Module

Luckily there are libraries available from Adafruit for this particular module that allows for easy setting up for a single ended operation using an Arduino.

Talking of programmable references here is a list of reference that can be used for a particular operation with this module.

ADSreference
Specs from the ADS1115 datasheet

The code for the master controller is written such that based on battery voltage selected the master charge controller configures the ADS1115 automatically to use a suitable reference voltage during the entire charge cycle.

Finally in order to provide good noise immunity, the battery voltage is fed into the ADS1115 after being passed through a precision op-amp configured as a voltage follower which then feeds a rather slow response low pass filter, with a 10K resistor and a 2200uf capacitor. I am not happy with the filter design and I feel this is a definite area of improvement and a good design can help reduce the battery temperature once it reaches end of charge, I might work on improving it in the future, however even though right now the measured voltage takes time to catch up to the actual terminal voltage of the battery but this does offer good noise immunity, when I tried experimenting with lower value capacitors the response did improve but this also increased the fluctuation in the measured voltage, any suggestions on this would be welcomed but for now the performance is some what acceptable.

 

The LCD Interface & Button Functions

The aim of the LCD user interface was to present the user with a simple setting up procedure and display important status and error information regarding the state of the charger whenever necessary.

The setting up of a charge cycle demands a minimum of two values, the battery voltage and the current at which the user wishes to charge the battery, the charger can be operated by simply using the four buttons on the front panel as shown below.

IMG_20170523_001839
Not the most elegant of finishes but gets the job done

The user is presented with RESET, CYCLE UP / INCREASE, CYCLE BACK / DECREASE and ENTER buttons, as the name implies the RESET button restarts the charger completely, the CYCLE UP is used to increase, say the battery voltage selection value or run through a menu, similarly the CYCLE BACK decreases the value in a voltage or current selection menu and also serves to enter a hidden service menu when a predefined sequence of operation is followed, and lastly the ENTER button selects an option of interest.

Lets take a moment to briefly look at the interface we are presented with when the charger is first turned ON,

1
The first screen that appears when the charging station is turned ON
2
Charger initializes processes in the background

After the first greeting message the initialization screen displaying the software version is displayed, while this is done, the master charge controller initializes variables, resets the PID controller, carries out depletion of main filter capacitor and checks the Temperature monitoring thermistor mounted on the LM2576’s heat sink (more on this later)

3
Voltage check of the main 24V PSU used for charging exclusively

Afterwards a check is made on the 24V SMPS supply used exclusively for charging only and an indication is forwarded to the LCD that the 24 V supply is above it’s minimum specified level and the charger is ready to proceed. This is important since the master charge controller uses a separate 5 volt power supply which will enable the charger to boot up normally even if the 24 V SMPS is not working, the scheme will become clear once we look at the schematic later on. In case the 24 V supply is below the specified limit an error will be displayed and charging function would be disabled even though the charger can proceed in a test mode for service purposes.

4
Ask the user to connect both charging terminals and press enter

The master controller then asks the user to connect both the charging terminals together and press enter, this is a preparation step for the next battery detection sequence to follow, once enter is pressed the below screen is displayed.

5
Once enter is pressed this screen is displayed for a few seconds, the dots in the second row appear one after the other indicating the sequence is in process and once completed, a sequence completed message is displayed

The charger then instructs the user to connect the battery, +ve to +ve and -ve to -ve this is very important! as currently the charger does not have reverse polarity protection!

6
Battery connection instruction

Once this display is shown the user is expected to connect the battery and press enter, the master controller then goes through a sequence of identifying if the battery is in fact connected or not and while doing so the below screen is displayed.

7
While the master controller is looking for the battery

While this screen is displayed the battery is connected to the internal circuitry with a relay, the in built ADC on the ATMega328P looks for an analog signal at one of it inputs to see if a voltage is present indicating a battery is connected, this scheme works well for a battery that isn’t fully discharged and has some terminal voltage, however for deeply discharged batteries this approach doesn’t work and an error message is displayed indicating that a battery is not found, the charger then asks if the user wants to do an active testing of the battery, if enter button is pressed the battery is fed with 600 mA of charging current and in case a battery is connected a voltage lower than what would normally appear due to the voltage divider in case of no battery connection is read by the controller and is acknowledged as an indication that a battery is present. In both case when the battery is detected the below confirmation screen is displayed followed by an option to select the battery voltage.

19
Battery detection confirmation

In case no battery is detected the master controller assumes the battery is faulty and further operation is not possible, the screen displays that the battery is faulty and a restart must be carried out.

8
Battery voltage selection screen

Once the initial setup is completed successfully the LCD displays a menu to enter the battery voltage of the connected cell, the CYCLE UP or CYCLE DOWN buttons can be used to enter the desired voltages. The voltage can be increased or decreased in 0.1 V intervals by pressing either the cycle up or cycle down button respectively.

9
Working with a 9.6 V battery

Once the voltages are selected the user presses enter and the next screen is shown, which requires the user to enter the desired charging current.

10
Ampere selection screen

The battery current is to be selected in the same way as described above, one can select a minimum of 0.1 A up to a maximum of 2.2 A, it is worth mentioning here that the 0.1 A range is a bit unstable with current’s reaching up to 115mA due to certain constraints with the accuracy of the components used, however operation is reasonable from 0.2 A up to the full capacity current of 2.2 A.

11
Selected 0.9 A current for demonstration purposes

Once the current is selected, pressing enter now will basically start the charging process, before doing so the master controller performs a safety check on the PID controller, transfers the current set point to it, in this case 0.9 A, and starts the charging process, however I feel there should be one more stage of confirmation where all the values are reconfirmed and the user is asked to press enter one more time before charging is started, I might do it in a later version of the code.

12
Once the current is selected and enter is pressed and the charger starts the initiation sequence a series of checks are performed which were too quick for my phone camera to capture, however here is one such screen that displays information about the ADC reference that is selected based on the user provided data
13
Display while charging is under way

The above screen is displayed when the charging has started, the current battery voltage is displayed in the bottom row whereas the battery sign in the top row serves as a rough battery charging meter, the flash sign just to the right of the battery flashes constantly indicating that the charger is running through its sequence loop and hasn’t halted. During the charging process the master controller keeps track of certain status signals of the charger and displays an error if a malfunction occurs, terminating the charging process as well, however we wish that that doesn’t happen.

At this point it is important to mention that the charger by default assumes that the user wants to fast charge the battery and hence use the negative delta V charge termination method that only works when the battery is charged at a rate of above 0.5C therefore entering a lower current value than whats specified will not result in a successful charge termination causing the battery to overheat and causing damage to it, since I always charge my batteries at 1C when fast charging therefore that is something that I don’t have to worry about however I haven’t tested this charger in ranges lower than 1C therefore I am not sure what the performance is going to be like, the delta V will be definitely smaller and harder to detect in that case, so I would highly recommend to always use a 1C rate when fast charging with this charger since the delta V will be flatter and flatter with decreasing charging current or like some NiMh’s the peak might not occur at all and voltage might just flatten out as the end of charge is reached. As a second line of defense I have built into the charger a timer that counts and cuts off the charge after around 1 hour 30 minutes.

As an additional safety feature I believe a voltage max level should at some point be defined in the software for terminating the charge whenever that level of voltage is reached for a particular battery pack to act as another line of defense against over charging. Like I said the software can be altered according to needs and is something that I do wish to do in the future as more and more problems are identified.

However if the user do wish to charge at a slower rate then the charger can be configured for a slow timed charging mode by selecting the relevant option in the hidden service menu.

The hidden service menu offers a few options and can be accessed once the charger goes into battery detection mode and displays, “Looking for batt” on the screen.

7
While on this screen keep the CYCLE DOWN button pressed

Once the charger detects that the battery is connected and displays the battery detected message it looks for the status of the CYCLE DOWN button and if the pin remains HIGH (by pressing the button) for a predefined time a hidden service menu is displayed.

14
The service menu starts by displaying this screen for a few seconds first

After this a series of screens are displayed and the user can simply press CYCLE UP to go to the next option in the service menu, once you leave an option you can not go back to it and in order to get back to that option that charger needs to be restarted and the service menu should be re-entered using the above described procedure.

15
First screen of the service menu

The first screen of the service menu displays the real time value of the 24 V DC supply and serves as diagnostic tool only in case you have encountered a low DC bus voltage error in the beginning, pressing CYCLE UP takes you to the next option.

16
Temperature monitor can be disabled

The temperature monitoring of the LM2576 heat sink can be disabled if required, during the start up of the charger the temperature monitor circuit is probed and if a deviation is recorded it is necessary to disable the temperature monitor to proceed with the charging process, if not disabled the charging wont start.

Under normal circumstances when no initialization error of the temperature monitor is detected, it serves a monitoring purpose displaying a warning on the charging screen (the thermometer symbol as shown above) once the temperature gets above a specified value alerting the user that something might be wrong. If the temperature doesn’t go down after a set interval the charging is terminated with a thermal shutdown error displayed on the screen.

17
Disable peak detection during start for batteries that false peak

The next option in the menu allows the user to disable peak monitoring during the first 7 minutes of a fast charging cycle to ignore false peaking of NiMh batteries and premature charge termination. To remind the user that this option is enabled, the symbol as displayed on the top right hand corner in the above picture is displayed on the charging screen indicating that peaks are being ignored, once the 7 minutes are over the symbol disappears and charger works normally and starts recording peaks.

The above shown option can be selected by pressing enter or the next option can be displayed by pressing CYCLE UP.

18
Slow timed charging option

Once enter is pressed on this option the negative delta V charge termination method is disabled and the user is then directed to enter the number of hours he or she wishes the charge cycle to last for, once this is enabled, on the charging screen the hour glass symbol as shown above and the peak delay symbols are displayed through out the charging cycle to indicate that no peak recording is being done and the charger is operating in a timed charge mode.

It is very important to mention that a high current selection for a timed charging mode can easily destroy the battery as charging will only end once the timer expires and by that time the battery might get overcharged! Hence it is imperative to select a low and safe charging current whenever using this mode of operation.

This was a brief overview of what to expect from the LCD user interface, errors and warnings haven’t been discussed here as most of the errors can’t be recreated easily for documentation purpose however the errors and possible diagnosis will be discussed below later on.

 

The Circuit Diagram

I think it is high time now to look at the actual circuit diagram and see all the components that are involved, I have divided the circuit diagram into three main sections,

1 – The power supply section

2- The PID controlled current source

3 – ATMega328P charge controller and master board

Unfortunately the entire circuit diagram is too big and complicated to be posted as pictures here, to get a better understanding of the diagram a PDF of the three page schematic can be downloaded from here (Circuit Diagram of the Programmable NiMh Battery Charger)

Once you have downloaded the circuit diagram we can now look at some important sections of the schematic diagram and discuss them in detail.

 

The Power Supply Section (Changes in the SMPS Design)

Once you have downloaded the Circuit diagram you can see on the first page that in order to provide power to charge the batteries I am now using an unregulated power as  supply as opposed to my earlier design of a 24 volt regulated power supply using an isolated feedback to shut down the IR2153 chip once the required voltage is reached. However I gave up on that design due to certain problems that faced once I put the circuit together. The power supply generated a lot of audible noise once under heavy load conditions, I faced multiple issues with the output inductor heating up and the MOSFET’s failing suddenly, I have no real explanation for these random behavior but I believe a poor layout was to blame for the condition, plus the shutdown pin on the IR2153 isn’t meant for regulation I believe, instead it shuts down the chip completely as a safety feature, I have yet to probe further regarding these suspicions of mine and I might do it at a later time but in order to complete the project and get a more reliable performance out of the SMPS I decided to go with an unregulated type power supply, using the center tap common line on the ATX transformer and two rectifier diodes in the final stage I was able to get a SMSP voltage output which doesn’t go below 24 volts under maximum desired load of 2.2 A which is just whats needed for this particular operation.

The basic configuration still remains the same the IR2153 is driving two IRF840 MOSFET’s in a half bridge configuration that supplies an ATX transformer primary and from the secondary side after the voltage is stepped down is rectified to get the final voltage. The Output then goes through a Pi filter, capacitors are placed in parallel to get a low effective ESR value which helps reduce ripple on the output, the capacitors bank on the output has a combination of ceramic and electrolytic capacitors, ceramic capacitors are important to suppress high frequency noise and I wish I could place more but this particular combination works fine for this project.

Unregulated SMPS
SMPS Board Layout

To give a basic idea of the kind of board layout I am using for this particular SMPS (since board layout is very important) you can have a look at these pictures.

IMG_20170428_022740
Another view of the SMPS board, the filter caps and the load resistors are on a separate piece of perf board

 

Current Spike Protection at Power Turn OFF (The PID Controller Current Source)

The PID current controller section remains largely unchanged this time around, however at the very end of the project I discovered a problem with the current controller circuit, while I had my multi-meter hooked up for reading the current on the milli-Amps range, as I turned off the charger with my multimeter connected the fuse on the milli-Amps range blew out. In order to diagnose the problem I hooked up the multimeter in the Amps range and turned off the charger and I could see 3 Amps flowing through my load as soon as the charger turned off for a good 1 to 2 seconds which was enough to blow out the 440mA fuse on the mili-Amps range in my multimeter.

It was immediately obvious that when the charger turned off the LM2576 no longer had a HIGH logic on its ON/OFF pin to keep the regulator off and since the 24 V PSU  will most likely have some voltage at it’s output capacitors for some time even after the charger is turned off so with the PID OFF and no feedback to control the LM2576 and ON/OFF pin in a floating state (which is effectively ON) the regulator pushes the maximum allowable current of 3 amps through the load connected at its output. I had to come up with a method to tap the LM2576 regulator after the ATTiny85 PID had turned off and I needed to do so until the 24V SMPS had gone below a certain minimum voltage level which wont be significant enough to push any kind of current through the connected load or in our case, batteries. In the simplest of manners the LM2576 can be pulled up to the supply voltage without a problem and that would have done the job but I wanted to take this opportunity to add to a visual (LED) indication to the whole process which would also help me judge the state of the PID.

InkedCurrent protection_LI
Current Spike Protection Circuit

Above is a section taken from the second page of the schematic in the PID current controller section highlighting the simple solution I came up with to protect the current spike at charger power OFF. I have simply placed an 1N4007 diode is reverse going through a 2.2K resistor and LED combination at the output of the 7400 chip which previously used to drive the LM2576 directly. The LED is an orange colored one with a forward voltage drop of 1.9 V in my particular case, the circuit operates in a rather simple manner, when the 7400 NAND gate IC’s output is HIGH telling the LM2576 to be OFF the diode on the output is reverse biased since the LED’s forward voltage drop is 1.9V, the LED lights up and 1.9V is applied to the LM2576 ON/OFF pin and as the datasheet indicates any voltage above 1.5V turns the LM2576 OFF, now as soon as the 7400 output goes low the diode is forward biased and since TTL is a sinking logic the LED’s Anode gets pulled LOW at around 0.8 Volts turning the LED OFF and the LM2576 ON.

During power OFF since the resistor LED combination is fed by the 24 V SMPS the LM2576’s ON/OFF pin receives 1.9 V even when the power is turned OFF and the capacitor banks on the SMPS has a voltage, during this time you can see the LED is still lit up which keeps the LM2576 OFF and successfully eliminates the current spike problem.

The LED with the largest forward drop is chosen to stay clear of the 1.5 V turn ON limit of the LM2576. Of course the inclusion of the LED is not essential but this serves the purpose of a visual diagnostic option which can come in handy.

 

The LM339 Temperature Threshold Detection Circuit

The LM2576 gets hot when operating at maximum current levels of 2.2 A, for cooling purposes I have mounted a 12 volt fan near the LM2576 however since the temperature can effect the current set point and the chip might heat up to an extent that the LM2576 is damaged especially in case the fan fails, therefore in order to monitor the heat sink temperature of LM2576 I have employed a simple temperature monitor circuit based on an LM339 comparator circuit that can be seen on the third page of the circuit diagram.

temp monitor
The simple temperature monitor circuit interfaced with the master charge controller

The set point for the temperature warning can be set by the 10K trimmer shown above, as soon as the value goes above the set value the LCD screen displays a warning symbol and if the raised temperature persists for a predefined time then the charging is terminated with a Temperature shunt down trigger error. For now I have set the temperature at over 60 degree Celsius.

 

A Brief Look At The Charge Controller

The charge controller as must be evident by now simply controls the charging process and displays relevant data on the LCD screen, however lets just briefly look at the pin functions of each of the utilized pins as it would serve to better understand the hardware features of the master charge controller as well.

Since I am using the ATMega328P which is the same chip that the Arduino UNO comes with and since it’s programmed using the Arduino IDE so its only logical to discuss the pin functionality by referring to these pins as they are labeled in the Arduino UNO board. I won’t go into much details but I have made a rather simple list briefly describing all the pin functions.

pin data charger
Pin Configuration and Function

Well most of whats stated above must be quite clear by now but the only thing I believe that might be a bit confusing right now is the functionality of PIN A3 and why do you need to discharge the main CAP going into the ADS1115 16 bit ADC, well that is for occasions when the ADC’s input exceeds its defined reference voltage (why would this happen in the first place? well more on this later when we discuss the error states the master controller is programmed to detect)

Depleting the capacitor should quickly lower the voltage at the ADC’s pin just to make sure damage is prevented to some extent, although a voltage at the ADC pin is OK as long as its within the supply voltage but overloads of this nature do create an undesirable condition which in my opinion should be cleared up soon.

 

Safety Monitoring, Error States and Critical Charge Termination Errors

This is a rather complex design for my standards, it’s probably the most complicated project I have taken up since the 2 years I have been actively involved in personal projects. I am not sure if any of you are aware but my day job is as a field service technician for Heidelberg Printing presses and I indulge in electrical troubleshooting on a regular daily basis, therefore I have learned to appreciate the need of self diagnostic error messages where the equipment has pre-programmed checks and conditions built into the software to guide the service technician in the direction of the actual fault, so this to me is very important and a lot of inspiration from my daily work has gone into this charger project when it came down to menu designing and trying to build in some degree of self diagnostic functions and error messages that would not only alert the user but most importantly perform steps to safeguard the electronics. There are not many but I have built in a few essential error conditions into the charger which I will discuss briefly.

1- TEMP MONITOR DEFECTIVE!

This is an initialization state error, when the charger starts up it reads the LM339 temperature monitor circuit’s input and if it isn’t HIGH, this error will be displayed, just so that the user know the user can proceed with normal charge cycle only after the Temperature Monitor has been disabled in the Service Mode as discussed before.

2- WARNING! BUS VOLTAGE TOO LOW

This message like the one discussed above is also an initialization error when the charger reads whether the 24VDC bus voltage is present or not, this is essential since even without the 24V the charger can be turned ON since it’s powering through a separate 5 volt supply. If this error occurs the user can only proceed in a test mode where inputs for the battery voltage and current set point can be selected but charging can’t be started. Upon trying to start the charge the process terminates with a “CHARGE NOT POSSIBLE-RESTART”error, this also happens when you try to start a charge without disabling the temperature monitor after the TEMP MONITOR DEFECTIVE error had occurred at initialization.

3- FAULTY BATTERY! RESTART NEEDED

The charger has two modes to detect that a battery has been connected, one is the default passive testing mode and in case that fails for a deeply discharged battery the active test should be performed, during this active testing a current of 600mA is made to flow through the battery and if the controller under this condition detects a voltage above the maximum threshold then this error is generated and charging can no longer work and a restart must be performed.

4- ERROR! – VOLTAGE TOO LOW

In the voltage selection menu if the user presses the enter button while the voltage is at 0.0V then this error is displayed however the user is taken back to the voltage selection menu after displaying the above error for a few seconds.

5- ERROR! – CURRENT VALUE TOO LOW

Same as the one discussed above, if the selected current value is 0.0 A then this message is displayed.

6- PID RUNAWAY ERROR DETECTED!

Before transferring the set point to the PID, the master charge controller checks its status and if an abnormality is detected the above message is displayed, however this message is self clearing and the master charge controller resets the PID and then transfers it the set point to clear out this condition.

7- EXCESS TEMP SHUT DOWN TRIG!

Once the charging cycle starts the master charge controller monitors the state of the temperature monitoring circuit, if a logic LOW is read by the controller then a thermometer warning sign is displayed on the LCD screen, if this conditions persists for a preset time and the temperature doesn’t fall below the limit in the specified time then the charging is terminated with the above error and the process has to be restarted, this particular condition does not immediately trigger a shut down as the output from the LM339 temperature monitor could in certain cases fluctuate due to noise in the system.

8- INTERNAL ERROR! ADC OVERLOAD

If for example the battery is disconnected during charging then the effective load the LM2576 sees due to the voltage divider network that feeds the battery voltage to the ADS1115 is around 12K ohms, this guarantees that close to 24 Volts will appear across the voltage divider and the voltage at the ADS1115’s pin will gradually or in some cases instantly rise above the selected reference voltage overloading the ADS1115, this is an undesirable condition but also represents an opportunity for the charge controller to eliminate charging if any such condition arises due to either human error or system hardware failure. The ADC isn’t supposed to reach it maximum number of increments under any charging combination and when this happens the charging is immediately terminated with the ADC overload error, disconnecting the battery by the internal relay and depleting the filter capacitor feeding the ADS1115.

9- DYNAMIC PID ERROR DETECTED!

During the charge cycle the charge controller repeatedly monitors the status of the PID current controller, if a deviation if found the charging cycle is immediately terminated with the above error and a charge cycle has to be restarted.

Finally every error message is accompanied by a sign at the end that alerts the user about the nature of the message, the below picture shows a typical error along with the error symbol at the end.

Capture3
The LCD showing an error condition with a symbol at the end common for all error messages.

Knowledge of these error messages should make it easy for the user to determine the nature of the problem faced with the charger, in fact after having written all of this I am not entirely sure if I designed this charger keeping in mind the common user. Well I guess complications are always fun.

 

The Final Code & The Arduino Crystal Oscillator Mode

Before we go into the final code, I think it is extremely important to discuss a problem a I faced while testing my design. During my testing I discovered that bringing my hand or finger close to the master controller board would cause the circuit to behave abnormally, the LCD would refresh and would display random characters, the battery connection relay would start clicking, basically the controller would go crazy and only a restart would normalize things.

The Arduino uses Low Power Crystal by default burnt into its fuse bits, this consumes less power but is more prone to malfunction in a noisy environment, in such cases it is recommended to use Full Swing Crystal Oscillator which requires changing of the fuse bits and burning the boot loader again to the Arduino chip.

In order to do proceed with this using IDE version 1.8.1, locate the board.txt file in your computer open the file with text editor and locate,

uno.bootloader.low_fuses=0xFF << lower four bits here select the clock source

uno.bootloader.high_fuses=0xDE

uno.bootloader.extended_fuses=0x05

In order to change to Full Swing Oscillator mode change 0xFF to 0xF7 and reburn the bootloader.

#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include <LiquidCrystal.h>
float peakVoltage = -50.0;
float minimumVoltage = 50.0;
float gainSelect = 0.0;
float voltage = 0.0;
unsigned long timer = 1;
int peakDelay = 0;
byte peakdelayEnable = 0;
int tempMonitor = 0;
byte count = 0;
byte ignore = 0;
byte chargedisable = 0;
byte noTempmon = 0;
byte slowTimed = 0;
int tinyReset = 8;
int cycle = 13;
int enter = 9;
int cycleback = 7;
int capDep = A3;
int battConnect = A2;
float deltaV = 0.0;
float voltset = 0.0;
float currentset = 0.0;
byte tempCount = 0;
byte thermometer[8] =
{
B00100,
B01010,
B01010,
B01110,
B01110,
B11111,
B11111,
B01110
};
byte battery[8] =
{
B00001,
B00001,
B00000,
B01101,
B10010,
B10110,
B11110,
B11110
};
byte peak[8] =
{
B11111,
B01110,
B00100,
B01110,
B11011,
B10001,
B10001,
B11111
};
byte hourGlass[8] =
{
B00000,
B11111,
B10001,
B01110,
B00100,
B01010,
B10101,
B11111
};
byte charging[8] =
{
B00001,
B00010,
B00100,
B01111,
B11110,
B00100,
B01000,
B10000
};

byte errorCode[8] =
{
B01100,
B01100,
B01100,
B01111,
B01111,
B00011,
B00011,
B00011
};
LiquidCrystal lcd(12,11,5,4,3,2);
Adafruit_ADS1115 ads(0x48); /* Use this for the 16-bit version – connect ADDR to GND */
//Adafruit_ADS1015 ads; /* Use thi for the 12-bit version */

void setup(void)
{
byte enterButton [8] =
{
B00001,
B00001,
B00001,
B00101,
B01101,
B11111,
B01100,
B00100
};
long amp = 0;
long longcurrent = 0;
byte cut = 0;
float busvoltage = 0.0;
byte tempchargedisable = 0;
byte meanTime = 0;
pinMode(1,INPUT_PULLUP);
analogReference(DEFAULT);
pinMode(tinyReset,OUTPUT);
digitalWrite(tinyReset, HIGH);
pinMode(6,OUTPUT); //Enable power chip
pinMode(10,OUTPUT); //pwm communication to ATtiny85
digitalWrite(10,LOW);
pinMode(tempMonitor,INPUT_PULLUP);
pinMode(cycle, INPUT);
pinMode(enter, INPUT);
pinMode(cycleback,INPUT);
pinMode(capDep, OUTPUT);
pinMode(battConnect, OUTPUT);
digitalWrite(capDep, HIGH); //Deplets capacitor
lcd.begin(16,2);
lcd.clear();
lcd.createChar(1,thermometer);
lcd.createChar(2,battery);
lcd.createChar(3,peak);
lcd.createChar(4,hourGlass);
lcd.createChar(5,charging);
lcd.createChar(6,enterButton);
lcd.createChar(7,errorCode);
lcd.setCursor(0,0);
lcd.print(” UNIVERSAL NiMh “);
lcd.setCursor(0,1);
digitalWrite(tinyReset, LOW);
lcd.print(“CHARGING STATION”);
delay(2000);
digitalWrite(tinyReset,HIGH);
lcd.clear();
lcd.print(“INITIALIZING..”);
lcd.setCursor(0,1);
lcd.print(“Ver 1.0”);//BETA 3 incorporates 24VDC monitoring and disabling charge if DC bus voltage is lower than 23 volts, also includes active menu in service mode to monitor actual voltage, BETA 4 detects if a battery is connected, doesn’t allow charging for battery voltage lower than 0.1 volts,
delay(2000);//simulate user delay //BETA 5 adds changes to the voltage calculation equation required for the hardware addition of a TL431 precision zener shunt,//BETA 7 improved charge termination, auto ADC reference selection
//digitalWrite(capDep, LOW); //Diabale capacitor depletion
if(digitalRead(tempMonitor) == LOW)
{
delay(1000);
if(digitalRead(tempMonitor) == LOW)
{
lcd.clear();
lcd.print(“TEMP MONITOR”);
lcd.setCursor(0,1);
lcd.print(“DEFECTIVE!”);
errorDisplay();
delay(3000);
tempchargedisable = 1;
}
}
if(analogRead(A0) >= 428) //
{
lcd.clear();
lcd.print(“24 V BUS VOLTAGE”);
lcd.setCursor(0,1);
lcd.print(“LEVEL OK”);
delay(2000);
//digitalWrite(battConnect, HIGH);
digitalWrite(capDep, LOW);
lcd.clear();
lcd.print(“SHORT CHARGING”);
lcd.setCursor(0,1);
lcd.print(“TERMINALS”);
lcd.setCursor(15,1);
lcd.write(6);
while(digitalRead(enter)!= HIGH)
{
delay(300);
}
lcd.clear();
lcd.print(“PREPAIRING….”);
lcd.setCursor(0,1);
int displaySecond = 0;
while(displaySecond <= 15)
{
lcd.setCursor(displaySecond,1);
lcd.print(“.”);
displaySecond++;
delay(500);
}
lcd.clear();
lcd.print(“SEQUENCE”);
lcd.setCursor(0,1);
lcd.print(“COMPLETED!”);
delay(3000);
lcd.clear();
lcd.print(“CONNECT BATTERY”);
lcd.setCursor(0,1);
lcd.print(“& PRESS ENTER”);
while(digitalRead(enter) != HIGH)
{
delay(300);
//waits for user to connect battery and then press enter
}
digitalWrite(capDep, HIGH);
lcd.clear();
lcd.print(“LOOKING FOR BATT”);
lcd.setCursor(0,1);
lcd.print(“?—–|:|:—–?”);
delay(10000);
digitalWrite(capDep, LOW);
digitalWrite(battConnect, HIGH);
delay(4000);
if(analogRead(A1) >= 30)//Battery detection is done by UNO//changed from 15 to 30
{
lcd.clear();
lcd.print(“BATTERY DETECTED”);
lcd.setCursor(0,1);
lcd.print(“OK—-|:|:—-OK”);
delay(2000);
}
else
{
lcd.clear();
lcd.print(“BATT NOT FOUND!”);
lcd.setCursor(0,1);
lcd.print(“—-X-|:|:-X—-“);
delay(2000);
lcd.clear();
lcd.print(“TEST BATTERY?”);
lcd.setCursor(0,1);
lcd.print(“PRESS ENTER”);
while(digitalRead(enter) != HIGH)
{
delay(300);
}
lcd.clear();
lcd.print(“ACTIVE BATT TEST”);
lcd.setCursor(0,1);
lcd.print(“I–>–|:|:–>–I”);
batteryTest();//battery testing procedure
delay(500);
}

}
if(analogRead(A0) < 428)
{
chargedisable = 1;
lcd.clear();
lcd.print(“WARNING! BUS”);
lcd.setCursor(0,1);
lcd.print(“VOLTAGE TOO LOW”);
errorDisplay();
delay(2000);
lcd.clear();
lcd.print(“PROCEED IN TEST”);
lcd.setCursor(0,1);
lcd.print(“OPERATION MODE?”);
while(digitalRead(enter) != HIGH)
{
delay(300);
}
lcd.clear();
lcd.print(“TEST MODE!”);
lcd.setCursor(0,1);
lcd.print(“CAN NOT CHARGE”);
delay(2000);
}
if(digitalRead(cycleback) == HIGH)
{
delay(2000);
if(digitalRead(cycleback) == HIGH)
{
lcd.clear();
lcd.print(” SERVICE MODE”);
//lcd.setCursor(15,0);
//lcd.write(7);
delay(2500);
lcd.clear();
lcd.print(“ACTUAL BUS VOLTS”);
while(digitalRead(cycle) != HIGH)
{
//lcd.clear();
// lcd.print(“ACTUAL BUS VOLTS”);
lcd.setCursor(0,1);
busvoltage = analogRead(A0);
busvoltage = (busvoltage*5.0/1024)*11; //Change power input to 12 volts at Vin
lcd.print(busvoltage);
lcd.setCursor(5,1);
lcd.print(“V”);
delay(200);
}
delay(300);
lcd.clear();
lcd.print(“DISABLE TEMP”);
lcd.setCursor(15,0);
lcd.write(1);
lcd.setCursor(0,1);
lcd.print(“MONITOR?”);
lcd.setCursor(15,1);
lcd.write(6);

while(digitalRead(cycle) != HIGH)
{
/*lcd.clear();
lcd.print(“DISABLE TEMP”);
lcd.setCursor(0,1);
lcd.print(“MONITOR?”);*/
if(digitalRead(enter) == HIGH)
{
lcd.clear();
lcd.print(“TEMP MONITOR”);
lcd.setCursor(0,1);
lcd.print(“DISABLED!”);
tempchargedisable = 0;
noTempmon = 1;
delay(2000);
break;
}
delay(200);
}
delay(300);
lcd.clear();
lcd.print(“ENABLE PEAK”);
lcd.setCursor(15,0);
lcd.write(3);
lcd.setCursor(0,1);
lcd.print(“DELAY AT START?”);
lcd.setCursor(15,1);
lcd.write(6);
while(digitalRead(cycle) != HIGH)
{
//lcd.clear();
//lcd.print(“ENABLE PEAK”);
//lcd.setCursor(0,1);
//lcd.print(“DELAY AT START?”);
if(digitalRead(enter) == HIGH)
{
lcd.clear();
lcd.print(“PEAK DELAY”);
lcd.setCursor(0,1);
lcd.print(“ENABLED!”);
peakdelayEnable = 1;
delay(2000);
break;
}
delay(200);
}
delay(300);
lcd.clear();
lcd.print(“ENABLE TIMED”);
lcd.setCursor(15,0);
lcd.write(4);
lcd.setCursor(0,1);
lcd.print(“CHARGE MODE?”);
lcd.setCursor(15,1);
lcd.write(6);

while(digitalRead(cycle) != HIGH)
{
//lcd.clear();
//lcd.print(“ENABLE TIMED”);
//lcd.setCursor(0,1);
//lcd.print(“CHARGE MODE?”);
if(digitalRead(enter) == HIGH)
{
lcd.clear();
lcd.print(“SLOW TIMED”);
lcd.setCursor(0,1);
lcd.print(“MODE ENABLED”);
peakdelayEnable = 1;
slowTimed = 1;
delay(2000);
lcd.clear();
lcd.print(“SELECT TIMER”);
while((digitalRead(enter) != HIGH)&& (timer >= 1))
{
if(digitalRead(cycle) == HIGH)
{
timer = timer + 1;
}
else if((digitalRead(cycleback) == HIGH)&&(timer > 1))
{
timer = timer – 1;
}
delay(200);
lcd.setCursor(0,1);
lcd.print(timer);
if(timer == 9)
{
lcd.setCursor(1,1);
lcd.print(” “);
}
lcd.setCursor(2,1);
lcd.print(“Hour”);
}
break;
}
delay(200);
}
}
}
timer = timer*60*60;
delay(300);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“SELECT BATT VOLT”);
lcd.setCursor(0,1);
//while(digitalRead(enter) != HIGH)
while(cut == 0)
{
if(voltset == 0)
{
if(digitalRead(enter) == HIGH)
{
lcd.clear();
lcd.print(“ERROR! – VOLTAGE”);
lcd.setCursor(0,1);
lcd.print(“TOO LOW”);
errorDisplay();
delay(1500);
lcd.clear();
lcd.print(“SELECT BATT VOLTAGE”);
}
}
if(voltset > 0)
{
if(digitalRead(enter) == HIGH)
{
cut = 1;
}
}
if(digitalRead(cycle) == HIGH)
{
if(meanTime >= 12)
{
voltset = voltset + 1.0;
}
else {
meanTime++;
voltset = voltset + 0.1;
}
}
else if(digitalRead(cycleback) == HIGH)
{
voltset = voltset – 0.1;
if(voltset < 0.0)
{
voltset = 0.0;
}
}
else
{
meanTime = 0;
}
delay(200);
lcd.setCursor(0,1);
lcd.print(voltset);
lcd.setCursor(5,1);
lcd.print(“V”);
}
cut = 0;
deltaV = voltset;
deltaV = deltaV/1.2;
deltaV = deltaV*0.002;
lcd.clear();
lcd.print(“deltaV = “);
lcd.print(deltaV,3);
lcd.print(“V”);
delay(2000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“SELECT BATT AMP”);
lcd.setCursor(0,1);
delay(200);
while(cut == 0)
{
if(currentset > 0)
{
if(digitalRead(enter) == HIGH)
{
cut = 1;
}
}
if(currentset == 0)
{
if(digitalRead(enter) == HIGH)
{
lcd.clear();
lcd.print(“ERROR! – CURRENT “);
lcd.setCursor(0,1);
lcd.print(“VALUE TOO LOW”);
errorDisplay();
delay(2000);
lcd.clear();
lcd.print(“SELECT BATT AMP”);
}
}
if(digitalRead(cycle) == HIGH)
{
currentset = currentset + 0.1;
if(currentset > 2.2)
{
currentset = 0.0;
}
}
if(digitalRead(cycleback) == HIGH)
{
currentset = currentset – 0.1;
if(currentset < 0.0)
{
currentset = 0.0;
}
}
delay (200);
lcd.setCursor(0,1);
//discur = currentset/10;
lcd.print(currentset);
lcd.setCursor(4,1);
lcd.print(“A”);

}

delay(300);
lcd.clear();
lcd.print(“CHECKING PID”);
delay(1000);
if(digitalRead(1) == HIGH)
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“PID RUNAWAY”);
lcd.setCursor(0,1);
lcd.print(“ERROR DETECTED!”);
errorDisplay();
delay(2000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“REINITIALIZING”);
lcd.setCursor(0,1);
lcd.print(“PID CONTROLLER”);
digitalWrite(tinyReset, LOW);
delay(500);
digitalWrite(tinyReset, HIGH);
delay(7000);
}
lcd.clear();
lcd.setCursor(0,0);
lcd.print(“TRANSFERING”);
lcd.setCursor(0,1);
lcd.print(“SETPOINT TO PID”);
delay(1000);
currentset = currentset*10;
longcurrent = currentset;
longcurrent = longcurrent – 1;
while (amp != longcurrent)
{
digitalWrite(10, HIGH);
delayMicroseconds(100);
digitalWrite(10, LOW);
delayMicroseconds (100);
amp++;
}
digitalWrite(10,HIGH);
delayMicroseconds(1000);
digitalWrite(10,LOW);

gainSelect = voltset*0.66;
gainSelect = gainSelect + voltset + currentset*0.01;
gainSelect = gainSelect*2.1760/12.1760;

if((gainSelect > 0.256) && (gainSelect < 0.512))
{
ads.setGain(GAIN_EIGHT);
gainSelect = 0.512;
}

else if((gainSelect > 0.512) && (gainSelect < 1.024))
{
ads.setGain(GAIN_FOUR);
gainSelect = 1.024;
}
else if((gainSelect > 1.024) && (gainSelect < 2.048))
{
ads.setGain(GAIN_TWO);
gainSelect = 2.048;
}
else //if((gainSelect > 2.048) && (gainSelect < 4.096))
{
ads.setGain(GAIN_ONE);
gainSelect = 4.096;
}
//else
//{
// ads.setGain(GAIN_TWOTHIRDS);
// gainSelect = 6.144;
//}

 

lcd.clear();
lcd.print(“ADC REFERENCE”);
lcd.setCursor(0,1);
lcd.print(gainSelect, 4);
lcd.setCursor(6, 1);
lcd.print(“V”);
delay(2000);
lcd.clear();
//lcd.print(“”);
lcd.print(“CHARGING ON”);
lcd.setCursor(0,1);
lcd.print(currentset/10);
lcd.setCursor(4,1);
lcd.print(“A”);
//Serial.println(“Hello!”);

//Serial.println(“Getting single-ended readings from AIN0..3”);
//Serial.println(“ADC Range: +/- 6.144V (1 bit = 3mV/ADS1015, 0.1875mV/ADS1115)”);
//lcd.begin(16,2);
//lcd.clear();
// The ADC input range (or gain) can be changed via the following
// functions, but be careful never to exceed VDD +0.3V max, or to
// exceed the upper and lower limits if you adjust the input range!
// Setting these values incorrectly may destroy your ADC!
// ADS1015 ADS1115
// ——- ——-
// ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 3mV 0.1875mV (default)
// ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 2mV 0.125mV
// ads.setGain(GAIN_TWO); // 2x gain +/- 2.048V 1 bit = 1mV 0.0625mV
// ads.setGain(GAIN_FOUR); // 4x gain +/- 1.024V 1 bit = 0.5mV 0.03125mV
//ads.setGain(GAIN_EIGHT); // 8x gain +/- 0.512V 1 bit = 0.25mV 0.015625mV
// ads.setGain(GAIN_SIXTEEN); // 16x gain +/- 0.256V 1 bit = 0.125mV 0.0078125mV

 

ads.begin();

if((chargedisable == 0)&&(tempchargedisable == 0))
{
digitalWrite(6,HIGH);//Enable LM2576 power chip
}
if((chargedisable == 1)&&(tempchargedisable == 1))
{
lcd.clear();
lcd.print(“CHARGE NOT”);
lcd.setCursor(0,1);
lcd.print(“POSSIBLE-RESET”);
errorDisplay();
for(;;){}
}
if(slowTimed == 0)
{
timer = 5400;
}
if(slowTimed == 1)
{
lcd.setCursor(13,0);
lcd.write(4); //
}

}

void loop(void)
{
int16_t adc0;

adc0 = ads.readADC_SingleEnded(0);
voltage = (adc0*gainSelect)*12.1760/32768/2.1760; //TL431 precision zener version
voltage = voltage – (currentset*0.01);
//Serial.print(“AIN0: “); Serial.println(adc0);
//Serial.print(“AIN1: “); Serial.println(adc1);
//Serial.print(“AIN2: “); Serial.println(adc2);
//Serial.print(“AIN3: “); Serial.println(adc3);
//Serial.println(” “);
/* lcd.setCursor(0,0);
lcd.print(“ADC “);
lcd.print(adc0);
lcd.setCursor(0,1);
lcd.print(“Volts “);
lcd.print(voltage);
delay(200);*/
//lcd.clear();
batteryMeter();
if((voltage >= peakVoltage)&&(peakdelayEnable == 0))
{
peakVoltage = voltage;
count = 0;
ignore = 0;
minimumVoltage = 50.0;

}
else if((voltage < peakVoltage)&&(peakdelayEnable == 0))
{
if(voltage <= minimumVoltage)
{
minimumVoltage = voltage;
if(((peakVoltage – voltage) >= deltaV)&&(peakdelayEnable == 0))
{
lcd.setCursor(14,0);
lcd.write(2);
ignore = 0;
count++;
if(count >= 5)
{

digitalWrite(6,LOW);//turn off LM2576 when charging finished, cuts off at 12.64951 V for a 9.6 V battery
lcd.clear();
lcd.print(“CHARGE DONE!”);
lcd.setCursor(0,1);
lcd.print(“DISCONNECT BATT!”);
digitalWrite(tinyReset, LOW);
delay(1000);
digitalWrite(tinyReset, HIGH);
digitalWrite(battConnect, LOW);
delay(100);
digitalWrite(capDep, HIGH);
delay(3000);
digitalWrite(capDep, LOW);
while(1)
{
lcd.clear();
lcd.print(“CHARGE REPORT”);
lcd.setCursor(0,1);
lcd.print(peakVoltage – voltage,5);
lcd.setCursor(7,1);
lcd.print(“V Delta V”);
delay(2000);
lcd.clear();
lcd.print(“CHARGE REPORT”);
lcd.setCursor(0,1);
lcd.print(peakVoltage,5);
lcd.setCursor(7,1);
lcd.print(“V Peak V”);
delay(2000);
}
}
}
}
else if((voltage > minimumVoltage)&&(peakdelayEnable == 0))
{
lcd.setCursor(14,0);
lcd.print(” “);
ignore++;
if(ignore >= 3)
{
count = 0;
ignore = 0;
}
}
}
//Serial.println(voltage,5);
lcd.setCursor(6,1);
lcd.print(“@ “);
lcd.setCursor(7,1);
lcd.print(” “);
lcd.setCursor(7,1);
lcd.print(voltage,4);
lcd.print(“V”);
lcd.setCursor(12,0);
lcd.write(5);
delay(500);
lcd.setCursor(12,0);
lcd.print(” “);
delay(500);
if(noTempmon == 0)
{
if(digitalRead(tempMonitor) == LOW)
{
lcd.setCursor(15,0);
lcd.write(1);
tempCount++;
if(tempCount >= 200)
{
digitalWrite(6, LOW);
digitalWrite(tinyReset, LOW);
delay(200);
digitalWrite(tinyReset, HIGH);
lcd.clear();
lcd.print(“EXCESS TEMP”);
lcd.setCursor(0,1);
lcd.print(“SHUT DOWN TRIG!”);
errorDisplay();
for(;;)
{
}
}
}
else
{
lcd.setCursor(15,0);
lcd.print(” “);
tempCount = 0;
}
}

if(adc0 >= 32767)//((adc0 >= 32767)||((adc0 >= 21845)&&(gainSelect == 6.144))) //Safety for ADC protection in case of overload or battery disconnection
{
digitalWrite(battConnect, LOW);//Battery disconnected
digitalWrite(capDep, HIGH);//ADC safety cap depletion is enabled
digitalWrite(6, LOW); //turn off current
digitalWrite(tinyReset, LOW);
delay(200);
digitalWrite(tinyReset, HIGH);
lcd.clear();
lcd.print(“INTERNAL ERROR!”);
lcd.setCursor(0,1);
lcd.print(“ADC OVERLOAD”);
errorDisplay();
delay(10000);
digitalWrite(capDep, LOW);
for(;;)
{}
}

if(peakdelayEnable == 0)
{
timer–;
}
if(peakdelayEnable == 1)
{
lcd.setCursor (15,1);
lcd.write(3);
peakDelay++;
timer–;
}
if((peakDelay == 420)&&(slowTimed == 0))
{
lcd.setCursor(15,1);
lcd.print(” “);
peakdelayEnable = 0;
}

if(timer == 0)
{
digitalWrite(6, LOW);
digitalWrite(tinyReset, LOW);
delay(100);
digitalWrite(tinyReset, HIGH);
lcd.clear();
lcd.setCursor(0,0);
if(slowTimed == 1)
{
lcd.print(“TIMER EXPIRED”);
lcd.setCursor(0,1);
lcd.print(“CHARGE DONE”);
}
else
{
lcd.print(“SAFETY TIMER UP!”);
lcd.setCursor(0,1);
lcd.print(“PEAK MISSED!”);
}
digitalWrite(battConnect, LOW);
delay(1000);
digitalWrite(capDep, HIGH);
delay(10000);
digitalWrite(capDep, LOW);
for(;;)
{}
}

if(digitalRead(1) == LOW)
{
digitalWrite(6, LOW);
lcd.clear();
lcd.print(“DYNAMIC PID”);
lcd.setCursor(0,1);
lcd.print(“ERROR DETECTED!”);
errorDisplay();
for(;;)
{
}
}

}

void batteryTest()
{
int A = 0;
//ads.begin();
while (A != 5)
{
digitalWrite(10, HIGH);
delayMicroseconds(100);
digitalWrite(10, LOW);
delayMicroseconds (100);
A++;
}
digitalWrite(10,HIGH);
delayMicroseconds(1000);
digitalWrite(10,LOW);
delay(2000);
digitalWrite(6,HIGH);
delay(10000);
if(analogRead(A1) > 650) //3.07V at the voltage divider node,corresponding to a battery voltage of 17.178V, a deeply discharged battery of 14.4V (12 AA cell) max wont be charged to 17.178 volts in 10 seconds indicating theres a battery connected, changed from 630 to 650
{
digitalWrite(6,LOW); //turn off LM2576
chargedisable = 1;
lcd.clear();
lcd.print(“NO BATTERY FOUND”);
lcd.setCursor(0,1);
lcd.print(“CHARGE DISABLED!”);
delay(5000);
lcd.clear();
lcd.print(“FAULTY BATTERY!”);
lcd.setCursor(0,1);
lcd.print(“RESTART NEEDED”);
errorDisplay();
digitalWrite(tinyReset, LOW);
delay(500);
digitalWrite(tinyReset, HIGH);
for(;;){}

}
else
{
digitalWrite(6,LOW);
lcd.clear();
lcd.print(“BATTERY FOUND!”);
lcd.setCursor(0,1);
lcd.print(“OK—-|:|:—-OK”);
digitalWrite(tinyReset, LOW);
delay(500);
digitalWrite(tinyReset, HIGH);
chargedisable = 0;
delay(3000);
}
}

void batteryMeter()
{
byte batteryEmpty[8] =
{
B01110,
B11011,
B10001,
B10001,
B10001,
B10001,
B10001,
B11111
};

byte batteryLevel1[8] =
{
B01110,
B11011,
B10001,
B10001,
B10001,
B10001,
B11111,
B11111

};

byte batteryLevel2[8] =
{
B01110,
B11011,
B10001,
B10001,
B10001,
B11111,
B11111,
B11111
};

byte batteryLevel3[8] =
{
B01110,
B11011,
B10001,
B10001,
B11111,
B11111,
B11111,
B11111
};

byte batteryLevel4[8] =
{
B01110,
B11011,
B10001,
B11111,
B11111,
B11111,
B11111,
B11111

};

byte batteryLevel5[8] =
{
B01110,
B11011,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};

byte batteryLevel6[8] =
{
B01110,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};

float barGraph = 0.0;
barGraph = 1.297*voltset +0.2236;
//barGraph = 1.297*voltset +0.242;
//barGraph = barGraph – (0.111*barGraph);
//barGraph = barGraph/6;
//barGraph = (barGraph – voltset)/6;
if(voltage >= barGraph) // 1
{
lcd.createChar(0,batteryLevel6);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else if (voltage >= barGraph*0.9815) // 0.9815
{
lcd.createChar(0,batteryLevel5);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else if(voltage >= barGraph*0.963) // 0.963
{
lcd.createChar(0,batteryLevel4);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else if(voltage >= barGraph*0.9445) // 0.9445
{
lcd.createChar(0,batteryLevel3);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else if(voltage >= barGraph*0.926) // 0.926
{
lcd.createChar(0,batteryLevel2);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else if(voltage >= barGraph*0.9075) // 0.9075
{
lcd.createChar(0,batteryLevel1);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
else
{
lcd.createChar(0,batteryEmpty);
lcd.setCursor(11,0);
lcd.write(byte (0));
}
}

void errorDisplay()
{
lcd.setCursor(15,1);
lcd.write(7);
}

Well the whole thing took around 1000 lines of code in total, now my programming skills aren’t that great, or in the words of Linus Torvalds, I don’t have good taste when it comes to coding, so there is much much room for optimization and improvement, and that isn’t just on the software side alone.

It is very easy to forget that we are working with two microcontrollers here, the above code is for the Arduino UNO (ATMega328P) based master charge controller, however there has been changes in the ATtiny85 based PID’s code as well, especially with the addition of set point communication protocol, the ATtiny85 is using an internal 16Mhz oscillator and details on how to program it are available in my previously listed posts on this topic, for now here is the modified code of the PID controller that I am using.

volatile int pwm_value = 0;
volatile int prev_time = 0;
volatile int cut = 0;
volatile int count = 0;
long duration, result, period;
int ON = 3;
int FB = 1;
//int comPIN = 2;
int Bias;
//int Compensate = 0, Count = 0;
float totalref = 0;
float Output;
float i = 0.0;
float Actual, lastError = 0.0, Error = 0.0, Derivative = 0.0, Integral = 0.0;
float Amp = 0.0;
float Kp, Ki, Kd;
float setPOINT = 0.0;
void setup()
{
float num = 0.0;
//interrupts();
//analogReference(INTERNAL2V56);// set internal reference to 1.1 V
analogReference(EXTERNAL);
delay(100);
pinMode(ON,OUTPUT); // ON/OFF signal for LM2576
pinMode(FB, OUTPUT); //PWM for LM2576 Feedback
//digitalWrite(ON, HIGH);
digitalWrite(ON,LOW);//Arduino controller edit
pinMode(2, INPUT);
delay(3000);
attachInterrupt(0, rising, RISING);
while(cut == 0)
{}
Amp = count;
Amp = Amp*0.1;
setPOINT = Amp*0.1*10.0*1024; // with the amplification factor included
setPOINT = setPOINT/2.47;
//Kp = pTUNE(setPOINT);
Kp = 0.03; //0.05 (send 5)//reducing or increasing has the same affect across all set points
Ki = 0.0006; //0.0008 (send 8)//The intergral sums up and reduces the error in some time completely
Kd = 0.0002;//0.0075 (send 75)
//Ki = iTUNE(setPOINT);
if(Amp == 0.1)
{
Bias = 67;
}
else
{
Bias = 68;
}
analogWrite(FB,70);
//digitalWrite(ON,LOW);
digitalWrite(ON,HIGH);//Arduino controller edit
delay(5000);

}

void loop()
{
Actual = analogRead(A2);
//delayMicroseconds(500);
Error = setPOINT – Actual;
Integral = Integral + Error;
if(Actual <= 15) //Forces the Integral to Zero when current below a certain value
{
Integral = 0;
}
if(Integral > 300000)//Limits the integral to prevent Integral windup
{
Integral = 300000;
}
if(Integral < -300000)
{
Integral = -300000;
}
Derivative = lastError – Error;
Output = Bias – (Kp*Error + Ki*Integral + Kd*Derivative);
if(Output > 255) //Limits the output to 5 volts maximum
{
Output = 255;
}
if(Output < 0)
{
Output = 10;
}
analogWrite(FB, Output);
lastError = Error;
}

void rising()
{
attachInterrupt(0, falling, FALLING);
count++;
prev_time = micros();
}
void falling()
{
attachInterrupt(0, rising, RISING);
pwm_value = micros()- prev_time;
if(pwm_value > 800)
{
detachInterrupt(0);
cut = 1;
}
}

 

Closing Remarks

Well this is it, at one point I didn’t think this was possible especially when your personal and professional life has been in termoil for the most part, but at last I have something to present and write about, the project at this point is still largely rough around the edges, I won’t be sharing any pictures of the circuits because quite frankly it’s a rats nest in there and sharing these pictures would be setting the wrong standards, also unfortunately in this part of the world access to 3D printing isn’t easy or should I say, 3D printing isn’t that mainstream yet, the future will hold better things I hope, this is primarly why I had the enclosure hand made from 4mm pastic sheets, I wish, along with all the little things, I could better this aspect of the design as well.

The techniques I have used in this project have drawn a fair share of criticism during the course of one year from online forums mainly, but I never meant to achieve anything apart from learning new things from this project, yes there are better ways of doing things but it’s essential to learn and make your own mistakes along the way, but that doesn’t imply that I don’t wish to acknowledge the valuable inputs given by more experienced members of the maker community.

Everything I have done in the past one year in summed up in this article, it’s open and free to experminet and alter, I will be really happy if someone comes forward asking for help or suggestions, finally I will say my good byes with a video of the working and the setting up of this charger, I hope to see you again soon with another exciting project, until then take good care!

 

If you think this effort was worth encouraging, please play your part by liking and sharing my blog, it brings a lot of motivation.

 

Advertisements

4 thoughts on “Universal Programmable NiMh Battery Charger

  1. How can we implement current mode control also for taking input voltage changes into account for culiding control loop of buck convertor

    Like

    1. Hello Tanvi, thanks for your interest. If I understand correctly you need to implement current control when the input voltage to the buck converter in changing, this particular arrangement doesnt need a dedicated control the existing control scheme takes care of it, just be sure that current flowing through a load is voltage applied across that load divided by the load resistance so make sure your input to the buck converter doesnt fall below a particular minimum value.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s