Read a MODBUS temperature sensor through USB-RS485 adapter on Ubuntu and Raspberry Pi

By: (plus.google.com) +David Herron; Date: June 7, 2017

Tags: MODBUS » Raspberry Pi

Running MODBUS/RTU over an RS485 network is pretty cool, in an old-school way. The technique was invented decades ago, and allowed you to connect to a few dozen MODBUS devices over simple twisted pair copper wire, over a thousand feet distance or more. It's such a successful technique that it has not been supplanted by new modern communication technologies like TCP/IP. Unfortunately most modern computers do not have RS-485 interfaces. Fortunately there are many USB-RS485 adapters available. In this article we'll use a cheap USB-RS485 adapter on both a Raspberry Pi and a regular x86 Linux box to communicate with a simple MODBUS temperature sensor.

What we'll do is create simple MODBUS client programs, in C and Python, to communicate with the chosen temperature sensor device.

For this project, we're using a cheap MODBUS temperature sensor that's probably not suitable for production use, but is good enough for small scale experimentation and learning. (See Overview of a simple MODBUS/RTU RS-485 temperature sensor) Additionally we're using this USB-to-RS485 adapter card:

Gikfun USB to RS485 TTL Serial Converter Adapter for Arduino EK1192
Gikfun USB to RS485 TTL Serial Converter Adapter for Arduino EK1192:

I've only used this device on Ubuntu, Raspbian (Raspberry Pi), and Mac OS X. Essentially you plug it into a USB port on your computer, and attach a MODBUS device to the jumper block, then run MODBUS software. On those operating systems a /dev/ttySOMETHING device is automatically set up, which you'll have to determine from the system log. The software has to connect through that device, and most of the details are automatically taken care of for you.

Close-up pictures:

These pictures show details of wiring the USB-to-RS485 adapter with this particular MODBUS device.

The device requires a 5 volt power supply, and the simplest way to get that power is via the jumper on the adapter board. The manufacturer did not bring the 5 volt connector out to the jumper block, but it is available there on the jumper pin. It's just a matter of connecting to that jumper pin using an extension cord. Otherwise "A" on the device is connected to "A" on the adapter, "B" on the device is connected to "B" on the adapter, as is "GND".

In theory this is how you connect multiple devices to this adapter. I have not tried this, however. It appears from the diagram that in this case, you're to use shielded cable, and the GND connection on each adapter board is to be connected to shield, which would then be connected to ground somewhere.

Connecting the adapter, learning the device name

On connecting the device to a USB port, the Linux/Unix kernel will scan the device and figure out what it is. For Linux, the device is automatically recognized (usually) and a series of messages like this might be printed:

May 31 13:49:02 nuc1 kernel: [ 2964.249228] usb 1-2: new full-speed USB device number 12 using xhci_hcd
May 31 13:49:02 nuc1 kernel: [ 2964.383074] usb 1-2: New USB device found, idVendor=0403, idProduct=6001
May 31 13:49:02 nuc1 kernel: [ 2964.383086] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
May 31 13:49:02 nuc1 kernel: [ 2964.383093] usb 1-2: Product: FT232R USB UART
May 31 13:49:02 nuc1 kernel: [ 2964.383100] usb 1-2: Manufacturer: FTDI
May 31 13:49:02 nuc1 kernel: [ 2964.383107] usb 1-2: SerialNumber: A91P30SH
May 31 13:49:02 nuc1 kernel: [ 2964.387488] ftdi_sio 1-2:1.0: FTDI USB Serial Device converter detected
May 31 13:49:02 nuc1 kernel: [ 2964.387641] usb 1-2: Detected FT232RL
May 31 13:49:02 nuc1 kernel: [ 2964.389048] usb 1-2: FTDI USB Serial Device converter now attached to ttyUSB1

The last line says the device name, in this case /dev/ttyUSB1.

If you should need to know, the manufacturer is at: (www.ftdichip.com) http://www.ftdichip.com/ Their chips are in a wide variety of products, and there is driver software on their site if you need it.

Programming in C with libmodbus

Okay, enough with setup let's get on with some code. We'll start with libmodbus because it's universally available, see (libmodbus.org) http://libmodbus.org/

For Ubuntu, and on Raspbian, you install the libmodbus-dev and libmodbus5 packages:

$ sudo apt-get install libmodbus5 libmodbus-dev

Once installed, we have this code for your perusal:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus-rtu.h>

const int EXCEPTION_RC = 2;

enum { TCP, TCP_PI, RTU };

#define SERVER_ID 1

int main(int argc, char *argv[]) {
    modbus_t *ctx = NULL;
    uint16_t *tab_rp_bits = NULL;
    struct timeval old_response;
    struct timeval new_response;
    uint32_t old_response_to_sec;
    uint32_t old_response_to_usec;
    uint32_t new_response_to_sec;
    uint32_t new_response_to_usec;
    uint32_t old_byte_to_sec;
    uint32_t old_byte_to_usec;
    int rc;
    int server_id = SERVER_ID;

    // UPDATE THE DEVICE NAME AS NECESSARY
    ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
    if (ctx == NULL) {
        fprintf(stderr, "Could not connect to MODBUS: %s\n", modbus_strerror(errno));
        return -1;
    }

    printf("Setting slave_id %d\n", server_id);
    fflush(stdout);
    rc = modbus_set_slave(ctx, server_id);
    if (rc == -1) {
        fprintf(stderr, "server_id=%d Invalid slave ID: %s\n", server_id, modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    modbus_set_debug(ctx, TRUE);
    // Not needed for USB-RS485 adapters
    // See: https://github.com/stephane/libmodbus/issues/316
    /* rc = modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    if (rc == -1) {
        fprintf(stderr, "server_id=%d Failed to set serial mode: %s\n", server_id, modbus_strerror(errno));
        // modbus_free(ctx);
        // return -1;
        server_id++;
        goto retry_slave;
    } */
    modbus_set_error_recovery(ctx,
                              MODBUS_ERROR_RECOVERY_LINK |
                              MODBUS_ERROR_RECOVERY_PROTOCOL);

    modbus_get_response_timeout(ctx, &old_response /* &old_response_to_sec, &old_response_to_usec /* */);
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    modbus_get_response_timeout(ctx, &new_response /* &new_response_to_sec, &new_response_to_usec /* */);

    tab_rp_bits = (uint16_t *) malloc(2 * sizeof(uint16_t));
    memset(tab_rp_bits, 0, 2 * sizeof(uint16_t));

    while (TRUE) {
        rc = modbus_read_input_registers(ctx, 0x1, 0x1, tab_rp_bits);
        if (rc == -1) {
            fprintf(stderr, "Failed to modbus_read_input_registers: %s\n", modbus_strerror(errno));
            /* modbus_free(ctx);
            return -1; */
        }

        printf("server_id=%d rc=%d temp=%f \n", rc, server_id, (double)(tab_rp_bits[0] / 100.0));
        sleep(1);
    }

close:
    /* Free the memory */
    free(tab_rp_bits);
    // free(tab_rp_registers);

    /* Close the connection */
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

Remember the device we're using is addressed as device ID 1, and we are to read register 1 for the temperature value.

The modbus_new_rtu function creates a MODBUS/RTU object. With modbus_set_slave we set the device ID to work with, and with modbus_connect we connect to the device.

There is a commented-out section where we would call modbus_rtu_set_serial_mode. With the USB adapter we have, the settings controlled by that function are handled for us automatically, and if we call that function errors will be generated.

With this:

tab_rp_bits = (uint16_t *) malloc(2 * sizeof(uint16_t));
memset(tab_rp_bits, 0, 2 * sizeof(uint16_t));

We set up a buffer to receive data from the MODBUS device. Each MODBUS register is 16 bits (a.k.a. "1 word") and we've allocated space for two registers. You should of course allocate space depending on the number of registers you'll be reading.

Then, in a loop, with a 1-second delay per iteration, we call modbus_read_input_registers to read from the device. Output is shown below.

The modbus_get_response_timeout and modbus_get_response_timeout functions have different signatures depending on the libmodbus version. To accommodate that difference we've got code you can comment/uncomment to match the library version.

To compile the program, type this:

$ gcc  -o temp temp.c `pkg-config --cflags --libs libmodbus`

Then run it as so:

$ sudo ./temp
[sudo] password for david:
server_id=1 rc=1 temp=30.750000
server_id=1 rc=1 temp=30.750000
server_id=1 rc=1 temp=30.750000
server_id=1 rc=1 temp=30.750000

Reading the temperature using minimalmodbus in Python

Compared to the libmodbus example using minimalmodbus in Python will feel like a nice cool breeze. This library makes MODBUS programming very simple, though it is on the limited side.

Library documentation is at: (minimalmodbus.readthedocs.io) https://minimalmodbus.readthedocs.io/en/master/readme.html

Installation:

$ pip install minimalmodbus

Then type this in as temp.py

#!/usr/bin/env python
import minimalmodbus
import time

minimalmodbus.BAUDRATE = 9600

# port name, slave address (in decimal)
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1)

while True:
    # Register number, number of decimals, function code
    temperature = instrument.read_register(1, 2, 4)
    print temperature
    time.sleep(1)

Before setting up the MODBUS connection, it's possible to set connection parameters (baud rate, etc) by assigning values to the minimalmodbus object. Then you connect to the device, minimalmodbus.Instrument, and interact with it using the API.

The instrument.read_register command reads a single register, to read multiple registers use instrument.read_registers. It takes the register number, and an indicator of the number of decimal points, and finally the function code. Function code 4 reads from the Holding Registers, of course.

The number of decimal points is the same issue where we divided by 100.0 in the C code. This device stores the temperature in Centigrade, but as an integer 100x than the actual value. For display you divide by 100, and automatically get two digits of precision.

Output will look like this:

$ sudo python temp.py
25.37
25.37
25.43
25.43
25.5
25.56
25.56
25.62

Reading the temperature using pymodbus in Python

The pymodbus library is far more comprehensive than minimalmodbus in that you can do a lot more with it. It does mean the library is harder to use.

Documentation: (pymodbus.readthedocs.io) https://pymodbus.readthedocs.io/en/latest/index.html

Home page: (riptideio.github.io) http://riptideio.github.io/pymodbus/

Installation

$ pip install  -U pymodbus

Type this code in as temp.py

#!/usr/bin/env python
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient(method='rtu', port='/dev/ttyUSB0', timeout=1, stopbits = 1, bytesize = 8,  parity='N', baudrate= 9600)
client.connect()

while True:
    rr = client.read_input_registers(address=1, count=1, unit=1);
    print rr.registers[0] / 100.0;

In this case you pass connection parameters to the object constructor as shown here. And then you simply call client.read_input_registers. If an error occurs, rr will be a None so be careful.

Output will look like this:

$ sudo python temp.py
26.37
26.43
26.43
26.5
26.56
26.56
26.56
26.62
26.62
26.62
« Hidden Cobra - North Korea's DDoS Botnet infrastructure Overview of a simple MODBUS/RTU RS-485 temperature sensor »
2016 Election Acer C720 Ad block AkashaCMS Amiga Android Anti-Fascism Apple Apple Hardware History Apple iPhone Hardware April 1st Arduino ARM Compilation Astronomy Asynchronous Programming Authoritarianism Automated Social Posting Bells Law Big Brother Black Holes Blade Runner Blogger Blogging Books Botnet Botnets Cassette Tapes Cellphones Christopher Eccleston Chrome Chrome Apps Chromebook Chromebooks Chromebox ChromeOS CIA CitiCards Civil Liberties Clinton Cluster Computing Command Line Tools Computer Hardware Computer Repair Computers Cross Compilation Crouton Curiosity Rover Cyber Security Cybermen Daleks Darth Vader Data backup Data Storage Database Database Backup Databases David Tenant DDoS Botnet Detect Adblocker Digital Photography DIY DIY Repair Docker Doctor Who Doctor Who Paradox Drobo Drupal Drupal Themes DVD Early Computers Election Hacks Electric Bicycles Electric Vehicles Electron Emdebian Enterprise Node ESP8266 Ethical Curation Eurovision Event Driven Asynchronous Express Facebook Fake News File transfer without iTunes FireFly Fraud Freedom of Speech Gallifrey git Gitlab GMAIL Google Google Chrome Google Gnome Google+ Government Spying Great Britain Home Automation HTTPS I2C Protocol Image Analysis Image Conversion Image Processing ImageMagick InfluxDB Internet Internet Advertising Internet Law Internet of Things Internet Policy Internet Privacy iOS Devices iPad iPhone iPhone hacking Iron Man Iternet of Things iTunes Java JavaScript JavaScript Injection JDBC John Simms Joyent Lets Encrypt LibreOffice Linux Linux Hints Linux Single Board Computers Logging Mac OS Mac OS X Make Money Online MariaDB Mars Matt Lucas MEADS Anti-Missile Mercurial Michele Gomez Military Hardware Minification Minimized CSS Minimized HTML Minimized JavaScript Missy Mobile Applications MODBUS Mondas Monty Python MQTT Music Player Music Streaming MySQL NanoPi Nardole NASA Net Neutrality Node Web Development Node.js Node.js Database Node.js Testing Node.JS Web Development Node.x North Korea Online advertising Online Fraud Open Media Vault Open Source Governance Open Source Software OpenAPI OpenVPN Personal Flight Peter Capaldi Photography PHP Plex Media Server Political Protest Postal Service Power Control Privacy Production use Public Violence Raspberry Pi Raspberry Pi 3 Raspberry Pi Zero Recycling Remote Desktop Republicans Retro-Technology Reviews Right to Repair River Song Rocket Ships RSS News Readers rsync Russia Russia Troll Factory Scheme Science Fiction Season 1 Season 10 Season 11 Security Security Cameras Server-side JavaScript Shell Scripts Silence Simsimi Skype Social Media Warfare Social Networks Software Development Space Flight Space Ship Reuse Space Ships SpaceX Spring Spring Boot SQLite3 SSD Drives SSD upgrade SSH SSH Key SSL Swagger Synchronizing Files Telescopes Terrorism The Cybermen The Daleks The Master Time-Series Database Torchwood Total Information Awareness Trump Trump Administration Ubuntu UDOO Virtual Private Networks VirtualBox VLC VNC VOIP Web Applications Web Developer Resources Web Development Web Development Tools Weeping Angels WhatsApp Wordpress YouTube