4-20mA R click notes

From Helpful
Jump to navigation Jump to search

⚠ This is for beginners and very much by a beginner / hobbyist

It's intended to get an intuitive overview for hobbyist needs. It may get you started, but to be able to do anything remotely clever, follow a proper course or read a good book.


Some basics and reference: Volts, amps, energy, power · batteries · resistors · transistors · fuses · diodes · capacitors · inductors and transformers · ground

Slightly less basic: amplifier notes · varistors · changing voltage · baluns · frequency generation · Transmission lines · skin effect


And some more applied stuff:

IO: Input and output pins · wired local IO · wired local-ish IO · ·  Various wireless · 802.11 (WiFi) · cell phone

Sensors: General sensor notes, voltage and current sensing · Knobs and dials · Pressure sensing · Temperature sensing · humidity sensing · Light sensing · Movement sensing · Capacitive sensing · Touch screen notes

Actuators: General actuator notes, circuit protection · Motors and servos · Solenoids

Noise stuff: Stray signals and noise · sound-related noise names · electronic non-coupled noise names · electronic coupled noise · ground loop · strategies to avoid coupled noise · Sampling, reproduction, and transmission distortions

Audio and video notes: See avnotes


Platform specific

Arduino and AVR notes · (Ethernet)
Microcontroller and computer platforms ··· ESP series notes · STM32 series notes


Less sorted: Ground · device voltage and impedance (+ audio-specific) · electricity and humans · Common terms, useful basics, soldering · landline phones · pulse modulation · PLL · Multimeter notes · signal reflection · Project boxes · resource metering · Radio and SDR · vacuum tubes · Unsorted stuff · 'E-fuse'

Some stuff I've messed with: Avrusb500v2 · GPS · Hilo GPRS · JY-MCU · DMX · Thermal printer ·

See also Category:Electronics.


4-20mA R click is a board that helps read out sensors that output 4-20mA current loop signals.

(there is a also a T click to transmit such signals)


Board has:

  • INA196 - current shunt monitor
measures current on the loop, outputs a voltage signal and feeds it to the ADC
  • MCP3204 - 12-bit ADC with SPI interface
can talk no faster than 1MHz (if on 3.3V supply)


  • TPS61041 - boost converter, creates the ~15V for the loop (at sufficient current)
its EN pin is on the microBUS Int line - lets you control when the sensor gets loop power (verify)


  • SMD jumper selects whether board's Vcc comes from the MicroBUS 3.3 or 5V line.
  • LED (directly on VCC)



Using / code

/*  Interfaces with a 4-20mA R click, on an Arduino socket-shield where:
     EN/INT is   PD2 (D2)
     CS is       PB2 (D10)
     MISO is     PB4 (D12)
     SCK is      PB5 (D13)
  In other words, the standard SPI pins, and D2 for enable (also fairly regular).


  To read out the MCP3201 ADC:
      You only need CS and MISO - output is triggered by device selection.
      CS should by default be high. To read out, assert CS low.
      The ADC will sample (two clock cycles), and output the bits on the successive clock signals
      (in fact, it will write more than the sample, which is why we care only about the first 12 bits)
    Readout code taken from http://www.speedlimit88.com/arduino/spi_adc/


   In the case this was made for, the sensor is a portable level readout on a liquid nitrogen dewar,
   and the actual readout is battery-powered, so we only want to trigger readout on explicit request.
    Seen from our side, readout is triggered when we put voltage on the loop, a.k.a. assert EN/INT.
    So assert EN, read out the ADC a few times, clean EN, and wait a while.  
  
   The "On request" part is implemented by "when we hear _anything_ on the serial port" 
   (which isn't ideal when the serial port sees reason to receive garbage).
*/
#include <SPI.h>

const int en_pin         = 2;  
const int select_adc_pin = 10;

const int    ewma_amount   =  250;  // more samples of the loop current makes the result (look) more stable.
const float  ewma_alpha    =  0.01; // EWMA lowpass. Lower adapts slower.
const float  om_ewma_alpha = (1.0-ewma_alpha);
// some specific-case tweaking of the following two can help
const float  empty_level   =  800;
const float  full_level    =  4000; 


void eat_serial(byte amount=50) {
  // Consume whatever's in the buffer. (and don't do so indefinitely)
  byte count=amount;
  while ((count>0) && (Serial.available()>0)) { 
    Serial.read();
    count--;
  }
}

unsigned int readout_mcp3201() { // Do a single readout
    byte inByte = 0;
    unsigned int result = 0;
    digitalWrite(select_adc_pin, LOW);
    result = SPI.transfer(0x00);
    result = result << 8;
    inByte = SPI.transfer(0x00);
    result = result | inByte;
    digitalWrite(select_adc_pin, HIGH);
    result = result >> 1;
    result = result & 0b0000111111111111;
    return result;
}


void setup() {
  Serial.begin(9600);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV4);
  pinMode(en_pin,              OUTPUT);
  digitalWrite(en_pin,         LOW);   // Don't power
  pinMode(select_adc_pin,      OUTPUT);
  digitalWrite(select_adc_pin, HIGH);  // Don't select
  eat_serial();
  delay(50); // probably nothing needs any delay, but we wouldn't notice this anyway.
}


void loop() {
  unsigned int single_sample;
  float        temp_intermediate=0.0;
  float        temp_frac;

  //Don't do anything until the serial link says something.
  while (Serial.read()==-1) {
     Serial.println("Power saving mode.  Say something over the serial port to sample the dewar.");
     delay(1000);
     // TODO: flush the full buffer, so that we read only once.
  }
  eat_serial();   // Okay, the serial link sent something. Read it all, so we only react with one sampling.

      
  digitalWrite(en_pin, HIGH); // Put current on the loop so that the dewar samples
  delay(500); // TODO: figure out how short this can be.

  temp_intermediate = (float)readout_mcp3201(); // first sample
  for (int i=0;i<ewma_amount;i++) {         // successive samples
    single_sample = readout_mcp3201();
    temp_intermediate = ewma_alpha*single_sample + om_ewma_alpha*temp_intermediate;
    //Serial.print(single_sample);    
    //Serial.print(" ");    
  }
  digitalWrite(en_pin, LOW);

  //Serial.println("\nReadout (smoothed but not yet scaled): ");  Serial.println(temp_intermediate);
  if (temp_intermediate<750) {
    Serial.println("No sensor connection");
  } else {
     temp_frac = (temp_intermediate-empty_level)/(full_level-empty_level);
     //Serial.print( 20.0*temp_frac );   Serial.print("mA, ");
     Serial.print( 100.0*temp_frac );  Serial.println("%");
  }

  delay(50); //can be short/removed when the "wait for serial talk" is in there
}

See also