Configure RTU communication between two Arduinos
masterclient-rtu and server-rtu examples are designed for Modbus RTU communication between two Arduino boards. To connect them, use the TX0/RX0 serial pins on both devices.repository·master·Indexed 21 days ago
https://github.com/debevv/nanomodbusA compact, platform-agnostic C library for implementing Modbus RTU/TCP protocols, specifically designed for embedded systems and microcontrollers. It supports both client and server roles and provides examples for Arduino, Raspberry Pi Pico W (RP2040), and STM32 (Blackpill). The library is highly configurable for reduced code size via macros and requires user-implemented transport read/write functions for platform-specific data handling.
client-rtu and server-rtu examples are designed for Modbus RTU communication between two Arduino boards. To connect them, use the TX0/RX0 serial pins on both devices.Both server callbacks and platform functions can access arbitrary user data through their void* arg parameter. This is useful for passing context, such as a specific connection handle or a hardware peripheral pointer.
To set these values, use the following API methods:
nmbs_set_callbacks_arg (for server callbacks)nmbs_set_platform_arg (for platform functions)To build the STM32 port of nanoMODBUS, use CMake and Make (or Ninja) within a build directory.
mkdir build
cd build
cmake ..
make -j16Copy nanomodbus.c and nanomodbus.h directly into your application codebase.
You can use FetchContent to include nanoMODBUS in your CMake project:
FetchContent_Declare(
nanomodbus
GIT_REPOSITORY https://github.com/debevv/nanoMODBUS
GIT_TAG master # or the version you want
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(nanomodbus)
#...
add_executable(your_program source_codes)
target_link_libraries(your_program nanomodbus)To use nanoMODBUS in an Arduino project, you must manually include the library files in your sketch folder. Copy nanomodbus.c and nanomodbus.h directly into your Arduino sketch directory to make them available for compilation.
# Copy these files into your Arduino sketch folder:
nanomodbus.c
nanomodbus.hThis guide provides the hardware configuration and build instructions for running nanoMODBUS on an STM32F401CCUx (Blackpill) board.
To use nanoMODBUS with this hardware, the following peripherals are configured:
PA9: TX1PA10: RX1PB3: SCK1PB4: MISO1PB5: MOSI1PA15: NSS (Software select)The project is tested on macOS Sonoma (Apple Silicon) and Windows 10. The following tools are required:
arm-none-eabi-gcccmakeninjaopenocdCMake and cortex-debugIf you have arduino-cli installed, you can compile the provided Arduino examples from the top-level nanoMODBUS directory using the provided shell script. The compiled binaries will be placed in the build folder.
./examples/arduino/compile-examples.shTo use the nanoMODBUS RTU client example on a Raspberry Pi Pico W (RP2040), you must have the pico-sdk installed. Follow these steps to build the project:
PICO_SDK_PATH environment variable to your local pico-sdk directory.build directory.cmake and make to compile the project.To view the output from the device, use a serial terminal like minicom connected to /dev/ttyACM0 at a baud rate of 115200.
export PICO_SDK_PATH=/path/to/your/pico/sdk
mkdir -p build && cd build
cmake ..
make -j8You can reduce the library's footprint by defining specific macros before including the headers. This is particularly useful for resource-constrained microcontrollers.
NMBS_CLIENT_DISABLED: Disables all client code.NMBS_SERVER_DISABLED: Disables all server code.If NMBS_SERVER_DISABLED is not set, you can still disable individual function codes:
NMBS_SERVER_READ_COILS_DISABLEDNMBS_SERVER_READ_DISCRETE_INPUTS_DISABLEDNMBS_SERVER_READ_HOLDING_REGISTERS_DISABLEDNMBS_SERVER_READ_INPUT_REGISTERS_DISABLEDNMBS_SERVER_WRITE_SINGLE_COIL_DISABLEDNMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLEDNMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLEDNMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLEDNMBS_SERVER_READ_FILE_RECORD_DISABLEDNMBS_SERVER_WRITE_FILE_RECORD_DISABLEDNMBS_SERVER_READ_WRITE_REGISTERS_DISABLEDNMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLEDNMBS_STRERROR_DISABLED: Disables the code that converts nmbs_error to strings.NMBS_BITFIELD_MAX: Sets the size of the nmbs_bitfield type (default is 2000).NMBS_DEBUG: Enables debug prints for received and sent messages.To use nanoMODBUS as a client, follow these steps:
nmbs_platform_conf object with the transport type (NMBS_TRANSPORT_TCP), your custom read and write functions, and your connection handle as the arg.nmbs_client_create.nmbs_set_read_timeout.nmbs_read_holding_registers or nmbs_write_multiple_registers.Note: Byte-level timeouts are handled by your transport functions; nmbs_set_read_timeout sets the overall response timeout.
#include <stdio.h>
#include "nanomodbus.h"
#include "my_platform_stuff.h"
int main(int argc, char* argv[]) {
// Set up the TCP connection
void* conn = my_connect_tcp(argv[1], argv[2]);
if (!conn) {
fprintf(stderr, "Error connecting to server\n");
return 1;
}
// my_transport_read() and my_transport_write() are implemented by the user
nmbs_platform_conf platform_conf;
nmbs_platform_conf_create(&platform_conf);
platform_conf.transport = NMBS_TRANSPORT_TCP;
platform_conf.read = my_transport_read;
platform_conf.write = my_transport_write;
platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions
// Create the modbus client
nmbs_t nmbs;
nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
if (err != NMBS_ERROR_NONE) {
fprintf(stderr, "Error creating modbus client\n");
return 1;
}
// Set only the response timeout. Byte timeout will be handled by the TCP connection
nmbs_set_read_timeout(&nmbs, 1000);
// Write 2 holding registers at address 26
uint16_t w_regs[2] = {123, 124};
err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs);
if (err != NMBS_ERROR_NONE) {
fprintf(stderr, "Error writing register at address 26 - %s", nmbs_strerror(err));
return 1;
}
// Read 2 holding registers from address 26
uint16_t r_regs[2];
err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs);
if (err != NMBS_ERROR_NONE) {
fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", nmbs_strerror(err));
return 1;
}
// Close the TCP connection
my_disconnect(conn);
return 0;
}After flashing your RP2040, you can check the program prints by connecting to the serial port using minicom. Ensure you use the correct baud rate (115200) and device path (typically /dev/ttyACM0).
minicom -b 115200 -o -D /dev/ttyACM0nanoMODBUS is platform-agnostic and requires you to provide two platform-specific functions to handle data transport (e.g., over a serial port or TCP connection). These are passed via the nmbs_platform_conf structure.
int32_t read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
int32_t write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);count bytes are processed or byte_timeout_ms expires.byte_timeout_ms < 0: Infinite timeout.byte_timeout_ms == 0: Non-blocking; read/write once and return immediately.< 0 in case of an error.0 and count - 1 is treated as a timeout.