Battery Charger Multiplexer
Living in the North East if you have an older car or a boat, or lawn mower or other vehicle that starts with a battery that you don’t drive or use in bad weather you have to decide what to do with the batteries each season. If you have one or two it’s pretty easy – you can get a trickle charger. The cheap simple ones are under $15 – but they are simple – no battery feed back or monitoring, they just put just over 13 volts to the battery. A better choice would be one of the smart chargers – they usually have regular higher/faster charge and a snow or trickle charge that adjusts it’s output based on the battery voltage and condition. They work very well – Harbor Freight has one for $39 – of course $29 on sale which is almost always.
Were I got hung up was I have my 1975 MGB (with a 3.5 liter Oldsmobile V8), a 1981 Toyota pickup, a boat and a generator – all with 12 volt batteries. And then if we go away in the winter we leave one of the cars here so there’s another battery. Well I’d have to get 5 battery minders – $150 if I go for the HF one on sale.
While thinking about this I thought I get one for each battery, put it on, it brings the batteries to a level and then it just monitors and trickle charges it for the rest of the time. So the idea comes to my – why couldn’t I hook it up to each battery a few hours a day – wouldn’t that work? Then if I could have it automatically switch between them?
Well I can. I took one of my Arduinos and wrote a program that does just that – and then I got carried away and added a few extra features. So here i present the details on what I ended up with. This version is setup for up to 6 batteries with auto sensing.
It will work with 1 to 6 batteries. The first battery must be hooked up to the number 1 spot – that connection is tied to the voltage regulator that powers the Arduino and other electronics. The 2nd and up can then be connected in any order. When you connect a battery the voltage is sensed and that battery is added to the charging cycle.
How it works – I wanted to accommodate 6 batteries. With an Arduino UNO A4 and A5 analog inputs are needed for the 2 wire LCD control and that leaves 4 analog inputs, too few. So I connected up an MCP-3008 8 channel analog inputs.
There are basically 4 functions called in the program.
- The function read_show_volts checks each connection to see if there is a battery connected by testing for greater than 8 volts available. If there is 8v or more it adds that connection to the charging loop.
- Function check_relays checks the timer counter and when it reaches the max it moves the charger to the next relay in line.
- Function update_display does just that – updates the display. First it shows the voltage at connections 1,2 on line 1 and the voltage of connections 3 and 4 on line 2. Then when the timer tells it it changes and displays the voltage of connections 5 and 6 on line 1 and because I had two extra analog inputs I show the system voltage (hopefully 5 volts) and the iref voltage – 3.3 volts. Those readings are not needed but there was a blank line and I didn’t know what else to show – I’m open to ideas.
- The final function is read_distance_update. This one is interesting and the most fun playing with. I hooked up an HCSR04 ultrasonic sonar distance measuring unit. I wanted the lcd to monitor the voltages but I am hardly every standing there looking at the lcd – most of the time it could be turned off. I could have put a switch – easy way out – but what I did was put the HCSR04 in and after so many seconds turn the lcd off. Then when I pass my hand closely over the HCSR04 I turn the lcd back on for x seconds.
There are variables for most everything so you can tailor it too fit you needs. I have set the charging timer to 1 hour per battery. So if there are two batteries connected it will charge each for 1 hour each time 12 times a day. With 4 batteries it would be 1 hour 6 times a day, and so on.
As long as I had the HCSR04 I added a routine that change the timer loop to a shorter time – a few seconds on each battery. The reason I did this was so i could see that it was switching between the batteries OK and I didn’t have to wait an hour to see it switch. If you hold your hand over the HCSR04 for a few seconds it flashes the lcd backlight on and off so you know it switched. You’ll then hear the relays switch every few seconds. Hold your hand over the HCSR04 again and it will switch back.
Here’s the parts list with links to where I got them:
1 – Arduino Uno – https://www.adafruit.com/product/2077
1 – Arduino proto board – https://www.adafruit.com/product/2077?gclid=Cj0KCQiAsdHhBRCwARIsAAhRhslL9qnzRRodjL6NMX6PapRZMLZWIipg0sijWcSPgmKR4GST8dFrBbgaAvTUEALw_wcB
1 – HC-SR04 – https://www.adafruit.com/product/3942
3 – dual 5v relays – https://www.amazon.com/Channel-optocoupler-Compatible-Atomic-Market/dp/B00TMFVVG6
1 – voltage regulator – https://www.amazon.com/gp/offer-listing/B00TMFKJ9Q/ref=dp_olp_new_mbc?ie=UTF8&condition=new
2 – 6 position terminal strips – check the picture for the type I used you can get them many places.
Here’s a zip of the code: BatteryMultiplexer
and here’s the code:
#include #include // Using version 1.2.1 #include #include #include // define pin connections for mcp3008 a-d converter #define CS_PIN 12 #define CLOCK_PIN 9 #define MOSI_PIN 11 #define MISO_PIN 10 // put pins inside MCP3008 constructor MCP3008 adc(CLOCK_PIN, MOSI_PIN, MISO_PIN, CS_PIN); auto timer = timer_create_default(); // create a timer with default settings // The LCD constructor - address shown is 0x27 - may or may not be correct for yours // Also based on YWRobot LCM1602 IIC V1 LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); int display_update = 5000; // how often to update the display int charge_update = 1000; // 1 sec time increment int charge_time_counter = 0; // how many charge_times were passed? int charge_time_max; // how many time increments should we count before changing? int charge_slow = 3600; // charge for 1 hour each battery int charge_fast = 2; // check for hookups every 1 second int went_low_count = 0; int went_low_limit = 4; int went_low_reset = 10; int went_high_count = 0; int was_fast_counter = 0; int was_fast_max = 50; int relay1 = 2; // each relay on it's own pin int relay2 = 3; int relay3 = 4; int relay4 = 5; int relay5 = 6; int relay6 = 7; int current_relay = 0; // which relay is the one on now? int max_relays = 5; // how may relays - 0 based. Start expecting all 6. double volts1; // what's the voltage of each battery double volts2; double volts3; double volts4; double volts5; double volts6; double volts7; double volts8; int min_volts = 8; // what is the minimum voltage a batter voltage should be to be charged. // if a battery is hooked up and starts less than this voltage we don't try to charge it int volts_update_count = 2; // how often to update the voltage reading in seconds int volts_update_counter = 0; // track the count of seconds gone by int volts_update = (2000); // how often to update the voltage update counter int read_distance_update = (200); // update the distance reader every this many millis int lcd_on_counter = 0; // counter for the display on/off int lcd_on_count = 200; // number of read_distance_update times the lcd will stay on // so multiply read_distance_update * lcd_on_count for actual time on int heart_beat = 0; // just a flipper thingy, used to toggle the lcd display float ref = 27.40; // this is the voltage that would make the ad convert read 1023, this can // be adjusted for variences in the resistor setup for the ad input. char buffer[16]; // for printing formatted output UltraSonicDistanceSensor distanceSensor(A0, A1); // Initialize sensor that uses digital pins 0 and 1. int distance; // this will hold the current distance that was read int relays[] = { relay1, relay2, relay3, relay4, relay5, relay6 }; int is_live[] = { 0, 0, 0, 0, 0, 0}; // 1 if live, 0 if not. I have only 6 void set_all_relays_to(int which) { // change all relays to HIGH or LOW, you tell me which:) digitalWrite(relay1, which); digitalWrite(relay2, which); digitalWrite(relay3, which); digitalWrite(relay4, which); digitalWrite(relay5, which); digitalWrite(relay6, which); } void toggle_relays(int whichOne) { set_all_relays_to(HIGH); // turn all off first digitalWrite(relays[current_relay], LOW); // and set the one that should be on on } void check_relays() { // move the current_relay to the next relay // if we hit the max, start at 0 // while you're here, call toggle_relays to make sure we're not charging a blank or dead one charge_time_counter++; if (charge_time_counter >= charge_time_max) { current_relay++; if (current_relay >= max_relays) { current_relay = 0; } toggle_relays(current_relay); charge_time_counter = 0; } } void blink_display() { lcd.noBacklight(); delay(60); lcd.backlight(); delay(60); lcd.noBacklight(); delay(60); lcd.backlight(); delay(60); lcd.noBacklight(); delay(60); lcd.backlight(); delay(60); lcd.backlight(); lcd.noBacklight(); delay(60); lcd.backlight(); delay(60); lcd.backlight(); } void read_distance() { // reaad the distance of anything from the HC-S204 // if it's something within 30cm then turn the display on and reset the display on counter // if the counter is reached then nothing is within 80cm so turn the display off distance = distanceSensor.measureDistanceCm(); // uncomment these to see the actual distance measured // Serial.print("Distance in CM: "); // Serial.println(distance); if (distance < 30) { // can be anything under 30 for my system uncomment above and check yours lcd.backlight(); // turn display on lcd_on_counter = 0; went_low_count++; } else { // maybe going off? lcd_on_counter++; if (lcd_on_counter > lcd_on_count) { lcd_on_counter = 0; lcd.noBacklight(); } went_high_count++; if (went_high_count > went_low_reset) { went_low_count = 0; // reset the went low counter went_high_count = 0; // and the high counter } } if (went_low_count > went_low_limit) { if (charge_time_max == charge_slow) { charge_time_max = charge_fast; } else { charge_time_max = charge_slow; } went_low_count = 0; blink_display(); } } double get_volts(int which) { double volts; volts = adc.readADC(which); // read Chanel 0 from MCP3008 ADC volts = (float)(volts / 1023) * ref; return (volts); } void update_display() { // with a 2 line display show the first four batteries, then next time show the last two batteries // and then just because we have 8 channels I show the system voltage and the iref (3.3v) voltage // then I show the relay that currently should be on. You could use ad in 7 and 8 for something else. if (heart_beat == 0) { lcd.setCursor(0, 0); lcd.print("1:"); dtostrf(volts1, 5, 2, buffer); lcd.print(buffer); lcd.print(" 2:"); dtostrf(volts2, 5, 2, buffer); lcd.print(buffer); lcd.setCursor(0, 1); lcd.print("3:"); dtostrf(volts3, 5, 2, buffer); lcd.print(buffer); lcd.print(" 4:"); dtostrf(volts4, 5, 2, buffer); lcd.print(buffer); heart_beat++; } else { lcd.setCursor(0, 0); lcd.print("5:"); dtostrf(volts5, 5, 2, buffer); lcd.print(buffer); lcd.print(" 6:"); dtostrf(volts6, 5, 2, buffer); lcd.print(buffer); lcd.setCursor(0, 1); lcd.print("R:"); dtostrf(volts7, 4, 2, buffer); lcd.print(buffer); lcd.print(" S:"); dtostrf(volts8, 4, 2, buffer); lcd.print(buffer); lcd.print(" "); heart_beat = 0; } } void read_volts() { // read the voltage at each battery input and the system and reference // (only cause I had 2 analog in's left over) // then if less then min_volts, there is nothing there worth charging, skip that connection // this gets called over and over so if a wire gets knocked off or // whatever it won't be in the charge loop volts1 = get_volts(7); volts2 = get_volts(6); volts3 = get_volts(5); volts4 = get_volts(4); volts5 = get_volts(3); volts6 = get_volts(2); volts7 = get_volts(1); volts8 = get_volts(0); // now test the voltages. If less than 10, then let's assume dead/bad or no battery // so take it out of rotation // start by clearing all relays out int temp_cnt = 0; // set all arrays to 0 - that's off relays[0] = 0; relays[1] = 0; relays[2] = 0; relays[3] = 0; relays[4] = 0; relays[5] = 0; if (volts1 > min_volts) { relays[temp_cnt] = relay1; // relay 1 is good temp_cnt++; } if (volts2 > min_volts) { relays[temp_cnt] = relay2; // relay 2 is good temp_cnt++; } if (volts3 > min_volts) { relays[temp_cnt] = relay3; // relay 3 is good temp_cnt++; } if (volts4 > min_volts) { relays[temp_cnt] = relay4; // relay 3 is good temp_cnt++; } if (volts5 > min_volts) { relays[temp_cnt] = relay5; // relay 3 is good temp_cnt++; } if (volts6 > min_volts) { relays[temp_cnt] = relay6; // relay 3 is good temp_cnt++; } max_relays = temp_cnt; } void setup() { Serial.begin(9600); Serial.println("Starting"); // just like to know it's alive lcd.begin(16, 2); // sixteen characters across - 2 lines lcd.backlight(); pinMode(relay1, OUTPUT); pinMode(relay2, OUTPUT); pinMode(relay3, OUTPUT); pinMode(relay4, OUTPUT); pinMode(relay5, OUTPUT); pinMode(relay6, OUTPUT); set_all_relays_to(HIGH); charge_time_max = charge_slow; // how many charge_update's to wait on each battery // setup timers. 3 timers - the time to turn the charger on each battery, // how often to show the voltage update // and how often to check for distance read to turn on the display read_volts(); // do the first time so we don't have to wait for the timer. check_relays(); update_display(); timer.every(charge_update, check_relays); timer.every(volts_update, read_volts); timer.every(display_update, update_display); timer.every(read_distance_update, read_distance); toggle_relays(0); // start with 1st relay on - if we're running that one has power Serial.println("OK, all setup, off we go ..."); lcd.noAutoscroll(); } void loop() { timer.tick(); // tick the timer }
Here’s it running with annotations.
Click on the schematic for larger version
BatteryMultiplexer and this is a zip of the code.