RS485 & ModbusRTU

Intro

The NEXTuinos MAXI and MEGA have an RS485 interface type SN65HVD08. RS485 is an interface standard for digital, line-connected and differential serial data transmission. It can carry signals up to about 1,200 metres with 32 subscribers. The RS485 driver module is connected to the UART3 (TxD3 / RxD3) of the ATmega2560.

IMPORTANT: Please select the proper target board in Tools > Board > NEXTuino RISE/MAXI/MEGA before uploading to your NEXTuino.

Hardware Required

  • 2x NEXTuino PEAK/MEGA
  • 12/24V DC Power supply

Note: Pin header is working on 5V TTL levels. Voltage levels over 5.5V can damage the NEXTuino permanently.

Demo RS485 Code

Upload the sending code on one NEXTuino and the receiving code on the other. Connect them according to the RS485 wiring diagram and open the Serial Monitor on the receiving NEXTuino.

Sending Device

#include <SPI.h>
#include <NEXTuino.h>

void TestRS485(int mode) {
  DDRJ = DDRJ | B01100000;
  PORTJ = PORTJ & B10011111;
  switch (mode) {
    case 0:
      PORTJ = PORTJ & B10011111;
      PORTJ = PORTJ | B01000000;
      delay(10);
      Serial.println("Sending test message, expected to return;");
      Serial3.print("UUUUU NEXTuino RS485 test Message.UUUUU");
      break;
    case 1:
      PORTJ = PORTJ & B10011111;
      PORTJ = PORTJ | B01100000;
      delay(10);
      Serial.println("Sending test message, not expected to return;");
      Serial3.print("UUUUU NEXTuino RS485 test Message.UUUUU");
      break;
    case 2:
      PORTJ = PORTJ & B10011111;
      PORTJ = PORTJ | B00100000;
      delay(10);
      Serial.println("Sending test message, not expected to be sent;");
      Serial3.print("UUUUU NEXTuino RS485 test Message.UUUUU");
      break;
    default:
      Serial.println("Wrong mode!");
      return;
  }
}

void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  NEXTuino_RS485Init();
}

void loop() {
  TestRS485(0); delay(2000);
  TestRS485(1); delay(2000);
  TestRS485(2); delay(2000);
}

Receiving Device

#include <SPI.h>
#include <NEXTuino.h>

void RecieveRS485() {
  Serial.println("Receiving RS485.");
  while (true) {
    if (Serial3.available()) {
      Serial.print((char)Serial3.read());
    }
  }
}

void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  NEXTuino_RS485Init();
}

void loop() {
  RecieveRS485();
}

ModbusRTU Code

The Modbus master periodically reads registers from the slave (analog input, digital input, message counters) and writes relay output states.

Registers layout:

  • [0] – analog NEXTuino_A0 (0–1023)
  • [1] – digital NEXTuino_D0 (0/1)
  • [2] – Modbus messages received
  • [3] – Modbus messages transmitted
  • [4] – relay NEXTuino_R0 (0/1)
  • [5] – relay NEXTuino_R1 (0/1)
  • [6] – relay NEXTuino_R2 (0/1)
  • [7] – relay NEXTuino_R3 (0/1)

To compile, add ModbusRtu.h to the sketch (Sketch > Add File…). Download it from the NEXTuino Library or find it in the NEXTuino Library folder.

ModbusRTU Master

#include <NEXTuino.h>
#include "ModbusRtu.h"

#define MasterModbusAdd 0
#define SlaveModbusAdd  1
#define RS485Serial     3

Modbus NEXTuinoModbusMaster(MasterModbusAdd, RS485Serial, 0);
uint16_t ModbusSlaveRegisters[8];
modbus_t ModbusQuery[2];
uint8_t myState;
uint8_t currentQuery;
unsigned long WaitingTime;

void setup() {
  Serial.begin(9600);
  ModbusQuery[0].u8id = SlaveModbusAdd;
  ModbusQuery[0].u8fct = 3;
  ModbusQuery[0].u16RegAdd = 0;
  ModbusQuery[0].u16CoilsNo = 4;
  ModbusQuery[0].au16reg = ModbusSlaveRegisters;

  ModbusQuery[1].u8id = SlaveModbusAdd;
  ModbusQuery[1].u8fct = 6;
  ModbusQuery[1].u16RegAdd = 4;
  ModbusQuery[1].u16CoilsNo = 1;
  ModbusQuery[1].au16reg = ModbusSlaveRegisters + 4;
  ModbusSlaveRegisters[4] = 1;

  NEXTuinoModbusMaster.begin(19200);
  NEXTuinoModbusMaster.setTimeOut(5000);
  WaitingTime = millis() + 1000;
  myState = 0;
  currentQuery = 0;
}

void loop() {
  switch (myState) {
    case 0:
      if (millis() > WaitingTime) myState++;
      break;
    case 1:
      NEXTuinoModbusMaster.query(ModbusQuery[currentQuery]);
      myState++;
      currentQuery++;
      if (currentQuery == 2) currentQuery = 0;
      break;
    case 2:
      NEXTuinoModbusMaster.poll();
      if (NEXTuinoModbusMaster.getState() == COM_IDLE) {
        myState = 0;
        WaitingTime = millis() + 1000;
      }
      break;
  }
}

ModbusRTU Slave

#include <NEXTuino.h>
#include "ModbusRtu.h"

#define SlaveModbusAdd 1
#define RS485Serial    3

Modbus NEXTuinoModbusSlave(SlaveModbusAdd, RS485Serial, 0);
uint16_t ModbusSlaveRegisters[8];

void setup() {
  pinMode(NEXTuino_R0, OUTPUT);
  pinMode(NEXTuino_R1, OUTPUT);
  pinMode(NEXTuino_R2, OUTPUT);
  pinMode(NEXTuino_R3, OUTPUT);
  pinMode(NEXTuino_D0, INPUT);
  pinMode(NEXTuino_A0, INPUT);
  NEXTuinoModbusSlave.begin(19200);
}

void loop() {
  NEXTuinoModbusSlave.poll(ModbusSlaveRegisters, 8);
  ModbusSlaveRegisters[0] = analogRead(NEXTuino_A0);
  ModbusSlaveRegisters[1] = digitalRead(NEXTuino_D0);
  ModbusSlaveRegisters[2] = NEXTuinoModbusSlave.getInCnt();
  ModbusSlaveRegisters[3] = NEXTuinoModbusSlave.getOutCnt();
  digitalWrite(NEXTuino_R0, ModbusSlaveRegisters[4]);
  digitalWrite(NEXTuino_R1, ModbusSlaveRegisters[5]);
  digitalWrite(NEXTuino_R2, ModbusSlaveRegisters[6]);
  digitalWrite(NEXTuino_R3, ModbusSlaveRegisters[7]);
}