Modbus TCP Temperature Sensor with Arduino and LM335

I used to hate on Arduino a bit, but have recently come around. I was having a conversation with a work colleague along the lines of how super easy it would be nowadays to build a temperature sensor with ethernet connectivity and a modbus interface. Remembering that I had an old Arduino board (with Ethernet) kicking around from some abandoned project years ago, I laid myself a little personal challenge: to see how quickly I could prototype such a device. The answer is: Really, surprisingly quickly!

Gluing together other people’s hard work together made this a trivial exercise, and it was kind of too easy. It felt a bit like cheating. And I think this kind of plug-n-play, lego block engineering has made me lazy – I know someone else has already done the legwork so all my time is spent evaluating the relative merit of one library against another, rather than engineering something tricky from first principles. I guess this is both the blessing and the curse of the Arduino phenomenon.

The original sketch to read q temperature measurement from the LM335 is based on the one found here: http://spacetinkerer.blogspot.com.

There are a handful of Modbus libraries but I chose to use https://github.com/andresarmento/modbus-arduino. I also used the runningaverage library found here, in an attempt to tame some of the noise and variability in measurements.

Of the whole exercise, what actually took the longest was debugging the poor performance of the LM335 sensor. In the end, I wisely took the datasheet’s advice and added a 10k potentiometer which just works as a resistive voltage divider into one of the pins of the LM335 which in turn gives a linear offset. The LM335 can then be easily calibrated against another thermometer.Screen Shot 2016-05-15 at 10.28.50 PM

To log, store and historise the data I of course used Mango Automation (and with the server located off-site, I just added a DST-NAT rule to forward TCP traffic on port 502 from my home internet to my Arduino).

ft_1463375105710_1456037700000_-1_1584

The data source configuration in Mango looks like this:

{
   "dataSources":[
      {
         "xid":"DS_714957",
         "name":"Arduino Modbus Sensor",
         "enabled":true,
         "type":"MODBUS_IP",
         "alarmLevels":{
            "POINT_WRITE_EXCEPTION":"DO_NOT_LOG",
            "POLL_ABORTED":"DO_NOT_LOG",
            "DATA_SOURCE_EXCEPTION":"DO_NOT_LOG",
            "POINT_READ_EXCEPTION":"DO_NOT_LOG"
         },
         "purgeType":"YEARS",
         "updatePeriodType":"SECONDS",
         "transportType":"TCP",
         "encapsulated":false,
         "host":"arduino.host.address",
         "port":502,
         "contiguousBatches":false,
         "createSlaveMonitorPoints":false,
         "discardDataDelay":0,
         "ioLogFileSizeMBytes":1.0,
         "logIO":false,
         "maxHistoricalIOLogs":1,
         "maxReadBitCount":2000,
         "maxReadRegisterCount":125,
         "maxWriteRegisterCount":120,
         "multipleWritesOnly":false,
         "quantize":false,
         "retries":2,
         "timeout":500,
         "updatePeriods":10,
         "purgeOverride":false,
         "purgePeriod":1
      }
   ]
}

The temperature value is read as a two-byte signed integer. The Mango data point configuration looks like this.

{
   "dataPoints":[
      {
         "xid":"DP_739238",
         "name":"Temperature Sensor 1",
         "enabled":true,
         "loggingType":"ALL",
         "intervalLoggingPeriodType":"MINUTES",
         "intervalLoggingType":"INSTANT",
         "purgeType":"YEARS",
         "pointLocator":{
            "range":"INPUT_REGISTER",
            "modbusDataType":"TWO_BYTE_INT_SIGNED",
            "writeType":"NOT_SETTABLE",
            "additive":0.0,
            "bit":0,
            "charset":"ASCII",
            "multiplier":0.01,
            "offset":100,
            "registerCount":0,
            "slaveId":1,
            "slaveMonitor":false
         },
         "eventDetectors":null,
         "plotType":"STEP",
         "unit":"?",
         "chartColour":"",
         "chartRenderer":null,
         "dataSourceXid":"DS_714957",
         "defaultCacheSize":1,
         "deviceName":"Arduino Modbus Temperature Sensor",
         "discardExtremeValues":true,
         "discardHighLimit":100.0,
         "discardLowLimit":0.0,
         "intervalLoggingPeriod":15,
         "intervalLoggingSampleWindowSize":0,
         "overrideIntervalLoggingSamples":false,
         "purgeOverride":false,
         "purgePeriod":1,
         "textRenderer":{
            "type":"ANALOG",
            "useUnitAsSuffix":true,
            "unit":"?",
            "renderedUnit":"?",
            "format":"##.##"
         },
         "tolerance":0.0
      }
   ]
}

And here’s my actual Arduino sketch:

#include <SPI.h>
#include <Ethernet.h>
#include <Modbus.h>
#include <ModbusIP.h>
#include <RunningAverage.h>

//Modbus Registers Offsets (0-9999)
const int SENSOR_IREG = 100; 
//Used Pins
const int sensorPin = A0;

RunningAverage myRA(30);
int samples = 0;

//ModbusIP object
ModbusIP mb;

float temp_in_celsius = 0, temp_in_kelvin = 0;
long ts;

void setup() {
    
    // The media access control (ethernet hardware) address for the shield
    byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  
    // The IP address for the shield
    byte ip[] = { 192, 168, 88, 18 };   
    //Config Modbus IP 
    mb.config(mac, ip);

    // Add SENSOR_IREG register - Use addIreg() for analog Inputs
    mb.addIreg(SENSOR_IREG);
    
    ts = millis();
    myRA.clear(); // explicitly clear the rolling average array
    Serial.begin(9600); //start serial port, 9600 baud
}

void loop() {
   //Call once inside loop() - all magic here
   mb.task();

  //Reads the input and converts it to Kelvin degrees
  temp_in_kelvin = analogRead(0) * 0.004882812 * 100;

  //Print the temperature in Celsius to the serial port for monitoring
  //Serial.print("Celsius: ");
  //Serial.println(temp_in_celsius);                  
  //Serial.println();
  //delay(1000); 

  //Converts Kelvin to Celsius minus 2.5 degrees error
  temp_in_celsius = temp_in_kelvin - 2.5 - 273.15; 

  myRA.addValue(temp_in_celsius * 100); //add the current temp to the myRA array
   
   //Read each two seconds
   if (millis() > ts + 2000) {   
       ts = millis();
       //Setting raw value (0-1024)
       mb.Ireg(SENSOR_IREG, myRA.getAverage());
   } 
}

Leave a Reply

Your email address will not be published. Required fields are marked *