Library / SDK
alejoseb/Modbus-STM32-HAL-FreeRTOS avatar
alejoseb/Modbus-STM32-HAL-FreeRTOS

Modbus-STM32-HAL-FreeRTOS: a Modbus stack for STM32 that ports by drag and drop

Modbus TCP and RTU, Master and Slave for STM32 using Cube HAL and FreeRTOS

848 stars238 forksCLGPL-2.1

At a glance

What is it?
This library gives an STM32 running Cube HAL and FreeRTOS a Modbus RTU or TCP master or slave over USART, USB-CDC or TCP, with seventeen board examples, DMA support for fast baud rates and a choice of shared or per data type slave memory. It suits embedded developers who need Modbus in a product, and it is a poor fit if you need a Modbus server on a general purpose Linux box.
Who is it for?
This library suits an embedded developer who already works in STM32CubeIDE and needs Modbus RTU or TCP in a product, since the porting path is a folder import rather than a rewrite and the examples cover seventeen board and transport combinations including DMA at 2 Mbps.
Can I use it commercially?
Yes, with conditions. LGPL-2.1 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 63 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One Modbus implementation across USART, USB-CDC and TCP

The library is a TCP, USART and USB-CDC Modbus RTU master and slave implementation for STM32 microcontrollers, based on Cube HAL and FreeRTOS.

Three transport options in one codebase is the thing that sets it apart. Most Modbus code you find for a microcontroller handles one physical layer, and a product that needs both a wired RS485 link and a TCP link ends up with two stacks. Here the same handler API sits on top of USART, USB-CDC or TCP sockets, and the difference between an RTU frame over a serial line and over a USB virtual serial port is handled for you.

The heritage is explicit. This is a port of the Modbus library for Arduino by smarmengol, rewritten for the STM32 HAL and RTOS world. That explains both the shape of the API and the audience: the Arduino library was itself built for people wiring sensors and actuators to a microcontroller, not for a Linux gateway.

The scope of the hardware is wide but bounded. The README says it is portable to any STM32 MCU supported by ST Cube HAL, and portable to other microcontrollers, giving the Raspberry Pi Pico as the example and pointing to a separate project, Modbus-PI-Pico-FreeRTOS, which requires little engineering effort.

Both master and slave roles are supported, multiple instances can run concurrently in the same MCU, and the implementation is described as multithread-safe on FreeRTOS. The practical limit on concurrency is stated plainly: the number of available UART and USART ports on the MCU. The project is LGPL-2.1 licensed.

The library is four files you drag into a Cube project

The file structure is short, and that is the clearest statement of how the library is meant to be adopted.

bash
├── LICENSE
├── README.md
├── Examples
├── Script
    ├── *.ipynb --> various master and slave Jupyter notebooks for testing
├── MODBUS-LIB --> Library Folder

The MODBUS-LIB directory contains an Inc folder with Modbus.h, a Config folder with ModbusConfigTemplate.h described as the configuration template, and a Src folder with Modbus.c and UARTCallback.c. That is the whole port: header, implementation, and the two files you have to adapt to your board.

The Examples directory is the bigger part of the repository, with seventeen board and transport combinations. Each name states the board and the role, for example ModbusBluepill for the STM32F103C8 USART slave, ModbusBluepillUSB adding USB-CDC master and slave, ModbusF429DMA as the NUCLEO-F429ZI RTU DMA master and slave, and ModbusH743TCP and ModbusF429TCP for the TCP examples. There is also ModbusSTM32F4-discovery, described as STM32F4-discovery TouchGFX plus Modbus Master, and a video demo for that board with TouchGFX linked from the README.

The Script directory holds Jupyter notebooks for master and slave testing, and the README flags a new set of script examples that test the library based on Pymodbus. That pairing is worth a note: testing an embedded Modbus server against an established Python implementation from a notebook is a much better feedback loop than a terminal and a USB serial cable, and it is the kind of thing that separates a maintained library from a port that was uploaded once.

Porting to your own MCU is a checklist, and the NVIC priority is the trap

The README gives an explicit porting procedure, and the order of the steps matters because the interrupt priority one is easy to get wrong.

Create a new project in STM32CubeIDE for your MCU. Enable FreeRTOS CMSIS_V2 in the middleware section of Cube-MX. Configure a USART and activate the global interrupt, and if you are using the DMA mode for USART, configure the DMA requests for RX and TX. Then set the preemption priority of the USART interrupt to a lower priority, 5 or a higher number for a standard configuration, than your FreeRTOS scheduler, and this parameter is changed in the NVIC configuration pane.

After that, import the MODBUS-LIB folder using drag-and-drop from your host operating system into your STM32CubeIDE project, choose link folders and files when asked, update the include paths in the project properties to include the Inc folder, and create a ModbusConfig.h using the ModbusConfigTemplate.h and add it to your project in your include path. Finally, instantiate a new global modbusHandler_t and follow the examples in the repository.

The priority rule is the one that bites. If the USART interrupt preempts the FreeRTOS scheduler and then calls a FreeRTOS API from that handler, the result is undefined behaviour rather than a clean error, and the symptom tends to appear far from the cause. Five is the number given for a standard configuration, and a higher number means a lower priority in the NVIC scheme, which is the opposite of the intuition that a more important peripheral deserves a lower number.

There is one more note in that list. If your project uses the USART interrupt service for other purposes, you have to modify UARTCallback.c accordingly, because that file is where the callback lives and it is not a drop-in for an existing handler.

Interrupt mode stops at 115200 baud, DMA carries 2 Mbps

The notes and known issues section addresses the performance limit directly.

The standard interrupt mode for Modbus RTU USART is suitable for 115200 bps or lower baud rates. For higher baud rates, tested up to 2 Mbps, it is recommended to use the DMA mode, and the corresponding examples exist. DMA mode will require extra configurations for the DMA channels in the Cube HAL.

That is a real architectural constraint rather than a tuning knob. RTU framing depends on the gap between frames, so a slave has to detect the end of a request by timing, and at 115200 baud and above the per byte interrupt cost becomes significant enough to distort the timing that the protocol depends on. Moving the bytes into memory by DMA removes the CPU from the timing question, which is exactly why the library asks you to change transport when you change speed.

The practical planning rule is therefore: if your Modbus bus runs at the usual 9600 or 19200 baud, the interrupt examples are fine and much simpler. If you are going to 1 Mbit or 2 Mbit, you are in DMA territory, with the extra Cube HAL channel configuration that implies.

For reference, the full interrupt and DMA examples cover the boards you would expect: NUCLEO-F429ZI for RTU DMA master and slave, NUCLEO-L152RE for an RTU DMA slave, and the P-NUCLEO-WB55 as an RTU DMA slave with RS485.

Two slave memory models, and the new one is per data type

The library supports two memory models for the slave, and both are configured in main.c or the equivalent application file.

Option one is the shared memory model, described as legacy and backward compatible, where all function codes share one address space.

Option two is separate independent memory regions per data type, with configurable Modbus start addresses. The README marks this as new while stating that it remains fully backward compatible with the legacy shared memory model. The important consequence is that the same firmware can present a clean, separated register map to a master, with holding registers, input registers, coils and discrete inputs each starting where you choose, while a project that already relies on the old layout can stay on it.

For a product going to a field with a Modbus master that someone else maintains, that configurability is the difference between a register map that is documented once and a register map that has to be reverse engineered from the code. A master integrator can be told where each block begins, and the slave can be moved to a different address range later without breaking the contract.

A related change is worth knowing about for TCP deployments: a TCP slave, described as a server, with multi-client support and a configurable auto-aging algorithm for management of TCP connections. On a TCP transport, clients come and go in ways a serial bus does not, so aging out stale connections is not optional housekeeping.

Getting started is import, compile, debug

The examples are provided for STM32CubeIDE version 1.8.0, and the workflow the README describes is deliberately short.

Import the examples in the STM32Cube IDE from the system folder. Connect your NUCLEO board. Compile and start your debugging session. If you need to adjust the baud rate or any other parameter, use the Cube-MX assistant, which the README marks as recommended. If you change the USART port you need to enable the interrupts for the selected USART, and the README points at UARTCallback.c for more details.

Two things are worth calling out from that list. The first is that the Cube-MX assistant is the intended way to change parameters, which keeps the configuration inside the tool the firmware is generated from rather than in hand-edited files that a regeneration will overwrite. The second is the USART change trap: moving to a different serial port means the interrupts for that port have to be enabled, and forgetting that produces a link that never comes up with no obvious cause.

For running the examples, the Script directory with its Jupyter notebooks and the Pymodbus based test scripts gives you something to talk to the board with, and the traditional Chinese translation of the README in the repository root means part of the documentation is available in another language if that helps your team.

The project is hosted on GitHub with no published GitHub releases, and the last push to the repository was on 2026-08-26. So the version you adopt is a commit, not a tag, and you should record which one you took.

Three gaps the README documents and one it does not

The known issues section is short and specific, and it is worth reading in full before you rely on any of it.

The USB-CDC example supports only the Bluepill development board, and it has not been validated with other development boards. To use that example you need to activate USB-CDC in your ModbusConfig.h file.

The TCP examples have been validated with the NUCLEO F429ZI and H743ZI, and to use them you need to activate TCP in your ModbusConfig.h file as well. The validation scope is narrow, and the examples cover specific boards rather than the transport in general.

The third is the most interesting. The HAL implementation for LWIP TCP of the CubeMX generates code that might not work if the cable is not connected from the very beginning, and the README calls this a known issue that can be solved manually by changing the generated code, with a link to the ST community thread describing the fix. It also tells you where to see the manual change: check the TCP example for the NUCLEO F429, which includes the manual modifications.

That last one is a real operational trap, because the failure is a configuration dependent one rather than a compile error. A device that boots with the cable unplugged and never recovers is a field failure, and the fix lives in generated code that a CubeMX regeneration can silently undo.

The gap the README does not document is any statement of supported toolchain versions beyond the example IDE version, and no release history, so version pinning is on you.

Editorial conclusion

This library suits an embedded developer who already works in STM32CubeIDE and needs Modbus RTU or TCP in a product, since the porting path is a folder import rather than a rewrite and the examples cover seventeen board and transport combinations including DMA at 2 Mbps. It does not suit someone who wants a Modbus server on a host system, and it does not suit a product that must ship with a long term support commitment, since the repository has no GitHub releases at all and the last push was on 2026-08-26. Before you build on it, read the known issues section and note three boundaries: the standard interrupt mode is only suitable for 115200 bps or lower, the USB-CDC example is validated on the Bluepill board only, and the TCP examples are validated on the NUCLEO F429ZI and H743ZI, with the LWIP cable-connected-at-boot issue needing a manual code change.

Frequently asked questions

What does the Modbus-STM32-HAL-FreeRTOS library support?

It provides Modbus RTU and TCP master and slave for STM32 microcontrollers over USART, USB-CDC and TCP, based on Cube HAL and FreeRTOS. It is described as portable to any STM32 MCU supported by ST Cube HAL, and to other microcontrollers such as the Raspberry Pi Pico.

How do I add the Modbus library to my STM32 project?

Import the MODBUS-LIB folder into your STM32CubeIDE project with drag and drop, choose link folders and files, add the Inc folder to the include paths, create a ModbusConfig.h from ModbusConfigTemplate.h, and instantiate a global modbusHandler_t. You also need FreeRTOS CMSIS_V2 enabled and a USART with its global interrupt activated.

When do I need DMA mode for Modbus RTU?

The README states that the standard interrupt mode is suitable for 115200 bps or lower, and that for higher baud rates, tested up to 2 Mbps, the DMA mode is recommended. DMA mode requires extra configuration for the DMA channels in Cube HAL.

Which slave memory models does the library offer?

Two: a shared memory model described as legacy and backward compatible, and separate independent memory regions per data type with configurable Modbus start addresses, which is the newer option and remains backward compatible with the legacy layout. Both are configured in main.c.

What licence is the Modbus-STM32-HAL-FreeRTOS library under?

It is licensed under LGPL-2.1, with the LICENSE file in the repository root. The library itself is a port of the Modbus library for Arduino by smarmengol.

Official sources

  1. alejoseb/Modbus-STM32-HAL-FreeRTOS on GitHub
  2. Issues
  3. License: LGPL-2.1
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alejoseb-modbus-stm32-hal-freertos.svg)](https://hysenlabs.com/projects/alejoseb-modbus-stm32-hal-freertos)

Was this useful?