How to make more precise the reading of AnalogPins in Arduino? - arduino

I'm new here so, if I make any mistake, sorry. Well, I'm working with Arduino (Mega2560) to construct an Ammeter and found out a little problem... Arduino Mega measures voltage from 0 to 5V, and the AnalogPins return a 10-bit value according with the reading (that is, 1 bit represents 5/(2^10)=4mV (approximately)). But, in the case of ammeter, I need to use a resistor with small resistance so that my circuit don't get changes. So my objective is read the voltage drop and from V = R.I, calculate the current. But, as the voltage drop is such as slowly, the pin can't read any value.
Eg.: there is a current flowing from 2mA in the region that I would like to measure. With a resistance of 0.3 ohms (the lower value I found here) , would be: V = 2m . 0.3 = 0.6mV.
As I said, the lower posssible value of reading in analogPins is 4mV.
Thus, how to improve my precision of reading? For example, instead of 1023 represents only 5V, the same value represents around of 30 or 40mV...
0 - 0 V
1023 - 30/40 mV

You can use 1.1V internal voltage reference, or some more precise external one (This can be archieved by analogReference). BTW with such a small currents it would be more convenient to use bigger resistor.
Or, forget about limited functionality of analogRead and do it directly. For example 2.56V reference, differential input with 10x or 200x gain (but you'll get range -512 to 511 -> 2.56/512).

In below example, voltage_meter reads 500 samples in about 1 millisecond and returns the average. I set the reference to 1.1v for better precision.
int battery_pin = A3;
float voltage_meter()
{
//read battery voltage per %
long sum = 0; // sum of samples taken
float voltage = 0.0; // calculated voltage
float output = 0.0; //output value
for (int i = 0; i < 500; i++)
{
sum += analogRead(battery_pin);
delayMicroseconds(1000);
}
// calculate the voltage
voltage = sum / (float)500;
// voltage = (voltage * 5.0) / 1023.0; //for default reference voltage
voltage = (voltage * 1.1) / 1023.0; //for internal 1.1v reference
//round value by two precision
voltage = roundf(voltage * 100) / 100;
return voltage;
}
void setup()
{
analogReference(INTERNAL); //set reference voltage to internal
Serial.begin(9600);
}
void loop()
{
Serial.print("Voltage Level: ");
Serial.print(voltage_meter(), 4);
Serial.println(" V");
delay(1000);
}

On ATmega based boards (UNO, Nano, Mini, Mega), it takes about 100 microseconds (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second.
100 Microsecond and no 1000 how the example

Related

To know the % charge of the battery using Arduino

I am using Arduino Nano and various Li-Fe , Li-Po batteries of 9.9V , 6.6V and 3.7V.
I can read the voltage of the battery using Arduino . My Arduino works at 5V so for batteries like 9.9V and 6.6V I have used a voltage divider using two 10k resistors.But the problem is I need to read the the % of charged battery , I tried something in the code but I am not sure about it. Please anyone help me with it.
My code is:
#define cellPin A0
const float mvpc = 4.55 ; //measured voltage of arduino through voltmeter
float counts = 0; //battery volts in millivolts
float mv = 0;
float multiplier = 2;
float output = 0;
int charge = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
counts = analogRead(cellPin);
Serial.println(counts);
mv = counts * mvpc;
Serial.println(mv);
output = (mv * multiplier)/1000 ;
Serial.print(output);
Serial.println("V");
charge = (counts/1024)*100;
Serial.print(charge);
Serial.println("%");
delay(1000);
}
In order to accurately determine the % of the charged battery, you need the discharge graph for each of the batteries. The discharge graph is usually non-linear for lithium batteries. The discharge curve is basically the voltage versus the % charge and it is different for charging and discharging batteries.
If you have the discharge curve, you can create a map for each of the % to the corresponding voltage value. Then you can map each voltage to a % value from the map you created.
For example:
100% -> 5.00 V
99% -> 4.95 V
....
0% -> 3.23 V
Create an array to store the map of size 100 (for each %): [5.00, 4.95, ... 3.23]
You can then find the %s using the voltage. I hope you can find the discharge graph, otherwise, you can manually find it yourself by discharging the battery using a safe current
In addition to maheenul I'd like to add that you're calculations are a bit off.
const float mvpc = 4.55 ; //measured voltage of arduino through voltmeter
I presume you're supplying your Arduino via USB. This voltage is not very reliable.
float counts = 0; //battery volts in millivolts
counts = analogRead(cellPin);
analogRead returns a 10bit (0-1023) ADC reading. A fraction of your voltage reference. It is not a value in millivolts! So in the following you shoudl use 1024 instead of 1000.
output = (mv * multiplier)/1000 ;
charge = (counts/1024)*100; this calculates the percentage of your Vref. It is not a charge as explained by maheenul.
If you want accurate measurements you should use a better Vref. Either the internal 1.1Vref or some well regulated Vref. Both with appropriate voltage dividers.
But I guess being 5-10% off is not a big deal for a battery charge measurement.

How to measure battery voltage with internal adc ESP32

i'm doing wireless sensor node using (esp32, DHT11, soil moisture and nrf24l01) and i want to add an battery to supply those sensors, also need to measure battery voltage.
For the battery, voltage always change to cant use as a Vcc reference, so i find there is an internal reference voltage.
Could anyone done with this give me some instruction.
Thank you
i'm gonna use LIFEPO4 3.3v normaly (3.6v at max) or 18650 3.7v/4.2v max
According to docs:
The default ADC full-scale voltage is 1.1V. To read higher voltages
(up to the pin maximum voltage, usually 3.3V) requires setting >0dB
signal attenuation for that ADC channel.
So set it to zero for 1.1v; next, you can read the voltage (in a loop for better accuracy) and then convert it to a valid voltage and find the percentage of battery level.
In the below example, the function would return the percentage of battery level. Remember to edit battery_max and battery_min based on your battery voltage levels. I assumed that you connect the battery to ADC1 channel 0 (GPIO 36).
Also, I recommend you create a resistor divider circuit to reduce the voltage level. If your input power supply drops down, the Arduino will feed directly from Analog input, which is undesirable. Remember that your voltage level should not exceed above 3.9v.
#include <driver/adc.h>
float battery_read()
{
//read battery voltage per %
long sum = 0; // sum of samples taken
float voltage = 0.0; // calculated voltage
float output = 0.0; //output value
const float battery_max = 3.6; //maximum voltage of battery
const float battery_min = 3.3; //minimum voltage of battery before shutdown
float R1 = 100000.0; // resistance of R1 (100K)
float R2 = 10000.0; // resistance of R2 (10K)
for (int i = 0; i < 500; i++)
{
sum += adc1_get_voltage(ADC1_CHANNEL_0);
delayMicroseconds(1000);
}
// calculate the voltage
voltage = sum / (float)500;
voltage = (voltage * 1.1) / 4096.0; //for internal 1.1v reference
// use if added divider circuit
// voltage = voltage / (R2/(R1+R2));
//round value by two precision
voltage = roundf(voltage * 100) / 100;
Serial.print("voltage: ");
Serial.println(voltage, 2);
output = ((voltage - battery_min) / (battery_max - battery_min)) * 100;
if (output < 100)
return output;
else
return 100.0f;
}
void setup()
{
adc1_config_width(ADC_WIDTH_12Bit);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_0db); //set reference voltage to internal
Serial.begin(9600);
}
void loop()
{
Serial.print("Battery Level: ");
Serial.println(battery_read(), 2);
delay(1000);
}
If you add a divider circuit, you need to change battery_min and battery_max according to the new output of the divider circuit.

analogRead() output oscillates even though the pin is grounded

I'm using Arduino Micro to read from 5 flex sensors and display the corresponding angles to the Serial monitor. I am currently having quite some problems with the oscillating values I am getting from the analogRead(). It doesn't seem to matter whether the pin is connected to a flex sensor or just grounded - the output is oscillating a lot.
Originally everything was being read and outputted just fine but I wanted to have an exact 100Hz sampling frequency and tried to play a bit with Timer Interrupts. And that's when this oscillating behaviour started. I reversed to my original code, which just uses some delay(), and simplified to only read from two pins, but cannot seem to shake off the oscillations.
I think I may have messed up something about ADC when trying to implement Interrupts, but I don't know how to check it or fix it. Please, help me figure out how to fix this!
This is the raw output of analogRead. The drop in values occurs when I bend the flex sensor
And this is the resulting calculated angle. Also oscillating.
Here is my code minimal working example:
int fin;
const int input[5] = {A0,A1,A2,A3,A4}; // the analog pins
int flex[5]; // analog signal read
float flexV;
float flexR[5]; // resistance on the 47k resistor
int angle[5]; // joint angles
const float VCC = 4.98; // Measured voltage of Arduino 5V line
// Measured resistance of the 47k resistors R1-R5
const float R[5] = {45900.0,45900.0,45900.0,45900.0,45900.0};
// Calibration values of resistance measured during straight phase and 90 deg bend phase
const float R_STRAIGHT[5] = {37651.0,37651.0,37651.0,37651.0,37651.0};
const float R_BEND[5] = {71783.0,71783.0,71783.0,71783.0,71783.0};
void setup() {
}
void loop() {
for(fin = 0; fin <= 4; fin++) {
flex[fin] = analogRead(input[fin]);
flexV = flex[fin]*VCC/1023.0;
flexR[fin] = R[fin] * (VCC/flexV - 1.0);
angle[fin] = map(flexR[fin],R_STRAIGHT[fin],R_BEND[fin],0,90.0);
delay(1);
}
Serial.print(angle[0]);
Serial.print(" ");
Serial.print(angle[1]);
Serial.print(" ");
Serial.print(angle[2]);
Serial.print(" ");
Serial.print(angle[3]);
Serial.print(" ");
Serial.print(angle[4]);
Serial.print(" ");
Serial.println(millis());
delay(6);
}
ok, analogReads normally do have a little oscillation, its normal! they are measuring voltage values and depending on the sensor you are using they will oscillate, same idea of measuring voltage using a multi meter. if you want to learn a bit more about this, ADC's conversor is a good way to start.
What you need to do in order to prevent those oscillations is to develop a filter. this could be done on hardware or software. Obviously the software is the easiest way to go.
My tip for you would be to a average filter! it's a simple concept, you will get X readings at the same time of that sensors (values would go up and down on variation) and you would get the avarage value out of it.
Here is a simple example using your code:
int fin;
const int input[5] = {A0,A1,A2,A3,A4}; // the analog pins
int flex[5]; // analog signal read
float flexV;
float flexR[5]; // resistance on the 47k resistor
float average; //Variable to store the sum of measurements
int nSamples = 4; //Number of reading you are going to use
int angle[5]; // joint angles
const float VCC = 4.98; // Measured voltage of Arduino 5V line
// Measured resistance of the 47k resistors R1-R5
const float R[5] = {45900.0,45900.0,45900.0,45900.0,45900.0};
// Calibration values of resistance measured during straight phase and 90 deg bend phase
const float R_STRAIGHT[5] = {37651.0,37651.0,37651.0,37651.0,37651.0};
const float R_BEND[5] = {71783.0,71783.0,71783.0,71783.0,71783.0};
void setup() {
}
void loop() {
for(fin = 0; fin <= 4; fin++) {
/* A new for here to make readings and store them on the average variable */
for(int x = 0; x <= nSamples; x++){
flex[fin] = analogRead(input[fin]);
average = average + flex[fin];
}
/*Do de avarage and clear the value on this variable*/
flex[fin] = average/nSamples;
avarage = 0;
flexV = flex[fin]*VCC/1023.0;
flexR[fin] = R[fin] * (VCC/flexV - 1.0);
angle[fin] = map(flexR[fin],R_STRAIGHT[fin],R_BEND[fin],0,90.0);
delay(1);
}
Serial.print(angle[0]);
Serial.print(" ");
Serial.print(angle[1]);
Serial.print(" ");
Serial.print(angle[2]);
Serial.print(" ");
Serial.print(angle[3]);
Serial.print(" ");
Serial.print(angle[4]);
Serial.print(" ");
Serial.println(millis());
delay(6);
}
The idea here is simple, to smooth the values by doing this average, leading to more consistent values. Obviously, Higher number of samples improve the results.
It's simple math, if you are getting 4 values like: 45, 50, 55, 50, your average would be 50 (45+50+55+50 = 200/nSamples = 50)

LM35 decreases when near heat

My LM35 connected to arduino decreases temperature value in celsius when near heat and increases value when far from heat. Could any one help or know why it's working other way round.
void setup() {
// put your setup code here, to run once:
//Start the serial connection with the computer
//to view the result open the serial monitor
// 9600 is the “baud rate”, or communications speed.
Serial.begin(9600);
}
void loop() {
delay(2000);
float tempValue = analogRead(A2);
// converting that reading to voltage
float tempVoltage = (tempValue/1024.0)*5.0;
float tempDegrees = (tempVoltage - 0.5) * 100.0 ;
//Multiplying tempDegrees by -1 to make it positive
tempDegrees =(tempDegrees * -1);
Serial.println("............................................");
Serial.println("Degrees");
Serial.println(tempDegrees);
delay(2000);
}
just randomly came accross your question. and it has been 6 years since I touched an LM35 :d
but I think you have a problem in that -0.5 thing. I did not really get that!
LM35's function as far as I remember was :
T = V/ 10mV
you might want to check the datasheet but I'm pretty positive this is the equation. when you get the voltage from ADC you have to put it in this equation and get the result.
be careful : you have to also attribute for the temperature error as well as ADC noise if temperature precision is important for you.
If you are using a 5 volts power supply to your arduino:
5 Volts in the Arduino are directly converted to 1023 in the output of the ADC
ADC_Outpput * 5000 / 1024, where 5000 is coming from 5volts as millivolts, 1024 is the 10 bist resolution
LM35 resolution is linearly generated with a rate of + 10-mV/°C
so the analogVolt = ADC_Outpput * 5000 / 1024
FinalTemperature = (analogVolt - 500) / 10

Inaccurate results for arduino uno and ultrasonic sensor

Good day, I have the following code for an ultrasonic sensor that I'm using, but the result is not being accurate and I'd like to display the distance from the target in centimetres.
#include <LiquidCrystal.h> //Load Liquid Crystal Library
LiquidCrystal LCD(10, 9, 5, 4, 3, 2); //Create Liquid Crystal Object called LCD
int trigPin=13; //Sensor Trip pin connected to Arduino pin 13
int echoPin=11; //Sensor Echo pin connected to Arduino pin 11
int myCounter=0; //declare your variable myCounter and set to 0
int servoControlPin=6; //Servo control line is connected to pin 6
float pingTime; //time for ping to travel from sensor to target and return
float targetDistance; //Distance to Target in inches
float speedOfSound=776.5; //Speed of sound in miles per hour when temp is 77 degrees.
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
LCD.begin(16,2); //Tell Arduino to start your 16 column 2 row LCD
LCD.setCursor(0,0); //Set LCD cursor to upper left corner, column 0, row 0
LCD.print("Target Distance:"); //Print Message on First Row
}
void loop() {
digitalWrite(trigPin, LOW); //Set trigger pin low
delayMicroseconds(2000); //Let signal settle
digitalWrite(trigPin, HIGH); //Set trigPin high
delayMicroseconds(15); //Delay in high state
digitalWrite(trigPin, LOW); //ping has now been sent
delayMicroseconds(10); //Delay in high state
pingTime = pulseIn(echoPin, HIGH); //pingTime is presented in microceconds
pingTime=pingTime/1000000; //convert pingTime to seconds by dividing by 1000000 (microseconds in a second)
pingTime=pingTime/3600; //convert pingtime to hourse by dividing by 3600 (seconds in an hour)
targetDistance= speedOfSound * pingTime; //This will be in miles, since speed of sound was miles per hour
targetDistance=targetDistance/2; //Remember ping travels to target and back from target, so you must divide by 2 for actual target distance.
targetDistance= targetDistance*63360; //Convert miles to inches by multipling by 63360 (inches per mile)
LCD.setCursor(0,1); //Set cursor to first column of second row
LCD.print(" "); //Print blanks to clear the row
LCD.setCursor(0,1); //Set Cursor again to first column of second row
LCD.print(targetDistance); //Print measured distance
LCD.print(" inches"); //Print your units.
delay(250); //pause to let things settle
}
I will appreciate any help.
You are performing a lot of floating point calculations on every iteration of your loop. Rather than performing these calculations you can pre-compute a single multiplier that will convert the pingTime to a distance in centimetres.
First we need to calculate the metric speed of sound from your 776.5 mph.
776.5 miles/hour * 1609.34 (meters in a mile) = 1249652.51 metres/hour
1249652.51 metres/hour / 3600 = 347.126 metres/second
So we can precompute a multiplier as follows:
float speedOfSound = 347.126; // metres per second
float speedOfSound_cm_p_us = speedOfSound * 100 / 1000000; // speedOfSound in centimetres per microsecond
Obviously, this could be reduced to simply:
float speedOfSound_cm_p_us = 0.0347126; // cm per microsecond
Next we can precompute the multiplier by dividing speedOfSound_cm_p_us by 2 as the sound has to travel to the reflective surface and back again.
float pingTime_multiplier = speedOfSound_cm_p_us / 2; // The sound travels there and back so divide by 2.
Again, we could simply replace this step with:
float pingTime_multiplier = 0.0173563;
However, this magic number should be well documented in your code.
Once we have pingTime_multiplier calculated the loop function becomes much simpler. Simply multiply the pingTime by the pingTime_multiplier to get the distance in centimetres.
pingTime = pulseIn(echoPin, HIGH); // pingTime is presented in microseconds
targetDistance = pingTime * pingTime_multiplier;
This significantly reduces the amount of work that the Arduino has to do each time through the loop.
Convert inches to centimeters by multiplying by 2.54
float targetDistanceCentimeters = targetDistance*2.54;
Your code looks correct, so my guess is that this is a hardware problem. In my experience ultra sound sensors are typically quite bad at measuring distance to any object that is not a wall or other large object with relatively flat surface. So if you want to test the accuracy of your system, test it against such object i.e. wall or book.
If your sensor is moving while making the measurements make sure that it is not moving too fast.
If you are measuring distance to small or thin object you will need to filter and take the average of the measurements. It’s all up to you how accurate you want to be, I’d say that average of twenty or so of filtered measurements will give you appropriate result.
Another things to consider is that you have the correct supply voltage and that the sensor is not directed against the floor at some angle, as this might give unwanted reflections.
I'd like to display the distance from the target in centimetres.
Google says an inch is 2.54 cm, so just multipy your result by this.
the result is not being accurate
For more accurate results, take an average of a number of samples (3 or more), and you could implement some heuristics which discard grossly over/under range results.
Say if three readings are around 0-5cm apart, then a fourth reading 40cm away is most likely an error. So just do the average of the three similar results.

Resources