Arduino Modbus RTU ADC

Modbus is an industry standard communications protocol for electronic devices. Given that most industrial sensors and meters provide their output by varying the voltage of the output pin between 0-10 Volts, Modbus compatible analog to digital converters are popular devices. Modbus is surprisingly resilient to interference. This is because it is based around the RS-485 standard. RS-485 is essentially a multi-point, balanced, version of RS-232 (which people might recognise as the serial port protocol), which is point-to-point, and unbalanced.

Given that the cheapest industrial unit I found retails for €75 ex.VAT, it is interesting to know that an Arduino UNO + MAX485 can do the same thing for €25 inc.VAT, ⅓ of the price.

The Arduino UNO has 5 analog inputs, which means it is capable of sampling 5 analog values and storing them storing them in 5 sequential registers. The Modbus master device can then query Arduino slave, and retrieve these values from the registers. If you find that you don't have enough device inputs with 5 analog inputs, consider using an Arduino Mega, which has 16 analog inputs.

Most sensors output a range between 0-10 V, while the Arduino accepts a maximum of 5 V. By passing the sensor output through a pair of resistors in parallel, we will reduce the voltage range from 0-10 V to 0-5 V, which is suitable for the Arduino.

The Arduino only has full duplex serial lines, so a MAX485 IC chip must be used to convert them into the half-duplex differential lines for RS-485. Due to the the fact that the MAX485 is half duplex, Pin2 is used as an output enable pin.

The code is based on this library by jpmzometa which must be downloaded and placed in the Arduino/Libraries directory.




#include 

/* create new mbs instance */
ModbusSlave mbs;

/* slave registers */
enum {        
        MB_A1,        /* analogIn 1 */
        MB_A2,        /* analogIn 2 */
        MB_A3,        /* analogIn 3 */
        MB_A4,        /* analogIn 4 */
        MB_A5,        /* analogIn 5 */
        MB_REGS       /* dummy register. using 0 offset to keep size of array */
};

int regs[MB_REGS];
unsigned long wdog = 0;         /* watchdog */

void setup(){
        
  /* Modbus slave configuration parameters */
  const unsigned char SLAVE = 10;      /* slaveId */
  const long BAUD = 19200;             /* baud rate */
  const char PARITY = 'n';             /* n=none; e=even; o=odd */
  const char TXENPIN = 2;              /* output driver enable pin */
  
  /* configure msb with config settings */
  mbs.configure(SLAVE,BAUD,PARITY,TXENPIN);

}

void loop()
{
/* pass current register values to mbs */
  if(mbs.update(regs, MB_REGS))
  wdog = millis();

  /* ADC reads are slow. sample every 5 seconds */  
  if ((millis() - wdog) > 5000)  {      
    regs[MB_A1] = analogRead(A1); /* read input A1 */
    regs[MB_A2] = analogRead(A2); /* read input A2 */
    regs[MB_A3] = analogRead(A3); /* read input A3 */
    regs[MB_A4] = analogRead(A4); /* read input A4 */
    regs[MB_A5] = analogRead(A5); /* read input A5 */
  }        
}




24 thoughts on “Arduino Modbus RTU ADC

  1. estou fazendo tcc na faculdade gostaria que me envia-se um codigo fonte de um escravo rs-485 modbus rtu que tera que ler um sensor analogico.

  2. Hi:
    I have issues with this example because the update rate of analog reading in the master modbus PC is very slow and I do not why because I use the same hardware and your code. When I test the code for write digital pin it work perfect but your codo not.
    Regards.

  3. Hi Jon:
    Excuse me, your code work well because I tested the communication with a modbus tester "ModScan32.exe" and go perfect, I think I have trouble with LabView Modbus driver.
    AnywayThanks.

    • Hi Carl,

      Good spot. It was used for debugging, but I forgot to remove it when publishing.

      I've removed it now.

      Thanks,
      Jon

  4. Hi... I'm trying to do using your drawing above. The problem is : I connect it to RS485 in my PC and try to read the data. Arduino send : 0x01 and my PC receive 0x7F, something is wrong, do you know why ?

    I'm using Serial.write(); function

    • Hi Stanley,

      It sounds as if your bits are being flipped, and there are endian issues too.

      0x01 = 00000001
      0x7F = 01111111

      Have you checked that you haven't inverted the data+ and data- RS485 lines?

      Jon

  5. Hi I tried to use this code using 5 digital inputs and 2 analog inputs. The code compiles properly when I comment out the last analog input.
    ie. use 5 digital inputs and 1 analog input. But as soon as I uncomment the last analog input the program wont compile. Do you know why?

    if ((millis() - wdog) > 5000) {
    regs[MAINS_FAIL_ALARM_REGISTER] = digitalRead(MAINS_FAIL_ALARM_PIN); // Mains Fail Alarm
    regs[EARTH_FAULT_ALARM_REGISTER] = digitalRead(EARTH_FAULT_ALARM_PIN); // Earth Fault Alarm
    regs[CHARGER_FAIL_ALARM_REGISTER] = digitalRead(CHARGER_FAIL_ALARM_PIN); // Charger Fail Alarm
    regs[HIGH_VOLTAGE_ALARM_PIN] = digitalRead(HIGH_VOLTAGE_ALARM_PIN); // High Voltage Alarm
    regs[LOW_VOLTAGE_ALARM_REGISTER] = digitalRead(LOW_VOLTAGE_ALARM_PIN); // Low Voltage Alarm
    regs[OUTPUT_DC_VOLTAGE_REGISTER] = analogRead(OUTPUT_DC_VOLTAGE_PIN); // Output DC Voltage
    // regs[CHARGING_CURRENT_REGISTER] = analogRead(CHARGING_CURRENT_PIN); // Charging Current
    }
    }

    • Hi Alex,

      Have you defined an appropriate number of registers in the slave registers enum?

      The if statement is there to limit the poll time. Without that, the Arduino spends all of its time updating the register and is then unavailable to respond to Modbus devices that query it.

      Jon

  6. Furthermore I don't understand why there is an If statement around the Modbus Update Function. If I comment this line out the program compiles too.

  7. Hello,

    When I try to compile it I get this message: "ModbusSlave does not name a type"
    How can I solve it?
    Thanks in advance,
    Rincon

  8. Hello, what is the address of the register MB_A1....A5? How can i add the digital inputs and what addresses will they have?

    • Hi Andreas,

      As far as I remember, the registers starts at 0. This comment from the library source code seems to confirm same.

      Jon

      /*
       * update(regs, regs_size)
       * 
       * checks if there is any valid request from the modbus master. If there is,
       * performs the requested action
       * 
       * regs: an array with the holding registers. They start at address 1 (master point of view)
       * regs_size: total number of holding registers.
       * returns: 0 if no request from master,
       *      NO_REPLY (-1) if no reply is sent to the master
       *      an exception code (1 to 4) in case of a modbus exceptions
       *      the number of bytes sent as reply ( > 4) if OK.
       */
      
  9. hi
    I want to know how to test the code above
    display the value of each firm once the need to master

Leave a Reply