Software apps and online services
I recently needed a Modbus RTU (Remote Terminal Unit) device to test some software against. Modbus is a serial communication command/response or master/slave protocol for connecting a master control device to various types of slave industrial devices known as Remote Terminal Units, using either point-to-point RS-232 asynchronous serial links, or point-to-multipoint RS-485 asynchronous serial buses.
After some research, I determined the shortest path to a working RTU device would be to make some very minor changes to the Linux demo RTU application included in the FreeMODBUS Modbus RTU library (hereafter FreeMODBUS). I ran the modified RTU demo app on a Raspberry Pi Zero running my own MuntsOS Embedded Linux, configured as a USB Serial Gadget device.
The FreeMODBUS architecture has a well-defined API (Application Programming Interface) between protocol handling code and user application code. The protocol handling code calls four service handler callback functions, one for each of the four Modbus data element types:
eMBRegDiscreteCBhandles requests for reading from Modbus discrete inputs.
eMBRegCoilsCBhandles requests for reading from and write to Modbus coil outputs.
eMBRegInputCBhandles requests for reading from Modbus (analog) input registers.
eMBRegHoldingCBhandles requests for reading from and writing to Modbus (analog output) holding registers.
The Linux demo RTU application runs the protocol handling code in a separate Posix thread spawned by the C main program. The C main program source file also contains implementations for the service handler callback functions.
This Make with Ada project illustrates how to implement the service handler callback functions in Ada, instead of C. It allowed me to quickly and easily leverage an enormous amount of Ada embedded systems code, from both my Linux Simple I/O Library (hereafter libsimpleio) and MuntsOS Embedded Linux (hereafter MuntsOS) repositories.
When a Linux microcomputer is configured as a USB Serial Gadget device, its USB port is configured for a very special form of serial port emulation. When you plug the device computer (e.g. a Raspberry Pi Zero) into a host computer (e.g. a Linux or Windows PC), the host computer will see its end of the USB cable as an ordinary USB serial port, named something like
/dev/ttyACM0 (Linux) or
com9: (Windows) and the device computer will see its end of the cable as serial port device
Often the host computer (Master) can supply power to the device computer (RTU) via the USB cable, too.
A Modbus RTU program running on the device computer opens serial port device
/dev/ttyGS0 and then listens for commands. A Modbus master program running on the host computer opens serial port e.g.
/dev/ttyACM0 to issue commands to and receive responses from the RTU program running on the device computer.
This MuntsOS application note (page 6) describes how to set up a Raspberry Pi Zero as a USB Serial Gadget device. Note that for this Make with Ada project, the
OPTIONS word in
config.txt must be changed to
0x7AC instead of
0x72D to prevent
mgetty (the serial console login manager in MuntsOS) from acquiring
Although the examples in this Make with Ada project use a Raspberry Pi Zero Wireless for the RTU, because of its low cost and low power consumption, other Linux microcomputers such as the BeagleBone Black, PocketBeagle, and other Raspberry Pi models can be used as well. Example #1 has been tested on a Raspberry Pi 4 Mode B.
make include file
freemodbus.mk defines rules that download the FreeMODBUS RTU library source code distribution tarball, unpack it, patch the source tree, and compile all of the C code required to build an RTU application.
freemodbus.mk must be included by each RTU project
The RTU application C main program has been adapted from the FreeMODBUS Linux example. It has been modified to call
adainit() to elaborate the Ada packages and then call
daemon() to run as a background process.
examples/ada/freemodbus/are two Ada package specifications:
- FreeMODBUS is a parent package that defines a few constants.
- FreeMODBUS.Services is a package specification for the callback functions that the FreeMODBUS library code will call to execute commands from a Modbus master device.
Each RTU project must contain two files customized for the particular project:
Providing Ada service callbacks to the FreeMODBUS C library is accomplished by using some special features of the
The following RTU project
make target extracted from the
Makefile for Example #1 shows how to combine a C main program, Ada packages, and
libsimpleio.so into a working program:
mkdir -p $(ADA_OBJ)
$(GNATMAKE) $(GNATMAKEFLAGS) -c freemodbus-services.adb -cargs $(GNATMAKECFLAGS)
$(GNATBIND) -n $(ADA_OBJ)/*.ali
$(GNATLINK) $(ADA_OBJ)/logging.ali $(MODBUS_SRC)/obj/*.o -o $@ -lpthread -lsimpleio
First the subordinate target
modbus_rtu_mk_freemodbus (defined in
freemodbus.mk) cross-compiles all of the FreeMODBUS C code as well as the C main program.
freemodbus-services.adb and its dependencies.
gnatbind -n indicates there will be no Ada main program. Instead,
gnatbind will append a subprogram called
adainit() to one of the
.ali files in the object directory. It is not obvious to me how
gnatbind selects which particular
.ali file, which will have to be explicitly provided to
gnatlink for the next step of the build process.
gnatlink links all of the objects and libraries into a single program file and
strip reduces the size of the program file by removing unnecessary debug information.
As part of this Make With Ada project, and in order to validate the RTU device framework, I also needed to write some Modbus master device support code in Ada. I accomplished this by adding the following six packages to libsimpleio (links are to the package specification files):
- libModbus is a straightforward Ada thin binding to the libmodbus Modbus master device library for Linux.
- Modbus is the parent package for Ada Modbus objects (i.e. classes). It defines a type
Busfor encapsulating the Modbus physical and transport layers.
- Modbus.DiscreteInputs defines a type
GPIO.PinInterface(part of libsimpleio) for Modbus discrete inputs.
- Modbus.Coils defines a type
GPIO.PinInterfacefor Modbus coil outputs.
- Modbus.InputRegisters defines a type
RegisterClass(and a corresponding access type
Register) for encapsulating Modbus 16-bit (analog) input registers.
- Modbus.HoldingRegisters defines a type
RegisterClass(and a corresponding access type
Register) for encapsulating Modbus 16-bit (analog output) holding registers.
The above packages fully support the four Modbus standard data element types, each of which has its own distinct address space. The libmodbus library also supports an additional semi-standard data element type, a 32-bit floating point value occupying two adjacent 16-bit (analog) input registers or two adjacent 16-bit (analog output) holding registers. I implemented support for 32-bit floating point registers by adding the following two additional generic packages to libsimpleio:
- Modbus.InputShortFloat must be instantiated with a floating point type (e.g.
Voltage.Voltsfrom libsimpleio) and defines a type
InputClassthat encapsulates 32-bit floating point registers in the (analog) input register address space.
- Modbus.OutputShortFloat must be instantiated with a floating point type and defines a type
OutputClassthan encapsulates 32-bit floating point registers in the (analog output) holding register address space.
This RTU example project maps Raspberry Pi GPIO pins to Modbus discrete inputs and coils. All GPIO pins are mapped into both the discrete input and coil address spaces. Writing to a coil will configure the corresponding GPIO pin as an output. Reading from a discrete input will configure the corresponding GPIO pin as an input. (Do not try to use the same GPIO pin for both!)
This Modbus master test program reads from a button connected to a Modbus discrete input and writes to an LED connected to a Modbus coil output.
I used a Raspberry Pi 4 running Raspbian for the Modbus master device, compiling the test program with the Debian native GNAT toolchain.
I used a Raspberry Pi Zero breadboard setup for the RTU hardware. The button is connected to GPIO 19 and the LED is connected to GPIO 26. The Raspberry Pi Zero Wireless is running MuntsOS. I compiled the RTU program with the GNAT cross-toolchain for MuntsOS.
This Modbus master test program toggles the relay once a second and then displays the states of the digital inputs and outputs as well as the analog input voltages.
I used a Raspberry Pi 4 running Raspbian for the Modbus master and a Raspberry Pi Zero Wireless running MuntsOS with an Automation pHAT for the RTU.
This Modbus master test program displays the analog voltages at
O2 once a second and then sets output
O1 to match input
O2 to match
IN2. Exception handlers prevent setting the outputs above 2.048V.
I used a Raspberry Pi 4 running Raspbian for the Modbus master and a Raspberry Pi Zero Wireless running MuntsOS with an ADC DAC Pi Zero for the RTU.