Skip to Content

Modbus RTU write single register (FC 06) with an M-Duino PLC

Function code 06 between two M-Duino PLCs, with tested code, the frame on the bus and the other function codes
October 19, 2021 by
Modbus RTU write single register (FC 06) with an M-Duino PLC
Boot & Work Corp. S.L, Fernandez Queralt Martinez

To write a single holding register with Modbus RTU, the master sends function code 06 with the register address and the new 16-bit value, and the slave answers with an echo of the same request. On an M-Duino PLC with the Industrial Shields Modbus library, the call is master.writeSingleRegister(slave, address, value), and the echo arrives through master.available().

In this tutorial one M-Duino works as Modbus RTU master and writes a holding register of a second M-Duino, which runs the slave example of the library. The register drives an analog output of the slave, so you can measure the result with a multimeter. At the end you will find how to test the other seven function codes with the same two PLCs.

What does Modbus function code 06 do?

Write Single Register (function code 06, 0x06) writes one holding register in a remote device. The request gives the address of the register and the value. Registers are addressed from zero, so register 1 (40001 in most manuals) is address 0. The normal answer repeats the request once the value has been written.

FieldRequestResponse
Slave address1 byte (1 to 247)Same
Function code0x060x06
Register address2 bytes (0x0000 to 0xFFFF)Same
Register value2 bytes (0x0000 to 0xFFFF)Same
CRC2 bytes, low byte first2 bytes

In this example the master writes 128 (0x0080) to address 0 of slave 31 (0x1F). The frame on the RS-485 bus is 1F 06 00 00 00 80 8B D4, and the slave answers with the same 8 bytes. The Modbus RTU frame and function codes guide explains every field and the CRC.

Hardware for a Modbus RTU test with two M-Duino PLCs

Connections between the two M-Duino PLCs

  1. Power both PLCs with 12 to 24 Vdc.
  2. Connect the RS-485 terminals with a twisted pair: A+ to A+ and B- to B-.
  3. Set the communication switch of both PLCs to half duplex (HD).
Two M-Duino PLCs connected over RS-485 with a twisted pair: B to B and A to A

On a bench, with a short cable, the link works without termination. On a real bus, fit a 120 Ω resistor between A+ and B- at the two ends of the line. The RS-485 basics explain why.

Master code: write a single register and read it back

Open a new sketch in the Arduino IDE and paste this code. Every second it alternates two requests: function 06 writes 128 to holding register 0 of slave 31, and function 03 reads the same register to check it. It compiles without warnings for an M-Duino 21+ with the Industrial Shields AVR boards 1.2.1 and the Modbus library 1.1.0.

// Modbus RTU master on an M-Duino PLC: write single register (FC 06), then read it back (FC 03)
// Slave: the ModbusRTUSlave example of the library, address 31, 38400 baud, 8E1
#include <RS485.h>
#include <ModbusRTUMaster.h>

const uint32_t BAUDRATE = 38400UL;
const uint8_t SLAVE_ID = 31;
const uint16_t REG_ADDR = 0;        // holding register 40001 = analog output A0_5 of the slave
const uint16_t VALUE = 128;         // 0 to 255 on an M-Duino analog output: 128 is about 5 V

ModbusRTUMaster master(RS485);
uint32_t lastRequest = 0;
bool writeNext = true;              // alternate: write, then read back

void setup() {
  Serial.begin(9600UL);
  RS485.begin(BAUDRATE, HALFDUPLEX, SERIAL_8E1);
  master.begin(BAUDRATE);
}

void loop() {
  if (!master.isWaitingResponse() && millis() - lastRequest >= 1000UL) {
    bool sent = writeNext ? master.writeSingleRegister(SLAVE_ID, REG_ADDR, VALUE)
                          : master.readHoldingRegisters(SLAVE_ID, REG_ADDR, 1);
    if (!sent) {
      Serial.println("Request not sent");
    }
    lastRequest = millis();
  }

  if (master.isWaitingResponse()) {
    ModbusResponse response = master.available();
    if (master.hasException()) {
      Serial.print("No valid answer: ");
      Serial.println(master.getExceptionMessage());
      master.clearException();
    }
    if (response) {
      if (response.hasError()) {
        Serial.print("Slave exception: ");
        Serial.println(response.getErrorMessage());
      } else if (response.getFC() == 6) {
        Serial.println("Register written");          // the slave echoed the request
      } else {
        Serial.print("Register value: ");
        Serial.println(response.getRegister(0));
      }
      writeNext = !writeNext;
    }
  }
}
  1. Select Tools > Board > Industrial Shields AVR Boards > M-Duino family.
  2. Select the model in Tools > Model. In our case, M-Duino 21+.
  3. Select the port in Tools > Port.
  4. Upload the sketch with the arrow button or Sketch > Upload.

The serial monitor (9600 baud) shows "Register written" and "Register value: 128" every second. "No valid answer: timeout" means the slave does not answer: check the slave address, the baud rate, the HD switch and A+ and B-.

Slave code: the ModbusRTUSlave example

Once the master is programmed, program the second M-Duino with the slave example of the library:

  1. Connect the USB cable to the other M-Duino PLC.
  2. Open File > Examples > Modbus > ModbusRTUSlave. It uses address 31, 38400 baud and 8E1, like the master.
  3. Select Tools > Board > Industrial Shields AVR Boards > M-Duino family and the model, in our case M-Duino 21+.
  4. Select the port in Tools > Port.
  5. Upload the sketch with Sketch > Upload.

The example maps the inputs and outputs of the first zone of the PLC to the four Modbus tables. On an M-Duino 21+ the map is:

Modbus tableAddressesM-Duino 21+ pinsValues
Coils0 to 4Q0_0 to Q0_40 or 1
Discrete inputs0 to 6I0_0 to I0_60 or 1
Holding registers0 to 2A0_5, A0_6, A0_7 (analog outputs)0 to 255 (0 to 10 V)
Input registers0 to 5I0_7 to I0_12 (analog inputs)0 to 1023 (0 to 10 V)

With the master running, measure A0_5 of the slave with a multimeter: it reads about 5 V. Change VALUE to 255 and it goes to about 10 V.

Why 128 and not 1000? A holding register accepts 0 to 65535, but the slave example sends it to analogWrite(), and the analog outputs of the M-Duino have 8-bit resolution (0 to 255). An earlier version of this tutorial wrote 1000: the PWM register keeps only the low 8 bits, so the output went to 232 (about 9 V) instead of failing. Scale the value in the master, or in the slave before you write the output.

Test the other Modbus function codes with the same PLCs

Keep the wiring and the slave. In the master, replace the request with one of these calls. The answer is read in the same way, with master.available().

FCMaster callWhat happens on the slaveRead the answer with
01master.readCoils(31, 0, 5)Returns the state of Q0_0 to Q0_4response.isCoilSet(i)
02master.readDiscreteInputs(31, 0, 7)Returns I0_0 to I0_6response.isDiscreteInputSet(i)
03master.readHoldingRegisters(31, 0, 3)Returns the values of A0_5 to A0_7response.getRegister(i)
04master.readInputRegisters(31, 0, 6)Returns the analog inputs I0_7 to I0_12response.getRegister(i)
05master.writeSingleCoil(31, 0, true)Switches Q0_0 onresponse.hasError()
06master.writeSingleRegister(31, 0, 128)Sets A0_5 to about 5 Vresponse.hasError()
15master.writeMultipleCoils(31, 0, coils, 5)Sets Q0_0 to Q0_4 from a bool coils[5] arrayresponse.hasError()
16master.writeMultipleRegisters(31, 0, values, 3)Sets A0_5 to A0_7 from a uint16_t values[3] arrayresponse.hasError()

Each call returns false if the master is still waiting for the previous answer, so send one request at a time. The library also has one ready-made master example for each function code in File > Examples > Modbus. The full reference, the wiring for Ardbox and ESP32 PLCs and an error table are in Modbus RTU with Arduino and ESP32 based PLCs.

Try a bus with more slaves. In the Modbus network simulator, an M-Duino 21 polls a temperature transmitter, a pressure transmitter, a flow meter and a frequency drive. Change a register, a data type or the word order and see the answer of the slave, frame by frame.

Open the Modbus network simulatorWrite the speed of a drive over Modbus RTU

FAQ about writing Modbus registers

What is the difference between function 06 and function 16?

Function 06 writes one register. Function 16 (0x10) writes from 1 to 123 consecutive registers in one request. Use 16 for 32-bit values, which take two registers. Some devices accept only one of the two, so check the manual.

Why does the slave answer but the value does not change?

Check the address: register 40001 is address 0, so writing to address 1 changes register 40002. Check also the range and the scale of the device: many devices store 23.5 as 235 (value x 10), and some reject values out of range with exception 03.

How do I write a negative value?

Write its 16-bit two's complement. For example, -1 is 65535 (0xFFFF). In C, cast it: (uint16_t)(int16_t)-1. The slave must treat that register as a signed integer.

Can I write the same register in all the slaves at once?

Modbus RTU has a broadcast address (0), and the slaves never answer a broadcast. The Industrial Shields master library rejects address 0, so with this library write to each slave in turn.

Is the value kept after a power cut?

It depends on the slave. In the ModbusRTUSlave example the holding registers are variables in RAM, so they start at 0 after a reset. Industrial devices usually say in their manual which registers are saved in non-volatile memory.

​Search in our Blog

Modbus RTU write single register (FC 06) with an M-Duino PLC
Boot & Work Corp. S.L, Fernandez Queralt Martinez October 19, 2021
Share this post

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.

PLC Comparison