I was looking for a real device project to get out from "blinking leds" of Arduino's tutorials and build a device that could be useful in some way. I always thought confuse to have to use two voltmeters, besides the excess of wires around, I already burnt almost all current scales from my voltmeters, to measure current and voltage simultaneously.
Versão em português desse artigo: Volt-Amperímetro com Arduino - Parte 1: Protoboard.
This project is based on model described in PIC Volt Ampere Meter. I thought that his project was viable, but I would need a PIC programmer and make all source code by hand, since they prefer don't disclose many project details in order to create interest in people in purchase their full assembly kit. As I already had an Arduino, I decided use it as prototyping and programming platform and then write the code by myself. Finally, the only advantages taken from the PIC model was the usage of shunt resistor in current measurement and the idea for a calibration mode, since the total circuit resistances could not be entirely predictable even when using 1% precision resistors.
The component list to build this breadboard project is:
- 1 Arduino;
- 1 Breadboard (don't forget the jumper wires);
- 1 Display 1602A (16x2 with backlight);
- 1 1x16 break away headers for display fixing;
- 1 Buzzer;
- 2 Screw terminals with 2 pins each one;
- 3 Tactile switches (buttons);
- 1 10k potentiometer;
- 6 10k resistors;
- 2 100k resistors;
- 1 100R resistor;
- 1 10R resistor;
- 1 0.47R 5W power resistor.
The components must be assembled in breadboard as following:
For those who yet don't know, the application used to create this breadboard schema was Fritzing. It's free and very easy to use. Click here to download the Fritzing source file for this project.
Bellow the Arduino sketch source code:
//version #define NAME "Arduino Ammeter" #define VERSION "0.9" //debug flag (avoid enabling. it makes your device slower) //#define DEBUG //pins const int PIN_BACKLIGHT = 7; const int PIN_BUZZER = 3; const int PIN_VOLTAGE = 0; const int PIN_CURRENT = 1; const int PIN_BUTTON_UP = 6; const int PIN_BUTTON_SETUP = 5; const int PIN_BUTTON_DOWN = 4; // includes #include <LiquidCrystal.h> #include <EEPROM.h> // initialize the library with the numbers of the interface pins LiquidCrystal lcd(13, 12, 11, 10, 9, 8); //variables //voltage int VOLTAGE_CURRENT; int VOLTAGE_LAST=99999; unsigned long VOLTAGE_MILLIS; float VOLTAGE_CALCULATED; float VOLTAGE_MAP = 50; //default voltage map... calibration needed //current int CURRENT_CURRENT; int CURRENT_LAST=99999; unsigned long CURRENT_MILLIS; float CURRENT_CALCULATED; float CURRENT_MAP = 10; //default current map... calibration needed //buttons boolean BUTTON_PRESSED = false; unsigned long BUTTON_MILLIS = false; byte BUTTON_LAST; boolean SETUP_MODE = false; byte SETUP_ITEM; boolean SETUP_DELAYBEEP; //... unsigned long MILLIS; unsigned long SETUP_BLINKMILLIS; boolean SETUP_BLINKSTATE; //parameters const int SENSOR_INTERVAL = 500; const int BUTTON_HOLDTIME = 2000; const int SETUP_MAXITEMS = 2; const int SETUP_BLINKINTERVAL = 300; const byte EEPROM_VALIDATOR = 73; //random number const float VOLTAGE_STEP = 0.1; const float CURRENT_STEP = 0.1; //configuration const byte EEPROM_CONFIGADDRESS = 0; struct config_t { byte Validator; ///////////////////// float VOLTAGE_MAP; float CURRENT_MAP; ///////////////////// byte ValidatorX2; } EEPROM_DATA; void setup() { //configure pins pinMode(PIN_BACKLIGHT, OUTPUT); pinMode(PIN_BUZZER, OUTPUT); pinMode(PIN_VOLTAGE, INPUT); pinMode(PIN_CURRENT, INPUT); pinMode(PIN_BUTTON_UP, INPUT); pinMode(PIN_BUTTON_SETUP, INPUT); pinMode(PIN_BUTTON_DOWN, INPUT); //set up LCD lcd.begin(16, 2); //initial message lcd.setCursor(0, 0); lcd.print(NAME); lcd.setCursor(0, 1); lcd.print("Version "); lcd.print(VERSION); //lights up digitalWrite(PIN_BACKLIGHT, HIGH); #ifdef DEBUG delay(2000); lcd.setCursor(0, 1); lcd.print("Debug enabled! "); lcd.print(VERSION); Serial.begin(9600); Serial.println("============================"); Serial.println(NAME); Serial.println("Version "); Serial.println(VERSION); Serial.println("============================"); Serial.println("Debug messages:"); Serial.println("----------------------------"); #endif //try to load the configuration loadConfiguration(); //show initial message for a while then clear and beep delay(2000); lcd.clear(); showLabels(); //beep beepStart(); } void loop() { processButtons(); MILLIS = millis(); if ( (MILLIS - VOLTAGE_MILLIS) >= SENSOR_INTERVAL ) { readVoltage(); if (!SETUP_MODE || SETUP_ITEM!=1) { showVoltage(); } VOLTAGE_MILLIS = MILLIS; } if ( (MILLIS - CURRENT_MILLIS) >= SENSOR_INTERVAL ) { readCurrent(); if (!SETUP_MODE || SETUP_ITEM!=2) { showCURRENT(); } CURRENT_MILLIS = MILLIS; } if (SETUP_MODE) { if ( (MILLIS - SETUP_BLINKMILLIS) >= SETUP_BLINKINTERVAL ) { if (SETUP_BLINKSTATE) { if (SETUP_ITEM==1) showVoltage(); else if (SETUP_ITEM==2) showCURRENT(); SETUP_BLINKSTATE = false; } else { if (SETUP_ITEM==1) hideVoltage(); else if (SETUP_ITEM==2) hideCURRENT(); SETUP_BLINKSTATE = true; } SETUP_BLINKMILLIS = MILLIS; } } } void processButtons() { if (digitalRead(PIN_BUTTON_UP) == HIGH) { if (!BUTTON_PRESSED) { #ifdef DEBUG showDebug("Pressed UP"); #endif BUTTON_LAST = PIN_BUTTON_UP; BUTTON_PRESSED = true; } } else if (digitalRead(PIN_BUTTON_SETUP) == HIGH) { if (!BUTTON_PRESSED) { #ifdef DEBUG showDebug("Pressed SETUP"); #endif beepButton(); BUTTON_LAST = PIN_BUTTON_SETUP; BUTTON_MILLIS = millis(); BUTTON_PRESSED = true; SETUP_DELAYBEEP = false; } else { if ((millis() - BUTTON_MILLIS) > BUTTON_HOLDTIME) if (!SETUP_DELAYBEEP) { beepButton(); SETUP_DELAYBEEP = true; } } } else if (digitalRead(PIN_BUTTON_DOWN) == HIGH) { if (!BUTTON_PRESSED) { #ifdef DEBUG showDebug("Pressed DOWN"); #endif BUTTON_LAST = PIN_BUTTON_DOWN; BUTTON_PRESSED = true; } } else { if (BUTTON_PRESSED) { if (BUTTON_LAST == PIN_BUTTON_SETUP) { #ifdef DEBUG showDebug("Released SETUP"); #endif if (!SETUP_MODE && (millis() - BUTTON_MILLIS) > BUTTON_HOLDTIME) { #ifdef DEBUG showDebug("Entered setup mode!"); #endif lcd.setCursor(0, 1); lcd.print(" Setup Mode "); SETUP_MODE = true; SETUP_ITEM = 1; } else { if (SETUP_ITEM == SETUP_MAXITEMS) { #ifdef DEBUG showDebug("Exited setup mode!"); #endif showLabels(); SETUP_MODE = false; SETUP_ITEM = 0; saveConfiguration(); } else { SETUP_ITEM++; } showVoltage(); showCURRENT(); } } else if (BUTTON_LAST == PIN_BUTTON_UP) { #ifdef DEBUG showDebug("Released UP"); #endif if (SETUP_MODE) { beepButton(); if (SETUP_ITEM==1) { //voltage VOLTAGE_MAP+=VOLTAGE_STEP; readVoltage(); #ifdef DEBUG startDebug("New VOLTAGE_MAP: "); Serial.println(VOLTAGE_MAP,6); #endif } else if (SETUP_ITEM==2) { //current CURRENT_MAP+=CURRENT_STEP; readCurrent(); #ifdef DEBUG startDebug("New CURRENT_MAP: "); Serial.println(CURRENT_MAP,6); #endif } } } else if (BUTTON_LAST == PIN_BUTTON_DOWN) { #ifdef DEBUG showDebug("Released DOWN"); #endif if (SETUP_MODE) { beepButton(); if (SETUP_ITEM==1) { //voltage VOLTAGE_MAP-=VOLTAGE_STEP; readVoltage(); #ifdef DEBUG startDebug("New VOLTAGE_MAP: "); Serial.println(VOLTAGE_MAP,6); #endif } else if (SETUP_ITEM==2) { //current CURRENT_MAP-=CURRENT_STEP; readCurrent(); #ifdef DEBUG startDebug("New CURRENT_MAP: "); Serial.println(CURRENT_MAP,6); #endif } } } BUTTON_PRESSED = false; } } } #ifdef DEBUG void showDebug(char* Message) { Serial.print(millis()); Serial.print(": "); Serial.println(Message); } void startDebug(char* Message) { Serial.print(millis()); Serial.print(": "); Serial.print(Message); } #endif void showLabels() { lcd.setCursor(0, 1); lcd.print("Volts Amps"); } void showVoltage() { lcd.setCursor(0, 0); lcd.print(VOLTAGE_CALCULATED, 2); lcd.print(" V"); if (VOLTAGE_CALCULATED<10) lcd.print(" "); } void hideVoltage() { lcd.setCursor(0, 0); lcd.print(" "); } void showCURRENT() { lcd.setCursor(9, 0); if (CURRENT_CALCULATED<10) lcd.print(" "); lcd.print(CURRENT_CALCULATED, 2); lcd.print(" A"); } void hideCURRENT() { lcd.setCursor(9, 0); lcd.print(" "); } void beepStart() { for (int i=0; i<300; i++) { digitalWrite(PIN_BUZZER, HIGH); delayMicroseconds(200); digitalWrite(PIN_BUZZER, LOW); delayMicroseconds(200); } } void beepButton() { for (int i=0; i<20; i++) { digitalWrite(PIN_BUZZER, HIGH); delayMicroseconds(700); digitalWrite(PIN_BUZZER, LOW); delayMicroseconds(700); } } void readVoltage() { VOLTAGE_CURRENT = analogRead(PIN_VOLTAGE); if ( VOLTAGE_CURRENT != VOLTAGE_LAST || SETUP_MODE ) { VOLTAGE_LAST = VOLTAGE_CURRENT; VOLTAGE_CALCULATED = fmap(VOLTAGE_CURRENT, 0, 1023, 0.0, VOLTAGE_MAP); #ifdef DEBUG if (!SETUP_MODE) { startDebug("New voltage: "); Serial.print(VOLTAGE_CALCULATED); Serial.println("V"); } #endif } } void readCurrent() { CURRENT_CURRENT = analogRead(PIN_CURRENT); if ( CURRENT_CURRENT != CURRENT_LAST || SETUP_MODE ) { CURRENT_LAST = CURRENT_CURRENT; CURRENT_CALCULATED = fmap(CURRENT_CURRENT, 0, 1023, 0.0, CURRENT_MAP); #ifdef DEBUG if (!SETUP_MODE) { startDebug("New current: "); Serial.print(CURRENT_CALCULATED); Serial.println("A"); } #endif } } float fmap(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } int EEPROM_writeConf() { byte Address = EEPROM_CONFIGADDRESS; const byte* p = (const byte*)(const void*)&EEPROM_DATA; int i; for (i = 0; i < sizeof(EEPROM_DATA); i++) EEPROM.write(Address++, *p++); return i; } int EEPROM_readConf() { byte Address = EEPROM_CONFIGADDRESS; byte* p = (byte*)(void*)&EEPROM_DATA; int i; for (i = 0; i < sizeof(EEPROM_DATA); i++) *p++ = EEPROM.read(Address++); return i; } void loadConfiguration() { //read data from eeprom EEPROM_readConf(); //verify validators if (EEPROM_DATA.Validator == EEPROM_VALIDATOR && EEPROM_DATA.ValidatorX2 == EEPROM_VALIDATOR*2) { //copy data VOLTAGE_MAP = EEPROM_DATA.VOLTAGE_MAP; CURRENT_MAP = EEPROM_DATA.CURRENT_MAP; #ifdef DEBUG showDebug("Configuration loaded from EEPROM!"); startDebug(" VOLTAGE_MAP: "); Serial.println(VOLTAGE_MAP,6); startDebug(" CURRENT_MAP: "); Serial.println(CURRENT_MAP,6); #endif } else { #ifdef DEBUG showDebug("Configuration NOT loaded from EEPROM!"); #endif } } void saveConfiguration() { if ( EEPROM_DATA.VOLTAGE_MAP != VOLTAGE_MAP || EEPROM_DATA.CURRENT_MAP != CURRENT_MAP ) { //copy validators EEPROM_DATA.Validator = EEPROM_VALIDATOR; EEPROM_DATA.ValidatorX2 = EEPROM_VALIDATOR*2; //copy data EEPROM_DATA.VOLTAGE_MAP = VOLTAGE_MAP; EEPROM_DATA.CURRENT_MAP = CURRENT_MAP; //save data to eeprom EEPROM_writeConf(); #ifdef DEBUG showDebug("Configuration saved!"); #endif } else { #ifdef DEBUG showDebug("Configuration not changed!"); #endif } }
Note that at beginning of code there is a DEBUG constant definition. Uncomment this line will activate event warnings that can be monitored through Arduino Serial Monitor. This feature could help in beadboard assembly and source code debugging, however, besides write a much larger sketch in microcontroller, it will also make the software considerably slower because serial ports has a fixed speed that is considerably low. So leaving this feature enabled unnecessarily is not recommended.
The 3 buttons are used for calibration. The center button is the configuration one and it activates the calibration mode if pressed for 2 seconds, confirmed by a beep. During the coding, I had the impression that it does not properly calculate the time, then I judged interesting to have a second beep to confirm this 2 seconds taking their own time to calculate. The other buttons on left and right are to decrease and increase the calibration respectively, followed by one beep. The calibration starts with voltage, then pressing the configuration button again switches to current and when pressed again saves the configuration to EEPROM and turn the device back to normal mode.
See the continuation of this article at: Final Part - Printed circuit board
Great work !!!
ReplyDeleteCould You check the Link to the Fitzing source? It don't work for me.
Thanks a lot!
I already started to move these links to Megaupload, but I don't know why I forgot this one. Thanks for reporting!
ReplyDeleteVery nice..
ReplyDeleteIs there a way to modify this to make an Arduino monitor its own power consumption and then output to either an LCD (the device I wish to monitor already has an 4x20 LCD hooked up) or serial connection?
I think that your LCD may to work with Arduino's LCD Library, but I never tried it before. For Arduino to read It's own consumption you will need a pretty different configuration. It's not easy if you pretend to use the onboard serial support to read the measurements and/or supply power to device. Just remember that it's not recommended to supply devices using the power from Arduino's ports and if you supply this device with variable voltage it will never find a good calibration.
ReplyDeleteRenato pode da uma luz no calculo da potencia, ja fiz varias alterações e nada.. de um bateria em porcentagem??
ReplyDeleteUm pack de bateria de 25v como esse http://www.batteryspace.com/LiFePO4-26650-Battery-25.6V-6.6Ah-168Wh-20A-rate-in-Aluminum-Box.aspx tem um range de trabalho entre 25.6v e 16v. Você precisa considerar que 25.6v é o 100% e o 16v seria o 0%. O consumo nao é linear e se você colocar uma carga alta ele pode ir instantaneamente para 80%, por exemplo, mas é assim que é :). Você até pode colocar o 100% na casa dos 23v, por exemplo, se quiser que o sistema fique mais tempo indicando "carregado", mas aí quando ele começar a cair acaba rápido como acontece com alguns celulares. Entendeu?
ReplyDeletePara fazer esse mapeamento de voltagem->percentual você pode usar a função 'fmap' do código acima dessa forma:
fmap(VOLTAGE_CALCULATED, 16, 25.6, 0, 100)
Sendo 16 e 25.6 o range de voltagem da bateria que eu te mostrei acima, substitua os valores para a bateria que você usa. Lembre que o 0 e 100 é o range percentual, você não precisa mudar.
Eu não testei isso, apenas digitei de cabeça, então talvez você precise alterar algo.
hello.
ReplyDeletevery nice is your work. please help me.can you tell me who is the model of lcd? because i use this lcd but the pins is not same with your lcd. we use this lcd http://www.marelectronics.gr/products.php?id=3254&lang=gr.
i would like to give me a link for your lcd model
Thank you very much
Hello, untypeable named guy! :P
ReplyDeleteWell, try these ones from sparkfun: http://www.sparkfun.com/search/results?term=1602&what=products
But your display, from URL, seems to have the same pinout. Believe, I checked the datasheet.
Note that I just rearranged the connections, then it will not look the same, or even work, with the original LCD display example from Arduino's web site.
Thanks for great tutorial! I'm having trouble finding .47ohm 5W power resistor in USA. Is that correct value? Does it have to be 5W? :)
ReplyDeletejust keep posting such an informative articles, I want to know more about this topic.
ReplyDeleteWould you post the Fritzing source please (the link does not work due to Megaupload being closed).
ReplyDeleteThanks
Could the voltage limit be raised to say 125V?
ReplyDeleteAnd could you also add a rectifier so the circuit could accept AC voltage within that range( 0-125VAC )?
Thanks,
cheapkid1
Good day. I tried to assemble your amazing project it works fine, however I encountered problem during calibration. As I press the calibration tact switch nothing happens. I double checked the connections at pin 4,5,6 in my breadboard and it seems the same as yours. Do you have any idea about my problem?
ReplyDeleteThis topic is something that I have been looking into for a while now and your insight is exceptional. Thanks for sharing this information.
ReplyDeletepcb assembly services
Hello Renato!
ReplyDeleteCan you help me, please. What is the value of fuses in microcontroller?
Thank for your answer.
Hello my friend, i need your HELP....
ReplyDeleteI made the circuit....works fine...!!!
I wanted to know what changes do i have to do so it can measure 200-250 VDC and the same ampere..!!!??????
Do i have to change the code or i have to change only the values of the resistors....????
Thanks a lot my friend..!!!
And keep up the good work..!!!!
I want to thank you for sharing this great Project. My "Arduino Battery Analyser" (short: ABA) is based on your circuit. I started building the ABA about one year ago. It's extended by a Constant DC Load described here: http://www.youtube.com/attribution_link?a=YyHL2x7IQ4d-_aMuS7w0lQ&u=/watch?v%3DhgrvJ2b2xfg%26feature%3Dshare controlled by a pwm pin of the arduino to adjust the load. The measured voltages and some header values like capacity are written to EEPROM. So the device can operate standalone. After measurement you can connect it to the PC to read the EEPROM Data. I'll share the circuit sheme and source code on my website in some weeks. You can see a preview of it on my Blog in here: http://1dev.de/arduino-basiertes-batterie-analysegeraet/64/
ReplyDeleteSorry, it's only aviable in german.
Hope it'll be useful for anybody!
is it possible to make the code working for an 16x2 lcd with I2C protocol? its much more easy to use nad requires less cables and connections.
ReplyDeletei cant do the conversino myself :(
The hot wire ammeter allows current to pass through wires, which then expands as the wires heat. The major disadvantage of using hot wire ammeters is that, they take a lot of time to respond and they have low accuracy. The hot wire ammeters were commonly used to measure the current in radio frequency.
ReplyDeleteammeter 101
I think that the biggest challenge with these measurements, even the voltage, is the scale or range. That's why all the multimetters have a scale switch, what changes the input reference, even for the automatic/electronic ones.
DeleteHello Renato.
ReplyDeleteVery good job.
I just want to implement your work to my power adapter. (1-12V)
Could you help me ? I would like to add to draft the appropriate code to create a short-circuit protection to disconnect some relay on the power supply output. I tried to add an additional condition of "if" but not working. :(
Thank You
Alice.
Why don't you consider adding a resettable fuse in power lines instead? It would be quite easier to do if you are looking for a simple protection. You could try, for example, opening the power line before etching and then adding a resettable fuse as jumper to close it again. A relay will require a few more components and code to work instead.
DeleteThank you for the quick reply.
DeleteI thought the easiest way to add a condition. something like:
"CURRENT_CURRENT = analogRead (PIN_CURRENT);
if (CURRENT_CURRENT! = CURRENT_LAST || SETUP_MODE) {
CURRENT_LAST = CURRENT_CURRENT;
CURRENT_CALCULATED = FMap (CURRENT_CURRENT, 0, 1023 0.0 CURRENT_MAP); "
and here, for example. "
If (CURRENT_CALCULATED >= 5A
digitalWrite (RelayPin, HIGH);
I also thought that it was possible to add to the SETUP can be set for this critical threshold (5A) and can change it.
Alice.
Well, your idea looks perfect, but I suppose you will have to add a kind of hysteresis in your relay decision to permit the circuit to cool down and to avoid it from keeping oscillating.
DeleteHi Renato.
ReplyDeleteThank You for Your answer.
0.47 Ohm resistor is already mounted on a large heatsink (15x15cm.) With a large fan. His temperature guarding the other Arduino so if there is a short circuit or overload critical I think that I will not have to wait long to resistor returned to normal. At that time I'll be able to run again power supply (reset).
This is my first steps in programming the Arduino and stuck in place to create an appropriate condition I wrote about earlier. Could You write me only this condition that I can add to the code that started the relay after crossing 5A. ?
Thank you in advance.
Alice.
Hi Renato.
ReplyDeleteI did not expect that it's not that simple.
Fortunately, I advised myself and now I'm almost at the end.
I went through probably the most difficult, to add the variable "float" to SETUP :)
Now step by step working on.
I'll brag about how I'm done.
Regards
Alice
Great! Good luck! :D
DeleteHi, nice project try to make this one to my homemade atx bench PS. little question, how many ampere max can be handled by this Multimeter ?
ReplyDeleteIt can measure until 5A. But how much time it can handle I cannot predict. It will also depends on how you construct it.
Deletehello, is that this circuit may measure a voltage of 380 V and a current of 100A ?! :)
ReplyDelete100 A would burn the board to hell. You need to look elsewhere or build a project yourself.
Delete38000 Wats? :D Tell us what you are doing...
DeleteHello Renato,
ReplyDeleteI love this project. It is very well done, I have built this and its working great. I would like to learn more about Arduino and I want to build cool projects like you. Therefore I have a question.
Can you please give a simple explanation of how does the source code work? I want to be progammer myself but I dont understand this code.
Thank you very much in advance,
Nguyen Sy Nam
Well... it is compiled and uploaded to the AVR micro controller using the Arduino board and the Arduino IDE.
DeleteIt keeps looping over and over again reading the inputs, but most of things are responsible by making the LCD and buttons work together like a real GUI. There is a video showing how it works, it is available in the part 2 of this article.
Oh I see. Atmega is measuring the current and voltage by itself. There is already voltmeter and ammeter in it right? So the function of code is to make a calibration mode and looping for more precise measuring?
DeleteThanks for reply :)
This comment has been removed by the author.
DeleteI am sorry if look dumb. I just want to understand the purpose of the code. :)
DeleteHi Renato and good job
ReplyDeletecould you help me please
i want to acquire those data (volt -amper) to Matlab
is this possible
thanks
I have no idea. Good luck trying and tell us if it worked!
Deleteis this circuit measure this range of current (6mA _ 100 mA)??
DeleteYes, but if this is your working range you may want to reduce the limits in the device as well in order to increase the precision.
DeleteShould i change the two resistors of 100K
Deletei didn't understand Renato,more explanation please !!
PessoALL,
ReplyDeleteao tentar compilar o programa ele da um Warning abaixo:
C:\teste_V_A.ino:33:18: warning: overflow in implicit constant conversion [-Woverflow]
int CURRENT_LAST=99999;
Alguem poderia me ajudar ... sou um em programação com arduino.
Usando ide 1.6.8 e OpenSource Funduino Uno.
Grato
Leandro Costa
Awsome project. I just want to ask what is the voltage measurement limit? And did you designed any pin for it?
ReplyDeleteCan I use 16x2 I2C lcd?
ReplyDeletesure, it need just small changes at initialization of the LCD
Delete