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 02: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[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.

 Daox 03-20-2014 04: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 05: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 05: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 05: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 06: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 06: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 06:40 PM

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

 mechman600 03-21-2014 04: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 04: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[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.

 JoeG 03-22-2014 09:18 PM

Great project! I was thinking about how to use an arduino for a charge controller for a battery cycler I'm building and here it is:thumbup:. Keep up the good work.
Joe

 mechman600 03-25-2014 01:23 AM

I was driving home from work the other day when I glanced at my fuel and temperature gauges, which currently do nothing, and suddenly thought, "man...I could totally put those to use now!!"

So today I found the wires that go to the ex-coolant temp and fuel level senders. When I ground the former, it draws 220mA and the coolant temp gauge pins to the max. When I ground the latter, it draws 117mA and the fuel gauge slowly moves to full. This will be incredibly simple to do....simply control current with an NPN transistor on each, feeding each a calibrated PWM.

Actually...I will use opto-isolators to keep the 12V accessory side isolated from the 72V pack side that the Arduino will be in bed with. However, I do not know what their characteristics are compared to regular old transistors. Something tells me they take a lot more current to trigger/switch than transistors which take hardly anything. I may have to up the Arduino's juice with a transistor to trigger an opto-isolator. I must acquire one ASAP and do some playing.

 Astro 03-25-2014 07:35 AM

Quote:
 Originally Posted by mechman600 (Post 416915) ... Actually...I will use opto-isolators to keep the 12V accessory side isolated from the 72V pack side that the Arduino will be in bed with. However, I do not know what their characteristics are compared to regular old transistors. Something tells me they take a lot more current to trigger/switch than transistors which take hardly anything. I may have to up the Arduino's juice with a transistor to trigger an opto-isolator. I must acquire one ASAP and do some playing.
I believe the driven side of the opto is just a led.
So between 4 and 20mA normally.
Someone correct me if this is wrong.

 Daox 03-25-2014 11:27 AM

You're correct.

 mechman600 03-25-2014 02:14 PM

Quote:
 Originally Posted by Astro (Post 416923) I believe the driven side of the opto is just a led. So between 4 and 20mA normally. Someone correct me if this is wrong.
Yup, that's right. It would be nice if it only took 20mA to fully trigger, as the Arduino can do 40mA per pin max output. If it takes more than 40mA I will have to amplify the signal with a transistor.

 mechman600 03-26-2014 10:58 AM

Yesterday I picked up a couple of PS2502-4 opto-isolators (4-channel):
http://i1119.photobucket.com/albums/...ps905d1242.png
I started to play around with it at home: a 0-5K pot (as a variable resistor) putting 5V into pins 1 & 2, and an LED load on 15 & 16, powered by a 20V AC-DC converter through a 1K resistor.

What I found is that the output is much more controllable by the input than a plain transistor. A transistor will trigger full blast with a 10K resistor on the gate. Even a 100K resistor. This chip needs more current, and I found that output basically peaks with a 220 ohm resistor into pins 1 & 2, drawing 13.7mA. And that's good, because the Arduino will have plenty of juice to power it.

I also tested to see how it responds with PWM and it works just fine. I will use my little PWM program to find PWM calibration values for the fuel and temperature gauges.
http://i1119.photobucket.com/albums/...psoaufd2s2.jpg
Quote:
 #include int potPin = 0; // Potentiometer pin int PWMout = 6; // Output to optoisolator LiquidCrystal lcd(12, 11, 5, 4, 3, 2); void setup() { pinMode(potPin, INPUT); pinMode(PWMout, OUTPUT); lcd.begin(20, 4); } void loop() { int value = map(analogRead(potPin), 0, 1023, 0, 255); analogWrite(PWMout, value); lcd.setCursor(11, 0); lcd.print(" "); lcd.setCursor(0, 0); lcd.print("PWM Value: "); lcd.print(value); delay(100); }

 P-hack 03-26-2014 11:30 AM

150ma collector emitter max on those optos, sounds like your "fuel" meter will work as-is, but the temp meter current is a bit high, but sounds like it needs some more resistance anyway (probably the original sensor wasn't 0 ohms at max temp, 12v+pot to determine FSD ohms). You are making it look easy :)

 mechman600 03-28-2014 03:28 AM

Today I went about testing the fuel and temp gauges with my simple PWM circuit and program. My LCD screen displayed "PWM Value", which is an 8-bit value (0-255).
Here is the circuit:

The coolant temp circuit worked great. The PWM range from the needle just barely off the bottom to the "H" line at the top is 60 to 185. This calibration will look something like this:
Quote:
 temp_PWM_out = map(temp_F, 30, 225, 60, 185); analogWrite(tGauge_pin, temp_PWM_out);
The 30 and 225 represent the min and max values (in Fahrenheit) that I want the gauge to read and the 60 and 185 represent the min and max PWM value that the temp will map to.

The fuel gauge did not work as well. The fuel gauge needs a very low resistance to show "full". The PS2502 opto-isolator's 1V collector emitter saturation voltage (woah, did I just write that??) keeps the gauge from getting past 7/8 full with the PWM at 255. I tried increasing the current into the diode side of the opto-isolator by using a smaller resistor but this didn't change anything. Empty happens at PWM 115. Oh well, I tried. I guess 7/8th of a tank is all I will get.
http://i1119.photobucket.com/albums/...psmjohb3os.jpg

This will look like this:
Quote:
 fuel_PWM_out = map(fuel_level, 0, 100, 115, 255); analogWrite(fGauge_pin, fuel_PWM_out);

 P-hack 03-28-2014 04:13 AM

7/8ths is plenty good. But if it is still bugging you, driving a mosfet with the opto might get you the low resistance it needs for fsd.

0.107ohms @ 150ma = 0.016v drop across the mosfet (\$0.29).

RFD3055LE Fairchild Semiconductor | Mouser

 mechman600 03-28-2014 10:58 AM

Quote:
 Originally Posted by P-hack (Post 417450) 7/8ths is plenty good. But if it is still bugging you, driving a mosfet with the opto might get you the low resistance it needs for fsd.
I tried this with a 2N7000, but I could not make it work for the life of me. It either stayed switched on (no matter what PMW value) or if I switched resistors to different spots (like a 5K from the 2N7000's gate to ground), my test LED switched on when PWM was 1 but the current did not increase as I turned it up.:confused:

 Astro 03-28-2014 07:12 PM

Quote:
 Originally Posted by mechman600 (Post 417495) I tried this with a 2N7000, but I could not make it work for the life of me. It either stayed switched on (no matter what PMW value) or if I switched resistors to different spots (like a 5K from the 2N7000's gate to ground), my test LED switched on when PWM was 1 but the current did not increase as I turned it up.:confused:

Something like this maybe?
http://i1310.photobucket.com/albums/...ps08ea8527.png

 P-hack 03-28-2014 07:18 PM

also, the pwm frequency doesn't need to be anymore than visual limits, like 60hz. But 7/8ths is plenty good too :)

 mechman600 03-31-2014 11:00 AM

Astro, the schematic you posted above is the exact circuit I attempted and could not make work. I suspect the opto is not very good at outputting a nice square wave, and since the transistor will stay on with the most minuscule voltage on the gate, it simply never turns off. I suppose I could try to slow down the PWM frequency (easy enough to do) but this throws all the delay timers off. And besides, I don't feel like doing it.:D

The solution I did come up with is simple enough - a 100 ohm bypass resistor between gauge signal and ground. At 0 PWM it sits on the empty line and at 255 PWM it is just short of the full line. Good enough!

Here is the schematic of the whole entire thing:
http://i1119.photobucket.com/albums/...ps0e4c7c9d.png

INTERESTING BITS:

Warning Lamp: the ex-low oil pressure warning lamp

Vac Pump Switch/"Run" +12V: monitors the vacuum pump switch when the car is on; if the switch remains closed for more than 10 seconds, the warning lamp is flashed and "VACUUM PUMP FAILURE" is displayed

"ACC" +12V: turns the display on when the keyswitch is turned the first click; I was going to trigger a pin on the Arduino (using lcd.display() and lcd.noDisplay() functions) but I ran out of pins :); opening the display's contrast pin also turns the screen off.

All my bits and pieces have arrived from Digikey. All I am waiting for is my Hong Kong sourced 20X4 LCD display.

 mechman600 04-06-2014 12:47 PM

Quote:
 Originally Posted by Daox (Post 416215) 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.
Did you include the capacitors that are in the datasheet? Omitting them would certainly cause erratic readings.
http://i1119.photobucket.com/albums/...psb2618e94.png
The 47nF cap between 0V and 5V isn't necessary because the arduino already has a much larger cap between its 5V and ground. But the 4.7nF cap between output and 0V will no doubt smooth out signal irregularities.

 P-hack 04-06-2014 01:08 PM

Code:

```tap  divider  sense  watts  resolution per bit 12v  20k/10k  4v    0.005  0.01171875v 24v  50k/10k  4v    0.010  0.0234375v 36v  80k/10k  4v    0.015  0.03515625v 48v  110k/10k  4v    0.020  0.046875v 60v  140k/10k  4v    0.025  0.05859375v 72v  170k/10k  4v    0.030  0.0703125v max bit level rounding error @ 72v = +-0.123046875v```

 Daox 04-07-2014 10:16 AM

Quote:
 Originally Posted by mechman600 (Post 418990) Did you include the capacitors that are in the datasheet? Omitting them would certainly cause erratic readings. http://i1119.photobucket.com/albums/...psb2618e94.png The 47nF cap between 0V and 5V isn't necessary because the arduino already has a much larger cap between its 5V and ground. But the 4.7nF cap between output and 0V will no doubt smooth out signal irregularities.
Its been a while so I'm not absolutely sure. I believe I did. In any case, I suggest testing things to make sure they're accurate before you go driving off into the sunset. :)

You're doing a great job! I look forward to more updates. :thumbup:

 All times are GMT -4. The time now is 02:52 AM.