Translate

Sunday, March 22, 2020

The ArduINA226 power monitor

Introduction
In the past I have developed various projects of ammeters based on Hall effect current sensors such as the ACS712, or on High-Side Current-Sense Amplifiers such as the MAX4080SASA or made with operational amplifiers. All these systems have an analog output which must then be digitized. The INA226 sensor has a digital output and incorporates a 16-bit ADC for which a high accuracy and precision is obtained.
It measures current and voltage and calculates power while Arduino communicates with the chip, presents the measurements on an LCD display and stores them on a micro SD card. This chip operates with a maximum voltage of 36 volts while the current is limited only by the shunt used.
There are some libraries for the INA226 chip, I used the Korneliusz Jarzebski library which seems to me quite complete even if I had to make some changes to two functions.
There are numerous possible applications for this monitoring tool: battery-powered devices such as scooters or pedal-assisted bicycles, photovoltaic panels, etc.


The INA226 sensor
In current measurements with the shunt there are two ways to insert it:
1)      To ground (low-side): the shunt is connected between the load and the ground.
2)      Towards the power supply (high-side): the shunt is connected between the power supply and the load.
The INA226 integrated circuit, by Texas Instruments, is a digital device that measures the current with a high-side or low-side shunt and also measures the voltage, calculates the power and provides a multifunctional alarm. The functional scheme is visible in figure 1.
Figure 1


The resolution of the shunt voltage is 2.5 mV with a full scale of 32768x2.5 = 81.92mV. For the VBUS voltage the resolution is 1.25 mV with a theoretical full-scale of 40.96 V even if the 36 V must not be exceeded. The resolution of the power is 25 times that of the current, with a full-scale that depends on the shunt used. So the system has a remarkable measurement accuracy.
The internal ADC is based on a 16-bit delta-sigma converter (ΔΣ) with a typical sampling frequency of 500 kHz (± 30%), so it is also suitable for currents that change rapidly over time.
The chip has 10 pins and very small dimensions, with a DGS case (VSSOP).
The INA226 is able to provide a hardware or software alert if a variable, selected by the user, has exceeded a limit. The user can select one of the five functions available to monitor and/or set the Conversion Ready bit. The five alert functions that can be monitored are:
•       Shunt Voltage Over-Limit (SOL): exceeding the maximum current threshold;
•       Shunt Voltage Under-Limit (SUL): exceeding the minimum current threshold;
•       Bus Voltage Over-Limit (BOL): exceeding the maximum voltage threshold;
•       Bus Voltage Under-Limit (BUL): exceeding the minimum voltage threshold;
•       Power Over-Limit (POL): exceeding the maximum power threshold;
The Alert output is open-drain and can be easily connected to a blocking device. The Alert output is open-drain and can be easily connected to a blocking circuit. For simplicity, I preferred to read the Shunt Voltage Over-Limit (SOL) type alert via software and present it on the display.

The INA226 module
On the market there are small breakout boards such as the one I used, visible in figure 2, of which I obtained the diagram in figure 3.
Figure 2

Figure 3
The device has two address pins, A0 and A1. The following table lists the pin connections for each of the 16 possible addresses.

A1
A0
Slave Address
A1
A0
Slave Address
GND
GND
1000000
SDA
GND
1001000
GND
VS
1000001
SDA
VS
1001001
GND
SDA
1000010
SDA
SDA
1001010
GND
SCL
1000011
SDA
SCL
1001011
VS
GND
1000100
SCL
GND
1001100
VS
VS
1000101
SCL
VS
1001101
VS
SDA
1000110
SCL
SDA
1001110
VS
SCL
1000111
SCL
SCL
1001111
The module mounts two pull-down resistors (R2 and R3), therefore the address is 0x40 if we don’t connect the jumpers placed on the opposite side to that of the components (see figure 2 on the right).
With the shunt resistor of 0.1 W (R100), mounted on the module, there is a current resolution of 2.5 mV / 0.1 = 0.025 mA, a full scale of 81.92mV/0.1W = 819.2 mA and a power resolution of 0.025 mA *25 = 0.625 mW. I preferred to mount an external shunt for high currents, unsolder the internal one, and insert an RC filter, as shown in the diagram in figure 6.

The LCD display
I used a common two-line 16-character LCD display with Hitachi HD44780 compatible controller and a backlit equipped with high efficiency LED diodes that consumed about 20 mA, which I reduced to 10 mA with an external resistor in series with the internal one. The connections with Arduino Nano are as follows:
LCD
pin
function
Arduino
 pin
LCD
pin
function
Arduino
 pin
14
D7  Data Bus Line
A3
5
R/W - Read/Write
Gnd
13
D6  Data Bus Line
A2
4
RS - Register Select
D7
12
D5  Data Bus Line
D4
3
VEE
-
11
D4  Data Bus Line
D5
2
VCC (+5V)
VCC
6
E - Enable Signal
D6
1
VSS (GND)
Gnd

The micro SD module
In the latest version I added an SD module suitable for Arduino, that is, with 5 V power supply and logic levels. The appearance of the module used is visible in figure 4.
Figure 4
The following table shows the connections and pins dedicated to the microSD:
J2
pin
SD module
name
Arduino
pin
6
CS
D10
5
SCK
D13
4
MOSI
D11
3
MISO
D12
2
VCC
+5 V
1
GND
GND


Calculations for the shunt used
I used a shunt I had at home, visible in figure 5, it was an accessory of the glorious ICE 680R analog tester.
Figure 5
As can be seen from the figure, it has an output of 100 mV with a current of 25 A, so Rs = 0.1/25 = 0.004 Ω.

I unsoldered the 0.1 Ω shunt, mounted on the INA226 module, and I soldered a 1 MF capacitor and two filter resistors. This expedient allows RC filtering, as suggested by the chip manufacturer.
With the full-scale shunt voltage in mind, the maximum measurable current now becomes:
Ifs = 81.92mV/4 mΩ = 20.48 A
The current resolution will be: 20.48 / 32768 = 0.625 mA.
The calibrate function has as input parameters the shunt resistance and the maximum current, in our case: rShunt = 0.004 Ω and iMaxExpected = 20.48 A.
It calculates several variables:
•       currentLSB = iMaxExpected / 32768 = 20.48 / 32768 = 0.625 [mA]
•       calibrationValue = 0.00512 / currentLSB / rShunt = 0.00512 / 0.000625 / 0.004 = 2048
•       powerLSB = currentLSB * 25 = 0.000625 * 25 = 15.625 [mW]
Another data to pass to the program is the current threshold of the setShuntVoltageLimit function.
If we set the upper limit of the instrument at 20 A, we can calculate:
setShuntVoltageLimit = 0.004 Ω * 20 A = 0.08 V

The prototipe
I used an Arduino Nano, of course you can use other Arduino boards, such as Arduino Uno or Arduino Pro but this board is very compact, complete with USB adapter, and can be mounted on a prototype pre-drilled pcb.
Arduino Nano has its own 5V regulator, a Low DropOut type AMS1117, but I preferred to power the system with a common LM7805, this is because the former accepts a maximum input voltage of 15V against the 35V of the second. If I have to power ArduINA226 with the voltage I have to monitor, it is better to have compatible values. Another reason is the backlight of the display. The consumption of the whole system is around 45 mA. The Schottky D1 diode, which has a drop of only 0.2V, is used to avoid conflicts between external power supply and USB power supply.
Figure 6 shows the wiring diagram of my realization.
Figure 6

List of components used
component
description
component
description
C1,C2,C4
100 nF 50V, ceramic capacitor
R4
100 ohm, ±5%, 0.5 W resistor
C3
10 MF 50V, electrolytic capacitor
U1
Arduino Nano board
C4
1 MF 25V, ceramic capacitor
U2
LM7805, 5V regulator
D1
1N5819, Schottky diode
U3
INA226 module
Rp1
22kW resistive trimmer
display
2 rows x 16 columns LCD
R1, R2
10 ohm, ±5%, 0.25 W resistor
modulo SD
micro SD board with 5V levels
R3
4.7 kohm, ±5%, 0.25 W resistor
Sw1
Normally Open (NO) push button
Note: capacity prefix 'M' stands for microfarad (1e-6 F)
Figures 7 shows the appearance of my prototype. I screwed the shunt directly on the instrument case, note also the slot for the micro SD card.
Figure 7
The tests
Figure 8 shows the instrument running during the tests.
Figure 8
With a ten measuring points, compared with a precision multimeter, the results have been very good, as can be seen from the graph in figure 9.
In my case, the VBUS voltage was very accurate and required no correction. The current is slightly lower than the expected value (correction of 1.0092) and the linearity was excellent with R = 0.999995.

Changes to the INA226.cpp library
There are some libraries for the INA226 chip, I used the Korneliusz Jarzebski library which seems to me quite complete. I changed the calibrate function because the shunt current was incorrect. The setShuntVoltageLimit function was also wrong. Then I realized that the latter, in version 1.1 of the library, had been corrected. Here are the modified functions, to replace the original ones:
bool INA226::calibrate(float rShunt, float iMaxExpected){ // MODIFIED by GCAR
    uint16_t calibrationValue;
    float iMaxPossible;
    iMaxPossible = vShuntMax / rShunt;
    currentLSB = iMaxExpected / 32768;// calculate current resolution
    powerLSB = currentLSB * 25;// power resolution
    calibrationValue = (uint16_t)((0.00512) / (currentLSB * rShunt));
    writeRegister16(INA226_REG_CALIBRATION, calibrationValue);
    return true;
}

void INA226::setShuntVoltageLimit(float voltage){// MODIFIED by GCAR
    uint16_t value = voltage/2.5e-6;
    writeRegister16(INA226_REG_ALERTLIMIT, value);
}

The program
The program requires only two parameters which are: rShunt and iMaxExpected. If you want to use an alert, you need to decide which signal is of interest. In my case I chose the maximum current, therefore, the maximum shunt voltage and use the enableShuntOverLimitAlert and setShuntVoltageLimit functions. In the case of a system powered by lead or lithium ion batteries, it would be more useful to check the minimum voltage of VBUS with the enableBusUnderLimitAlert and setBusVoltageLimit functions.
If we have not inserted the SD card or if it is not valid, the message "SD not present!" is printed.
At the end of the setup () function, the program prints "Push to Start" and waits for the SS button to be pressed, if the SD is present, begins to acquire the measurements on file, otherwise it prints them only on the display.
As for the LCD display, two rows and 16 columns are just enough, so you need to manage your prints well. Taking into account the resolution of the variables to be printed, these are their formats:
•       Bus voltage: V = xx.xxx (8 characters)
•       Shunt current: I = xx.xxx (8 characters)
•       Bus power: W = xxx.xx (8 characters printed)
So, in the first line I enter the voltage and power, separated by a space, in the second the current and the number of samples saved on SD (if inserted), in this way:
V=xx.xxx W=xxx.xx
I=xx.xxx N=xxxxx
If the alert that I set as exceeding the maximum current limit (SOL, Shunt Voltage Over-Limit) occurs, the "SOL ALERT" print will appear on the second line, instead of the current value and on the first the shunt voltage:
Shunt V=xx.xxxxx
SOL ALERT

The code
/* Program ArduINA226 to current, voltage and power
 with Arduino Nano and INA226 module
uses Korneliusz Jarzebski Library for INA226 (modified)
save measurements on SD if present
Giovanni Carrera, 14/03/2020
 */

#include <SPI.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include <SD.h>
#include <INA226.h>
// LCD pins
#define rs 7
#define en 6
#define d4 5
#define d5 4
#define d6 A2
#define d7 A3
#define SSbutton 2
#define SD_CS 10
// initialize the library by associating any needed LCD interface pin
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
INA226 ina;


char bline[17] = "                ";// blank line
const int deltat= 500;// sampling period in milliseconds
unsigned long cmilli, pmilli;
boolean SDOk = true, FileHeader = true, ACQ = false;
unsigned int ns=0;

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  pinMode(SSbutton, INPUT);// Start/Stop button
  pinMode(SD_CS, OUTPUT);// SD chip select
  // Default INA226 address is 0x40
  ina.begin(0x40);
  lcd.print("ArduINA226");
  lcd.setCursor(0, 1);// print on the second row
  lcd.print("Power Monitor");
  // Configure INA226
  ina.configure(INA226_AVERAGES_1, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT);
  // Calibrate INA226. Rshunt = 0.004  ohm, Max expected current = 20.48 A
  ina.calibrate(0.004, 20.48);
  ina.enableShuntOverLimitAlert();// enable Shunt Over-Voltage Alert, current over the limit
  ina.setShuntVoltageLimit(0.08); // current limit = 20 A for 0.004 ohms
  ina.setAlertLatch(true);
  if (!SD.begin(SD_CS)) {
    LCDprintLine("SD not present!", 1);
    delay(5000);
    SDOk = false;
  }
  LCDprintLine("Push to Start", 1);
  while (digitalRead(SSbutton) == HIGH) {};// wait for start
  if (SDOk) ACQ = true;
}


void loop(){
  cmilli = millis();
  if (cmilli - pmilli > deltat) {
    pmilli = cmilli;
    float volts= ina.readBusVoltage();
    float current = ina.readShuntCurrent();
    LCDprintLine("V=", 0);// print bus voltage
    lcd.print(volts,3); 
    float power = ina.readBusPower();// INA calculate power
//    float power = volts * current;// Arduino calculate power
    lcd.print(" W=");// print power
    lcd.print(power,4);
    float Vshunt= ina.readShuntVoltage();
    String dataString = String(volts,3)+','+String(current,4)+','+String(power,4)+','+String(Vshunt,6);
    if (ACQ){
      File dataFile = SD.open("powerlog.csv", FILE_WRITE);     
      if (dataFile) {
        if (FileHeader){// print file header
          dataFile.print("Deltat [ms] = ");
          dataFile.println(deltat);
          dataFile.println("Vbus[V], Ishu [A], P [W], Vshu [V]");
          FileHeader = false;
        }
        dataFile.println(dataString);
        ns++;// number of acquired samples
        dataFile.close();
        if (digitalRead(SSbutton) == LOW && ns>=10){ //stop after at least 10 samples
          ACQ = false;// stop acquisition
          LCDprintLine(String(ns), 1);
          lcd.print(" samples");
          delay(5000);        
        }
      } else {
        LCDprintLine("Can't open file!", 1);
        ACQ = false;
        delay(5000);
      }
    }
    if (ina.isAlert()) {
      LCDprintLine("Shunt V=", 0);
      lcd.print(Vshunt,5);
      LCDprintLine("SOL ALERT", 1);
    }
    else {
      LCDprintLine("I= ", 1);
      lcd.print(current,3);
      if (ACQ){
        dataString = " N=" + String(ns);
        lcd.print(dataString);// print number of acquired sample
      }     
    }
  }
}
/************************** Functions **************************/
void LCDprintLine(String text, byte line){
   lcd.setCursor(0, line);
   lcd.print(bline);// clear the second row
   lcd.setCursor(0, line);
   lcd.print(text);// print text
}

The system can operate with even very small deltat periods, but the display, printouts and writing to file take some time: if we want to reduce it, for example to 100 ms, the measurements must be shown every 5 samples. Some hardware circuits can also be implemented, such as using the alert output to disconnect the power supply to the load via a static or electromechanical relay or to make a buzzer sound via Arduino.
ArduINa226, if a micro SD card has been inserted, transfers the measurements to the "powerlog.csv" file.
The acquisition starts by pressing the 'SS' (Start / Stop) button which also serves to end the recording after at least ten samples.
The first two lines contain the deltat sampling period in milliseconds and the names of the signals, as seen in the following example:
Deltat [ms] = 500
Vbus[V], Ishu [A], P [W], Vshu [V]
10.021,0.0950,0.9531,0.000375
10.023,0.0944,0.9531,0.000377
10.023,0.0944,0.9531,0.000375
……
The data are appended on the file which is not overwritten.
If we need to calculate the energy
The energy supplied by the source is the integral of the power over time. The simplest system for making the definite integral of a function, although roughly enough, is that of trapezoids. The power curve is approximated in many trapezoids, of which it is easy to calculate the area, for two points we have:
This method will give better results for smaller time intervals.
The integral will be equal to the sum of the individual areas. In our case: (t2-t1) = deltat and f(t) is the power P(t), so:
E2 = deltat * (P1 + P2) / 2 + E1 [J]
Where P1 and P2 are the powers in two successive instants t1, t2 and E1, E2 the respective energies expressed in W×s = joules, which can be easily expressed in watt hours [Wh] dividing the joules by 3600.
Here's how to calculate energy with Arduino:
watthour = watthour + (power + ppower)/2.0*deltath; // integer for trapezoids
ppower = power; // then updates the power
Where power is the current power, ppower that of the previous instant and
deltath = deltat / 3.6e6; // sampling period in hours (deltat in [ms])
At the beginning you have to reset the watthour variable.
I didn't add this function because I had no more space on the display and I preferred to calculate the energy on the PC with more sophisticated integration algorithms.

References
1.      “INA226 Bi-directional Current/Power Monitor Arduino Library”, Korneliusz Jarzebski, https://github.com/jarzebski/Arduino-INA226
2.      “INA226 High-Side or Low-Side Measurement, Bi-Directional Current and Power Monitor with I2C Compatible Interface”, Texas Instruments, SBOS547A –June 2011–revised august 2015
3.      “VARDULOG - Data logger dei consumi di un apparato elettrico”, Giovanni Carrera, rivista Fare Elettronica n. 361-362, Novembre-Dicembre 2015.
4.      “Progetto ArduWattmeter”, Giovanni Carrera, rivista Fare Elettronica n. 367/368 ¬ Giugno/Luglio 2016.
5.      “ArduAmmeter, a simple circuit for measuring electrical current with Arduino”, Giovanni Carrera, https://www.hackster.io/ArduPic/arduammeter-b59d40, July 16, 2019.

Sunday, March 8, 2020

How to connect an LCD display with I2C bus to ESP


The changes hw & sw are described to adapt an I2C LCD display to ESPs or other microcontrollers powered at 3.3V.

An LCD display, compatible with the Hitachi HD44780 controller, connected with 4 data bus bits, requires a minimum of 6 GPIO pins. Using a special module it is possible to use only two pins with I2C bus and several Arduino libraries are also available for it. I used the LiquidCrystal_I2C [1] which, although the Arduino compiler prints a warning message that is for AVRs, has also worked well for ESP.
Figure 1 shows the module I used, made specifically to be soldered directly to a common alphanumeric LCD display.
 
Figure 1
Before soldering it, check the position of the pins and compare them with those in the figure. The complete scheme of this module is shown in figure 2.
Figure 2
This module uses the PCF8574 chip, 8-bit I/O expander with I2C bus, which connects to the 4-bit bidirectional bus display. It also controls the display backlighting via PA3 and the transistor Q1, with the jumper inserted.

I preferred to use an LCD display because it consumes much less than an Oled, and my goal was to operate the ESP in deep sleep mode and power everything on battery power.
For these reasons I used a display without backlight, with a 14-pin connection and therefore I had to cut the two pins 15 and 16 of the module in order to insert it.

I/O level adapting
No problem for interfacing with Arduino Uno and similar with 5V power supply and I/O signals with the same level.
The ESP8266/32 have 3.3V power supply and GPIO levels are not 5V tolerant, so the first thing I looked at is the level of the output signals.
The characteristic of I2C is that every device on the bus must connect to both the clock signal (SCL) and the data signal (SDA) via open-drain (or open-collector) output drivers. This means that controllers can only bring the output to zero and that pull-up resistors must be used to return to high level.
So it is not necessary to use a level adapter, but to check if the pull-up resistors are mounted and at what voltage. As can be seen in the diagram, these are present and connected to the 5V.
If we want to connect this module to ESP or other 3.3V powered microcontrollers, we have to unsolder the 4.7 kW resistors R5 and R6. Since my intention is also to consume less current, I also removed R1 (1 kW) of the useless red  LED1 and R7, near the transistor Q1. Figure 3 shows the positions of the four resistors to be unsoldered. If you want to use a backlit display, R7 should not be removed.
Figure 3

Obviously two 4.7 kW resistors connected to + 3.3V must be mounted externally, as in figure 4.
Then we have to set the I2C address using the three jumpers near the trimmer that regulates the contrast. The three 10 kW resistors R2, R3, R4 are pull-ups of the three bits A0, A1, A2 of address I2C of the PCF8574. The other four bits, of the seven I2C address bits, are fixed and have the following values:
0
1
0
0
A2
A1
A0
If we do not solder the jumpers: A0 = A1 = A2 = 1, so the address will be 0x27, each jumper will produce a zero as it is connected to ground.
Figure 4 shows the connections of the LCD display complete with adapter with a NodeMCU card with ESP8266. I used pin D5 (GPIO14) for SCL and D4 (GPIO2) for SDA.
Figure 4

Figure 5 shows the two-line 16-character display to which the modified module has been soldered. Consumption is around 2 mA.
Figure 5
Figure 6 shows the display under test, made with a breadboard for the resistors and the connection wires with the NodeMCU board.

Anyone wishing to mount a display with backlighting must, in addition to not remove the R7 and not cut pins 15 and 16, also enter the instruction that lights the display LEDs:
   lcd.backlight ();
In the next realization I intend to use only the ESP-V12f module, always to reduce power consumption.
Figure 6
The test program
To test the system I wrote a simple program that prints the seconds on the second line.
To avoid overlapping the text on the previous print, I first print a row of spaces.
The library must be configured with the instruction:
LiquidCrystal_I2C lcd (0x27,16,2);
Which allows you to set the display address (0x27), the number of columns and rows.
If more than one LCD display is used, jumpers must be set to create a different address.
On the ESP8266 almost all I/O bits can be used for the I2C interface. The LiquidCrystal_I2C library allows you to assign the two pins using the function:
lcd.begin (SDA, SCL);
Below is the list of the test programs.
/*program ESP_LCD.ino, for test
 ESP8266 with a I2C LCD display
 Giovanni Carrera, rev. 22/02/2020 */

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define SDA 2    // D4 for NodeMCU
#define SCL 14   // D5 for NodeMCU


char bline[17] = "                ";// blank line
int sec = 0;
unsigned long cmilli, pmilli;
const int deltat = 1000;

LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup(){
  Wire.begin (SDA, SCL);// pin used for I2C bus
  lcd.begin(SDA, SCL);// for ESP8266
  lcd.setCursor(0,0);// lcd.setCursor(col, row)
  lcd.print("ESPV12 LCD test"); // Print intro message
}

void loop(){
  cmilli = millis();
  if (cmilli - pmilli >= deltat) {
    pmilli = cmilli;
    lcd.setCursor(0, 1);
    lcd.print(bline);// clear the second row
    lcd.setCursor(0, 1);
    lcd.print(sec);// print seconds on the second row
    sec++;
    lcd.setCursor(6, 1);
    lcd.print("seconds");   
  }
}


References
1.       “LiquidCrystal I2C”, Marco Schwartz, https://github.com/marcoschwartz/LiquidCrystal_I2C/archive/master.zip


Sunday, July 14, 2019

A simple circuit for measuring electrical current with Arduino


The current measurement requires the use of an ammeter placed in series with the load. An ideal ammeter has no voltage drop, i.e. it is a short circuit. But most of the current sensors are based precisely on the measurement of the voltage drop on a resistor which, according to Ohm's law, is proportional to the current that passes through it.
For what has been said, a good real ammeter must have a very small voltage drop, this in order not to alter the circuit under measurement with its insertion. The sensor I present has a voltage drop of only 50 mV and uses easily available components.

The shunt
The most accurate system is to use a shunt, i.e. a resistor with very low resistance compared to that of the load and the generator. Of course today we also find on the market sensors based on the Hall effect, that is a device that measures the magnetic field generated by the current passing through a wire for the well-known Ampere’s law, but these semiconductor sensors do not have comparable stability and precision. Figure 1 shows the typical appearance of a 10A and 75mV shunt.

For low currents it is possible to realize the shunt with 0.1 Ω wire resistors, possibly put in parallel, or with a piece of constantan wire of adequate section and length.

Measurement techniques
There are two ways to insert the shunt:
1)      low-side: the shunt is connected between the load and the mass.
2)      high-side: the shunt is connected between the power supply and the load.
Figure 2 shows the two connection modes.

The first mode has as advantages a low common mode voltage, inputs and outputs related to ground and circuit simplicity but is affected by disturbances of ground returns
The second way is not affected by mass disturbances but the amplifier must work with high common mode voltage values and the circuit is more complex.

A low-side solution
There are several breakout boards available on the market ready to measure the current with Arduino, but for those who love self-construction and also to understand the functioning of the circuits, I suggest to carry out my project. It is a fairly accurate current sensor while using common components.
I use the shunt in low-side mode connected to a differential amplifier with Arduino-compatible output with internal reference, therefore with ADC overall voltage measurement range equal to 1.1 volts. With this solution a more stable and less noisy reference is obtained with a resolution of 1100/1024 = 1.074 mV.
Using operational power amplifiers with a single power supply there are problems if the signals approach zero. Even using special operational amplifiers called "rail to rail", whose output swing is closer to the rails, rarely they have outputs below 20 mV at zero input. A similar output can also be obtained using a most common LM358 suitable for working even with single power supply.
An expedient to overcome this drawback is to operate on a value higher than zero and therefore to subtract this constant at software level. To have a zero in output and also to eliminate the off-set I added a Vsh constant of about 100 mV. For this circuit I used a differential amplifier U1a, whose output is equal to:
Vo = (Vin+ - Vin-)R2/R1+Vsh
The second operational U1b, connected as voltage follower, generates the voltage Vsh by means of a divider on the Arduino Vref.
For correct operation the following relations must be respected: R1=R3 and R2=R4 with very low tolerances. I have selected them among metal film resistors with a 1% tolerance, now easily available, using a good ohmmeter. Since the double LM358 does not have a very low offset I preferred not to overdo the gain, so R2/R1 = 20.
The ADC converter has a full scale of 1100 mV and I take 100 out of it to make the zero, so I can have 1000 mV for the measurement, this means that the maximum input voltage must be 1000/20 = 50 mV.
The following table shows some shunt values and related flow rates.
I max [A]
Shunt [Ω]
0.5
0.1
1
0.05
5
0.01
10
0.005
It is also possible to use commercial shunts. Many of them have a voltage drop of 75 mV so we can vary the gain that becomes 1000/75 = 200/15 = 13.33 or calculate the new full scale which is equal to 2/3. For example, a 10 A and 75 mV shunt has a resistance Rs = 7.5 mΩ and a full scale I = 50 / 7.5 = 6.66 A.
To have a sensitivity of 75 mV, suitable for many commercial shunts, it is sufficient to set R1 = R3 = 15kΩ and R2 = R4 = 200kΩ, standard and easily available values.
Figure 3 shows the circuit and its connections with Arduino.
To test this circuit I used a 0.1 Ω shunt resistor and a 0.1 A current source.

Significant improvements are achieved by replacing the LM358 with a low off-set input like the OP290, OPA2196, OPA2277 and similar chips.
In figure 4 we can see the layout and the connections of the components of my prototype, made on a small pre-drilled pcb board.
The op-amp offset
Not considering here the problem of saturation already seen for the single power supply, a real operational amplifier has no zero-volt output with zero input, the input offset is defined as the voltage I have to give to the input to have zero in output.
Figure 5 shows the model for evaluating the offset of a differential amplifier.
Considering the Vos offset voltage, the output voltage with zero input is:
Vo = (1+R2/R1)Vos
For example: if an LM358 has an offset of 2 mV, with R2/R1= 20 there would be a difference of 44 mV (2x21 + 2, including that of U1b) between the voltage Vo and Vsh. The offset is temperature dependent and causes a thermal drift, this operational has an input offset voltage drift of about 7 µV/°C.
For this application the input offset is very important as this constant is amplified by the amplifier.
Ignoring the thermal drift, we can remove the offset using a trimmer on the divider that produces Vsh. The rest will do the software by subtracting a constant. For Vsh= 100 mV this number is equal to:
Nsh =100*1023/1100 = 93
This is the constant to be removed and we should set the Rp1 trimmer until zero is read for zero current. In the test phase I increased this value to 98 to work better with the trimmer.
Components list
component
description
component
description
R1, R3
11 kW ± 1% metal film
Rp1
500 Ω multi-turn trimmer
R2, R4
220 kW ± 1% metal film
Rs
0.1 Ω  shunt
R5
10 kW ± 2% metal film
C1
10 µF,25V Aluminum electrolytic
R6
1 kW ± 2% metal film
Arduino
Arduino UNO or Nano board
R7
10 kW ±5%
U1
Dual op amp LM358

The program
The example program is very simple, the values used are relative to my system. To calibrate the Rp1 trimmer, just run the program and turn the trimmer to read zero on the serial monitor with zero input current. If it is not possible to zero the trimmer, it is necessary to slightly modify the Nsh constant to be subtracted from the measurement.
The constant mVtomA is derived from experimental measurements using a good precision ammeter, a power supply and some resistive loads such as car bulbs or wire resistors of adequate power.

/* program ArduAmmeter.ino Arduino current meter
 Giovanni Carrera, rev. 11/07/2019 */

float NtomV;
const float VREF = 1095;// in mV, this value can be read on VREF pin
const int Nsh = 98;// shift value corresponding to about 100 mV
const float mVtomA = 2.17;// value obtained by a calibration

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL); // internal ADC reference input = 1100 mV
  NtomV = VREF/1023;// constant of conversion into millivolts
}

void loop() {
  int val = analogRead(A0)-Nsh;// read the current sensor and remove the shift
  float mvolt = NtomV*val;// convert to millivolt
  float mamp = mvolt/mVtomA;// convert to milliampere
  Serial.print("Vo = ");
  Serial.print(mvolt,1);
  Serial.print(" mV - Current = ");
  Serial.print(mamp,0);
  Serial.println(" mA");
  delay(1000);
}



References
1.       “AN1332, Current Sensing Circuit Concepts and Fundamentals”, Microchip DS01332B, 2011.
2.       “AN39, Current measurement applications handbook”, Peter Abiodun Bode, Zetex Semiconductors, 2008.
3.       “AN105, Current Sense Circuit Collection”, Tim Regan, Linear Technology, dec 2005.
4.       “Fully Integrated, Hall Effect-Based Linear Current Sensor IC with 2.1 kVRMS Isolation and a Low-Resistance Current Conductor”, ACS712-DS, Rev. 15,  Allegro MicroSystems – 2013.
5.       “VARDULOG - Data logger dei consumi di un apparato elettrico”, Giovanni Carrera, rivista FARE ELETTRONICA n. 361-362, Novembre-Dicembre 2015;
6.       “Progetto ArduWattmeter”, Giovanni Carrera, rivista FARE ELETTRONICA n. 367/368 ­ Giugno/Luglio 2016;
7.       “Op Amp Input Offset Voltage”, Analog Devices, MT-037 Rev.0, 10/08, WK.