 EcoModder.com (https://ecomodder.com/forum/)
-   Fossil Fuel Free (https://ecomodder.com/forum/fossil-fuel-free.html)
-   -   Inexpensive DIY EV Monitoring System (Arduino based) (https://ecomodder.com/forum/showthread.php/inexpensive-diy-ev-monitoring-system-arduino-based-28500.html)

 mechman600 03-20-2014 01:42 PM

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:
http://i1119.photobucket.com/albums/...pstah563ys.jpg

It even works for charging, when current is reversed:
http://i1119.photobucket.com/albums/...pslcofxteu.jpg

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 = { // These two together make a nice battery symbol B01100, B11111, B10000, B10010, B10111, B10010, B11111, }; byte batt2 = { 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.

 Daox 03-20-2014 03:33 PM

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?

 mechman600 03-20-2014 04:03 PM

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.

 Daox 03-20-2014 04:14 PM

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?

 P-hack 03-20-2014 04:33 PM

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! :)

 mechman600 03-20-2014 05:13 PM

Yup, I was about to write that. 10 bits = 0-1023 output = about 0.1V resolution @ 100V, which is fine for me. If I had a lithium pack I would use multiple boards to get the resolution higher. 25+ separate measurements would require that anyway.

So bank2 reading - bank 1reading = bank 2 voltage, and so on.

Also, the Vref pin is only ever used if you want to read an analog signal of less than 0-5V using all 10 bits of ADC. For example, if you wanted to measure a 0-2V signal with 1023 steps of measurement, you would feed a 2V signal to Vref and write the code accordingly.

Normally, the ADC compares each analog input to +5, but the measured voltage potential needs GND to make the circuit. So I will hook GND up to the lowest pack potential as the zero ref.

Schematics to come.

 P-hack 03-20-2014 05:36 PM

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.

 Daox 03-20-2014 05:40 PM

I got around that by resetting the counters once the charger hit full charge.

 mechman600 03-21-2014 03:33 AM

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.

 mechman600 03-22-2014 03:52 PM

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 = { // These two together make a nice battery symbol B01100, B11111, B10000, B10010, B10111, B10010, B11111, }; byte batt2 = { B00110, B11111, B00001, B00001, B11101, B00001, B11111, }; byte timer = { 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.

All times are GMT -4. The time now is 08:15 AM.