4-20mA R current loop Arduino tutorial Part III: receiver


Today we come to the third and last part of the 4-20mA communication tutorial using Arduino UNO boards and the pair of 4-20mA R  and 4-20mA T  click boards from MikroElektronika. In this post I will describe the calibration procedure and I provide the code for the receiver. I will also show a method to implement open loop and short circuit detection using the above click boards.

So. we begin with the calibration procedure. We need to know the ADC output for the situation when the current through the loop is 4mA, and the ADC output for the situation when the current through the loop is 20mA. First, both transmitters and receivers must be powered. Then the calibration code on the transmitter is used to set the current through the loop is 4mA. The following code was used to output the ADC result:

        
/*
  4-20mA R click calibration
  Reads result on 4-20mA bus and outputs the
  result of the conversion on serial monitor
 */
#include <SPI.h>
 
// Arduino UNO with Mikroe Arduino Uno Click shield
// 4-20mA R click is placed in socket #2
// CS   is pin 9
// SCK  is pin 13
// MISO is pin 12
// MOSI is pin 11
#define ADC_CS 9
 
unsigned int ADC_result;
float ADC_avrg;
 
void setup() {
/* Resetting MCP3201
 * From MCP3201 datasheet: If the device was powered up
 * with the CS pin low, it must be brought high and back low
 * to initiate communication.
 * The device will begin to sample the analog
 * input on the first rising edge after CS goes low. */
 pinMode (ADC_CS, OUTPUT);
 digitalWrite(ADC_CS, 0);
 delay(100);
 digitalWrite(ADC_CS, 1);
 
 // initialize serial
 Serial.begin(9600);
 // initialize SPI
 SPI.begin();
 
}
 
void loop() {
  ADC_avrg = 0;
  // Average of 100 measurements 
  for (int i=0; i<100; i++){
  // put your main code here, to run repeatedly:
  ADC_result = get_ADC();
  //Serial.print("Conversion result: ");
  //Serial.println(ADC_result);
  // Dont print too often
  delay(10);
  ADC_avrg = ADC_avrg + ADC_result;
  }
  Serial.print("Conversion average: ");
  Serial.println(ADC_avrg / 100);
  delay(500);
}
 
unsigned int get_ADC(void){
/*
DAC works on SPI
We receive 16 bits
Of which we extract only 12 bits
MCP3201 has a strange way of formatting data
with 5 bits in the first byte and
the rest of 7 bits in the second byte
*/
  unsigned int result;
  unsigned int first_byte;
  unsigned int second_byte;
 
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE1));
  digitalWrite(ADC_CS, 0);
  first_byte = SPI.transfer(0);
  second_byte = SPI.transfer(0);
  digitalWrite(ADC_CS, 1);
  SPI.endTransaction();
 
  /* After the second eight clocks have been
  sent to the device, the MCU receive register
  will contain the lowest order seven bits and
  the B1 bit repeated as the A/D Converter has begun
  to shift out LSB first data with the extra clock.
  Typical procedure would then call for the lower order
  byte of data to be shifted right by one bit
  to remove the extra B1 bit.
  See MCP3201 datasheet, page 15
  */
  result = ((first_byte & 0x1F) << 8) | second_byte;
  result = result >> 1;
  return result;
}
        
    

An average of 100 measurements was taken into account. One might observe that the INA196 current shunt has some variations in the output voltage, even if the current through the loop is stable up to microamps level. A method to calculate the accuracy variations is given in the INA196 datasheet, pages 17-18.

So, for 4mA through the loop I’ve got an average of 788, with a minimum of 775 and a maximum of 800.

For 20mA through the loop the ADC average output was 3964, with a minimum of 3952 and a maximum of 3982.

With the above values we can now write the receiver code:


/*
  4-20mA R click receiver
  Reads result on 4-20mA bus
*/
#include <SPI.h>

// Arduino UNO with Mikroe Arduino Uno Click shield
// 4-20mA R click is placed in socket #2
// CS   is pin 9
// SCK  is pin 13
// MISO is pin 12
// MOSI is pin 11
#define ADC_CS 9

int loop_current;
int received_data;

// Calibration data obtained by running the calibration code
const int ADC_4mA  = 784;
const int ADC_20mA = 3954;

// Data min and max range
// Matches the values on the transmitter code
// But it's a good ideea to resample to a lower resolution
const int data_min_range = 0;
const int data_max_range = 1023;

void setup() {
/* Resetting MCP3201
 * From MCP3201 datasheet: If the device was powered up
 * with the CS pin low, it must be brought high and back low
 * to initiate communication.
 * The device will begin to sample the analog
 * input on the first rising edge after CS goes low. */
 pinMode (ADC_CS, OUTPUT);
 digitalWrite(ADC_CS, 0);
 delay(100);
 digitalWrite(ADC_CS, 1);

 // initialize serial
 Serial.begin(9600);
 // initialize SPI
 SPI.begin();

}

void loop() {

  // Read the loop current
  loop_current = ReadFrom420mA();
  // Error checking
  if (loop_current == -1)
    Serial.println("Error: open loop");
  else if (loop_current == -2)
    Serial.println("Error: current loop is in short circuit");
  // All is OK, remapping to initial data range
  else { 
    received_data = map(loop_current, ADC_4mA, ADC_20mA, data_min_range, data_max_range);
    Serial.print("Received value is: ");
    Serial.println(received_data);
  }
}

unsigned int get_ADC(void){
/*
DAC works on SPI
We receive 16 bits
Of which we extract only 12 bits
MCP3201 has a strange way of formatting data
with 5 bits in the first byte and
the rest of 7 bits in the second byte
*/
  unsigned int result;
  unsigned int first_byte;
  unsigned int second_byte;

  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE1));
  digitalWrite(ADC_CS, 0);
  first_byte = SPI.transfer(0);
  second_byte = SPI.transfer(0);
  digitalWrite(ADC_CS, 1);
  SPI.endTransaction();

  /* After the second eight clocks have been
  sent to the device, the MCU receive register
  will contain the lowest order seven bits and
  the B1 bit repeated as the A/D Converter has begun
  to shift out LSB first data with the extra clock.
  Typical procedure would then call for the lower order
  byte of data to be shifted right by one bit
  to remove the extra B1 bit.
  See MCP3201 datasheet, page 15
  */
  result = ((first_byte & 0x1F) << 8) | second_byte;
  result = result >> 1;
  return result;
}

int ReadFrom420mA(void)
{
  int result;
  int ADC_result;
  float ADC_avrg = 0;
  for (int i = 0; i < 100; i++){
    ADC_result = get_ADC();
    // Measure every 1ms
    delay(1);
    ADC_avrg = ADC_avrg + ADC_result;
  }
  result = (int)(ADC_avrg/100);

  // now we do some shortcircuit and open loop checking
  // open loop
  if (result < (ADC_4mA - 50)){
    return -1;
  }
  // shortcircuit
  if (result > (ADC_20mA + 50)){
    return -2;
  }
  // everything is OK
  return result;
}
    

In the receiver code I have implemented a simple detection routine for open loop and shortcircuit conditions. With an open loop the ADC output is somewhere around 5, so the threshold I’ve set should be more than enough. The output of the ADC in shortcircuit situations depends on where the shortcircuit is located. The worst situation is with a very long loop cable and the shortcircuit near the transmitter. Again, the threshold level must be set as to allow for the detection of shortcircuits while minimizing false indications.

Last, but not least, there’s a good idea to re sample the result to a lower resolution. In the MikroElektronika examples only 100 discrete levels are used. I guess that resampling to 8 bits should be enough for most applications.

Also in this tutorial

4-20mA current loop Arduino tutorial Part I: hardware
4-20mA current loop Arduino tutorial Part II: transmitter

Post a Comment

0 Comments