Arduino 4-20 MA Reader and Datalogger (Industrial Instrumentation)

by wierzbickimc in Circuits > Arduino

70 Views, 0 Favorites, 0 Comments

Arduino 4-20 MA Reader and Datalogger (Industrial Instrumentation)

IMG_6536.jpeg
IMG_6538.jpeg
4-20mA loop arduino 20250718_bb.png

This project converts industrial 4-20ma instrumentation signals to something an Arduino can measure by way of a 250 ohm resistor.

The 250 ohm resistor value is calculated using Ohms law for converting the 20mA signal to a 5V signal. The following link will allow you to modify the values to match your project.

https://www.calculator.net/ohms-law-calculator.html?v=5&vunit=volt&c=20&cunit=milliampere&r=&runit=ohm&p=&punit=watt&x=Calculate

This project has one "trick" that changed it from a neat toy to something practical, it uses the 328P Arduino chip vref as something like an internal calibration. This eliminated a ton of drift as a result of battery degradation or USB fluctuation.

I have an external LCD screen showing current mA, reading in engineering units, and the datafile name. The datafile name display helps keep track of which datafile corresponds to which instrument read.

The SD card records the data in a .txt file. I do have it set to create a new file with each reboot.

Supplies

Arduino Nano - note 328p chip is important as it has an internal reference that will will be using to normalize for battery fluctuations.

One Bauer 20V power tool battery

One Power Wheel DIY adapter for Bauer 20V Battery dock power connector

One 250 ohm metal film resistor with 1% tolerance (or better)

2 in 4 out Quick Wire Connector - I love these things. I can't think of a better way to join or split wires

Industrial Instrument (such as Siemens SISTRANS P320)

Buck Converter

Wires

SD Card reader

32gb or less micro SD card. Be sure to reformat to FAT32 (long reformat) before use.

Wiring It Up

IMG_6492.jpeg

Ensure the 20V battery adapter switch is in the off state

Ensure the 20V battery adapter fuse holder has a fuse

2 to 4 connector #1

Connect a wire from the battery adapter positive to the single Orange side of 2 to 4 connector #1

Connect a wire from the battery adapter negative to the single blue side of 2 to 4 connector #1

Connect a wire from the Industrial Instrument (+) to the double orange side of 2 to 4 connector #1

Connect a wire from Buck Converter Vin + to the double orange side of 2 to 4 connector #1

Connect a wire from double blue side of 2 to 4 connector #2 to blue side of 2 to 4 connector #1

Connect a wire from Buck Converter Vin - to the double blue side of 2 to 4 connector #1


2 to 4 connector #2

Insert a 250 ohm resistor across the single sided blue to orange.

Connect a wire from Arduino pin A1 to double orange side of 2 to 4 connector #2

Connect a wire from the Industrial Instrument (-) to double orange side of 2 to 4 connector #2

Connect a wire from Arduino GND to double blue side of 2 to 4 connector #2

The last remaining blue connector on connector #2 is already filled as described above.


Powering the Arduino

The Buck Converter has been connected to power above. You must now set the poteniatmeter to output 5v as to not fry everything.

Turn on the power source and press the buck converter button. It will now show input voltage and illuminate the "IN" LED

Press the button again and it should switch to output voltage. Turn the potentiometer screw counter clockwise until you achieve 5V. Get it close, but don't worry about perfection. We will be using the 328P internal reference to normalize for fluctuations in supply voltage.


I2C LCD

Connect a wire from buck converter Vout (+) to VCC of I2C LCD

Connect a wire from buck converter Vout(-) to GND of I2C LCD

Connect a wire from I2C LCD SDA to Arduino Pin A4

Connect a wire from I2C LCD SCL to Arduino Pin A5


SD Card reader

SD Card Startup

CS = D4 (4 is built into the code)

SCK = D13

MOSI = D11

MISO = D12

VCC = 5v (note - some SD card readers must go off of the 3.3v. Check yours to be sure)

GND = GND

Code

#include <Wire.h>
#include <LiquidCrystal_I2C.h> // V1.1.2 installed

#include <SPI.h>
#include <SD.h>
const int chipSelect = 4;

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

//Define measurement device Parameters

float DeviceSpanLow = 0;
float DeviceSpanHigh = 10;
char DeviceEngUnits = "PSI";
//

//clear any existing variable data
float sensorValue = 0;
float sum = 0;
float averagebits = 0;
float Volts = 0;
float Current = 0;
float Pressure = 0;
float VoltsCorrected = 0;
int Delay = 2000;
int ReadFreq = 10; // Reads every 10ms
int numReadings = Delay/ReadFreq -1; // Number of readings to average, -1

//Setting up SD card File
File Dataloggerfile;
int i = 0; //file name incrementor
boolean SDfilefound = false; //mark true when file not found, vale of i then becomes file name.
char FileName[13];

// internal voltage calibration
// The 328P chip has an internal 1.1v reference to do somthing like an intneral calibration. Its not 100%
// accurate, but it is fairly precise. With this section this project goes from a neat trick to something
// actually useful.

float readVcc() {
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1125300L / result; // Back-calculate AVcc in mV
return result;
}


void setup() {
Serial.begin(9600);
Serial.println("New read started BB");
lcd.init(); // initialize the lcd
lcd.backlight(); // Turn on the LCD screen backlight



//SD Card Startup
//CS = D4
//SCK = D13
//MOSI = D11
//MISO = D12
//VCC = 5v
//GND = GND


while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.print("Initializing SD card...");
if (!SD.begin(4)) { //this is chipselect pin 4
Serial.println("initialization failed!");
while (1);
}
Serial.println("initialization done.");

// This section creates a unique,sequential SD card file name
while (SDfilefound == false) {
sprintf(FileName, "Data%i.txt", i);//set the file name
Dataloggerfile = SD.open(FileName);//look for this file name
if (Dataloggerfile) {//you found this file try again
i = i + 1;
Dataloggerfile.close();
} else {
SDfilefound = true;
Dataloggerfile.close();
}//you found the end of the file names, use the value of i as your new file name.
}
Serial.println(FileName); // serial print the file name for your refernce.

}

// Establish the smoothing function. This takes reading every 10ms over 2 seconds and averages it.
void loop() {
sum = 0; // clear the sum variable
for(int i = 0; i < numReadings; i++) {
sensorValue = analogRead(A1); // Read A1 bits
sum += sensorValue;
delay(ReadFreq);
}


// Convert from ADC bits to volts. Volts to mA. mA to pressure.
// Instrument variables set at the very top of the code.
averagebits = sum / numReadings; // Average bits over sample period
Volts = averagebits / 1024 * 5; // assume 250 ohm resistor
VoltsCorrected = 5000 / readVcc();
Current = Volts * 4 / VoltsCorrected; // 20ma to 5v
Pressure = (Current - 4) * DeviceSpanHigh / 16 + DeviceSpanLow;


// Serial Readout for troubleshooting
Serial.print(" Pressure=");
Serial.print(Pressure, 3); // 3 decimal places
Serial.print(" ");
Serial.print(" time since start=");
Serial.print(millis()/1000 );
Serial.print(" ");
Serial.print("A = ");
Serial.println(Current, 4); // 4 decimal places

// LCD stuff. This will play in sequence on the screen. The ( ) portion is the location on the screen.
// each delay extends the time between sensor reads, so don't go too crazy with it.
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Current mA");
lcd.setCursor(2, 1);
lcd.print(Current);
delay(1000);
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Current PSI ");
lcd.setCursor(1, 1);
lcd.print(Pressure);
delay(1000);
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Run Time (min) ");
lcd.setCursor(1, 1);
lcd.print(millis()/1000/60);
delay(1000);
lcd.clear();

lcd.setCursor(1, 0);
lcd.print("File Name: ");
lcd.setCursor(1, 1);
lcd.print(FileName);; // no need for the last clear, nor a delay.


//SD Card this creates an indexed file name so that each power-on results in a new file.
Dataloggerfile = SD.open(FileName, FILE_WRITE); // use this version for new file each time.
// if the file opened okay, write to it:
if (Dataloggerfile) {
Serial.print("Writing to SD...");
Dataloggerfile.print("Time since start(millis): ");
Dataloggerfile.print(millis());
Dataloggerfile.print(" ");
Dataloggerfile.print("mA Readings: ");
Dataloggerfile.println(Current,4);
Dataloggerfile.print(" ");
Dataloggerfile.print("ENG Units ");
Dataloggerfile.println(Pressure);
// close the file:
Dataloggerfile.close();
Serial.println("done.");
} else {
// if the file didn't open, print an error:
Serial.println("error opening SD card File");
}
}

Downloads

Adding a Bias

At this point you should get a pretty close mA reading from the device, however it will be skewed because of various variables (resistor quality, etc.). You can add this bias into the code if you so desire. Simply take the device screen reading / Arduino reading. Use this correction factor to multiply the the "current" calculation.

From my experience the correction factor is a better approach than simply adding "0.02mA". You can do a current output test on the device to verify a curve.

Stability Over the Battery Lifespan

Datalog Hours.png

Using a Bauer 20V, 1.5AH battery with all accessories ON (LCD backlight, etc.) I was able to get 29.7 hours of stable datalogging before my Siemens P320 turned off due to insufficient power (10v cutoff). The Arudino soldiered on for another 10 of so minutes before it too shut off.

Battery V initally was ~20.8V as indicated on the buck converter in.

After ~20 hours it was ~18.5v.

There was no signal accuracy degradation noticed.

This, to me, is sufficiently insensitive to battery voltage to be reliable and fails in a very obvious way, there was no mistaking what happened when the signal dropped off (no power to the measuring instruments shows as 3.25mA, less than 4mA is a fault.)


The 20,796 datapoints (~5 second read interval) created a 1.1MB file.

Power Consumption Optimization

Since this device is battery operated, I wanted to understand how much power was being drawn by each component to rationalize what feature were worth the power being used. Knowing that the device will last 29.7 hours in the "base" configuration, I also extrapolated the expected runtime for each arrangement. I sorted the options based on run time and highlighted the features turned off in each step.

mA was measured using a multimeter at the battery. The Siemens P320 pressure instrument was kept at a constant 6.57mA. mW was calculated using the full 20V battery voltage (21v in my case).

It's worth noting that the LowPower.h library with sleep, ADC off and BOD off only reduced consumption by 1.5mA. There are some hardware tricks that can be done to greatly decrease draw, but that's for another day.

Something I did not expect is the massive draw of the Buck Converter LCD, it was nearly 40% of the entire baseline!

Additionally, when spaning your measurement the device, it would be more energy efficient to span it such that is stays closer to 4mA. Not something I had considered before this exercise.