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

Aug 23, 2016 0 comments

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 SPIWe receive 16 bitsOf which we extract only 12 bitsMCP3201 has a strange way of formatting datawith 5 bits in the first byte andthe 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 9int loop_current;int received_data;// Calibration data obtained by running the calibration codeconst 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 resolutionconst 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 SPIWe receive 16 bitsOf which we extract only 12 bitsMCP3201 has a strange way of formatting datawith 5 bits in the first byte andthe 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.

Name

Email *

Message *