EcoModder Forum Inexpensive DIY EV Monitoring System (Arduino based)

Register Now
 Remember

Master EcoModder

Join Date: Jul 2008
Location: Langley, BC
Posts: 1,226

Fusion - '16 Ford Fusion Hybrid SE
Thanks: 190
Thanked 272 Times in 166 Posts
Inexpensive DIY EV Monitoring System (Arduino based)

Not too long ago I decided to attempt to learn digital electronics. It is something that has always intrigued me, but something that has always been over my head. Circuits and wiring? Easy. Computer code? Not so much.

Long story short, I ended up getting a couple of Arduino boards and started playing around. Armed with a helpful tutorial in C here: HowStuffWorks "The Basics of C Programming" I started to get the hang of it.

The reality is, there really is no end to what an Arduino board is capable of. My \$14 Chinese Arduino Nano copy has 8 analog inputs and 13 digital I/0s. All that is required is the brain power to program it to make it do what you want.

So I am now testing my code on a better battery monitoring system for the Electric Booger. Since it is 72V, all I need is 6 voltage monitoring inputs, one input from the amp sensor and maybe a temp sensor on the motor. I have successfully make an Ah counter program that works very well. Here it is running with a simulated volt and amp input:

It even works for charging, when current is reversed:

Here is the code so far:
Quote:
 // AH Counter Simulation #include #include // Adjustable parameters const float charge_correct = 0.8; // reverse current charing efficiency factor const int fuel_tank = 83; // total avail capacity in Ah const int voltPin = 0; const int ampPin = 4; const int reset = 6; LiquidCrystal lcd(12, 11, 5, 4, 3, 2); byte batt1[8] = { // These two together make a nice battery symbol B01100, B11111, B10000, B10010, B10111, B10010, B11111, }; byte batt2[8] = { B00110, B11111, B00001, B00001, B11101, B00001, B11111, }; float battVolt = 0; float kwatts = 0; int sample = 0; int time = 0; float raw_amps = 0; float amps = 0; float totCharge = 0; float avg_amps = 0; float ampSec = 0; float ampHour = 0; float kwHour = 0; float fuel_level = 0; void setup() { pinMode(voltPin, INPUT); pinMode(ampPin, INPUT); pinMode(reset, INPUT_PULLUP); lcd.begin(20, 4); lcd.createChar(0, batt1); lcd.createChar(1, batt2); } void loop() { battVolt = 75; // Simulated Value raw_amps = -100; // Simulated Value if(raw_amps < 0) { amps = raw_amps * charge_correct; // Charging efficiency factor } else { amps = raw_amps; } kwatts = raw_amps * battVolt / 1000; //kW for display only sample = sample + 1; time = millis() / 1000; totCharge = totCharge + amps; avg_amps = totCharge / sample; ampSec = avg_amps * time; ampHour = ampSec / 3600; kwHour = battVolt * ampHour / 1000; fuel_level = (fuel_tank - ampHour) / fuel_tank * 100; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Volt: "); lcd.print(battVolt, 1); lcd.setCursor(11, 0); lcd.print("Amp: "); lcd.print(raw_amps, 0); lcd.setCursor(0, 1); lcd.print("kW: "); lcd.print(kwatts, 1); lcd.setCursor(12, 1); lcd.print("Sec: "); lcd.print(time); lcd.setCursor(0, 2); lcd.print("Ah: "); lcd.print(ampHour, 1); lcd.setCursor(10, 2); lcd.print("kWh: "); lcd.print(kwHour, 2); lcd.setCursor(4, 3); lcd.write(byte(0)); lcd.write(byte(1)); lcd.print(" "); lcd.print(fuel_level, 1); lcd.print((char)37); delay(200); }
The adjustable parameters at the top:
1) charge_correct: counts reverse amps at an 80% rate to account for battery charging inefficiency
2) fuel_tank: size of available Ah to begin with.

 The Following 2 Users Say Thank You to mechman600 For This Useful Post: Cobb (03-22-2014), P-hack (03-20-2014)
 Today Popular topics Other popular topics in this forum...
 03-20-2014, 03:33 PM #2 (permalink) Administrator     Join Date: Dec 2007 Location: Germantown, WI Posts: 11,166 CM400E - '81 Honda CM400E 90 day: 51.49 mpg (US) Daox's Grey Prius - '04 Toyota Prius Team Toyota 90 day: 49.53 mpg (US) Daox's Insight - '00 Honda Insight 90 day: 64.33 mpg (US) Swarthy - '14 Mitsubishi Mirage DE Mitsubishi 90 day: 56.69 mpg (US) Thanks: 2,464 Thanked 2,535 Times in 1,529 Posts Very cool! I'm glad someone else is trying this out. I am curious how you plan on monitoring voltage and current. I would imagine you're going to tap into your ammeter's shunt? I used one of the Haas current sensors that Paul's open revolt uses in my BMS, but it wasn't as accurate as I'd like it to be. What is the plan for the voltage monitoring? __________________ Current project: A better alternator delete
 03-20-2014, 04:03 PM #3 (permalink) Master EcoModder     Join Date: Jul 2008 Location: Langley, BC Posts: 1,226 Fusion - '16 Ford Fusion Hybrid SE Thanks: 190 Thanked 272 Times in 166 Posts My car doesn't actually have an ammeter. I know, I know. Lame. I have used a remote one on my multimeter in the past, so I have a pretty good idea of current vs voltage sag, so I just never bothered. But with an Ah counter, it will be essential. I still have the hall effect amp transducer that I used to use when I was running a separate field controller with my former sepex motor. It is a 0-400A amp sensor (600A peak) - 0-5V analog output. 2.5V is 0 amps. Output goes up for one current direction and down in reverse direction. It is quite accurate, as far as I can tell. This one: http://www.farnell.com/datasheets/30908.pdf Volts is easy. Arduino GND to the most negative battery pack post. Then each positive post signal goes through a 10K/500 voltage splitter (so 100V = 5V) to Arduino A0-A5. __________________ http://electric-booger.blogspot.ca
 03-20-2014, 04:14 PM #4 (permalink) Administrator     Join Date: Dec 2007 Location: Germantown, WI Posts: 11,166 CM400E - '81 Honda CM400E 90 day: 51.49 mpg (US) Daox's Grey Prius - '04 Toyota Prius Team Toyota 90 day: 49.53 mpg (US) Daox's Insight - '00 Honda Insight 90 day: 64.33 mpg (US) Swarthy - '14 Mitsubishi Mirage DE Mitsubishi 90 day: 56.69 mpg (US) Thanks: 2,464 Thanked 2,535 Times in 1,529 Posts Cool, your current sensor is quite similar to the one I was using. Just make sure to test it with a known load so you know you're getting accurate readings over time. Like I said, I had issues. I'm not an electronics guru. I do know a little programming so dealing with stuff like noise on lines is not easy for me. I'd be interested to see how you plan on wiring / program the voltage monitoring to the arduino. Because your batteries are in series, each divider will add to the voltage of the previous batteries unless you have some way of getting around that. I assume you'll just have to compensate for the different voltage ranges in your code? __________________ Current project: A better alternator delete
 03-20-2014, 04:33 PM #5 (permalink) Master EcoModder     Join Date: Oct 2012 Location: USA Posts: 1,408 awesomer - '04 Toyota prius Thanks: 102 Thanked 249 Times in 201 Posts cool, just fyi 10 bit adc @ 100v will give you about 0.1v resolution at the top of the pack, then you have to subtract out the lower voltage readings to get the individual voltage which create rounding errors on the "higher up" batteries, with 6 batteries it won't be a big deal though. Also, you can use createChars to make 6 mini bargraphs (speaking of rounding errors ) on the display, one for each battery, from zero pixels to 8 pixels high (blank and full are already existing characters probably, maybe even 1 pixel high already exists), or expand them to 4 lines high. Lucky you have 8 ADC and 6 batteries!
 The Following User Says Thank You to P-hack For This Useful Post: Daox (03-20-2014)
 The Following User Says Thank You to mechman600 For This Useful Post: Daox (03-20-2014)
 03-20-2014, 05:36 PM #7 (permalink) Master EcoModder     Join Date: Oct 2012 Location: USA Posts: 1,408 awesomer - '04 Toyota prius Thanks: 102 Thanked 249 Times in 201 Posts you do have to watch out for overflow conditions. millis() overflows in 49 days, some of your "accumulators" might run out of room well before then too. Floating point is convenient, but the raw adc data is bits and might be more accurately accumulated in a double, or a int64_t if you have room. Adding 1024 to itself, 5 times per second will overflow a int32 in 4 days. Dunno if it matters though, depends how accurate it needs to be.
 03-20-2014, 05:40 PM #8 (permalink) Administrator     Join Date: Dec 2007 Location: Germantown, WI Posts: 11,166 CM400E - '81 Honda CM400E 90 day: 51.49 mpg (US) Daox's Grey Prius - '04 Toyota Prius Team Toyota 90 day: 49.53 mpg (US) Daox's Insight - '00 Honda Insight 90 day: 64.33 mpg (US) Swarthy - '14 Mitsubishi Mirage DE Mitsubishi 90 day: 56.69 mpg (US) Thanks: 2,464 Thanked 2,535 Times in 1,529 Posts I got around that by resetting the counters once the charger hit full charge. __________________ Current project: A better alternator delete
 03-21-2014, 03:33 AM #9 (permalink) Master EcoModder     Join Date: Jul 2008 Location: Langley, BC Posts: 1,226 Fusion - '16 Ford Fusion Hybrid SE Thanks: 190 Thanked 272 Times in 166 Posts I will have a reset button for when it is fully charged. The negative current measuring and resulting "battery capacity regeneration" on the fuel gauge is more for if I want to graze charge while doing errands, charging a bit here and there while [hopefully] keeping my fuel gauge somewhat accurate. It will be interesting to see what sort of charge correction factor I will end up needing to give it some accuracy. I guess it will largely depend on how deeply discharged the pack is while charging - charging at the deep end is far more efficient than at the top end. __________________ http://electric-booger.blogspot.ca
Master EcoModder

Join Date: Jul 2008
Location: Langley, BC
Posts: 1,226

Fusion - '16 Ford Fusion Hybrid SE
Thanks: 190
Thanked 272 Times in 166 Posts
Now on to the charger monitoring side. I paid a solid \$14 for this Arduino and plan to get all my money's worth!

Currently (PUN!), I have six 12V chargers. They put out 10A up to 14.7V and then hold 14.7V until the current drops down enough to switch to a 13.8V float. Too see what's going on remotely, I go look at my Kill A Watt, which I have learned to interpret pretty well. But this involves going down to the garage. I know, I know, first world problem, but wouldn't it be nicer to simply look out the window and see an LED status light flashing in the car? Yes, yes it would.

I always look at the Kill A Watt at the beginning and end of a charge to make sure all chargers were working because I had a near fatal (from a battery's perspective) incident where a fuse popped in one charger without me noticing. I haven't gotten there yet, but my future code will watch for differences in voltages between banks and flag a warning LED and message (or text me if I buy a GSM Arduino shield?) if something is out of whack.

Anyway, here is a circuit simulation:

And here is the code for this part. Many things will cross over from the battery fuel gauge code, as they will share the same inputs and logic.

EDIT: I played around with it more and found that if the charger was turned off with the switch before it was finished, it would assume that it was on float and would start the float timer, permanently cutting off the charger relay (ch_enable pin) unless the arduino was reset. This, combined with possible reliability issues, has led me to use a SPDT switch - OFF, charge and charge with float timer, requiring two pins. I have changed the code below to reflect this.

Quote:
 /* The electric Booger charging monitor screen This code monitors charging voltage and determines when a) volt limiting charging has been reached (MODE 2) and when float has been reached (MODE 3), where a timer (turned on by ch_butt2) will automatically turn the charger off with ch_enable. */ #include const int voltPin = 0; const int displ = 8; // Turn LCD screen on/off with acc power const int ch_butt1 = 7; // Charge without float timer const int ch_butt2 = 10; // Charge with float timer const int ledPin = 6; // Charge indicator LED, mounted on right side of dash const int ch_enable = 13; // 120V relay to power chargers int float_delay = 0; // How long float charge will last float amps = 0; float c_amps = 0; float volts = 0; int watts = 0; float ampHour = 0; float kWh = 0; long sample = 0; long time = 0; float totCharge = 0; float avgAmps = 0; long ch_count = 0; int charge_mode = 0; int charge_latch = 0; int ledState = LOW; long previousMillis = 0; int ledInterval = 0; int ch_en_latch = 0; int ch_en_sig = 0; LiquidCrystal lcd(12, 11, 5, 4, 3, 2); byte batt1[8] = { // These two together make a nice battery symbol B01100, B11111, B10000, B10010, B10111, B10010, B11111, }; byte batt2[8] = { B00110, B11111, B00001, B00001, B11101, B00001, B11111, }; byte timer[8] = { B11111, B10001, B01110, B00100, B01010, B10001, B11111, }; void setup() { lcd.begin(20, 4); pinMode(voltPin, INPUT); pinMode(displ, INPUT_PULLUP); pinMode(ch_butt1, INPUT_PULLUP); pinMode(ch_butt2, INPUT_PULLUP); pinMode(ledPin, OUTPUT); pinMode(ch_enable, OUTPUT); lcd.createChar(0, batt1); lcd.createChar(1, batt2); lcd.createChar(2, timer); } void loop() { if(digitalRead(displ) == LOW) { lcd.noDisplay(); } else { lcd.display(); } if(digitalRead(ch_butt1) == LOW) { // Charge start button ch_en_latch = 1; float_delay = 90000; } else if(digitalRead(ch_butt2) == LOW) { ch_en_latch = 2; float_delay = 25; } else { ch_en_latch = 0; float_delay = 90000; ch_count = 0; } amps = 0; // Simulated value for testing volts = analogRead(voltPin) / 11.6; // Simulated volt value from 5K POT c_amps = -amps; // If current goes backwards, start working/counting if(c_amps < 0) { c_amps = 0; } if(ch_en_latch == 1 || ch_en_latch == 2) { // Criteria for MODE 1 charge_mode = 1; } else { charge_mode = 0; } if(charge_mode == 1 && volts >= 87) { // Criteria for MODE 2 charge_mode = 2; charge_latch = 1; } if(charge_latch == 1 && volts >= 83.5) { // Stay in MODE 2 until volts drop to float level charge_mode = 2; } if(volts < 83.5 && charge_latch == 1) { // Criteria for MODE 3 charge_mode = 3; ch_count = ch_count + 1; } if(ch_count > float_delay) { // Float timer charge_mode = 4; } if(c_amps < 0) { // Reset charge Ah counter if I forget to reset charge_mode = 0; // the Arduino and I start driving } if(ch_en_latch == 0) { // Start/stop charge button digitalWrite(ch_enable, LOW); charge_mode = 0; } watts = volts * c_amps; // Ah counter calculations; not sure if I will use all of these sample = sample + 1; time = millis() / 1000; totCharge = totCharge + c_amps; avgAmps = totCharge / sample; ampHour = avgAmps * time / 3600; kWh = volts * ampHour / 1000; lcd.clear(); // LCD stuff lcd.setCursor(0, 1); lcd.print("Volt: "); lcd.print(volts, 1); lcd.setCursor(11, 1); lcd.print("Amp: "); lcd.print(c_amps, 1); lcd.setCursor(0, 2); lcd.print("Watt: "); lcd.print(watts); lcd.setCursor(10, 2); lcd.print("kWH: "); lcd.print(kWh); lcd.setCursor(0, 3); lcd.print("Mode: "); lcd.print(charge_mode); lcd.setCursor(9, 3); lcd.write(byte(0)); lcd.write(byte(1)); lcd.print(" 0"); lcd.print(char(37)); if(charge_mode == 0) { digitalWrite(ch_enable, LOW); } unsigned long currentMillis = millis(); // Counter for LED flashing if(charge_mode == 1 || charge_mode == 2 || charge_mode == 3) { // All this so no delay() is required to flash the LED if(currentMillis - previousMillis > ledInterval) { // save the last time you blinked the LED previousMillis = currentMillis; // if the LED is off turn it on and vice-versa: if (ledState == LOW) { ledState = HIGH; } else { ledState = LOW; } } } if(charge_mode == 1 || charge_mode == 2) { lcd.setCursor(6, 0); lcd.print("CHARGING"); digitalWrite(ch_enable, HIGH); ledInterval = 2000; } if(charge_mode == 3) { lcd.setCursor(6, 0); lcd.print(" FLOAT "); digitalWrite(ch_enable, HIGH); ledInterval = 400; } if(charge_mode == 4) { lcd.setCursor(2, 0); lcd.print("CHARGE COMPLETE"); digitalWrite(ch_enable, LOW); ledState = HIGH; } if(ch_en_latch == 1) { lcd.setCursor(19, 0); lcd.print(" "); } if(ch_en_latch == 2) { lcd.setCursor(19, 0); lcd.write(byte(2)); } digitalWrite(ledPin, ledState); delay(400); }
So far this is using 21.6% of the ATmega328's memory. Still lots of room left.

Last edited by mechman600; 03-25-2014 at 12:12 AM..

 The Following 2 Users Say Thank You to mechman600 For This Useful Post: Cobb (03-22-2014), Daox (03-22-2014)