Skip to Content

PLC Modbus Tutorial: How to get temperature with a Dallas sensor

DS18B20 sensor, Ardbox PLC as Modbus RTU slave and a Python master
July 30, 2021 by
PLC Modbus Tutorial: How to get temperature with a Dallas sensor
Boot & Work Corp. S.L, Fernandez Queralt Martinez

Complete guide: Modbus RTU and TCP with Python and Node-RED

To share the temperature of a Dallas DS18B20 sensor over Modbus, the PLC that has the sensor reads it with the OneWire and DallasTemperature libraries, multiplies it by 100 and copies it into an input register as a signed 16-bit value. A Modbus RTU master, such as a SCADA, an HMI or another PLC, reads that register with function code 04 over RS-485 and divides it by 100: 2150 is 21.50 °C and -550 is -5.50 °C.

In the previous post we connected a DS18B20 to an Ardbox, an Arduino based PLC. Here the Ardbox becomes a Modbus RTU slave that publishes the temperature, and a short Python program reads it as the master.

How does the temperature reach the Modbus master?

  1. Step 1DS18B20Digital sensor on a 1-Wire bus, from -55 to 125 °C.
  2. Step 2Ardbox PLCReads the sensor every 2 s and saves the value x 100 in an input register.
  3. Step 3RS-485Modbus RTU, 9600 bit/s, 8N1, slave address 1.
  4. Step 4MasterReads input registers 0 and 1 with FC04 and divides the temperature by 100.
Input register (FC04)ContentFormat
Address 0 (30001)Temperature x 100Signed 16-bit (int16): 2150 = 21.50 °C, -550 = -5.50 °C
Address 1 (30002)Sensor status1 = reading OK, 0 = sensor not found or bad reading

Modbus registers are 16-bit words with no decimals, so the temperature travels as hundredths of a degree. A negative temperature becomes a value above 32767 if the master reads it as unsigned: configure the master to read a signed integer (INT16).

Modbus in two minutes

Modbus is a request and response protocol that Modicon published in 1979 for its PLCs, and it is still the most common way to connect industrial devices. A master (client) sends a request with the address of one slave (server), and only that slave answers. Modbus RTU runs on serial lines such as RS-485, and Modbus TCP runs on Ethernet. Every frame carries a CRC, so the receiver can discard a corrupted frame.

Modbus network diagram: SCADA and a web browser on Modbus TCP over Ethernet, and an operator panel and a temperature controller on Modbus RTU over RS-485
Modbus TCP on Ethernet between SCADA, operator panels and controllers, and Modbus RTU on RS-485 down to an operator panel and a temperature controller.

A slave exposes four data areas: coils and discrete inputs (bits), holding registers and input registers (16-bit words). A measurement that the master only reads, like this temperature, fits in an input register.

Libraries for the Arduino IDE

You also need the Industrial Shields boards in the Arduino IDE, for the RS-485 port and the pins of the PLC. Follow how to install the Industrial Shields boards in the Arduino IDE.

Arduino code of the Modbus slave

// Ardbox Analog HF+ w/ HW RS-485 as Modbus RTU slave with a DS18B20 sensor
// Input register 0: temperature x 100, signed (2150 = 21.50 C, -550 = -5.50 C)
// Input register 1: sensor status (1 = reading OK, 0 = no sensor or bad reading)
#include <OneWire.h>
#include <DallasTemperature.h>
#include <RS485.h>
#include <ModbusRTUSlave.h>

#define ONE_WIRE_BUS 2          // DS18B20 data pin, wired as in the connection guide
#define RS485_RATE 9600UL       // must match the master
#define SLAVE_ADDRESS 1         // Modbus address of this PLC (1 to 247)
#define READ_PERIOD_MS 2000UL   // new temperature every 2 s
#define CONVERSION_MS 750UL     // DS18B20 conversion time at 12-bit resolution

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensor(&oneWire);

uint16_t inputRegisters[2];
ModbusRTUSlave modbus(RS485, SLAVE_ADDRESS);

uint32_t requestTime;
bool resultPending = true;  // a conversion was started and not read yet

void setup() {
  Serial.begin(9600);
  sensor.begin();
  sensor.setWaitForConversion(false);  // do not block the Modbus slave for 750 ms
  sensor.requestTemperatures();
  requestTime = millis();

  RS485.begin(RS485_RATE, HALFDUPLEX, SERIAL_8N1);
  modbus.begin(RS485_RATE);
  modbus.setInputRegisters(inputRegisters, 2);
}

void loop() {
  if (resultPending && millis() - requestTime >= CONVERSION_MS) {
    float temp = sensor.getTempCByIndex(0);
    if (temp == DEVICE_DISCONNECTED_C) {
      inputRegisters[1] = 0;  // keep the last value and flag the error
    } else {
      int16_t centi = (int16_t)(temp * 100.0f + (temp >= 0 ? 0.5f : -0.5f));
      inputRegisters[0] = (uint16_t)centi;  // two's complement for negative values
      inputRegisters[1] = 1;
      Serial.println(temp);
    }
    resultPending = false;  // read each conversion only once
  }
  if (millis() - requestTime >= READ_PERIOD_MS) {
    sensor.requestTemperatures();  // start the next conversion
    requestTime = millis();
    resultPending = true;
  }
  modbus.update();  // answer the master as often as possible
}

What each part does:

  • Non-blocking conversion. A DS18B20 needs up to 750 ms to convert at 12-bit resolution. With setWaitForConversion(false) the sketch starts the conversion and reads the result 750 ms later, so modbus.update() keeps answering the master in the meantime.
  • Signed value. The temperature x 100 goes through int16_t before it is saved in the register, so negative temperatures arrive as two's complement and the master can read them as INT16.
  • Status register. If the library returns DEVICE_DISCONNECTED_C (-127), the sketch keeps the last value and writes 0 in register 1. The master knows that the reading is not valid.
  • Pin 2. The data line of the sensor goes to Arduino pin 2, as in the wiring post. On the Ardbox HF+ this pin is also the SDA line of I2C, so do not use I2C at the same time.

In the Arduino IDE, select Tools > Board > Industrial Shields boards > Ardbox family and, in Model, Ardbox Analog HF+ w/ HW RS-485. Then select the port, upload the sketch and open the serial monitor at 9600 to see the temperature.

Changes from the first version of this post. The first sketch saved temp * 100 straight into an unsigned register, so a negative temperature had no defined value. It also blocked the Modbus slave during each 750 ms conversion, and it published -127 °C as a temperature when the sensor was missing. Its code blocks also had non-breaking spaces, and the compiler stops at them with "stray '\240' in program".

Read the temperature from a Python master

Any Modbus RTU master can read the Ardbox. This example uses pymodbus 3.16.1 on a Raspberry PLC, or on a PC with a USB to RS-485 adapter. Connect A+ to A+ and B- to B- of the two RS-485 ports. The installation of pymodbus and the RS-485 port names of each Raspberry PLC version are in the Modbus guide for Raspberry Pi PLCs.

from pymodbus.client import ModbusSerialClient
from pymodbus.exceptions import ModbusException
from pymodbus.framer import FramerType

PORT = "/dev/ttySC0"  # RS-485 port of the master, check the name for your PLC version
DEVICE_ID = 1         # SLAVE_ADDRESS of the Ardbox sketch

client = ModbusSerialClient(port=PORT, framer=FramerType.RTU, baudrate=9600,
                            bytesize=8, parity="N", stopbits=1, timeout=1)
if not client.connect():
    raise SystemExit(f"Cannot open {PORT}")

try:
    rr = client.read_input_registers(0, count=2, device_id=DEVICE_ID)  # FC04
    if rr.isError():
        print("Exception response:", rr)
    elif rr.registers[1] == 0:
        print("Sensor not found or bad reading")
    else:
        raw = rr.registers[0]
        centi = raw - 65536 if raw > 32767 else raw  # signed 16-bit
        print(f"Temperature: {centi / 100:.2f} C (raw {raw})")
except ModbusException as exc:
    print("No valid answer:", exc)
finally:
    client.close()

Output with the slave values that we used in the test:

Temperature: 21.50 C (raw 2150)
Temperature: -5.50 C (raw 64986)
Sensor not found or bad reading

What we tested. The slave sketch compiles without errors for the Ardbox Analog HF+ w/ HW RS-485 with the Industrial Shields AVR boards, arduino-Modbus 1.1.0, OneWire 2.3.8 and DallasTemperature 4.0.6 (13,366 bytes of program memory, 40.8 %). The Python master ran with pymodbus 3.16.1 against a simulated Modbus RTU slave over a virtual serial link, with the values 2150, 64986 (-550) and a status 0. We did not run the sketch on an Ardbox with a real sensor for this update.

Common problems

What you seeUsual causeWhat to check
Status 0, or -127 in the serial monitorThe sensor does not answer on the 1-Wire busWiring, sensor power and the 4.7 kΩ pull-up resistor between data and VDD
85.00 °C85 °C is the power-on value of the DS18B20: the conversion did not finishSensor power, parasite power mode, and that the reading waits for the conversion
A value near 65000The master reads a negative temperature as unsignedRead the register as INT16, or subtract 65536 above 32767
Modbus timeoutNo answer from the slaveSlave address, 9600 8N1 on both sides, A and B not swapped, RS-485 termination
Exception 02 (illegal data address)The master reads a register that does not existOnly input registers 0 and 1 exist in this sketch

For the RS-485 side, follow the step-by-step RS-485 bus diagnosis.

Temperature over Modbus in a real project. In the fishing vessel case study, one Arduino based PLC per refrigerated hold measures temperature, humidity and air quality, and Modbus connects the PLCs with the screens of the control room.

Read the fishing vessel case studyChoose an industrial controller

FAQ about DS18B20 and Modbus

Can I connect a DS18B20 directly to a Modbus network?

No. The DS18B20 only speaks 1-Wire. A PLC or a converter reads it and publishes the value in a Modbus register.

Input register or holding register?

An input register, because the master only reads the measurement. Some masters only read holding registers (FC03): in that case use setHoldingRegisters in the sketch.

How do I send decimals over Modbus?

Scale the value: x 100 gives two decimals in one 16-bit register. For a wider range or more resolution, use two registers with a 32-bit integer or a float.

Can I read several DS18B20 on the same pin?

Yes. Each DS18B20 has its own 64-bit ROM code, so several sensors share the 1-Wire bus. Read them with getTempCByIndex(0), getTempCByIndex(1) and so on, and give each one its own input register.

Does the code work on other Industrial Shields PLCs?

The libraries are the same. Select your board in the Arduino IDE and check the pin of the 1-Wire bus and the RS-485 port in its user guide. We only compiled it for the Ardbox Analog HF+ w/ HW RS-485.

​Search in our Blog

PLC Modbus Tutorial: How to get temperature with a Dallas sensor
Boot & Work Corp. S.L, Fernandez Queralt Martinez July 30, 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