nanoMODBUS Documentation

repository·master·Indexed 21 days ago

https://github.com/debevv/nanomodbus

A 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.

Tokens
2.6K
Snippets
7
Records
12
Agent score
75%

What's inside nanoMODBUS

  1. Use user data in callbacks and platform functions

    master

    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)
  2. Install nanoMODBUS manually or via CMake

    master

    Manual Installation

    Copy nanomodbus.c and nanomodbus.h directly into your application codebase.

    CMake Integration

    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)
  3. Use nanoMODBUS with the Arduino IDE

    master

    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.h
  4. Port nanoMODBUS to STM32 (Blackpill)

    master

    This guide provides the hardware configuration and build instructions for running nanoMODBUS on an STM32F401CCUx (Blackpill) board.

    Target Hardware Configuration

    To use nanoMODBUS with this hardware, the following peripherals are configured:

    • USART1 (for Modbus RTU, with or without DMA):
      • PA9: TX1
      • PA10: RX1
    • SPI1 (for Modbus TCP via W5500, with DMA):
      • PB3: SCK1
      • PB4: MISO1
      • PB5: MOSI1
      • PA15: NSS (Software select)

    Toolchain Requirements

    The project is tested on macOS Sonoma (Apple Silicon) and Windows 10. The following tools are required:

    • arm-none-eabi-gcc
    • cmake
    • ninja
    • openocd
    • VS Code Extensions: CMake and cortex-debug
  5. Set up a Raspberry Pi Pico W (RP2040) RTU client

    master

    To 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:

    1. Set the PICO_SDK_PATH environment variable to your local pico-sdk directory.
    2. Create a build directory.
    3. Run 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 -j8
  6. Configure nanoMODBUS for reduced code size

    master

    You can reduce the library's footprint by defining specific macros before including the headers. This is particularly useful for resource-constrained microcontrollers.

    Disabling Client/Server Roles

    • NMBS_CLIENT_DISABLED: Disables all client code.
    • NMBS_SERVER_DISABLED: Disables all server code.

    Disabling Specific Server Callbacks

    If NMBS_SERVER_DISABLED is not set, you can still disable individual function codes:

    • NMBS_SERVER_READ_COILS_DISABLED
    • NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED
    • NMBS_SERVER_READ_HOLDING_REGISTERS_DISABLED
    • NMBS_SERVER_READ_INPUT_REGISTERS_DISABLED
    • NMBS_SERVER_WRITE_SINGLE_COIL_DISABLED
    • NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED
    • NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED
    • NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED
    • NMBS_SERVER_READ_FILE_RECORD_DISABLED
    • NMBS_SERVER_WRITE_FILE_RECORD_DISABLED
    • NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED
    • NMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLED

    Other Size Optimizations

    • NMBS_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).

    Debugging

    • NMBS_DEBUG: Enables debug prints for received and sent messages.
  7. How to use nanoMODBUS as a TCP client

    master

    To use nanoMODBUS as a client, follow these steps:

    1. Create a connection handle using your platform's networking stack.
    2. Configure an nmbs_platform_conf object with the transport type (NMBS_TRANSPORT_TCP), your custom read and write functions, and your connection handle as the arg.
    3. Create the client instance using nmbs_client_create.
    4. Set timeouts using nmbs_set_read_timeout.
    5. Call Modbus functions like 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;
    }
  8. Implement required transport read/write functions

    master

    nanoMODBUS 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.

    Function Signatures

    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);

    Implementation Requirements

    • Blocking Behavior: Both methods should block until either count bytes are processed or byte_timeout_ms expires.
    • Timeout Handling:
      • byte_timeout_ms < 0: Infinite timeout.
      • byte_timeout_ms == 0: Non-blocking; read/write once and return immediately.
    • Return Values:
      • Return the number of bytes actually read/written.
      • Return < 0 in case of an error.
      • A return value between 0 and count - 1 is treated as a timeout.
      • Any other value is treated as a transport error.