Modbus is a well known standard to communicate to devices. The modbus specification is free available from http://www.modbus.org
Different media is available:
Serial communication point to point via RS232
Multidrop bus RS485
TCP/IP Ethernet port 502 (and for non root users 1502. Non root users may or may not run in trouble with 502)
In simple terms, modbus has a server (slave) that has been polled by a master (client). The data exchanged can be either bits or 16bit wide words. Modbus supports the following data objects, that can exist multiple times in a device and therefore an address is used to select:
Coils are bit outputs, that probably traditionally controlled relays coils. Their value can be read back.
Discrete inputs are bit inputs
Input registers are 16 bit wide
Holding registers are 16 bit wide output registers that can be read back.
Inside the modbus protocol the address can be a value starting from 0. To know what is addressed the address plus the data object (Coils, Discrete Input, Input register or Holding register) must be specified as well.
Occasionally an additional number gets added to the address defining the data object. The following assignment results, where the first number is just a prefix telling how to access and does not take part of the modbus address:
| 0xxxx - Coils (outputs) can be read and written (bit oriented) |
| 1xxxx - Discrete inputs, ( input_status) can be read (bit oriented) |
| 3xxxx - Input registers (it is not a bug 3 and not 2 is the prefix), can be read (16bit oriented) |
| 4xxxx - Holding registers (output registers) can be read and written (16bit oriented) |
| A special feature is that those object addresses can be considered as be in a table and those tables could overlap, so writing a single bit might affect a single bit in a register. |
An other variation might exist, the lowest modbus address is 0, inside documentation of a device it might be 1. This is just a redefinition or mapping issue.
Commands are:
Write Single Coil (function code 0x05) means write an output bit, probably the first modbus implementations used relays to exchange data
Read Coils (function code 0x01) means read back the status of the output bits
Read Discrete Inputs (function code 0x02) means read the input bits
Data is exchanged in the following formats:
Protocol Data Unit (PDU) is the pure data free of addressing information
Application Data Unit (ADU) is PDU plus addressing data and checksum. In case of Modbus TCP/IP, no check sum is there since this is already very well handled by TCP/IP.
Within the PDU there are two data files:
The 8bit wide function Function code. Usually the function code is sent is echoed back. However on a failure, the most significant bit is set to indicated that the transmission of the data failed. This is called an exception.
Contains the data to be transmitted. It can be data to be sent or to be received or in case of failure, it can be an exception code that indicates the reason. Due to historic reasons the data can be maximum 253 byte, since the size of the RS232/485 ADU was set to 256 byte, on TCP/IP the ADU size can be up to 260 bytes.
The data transmission mode must be per default RTU (Remote Terminal Unit) that uses binary data to be more compact, as an option it can also be human readable ASCII.
The addressing within TCP/IP is:
| 2 Bytes Transaction number that is incremented by every transaction |
| 2 Bytes protocol header that is 0x0000 for modbus |
| 2 Bytes length of the following data |
| 1 Byte Unit identifier, to allow to route the modbus telegram within the TCP/IP computer to other devices attached to the computer (e.g. the TCP/IP computer could have many modbus RS485 nodes attached). Set to 0x00 or 0xff if not used |
Many things can go wrong so it is wise to play around with a command line tool until it is clear how to communicate.
mbpoll from https://github.com/epsilonrt/mbpoll is open source. If not available via package manager, it can be easily installed taking the debian package and extracting it. A single mbpoll file appears that can be run. All it needs is having libmodbus to be installed.
see mbpoll -h for all options
mbpoll -p1502 -a71 -r514 -c1 -0 -1 192.168.1.204 reads at the ip address 192.168.1.204 and its port 1502 (at unit id -a71) a unsigned value from one 16 bit register (-c1) at 514. -1 reads just once and does not poll. -0 says that first modbus register is at address 0 not 1. It can format the data as -t4:float
mbpoll -p1502 -a71 -r1042 -0 -1 192.168.1.204 20 -t4:float writes the value 20 as float to register 1042
It is also possible to write sequential registers of the same data type just add additional values mbpoll -p1502 -a71 -r1042 -0 -1 192.168.1.204 20 21 -t4:float
If the values are negative then the escape string -- must be put into the command mbpoll -p1502 -a71 -r1042 -0 -1 192.168.1.204 -- -20 -t4:float
Addresses can also be entered as hex number using the 0x prefix (-r0x2004)
modpoll from https://www.modbusdriver.com/modpoll.html is a free-ware single file binary. It is not open source.
see modpoll -h for all options
It can not handle Big Endians, so it is not recommended. Use mbpoll instead.
modpoll works for observing single 16bit register content.
Libmodbus is a library to get modbus support under Linux. First install libmodbus on the PC. The installation of the libraries does not necessarily install the sample code. Therefore get the archive (for Gentoo Linux /usr/portage/distfiles/libmodbus-) and copy/extract the libmodbus archive to a directory where you like to work. <n.m.o>.tar.gz
Goto the test directory where sample applications are. There read README. There are always two sample applications a client application that requests the connection and a server application that responses. Now compile
them all with make or individually as:
gcc random-test-server.c -o random-test-server `pkg-config --libs --cflags libmodbus`
If the pkg-config program does not find the desired files (as if the system administrator refuses to install libmodbus):
echo $PKG_CONFIG_PATH
The result shows if this environmental variable points to the files. If not, reinitialize the variable and export it (Without export the variable is set in the current process but not passed to the child process within later gcc is running):
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/
If the commands are put into a bash script then the script needs to be called as follows:
source ./<name of bash script>
Otherwise the variables are just exported to the process of the bash script but not to the console that calls the bash scripts. However the system administrator of the Linux computer should be fixed to set the environment variables correctly.
Running pkg-config --libs --cflags libmodbus will show -I/usr/include/modbus -lmodbus that needs to be told to gcc to find and use libmodbus.
To test you need to start first the server in a window and then the slave in an other window. The programs communicates just with the IP address 127.0.0.1 and therefore the data does not leave the computer.
ctx = modbus_new_tcp("127.0.0.1", 1502);
To communicate to an other device, the source code must be modified. Therefore check out the IP address and port and add it to the master code.
ctx = modbus_new_tcp("192.168.1.30", 502);
Now compile it gcc random-test-client.c -o random-test-client `pkg-config --libs --cflags libmodbus` and run it ./random-test-client Since it randomly reads, it try to read also from locations that do not exist and therefore lots of errors are produced.
For Python many implementations are available:
https://pythonhosted.org/pymodbus/ that supports now also python3 (therefore https://libraries.io/pypi/pymodbus3 is no more longer required)
The freemodbus package is more targeted to small cpu's not necessary running an operating system. See https://www.freemodbus.org/. There are sample demo projects for different microcontrollers as for the AVR family. However your specific AVR might not be directly supported, as the ATmega324P. To support it, you have to modify just the files in the subdirectory /port. Files to modify are:
port.h
portserial.c
Since the freemodbus packed is targeted to small microprocessors and such microprocessors do not have a huge amount of RAM, no modbus table is preset. Instead freemodbus allows to access the peripherals directly without the need to copy the data into RAM. The following functions are called to get and write the data:
eMBRegInputCB
eMBRegHoldingCB
eMBRegCoilsCB
eMBRegDiscrete
A parameter can be passed to the functions eMBRegCoilsCB and eMBRegCoilsCB to define if the function writes or reads.
Freemodbus supports RS485. With a #define RTS_ENABLE a pin of the microcontroller gets active that can control the direction of the RS485 driver chip (75176).
Support for modbus TCP/IP is there but this has a higher complexity since a TCP/IP stack is required.