├── examples
├── win32
│ ├── vs2022
│ │ ├── .gitignore
│ │ ├── modbus_cli.vcxproj.user
│ │ ├── modbus_cli.vcxproj.filters
│ │ ├── modbus_cli.sln
│ │ └── modbus_cli.vcxproj
│ ├── comm.h
│ ├── modbus_cli.c
│ └── comm.c
├── stm32
│ ├── .gitignore
│ ├── bsp
│ │ └── blackpill
│ │ │ ├── blackpill.h
│ │ │ └── blackpill.c
│ ├── readme.md
│ ├── nmbs
│ │ ├── port.h
│ │ └── port.c
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── modbus_rtu.c
│ ├── modbus_tcp.c
│ ├── CMakeLists.txt
│ ├── FreeRTOSConfig.h
│ └── stm32f4xx_hal_conf.h
├── rp2040
│ ├── README.md
│ ├── CMakeLists.txt
│ └── rtu-client.c
├── arduino
│ ├── README.md
│ ├── compile-examples.sh
│ ├── client-rtu
│ │ └── client-rtu.ino
│ └── server-rtu
│ │ └── server-rtu.ino
└── linux
│ ├── client-tcp.c
│ ├── platform.h
│ └── server-tcp.c
├── .vscode
├── launch.json
├── extensions.json
└── settings.json
├── .gitignore
├── Doxyfile
├── .github
└── workflows
│ ├── clang-format-check.yml
│ ├── repository-traffic.yml
│ └── ci.yml
├── .clang-tidy
├── tests
├── server_disabled.c
├── client_disabled.c
├── multi_server_rtu.c
└── nanomodbus_tests.h
├── LICENSE
├── CMakeLists.txt
├── .clang-format
├── README.md
└── nanomodbus.h
/examples/win32/vs2022/.gitignore:
--------------------------------------------------------------------------------
1 | x64/
2 | Debug/
3 | .vs/
4 |
--------------------------------------------------------------------------------
/examples/stm32/.gitignore:
--------------------------------------------------------------------------------
1 | build/*
2 | Core/*
3 | Drivers/*
4 | .cproject
5 | .mxproject
6 | .project
7 | *.ld
8 | .vscode/*
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | // Use the CMake panel to launch/debug
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "llvm-vs-code-extensions.vscode-clangd",
4 | "ms-vscode.cmake-tools",
5 | "twxs.cmake",
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .cache
3 | compile_commands.json
4 | cmake-build*
5 | build*
6 | /tests/cmake-build*
7 | /tests/build*
8 | /examples/cmake-build*
9 | /examples/build*
10 | doxygen
11 |
--------------------------------------------------------------------------------
/Doxyfile:
--------------------------------------------------------------------------------
1 | PROJECT_NAME = nanoMODBUS
2 | INPUT = nanomodbus.c nanomodbus.h
3 | OUTPUT_DIRECTORY = doxygen
4 | OPTIMIZE_OUTPUT_FOR_C = YES
5 | GENERATE_LATEX = NO
6 |
--------------------------------------------------------------------------------
/examples/win32/vs2022/modbus_cli.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/stm32/bsp/blackpill/blackpill.h:
--------------------------------------------------------------------------------
1 | #ifndef BLACKPILL_H_
2 | #define BLACKPILL_H_
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include "stm32f4xx_hal.h"
9 |
10 | void BSP_Init(void);
11 |
12 | #ifdef __cplusplus
13 | }
14 | #endif
15 |
16 |
17 | #endif
--------------------------------------------------------------------------------
/examples/rp2040/README.md:
--------------------------------------------------------------------------------
1 | # Raspberry Pi Pico W (RP2040) RTU client
2 |
3 | ## Setup
4 |
5 | Install pico-sdk from https://github.com/raspberrypi/pico-sdk
6 |
7 | ``` sh
8 | export PICO_SDK_PATH=/path/to/your/pico/sdk
9 | mkdir -p build && cd build
10 | cmake ..
11 | make -j8
12 | ```
13 |
14 | ## Check prints
15 |
16 | ``` sh
17 | minicom -b 115200 -o -D /dev/ttyACM0
18 | ```
19 |
--------------------------------------------------------------------------------
/examples/win32/comm.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | // function prototypes
8 | bool SetLocalBaudRate(HANDLE hComm, DWORD baudrate);
9 | bool InitCommPort(HANDLE* hComm, int PortNumber);
10 | bool CloseCommPort(HANDLE* hComm);
11 | int32_t WriteToCommPort(HANDLE hComm, uint8_t* data_ptr, int data_length);
12 | int32_t ReadCommPort(HANDLE hComm, uint8_t* buf, uint16_t count, int32_t byte_timeout_ms);
--------------------------------------------------------------------------------
/.github/workflows/clang-format-check.yml:
--------------------------------------------------------------------------------
1 | name: clang-format check
2 | on: [workflow_dispatch, pull_request]
3 | jobs:
4 | formatting-check:
5 | name: Formatting check
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - name: Run clang-format style check for C/C++/Protobuf programs.
10 | uses: jidicula/clang-format-action@v4.10.1
11 | with:
12 | clang-format-version: '13'
13 | check-path: '.'
14 | include-regex: '^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$)))$'
15 |
--------------------------------------------------------------------------------
/examples/arduino/README.md:
--------------------------------------------------------------------------------
1 | ## Arduino examples
2 |
3 | This folder contains nanoMODBUS examples for Arduino.
4 | To build and load a sketch with the Arduino IDE, copy `nanomodbus.c` and `nanomodbus.h` inside its folder.
5 |
6 | `client-rtu` and `server-rtu` are meant to be used with two Arduinos connected via their TX0/RX0 serial pins.
7 |
8 | If you have [arduino-cli](https://github.com/arduino/arduino-cli) installed, you can run
9 | `examples/arduino/compile-examples.sh` from the top-level nanoMODBUS directory to compile the examples.
10 | They will be available in the `build` folder.
11 |
--------------------------------------------------------------------------------
/.clang-tidy:
--------------------------------------------------------------------------------
1 | ---
2 | Checks: >
3 | *,
4 | -altera-*,
5 | -bugprone-easily-swappable-parameters,
6 | -cppcoreguidelines-avoid-magic-numbers,
7 | -cppcoreguidelines-avoid-non-const-global-variables,
8 | -google-readability-braces-around-statements,
9 | -hicpp-braces-around-statements,
10 | -hicpp-signed-bitwise,
11 | -llvmlibc-restrict-system-libc-headers,
12 | -readability-braces-around-statements,
13 | -readability-magic-numbers,
14 | -readability-function-cognitive-complexity,
15 | -readability-identifier-length,
16 | ...
17 | WarningsAsErrors: '*'
18 | FormatStyle: none
19 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cmake.configureOnOpen": true,
3 | "cmake.configureArgs": [
4 | "-DBUILD_TESTS=ON",
5 | "-DBUILD_EXAMPLES=ON"
6 | ],
7 | "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json",
8 | "C_Cpp.intelliSenseEngine": "disabled",
9 | "clangd.path": "clangd",
10 | "editor.formatOnSave": true,
11 | "editor.rulers": [
12 | 120
13 | ],
14 | "[c]": {
15 | "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
16 | },
17 | "[cpp]": {
18 | "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
19 | },
20 | }
--------------------------------------------------------------------------------
/examples/arduino/compile-examples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/env bash
2 | set -e -o pipefail
3 |
4 | # Set default board
5 | BOARD=$1
6 | if [ -z "${BOARD}" ]
7 | then
8 | echo No board specified, using arduino:avr:uno
9 | BOARD=arduino:avr:uno
10 | fi
11 |
12 | # Set up example folders
13 | rm -rf build/arduino
14 | mkdir -p build/arduino
15 | cp -r examples/arduino build
16 | cp nanomodbus.h nanomodbus.c build/arduino/server-rtu/
17 | cp nanomodbus.h nanomodbus.c build/arduino/client-rtu/
18 |
19 | # Ensure ardunio-cli is up to date
20 | arduino-cli core update-index
21 | arduino-cli core install arduino:avr
22 |
23 | # Compile both examples
24 | arduino-cli compile --clean -b $BOARD --output-dir build/arduino -- build/arduino/server-rtu
25 | arduino-cli compile --clean -b $BOARD --output-dir build/arduino -- build/arduino/client-rtu
26 |
27 |
--------------------------------------------------------------------------------
/examples/stm32/readme.md:
--------------------------------------------------------------------------------
1 | # STM32 nanomodbus porting
2 |
3 | ## Target hardware
4 |
5 | 
6 |
7 | - Blackpill board
8 | - STM32F401CCUx
9 | - USART1 (with/without) DMA
10 | - PA9 : TX1
11 | - PA10 : RX1
12 | - SPI1 with DMA (connected to W5500)
13 | - PB3 : SCK1
14 | - PB4 : MISO1
15 | - PB5 : MOSI1
16 | - PA15 : NSS (Software select)
17 |
18 | ## Toolchain and environment
19 |
20 | Tested on Mac OS Sonoma(Apple Silicon) & Windows 10 but other os having same toolchain should have no problem.
21 |
22 | - arm-none-eabi-gcc
23 | - cmake
24 | - ninja
25 | - openocd
26 | - vscode
27 | - CMake
28 | - cortex-debug
29 |
30 | ## Building
31 |
32 | ```
33 | mkdir build
34 | cd build
35 | cmake ..
36 | make -j16
37 | ```
38 |
--------------------------------------------------------------------------------
/.github/workflows/repository-traffic.yml:
--------------------------------------------------------------------------------
1 | name: repository traffic
2 | on:
3 | workflow_dispatch:
4 |
5 | jobs:
6 | traffic:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
11 | - uses: actions/checkout@v2
12 | with:
13 | ref: "traffic"
14 |
15 | # Calculates traffic and clones and stores in CSV file
16 | - name: GitHub traffic
17 | uses: sangonzal/repository-traffic-action@v.0.1.6
18 | env:
19 | TRAFFIC_ACTION_TOKEN: ${{ secrets.TRAFFIC_ACTION_TOKEN }}
20 |
21 | # Commits files to repository
22 | - name: Commit data
23 | uses: EndBug/add-and-commit@v4
24 | with:
25 | author_name: debevv
26 | message: "GitHub traffic"
27 | add: "./traffic/*"
28 | ref: "traffic" # commits to branch "traffic"
29 |
--------------------------------------------------------------------------------
/examples/stm32/nmbs/port.h:
--------------------------------------------------------------------------------
1 | #ifndef NANOMODBUS_CONFIG_H
2 | #define NANOMODBUS_CONFIG_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | // Max size of coil and register area
9 | #define COIL_BUF_SIZE 1024
10 | #define REG_BUF_SIZE 2048
11 |
12 | // NanoModbus include
13 | #include "nanomodbus.h"
14 | #include "stm32f4xx_hal.h"
15 |
16 | #ifdef NMBS_TCP
17 | // modbus tcp
18 | #define MB_SOCKET 1
19 | #include
20 | #endif
21 |
22 | #ifdef NMBS_RTU
23 | // modbus rtu
24 | #define MB_UART huart1
25 | #define MB_UART_DMA 1
26 | #define MB_RX_BUF_SIZE 256
27 | extern UART_HandleTypeDef MB_UART;
28 | #endif
29 |
30 | typedef struct tNmbsServer {
31 | uint8_t id;
32 | uint8_t coils[COIL_BUF_SIZE];
33 | uint16_t regs[REG_BUF_SIZE];
34 | } nmbs_server_t;
35 |
36 | nmbs_error nmbs_server_init(nmbs_t* nmbs, nmbs_server_t* server);
37 | nmbs_error nmbs_client_init(nmbs_t* nmbs);
38 |
39 | #ifdef __cplusplus
40 | }
41 | #endif
42 |
43 | #endif
--------------------------------------------------------------------------------
/tests/server_disabled.c:
--------------------------------------------------------------------------------
1 | #include "nanomodbus.h"
2 |
3 | #define UNUSED_PARAM(x) ((x) = (x))
4 |
5 |
6 | int32_t read_empty(uint8_t* data, uint16_t count, int32_t timeout, void* arg) {
7 | UNUSED_PARAM(data);
8 | UNUSED_PARAM(count);
9 | UNUSED_PARAM(timeout);
10 | UNUSED_PARAM(arg);
11 | return 0;
12 | }
13 |
14 |
15 | int32_t write_empty(const uint8_t* data, uint16_t count, int32_t timeout, void* arg) {
16 | UNUSED_PARAM(data);
17 | UNUSED_PARAM(count);
18 | UNUSED_PARAM(timeout);
19 | UNUSED_PARAM(arg);
20 | return 0;
21 | }
22 |
23 |
24 | int main(int argc, char* argv[]) {
25 | UNUSED_PARAM(argc);
26 | UNUSED_PARAM(argv);
27 |
28 | nmbs_t nmbs;
29 |
30 | nmbs_platform_conf platform_conf_empty;
31 | nmbs_platform_conf_create(&platform_conf_empty);
32 | platform_conf_empty.transport = NMBS_TRANSPORT_TCP;
33 | platform_conf_empty.read = read_empty;
34 | platform_conf_empty.write = write_empty;
35 |
36 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf_empty);
37 | if (err != 0)
38 | return 1;
39 |
40 | return 0;
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Valerio De Benedetto (@debevv)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/client_disabled.c:
--------------------------------------------------------------------------------
1 | #include "nanomodbus.h"
2 |
3 | #define UNUSED_PARAM(x) ((x) = (x))
4 |
5 |
6 | int32_t read_empty(uint8_t* data, uint16_t count, int32_t timeout, void* arg) {
7 | UNUSED_PARAM(data);
8 | UNUSED_PARAM(count);
9 | UNUSED_PARAM(timeout);
10 | UNUSED_PARAM(arg);
11 | return 0;
12 | }
13 |
14 |
15 | int32_t write_empty(const uint8_t* data, uint16_t count, int32_t timeout, void* arg) {
16 | UNUSED_PARAM(data);
17 | UNUSED_PARAM(count);
18 | UNUSED_PARAM(timeout);
19 | UNUSED_PARAM(arg);
20 | return 0;
21 | }
22 |
23 |
24 | int main(int argc, char* argv[]) {
25 | UNUSED_PARAM(argc);
26 | UNUSED_PARAM(argv);
27 |
28 | nmbs_t nmbs;
29 |
30 |
31 | nmbs_platform_conf platform_conf_empty;
32 | nmbs_platform_conf_create(&platform_conf_empty);
33 | platform_conf_empty.transport = NMBS_TRANSPORT_TCP;
34 | platform_conf_empty.read = read_empty;
35 | platform_conf_empty.write = write_empty;
36 |
37 | nmbs_callbacks callbacks_empty;
38 | nmbs_callbacks_create(&callbacks_empty);
39 |
40 | nmbs_error err = nmbs_server_create(&nmbs, 1, &platform_conf_empty, &callbacks_empty);
41 | if (err != 0)
42 | return 1;
43 |
44 | return 0;
45 | }
46 |
--------------------------------------------------------------------------------
/examples/stm32/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug Microcontroller - OpenOCD",
9 | "cwd": "${workspaceFolder}",
10 | "type": "cortex-debug",
11 | "executable": "${command:cmake.launchTargetPath}", //or fixed file path: build/stm32h735g-dk-led.elf
12 | "request": "launch", //Use "attach" to connect to target w/o elf download
13 | "servertype": "openocd",
14 | "device": "STM32F401", //MCU used, ex. "STM32H735IG"
15 | "interface": "swd",
16 | "serialNumber": "", //Set ST-Link ID if you use multiple at the same time
17 | "runToEntryPoint": "main",
18 | "v1": false,
19 | "showDevDebugOutput": "both",
20 | "configFiles": [
21 | "interface/stlink.cfg",
22 | "target/stm32f4x.cfg"
23 | ],
24 | "liveWatch": {
25 | "enabled": true,
26 | "samplesPerSecond": 4
27 | }
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/examples/win32/vs2022/modbus_cli.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 |
29 |
30 | Header Files
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/win32/vs2022/modbus_cli.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.32916.344
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modbus_cli", "modbus_cli.vcxproj", "{CBA78147-81C5-4DED-B716-F6363421298B}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {CBA78147-81C5-4DED-B716-F6363421298B}.Debug|x64.ActiveCfg = Debug|x64
17 | {CBA78147-81C5-4DED-B716-F6363421298B}.Debug|x64.Build.0 = Debug|x64
18 | {CBA78147-81C5-4DED-B716-F6363421298B}.Debug|x86.ActiveCfg = Debug|Win32
19 | {CBA78147-81C5-4DED-B716-F6363421298B}.Debug|x86.Build.0 = Debug|Win32
20 | {CBA78147-81C5-4DED-B716-F6363421298B}.Release|x64.ActiveCfg = Release|x64
21 | {CBA78147-81C5-4DED-B716-F6363421298B}.Release|x64.Build.0 = Release|x64
22 | {CBA78147-81C5-4DED-B716-F6363421298B}.Release|x86.ActiveCfg = Release|Win32
23 | {CBA78147-81C5-4DED-B716-F6363421298B}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {8835A584-0C6B-42CA-B3F8-0CEAB23D83A7}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/examples/rp2040/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.12)
2 |
3 | # Pull in SDK (must be before project)
4 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
5 |
6 | project(p C CXX ASM)
7 | set(CMAKE_C_STANDARD 11)
8 | set(CMAKE_CXX_STANDARD 17)
9 |
10 | set(PICO_BOARD pico_w CACHE STRING "Board type")
11 |
12 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.2.0")
13 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.2.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
14 | endif ()
15 |
16 | # Initialize the SDK
17 | pico_sdk_init()
18 |
19 | add_compile_options(-Wall
20 | -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int
21 | -Wno-unused-function # we have some for the docs that aren't called
22 | )
23 |
24 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
25 | add_compile_options(-Wno-maybe-uninitialized)
26 | endif ()
27 |
28 | # Create an executable for the app example using the gathered and filtered sources
29 | add_executable(rp2040 ../../nanomodbus.c rtu-client.c)
30 |
31 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../..)
32 |
33 | # Link against the pico-sdk libraries as needed
34 | target_link_libraries(rp2040 pico_stdlib)
35 | # Link hardware libraries. More than one library can be added here separated by spaces.
36 | target_link_libraries(rp2040 hardware_rtc
37 | hardware_flash
38 | hardware_sync
39 | hardware_adc
40 | )
41 |
42 | target_include_directories(rp2040 PRIVATE
43 | ${CMAKE_CURRENT_LIST_DIR}
44 | ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
45 | )
46 |
47 | pico_enable_stdio_usb(rp2040 1)
48 | pico_enable_stdio_uart(rp2040 0)
49 |
50 | pico_add_extra_outputs(rp2040)
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.16)
2 | project(nanomodbus C)
3 |
4 | set(CMAKE_C_STANDARD 99)
5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wswitch-enum -Wcast-qual -Woverflow")
6 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g")
7 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -g0")
8 |
9 | include_directories(tests examples/linux .)
10 |
11 | # Define BUILD_SHARED_LIBS=ON to build a dynamic library instead
12 | add_library(nanomodbus nanomodbus.c)
13 | target_include_directories(nanomodbus PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
14 |
15 | if (BUILD_EXAMPLES)
16 | add_executable(client-tcp examples/linux/client-tcp.c)
17 | target_link_libraries(client-tcp nanomodbus)
18 | add_executable(server-tcp examples/linux/server-tcp.c)
19 | target_link_libraries(server-tcp nanomodbus)
20 | endif ()
21 |
22 | if (BUILD_TESTS)
23 | add_executable(nanomodbus_tests nanomodbus.c tests/nanomodbus_tests.c)
24 | target_link_libraries(nanomodbus_tests pthread)
25 |
26 | add_executable(server_disabled nanomodbus.c tests/server_disabled.c)
27 | target_compile_definitions(server_disabled PUBLIC NMBS_SERVER_DISABLED)
28 |
29 | add_executable(client_disabled nanomodbus.c tests/client_disabled.c)
30 | target_compile_definitions(client_disabled PUBLIC NMBS_CLIENT_DISABLED)
31 |
32 | add_executable(multi_server_rtu nanomodbus.c tests/multi_server_rtu.c)
33 | target_compile_definitions(multi_server_rtu PUBLIC NMBS_DEBUG)
34 | target_link_libraries(multi_server_rtu pthread)
35 |
36 | enable_testing()
37 | add_test(NAME test_general COMMAND $)
38 | add_test(NAME test_server_disabled COMMAND $)
39 | add_test(NAME test_client_disabled COMMAND $)
40 | add_test(NAME test_multi_server_rtu COMMAND $)
41 | endif ()
--------------------------------------------------------------------------------
/examples/stm32/modbus_rtu.c:
--------------------------------------------------------------------------------
1 | #include "blackpill/blackpill.h"
2 | #include "nanomodbus.h"
3 | #include "nmbs/port.h"
4 |
5 | #include "FreeRTOS.h"
6 | #include "task.h"
7 |
8 | #define TEST_SERVER 1
9 | #define TEST_CLIENT 0
10 |
11 | static void blink(void* args);
12 | static void modbus(void* args);
13 |
14 | uint32_t HAL_GetTick(void) {
15 | return xTaskGetTickCount();
16 | }
17 |
18 | int main(void) {
19 | BSP_Init();
20 |
21 | xTaskCreate(blink, "blink", 128, NULL, 4, NULL);
22 | xTaskCreate(modbus, "modbus", 128 * 16, NULL, 2, NULL);
23 |
24 | vTaskStartScheduler();
25 |
26 | for (;;) {}
27 | }
28 |
29 | static void blink(void* args) {
30 | for (;;) {
31 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
32 | vTaskDelay(500);
33 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
34 | vTaskDelay(500);
35 | }
36 | }
37 |
38 | nmbs_t nmbs;
39 | nmbs_server_t nmbs_server = {
40 | .id = 0x01,
41 | .coils =
42 | {
43 | 0,
44 | },
45 | .regs =
46 | {
47 | 0,
48 | },
49 | };
50 |
51 | static void modbus(void* args) {
52 | #if TEST_SERVER
53 | nmbs_server_init(&nmbs, &nmbs_server);
54 | #endif
55 |
56 | #if TEST_CLIENT
57 | uint8_t coils_test[32];
58 | uint16_t regs_test[32];
59 | nmbs_client_init(&nmbs);
60 | #endif
61 |
62 | for (;;) {
63 | #if TEST_SERVER
64 | nmbs_server_poll(&nmbs);
65 | taskYIELD();
66 | #endif
67 | #if TEST_CLIENT
68 | nmbs_set_destination_rtu_address(&nmbs, 0x01);
69 | nmbs_error status = nmbs_read_holding_registers(&nmbs, 0, 32, regs_test);
70 | status = nmbs_write_multiple_registers(&nmbs, 0, 32, regs_test);
71 | if (status != NMBS_ERROR_NONE) {
72 | while (true) {}
73 | }
74 | #endif
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: LLVM
2 | AccessModifierOffset: -4
3 | AlignAfterOpenBracket: Align
4 | AlignConsecutiveAssignments: false
5 | AlignOperands: Align
6 | AllowAllArgumentsOnNextLine: false
7 | AllowAllConstructorInitializersOnNextLine: false
8 | AllowAllParametersOfDeclarationOnNextLine: false
9 | AllowShortBlocksOnASingleLine: Empty
10 | AllowShortCaseLabelsOnASingleLine: false
11 | AllowShortFunctionsOnASingleLine: Empty
12 | AllowShortIfStatementsOnASingleLine: Never
13 | AllowShortLambdasOnASingleLine: All
14 | AllowShortLoopsOnASingleLine: false
15 | AlwaysBreakAfterReturnType: None
16 | AlwaysBreakTemplateDeclarations: Yes
17 | BreakBeforeBraces: Custom
18 | BraceWrapping:
19 | AfterCaseLabel: false
20 | AfterClass: false
21 | AfterControlStatement: Never
22 | AfterEnum: false
23 | AfterFunction: false
24 | AfterNamespace: false
25 | AfterUnion: false
26 | BeforeCatch: true
27 | BeforeElse: true
28 | IndentBraces: false
29 | SplitEmptyFunction: false
30 | SplitEmptyRecord: true
31 | BreakBeforeBinaryOperators: None
32 | BreakBeforeTernaryOperators: true
33 | BreakConstructorInitializers: BeforeColon
34 | BreakInheritanceList: BeforeColon
35 | ColumnLimit: 120
36 | CompactNamespaces: false
37 | ContinuationIndentWidth: 8
38 | IndentCaseLabels: true
39 | IndentPPDirectives: None
40 | IndentWidth: 4
41 | KeepEmptyLinesAtTheStartOfBlocks: true
42 | MaxEmptyLinesToKeep: 2
43 | NamespaceIndentation: All
44 | ObjCSpaceAfterProperty: false
45 | ObjCSpaceBeforeProtocolList: true
46 | PointerAlignment: Left
47 | ReflowComments: false
48 | SpaceAfterCStyleCast: true
49 | SpaceAfterLogicalNot: false
50 | SpaceAfterTemplateKeyword: false
51 | SpaceBeforeAssignmentOperators: true
52 | SpaceBeforeCpp11BracedList: false
53 | SpaceBeforeCtorInitializerColon: true
54 | SpaceBeforeInheritanceColon: true
55 | SpaceBeforeParens: ControlStatements
56 | SpaceBeforeRangeBasedForLoopColon: true
57 | SpaceInEmptyParentheses: false
58 | SpacesBeforeTrailingComments: 4
59 | SpacesInAngles: false
60 | SpacesInCStyleCastParentheses: false
61 | SpacesInContainerLiterals: false
62 | SpacesInParentheses: false
63 | SpacesInSquareBrackets: false
64 | TabWidth: 4
65 | UseTab: Never
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 | Build:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Clone repo
9 | uses: actions/checkout@v3
10 | - name: configure
11 | run: |
12 | cmake -S . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug
13 | - name: build
14 | run: |
15 | cmake --build build --config Debug
16 | - name: Compress Build Directory
17 | run: tar -czf build.tar.gz build/
18 | - name: Upload Build Artifact
19 | uses: actions/upload-artifact@v4
20 | with:
21 | name: build
22 | path: build.tar.gz
23 | Embedded:
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: Clone repo
27 | uses: actions/checkout@v3
28 | - name: Build Arduino examples
29 | run: |
30 | mkdir -p build
31 | pushd build
32 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
33 | popd
34 | export PATH="build/bin:$PATH"
35 | ./examples/arduino/compile-examples.sh
36 | - name: Install ARM dependencies
37 | run: |
38 | sudo apt update
39 | sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential
40 | - name: Build rp2040 examples
41 | run: |
42 | pushd examples/rp2040
43 | git clone --depth=1 https://github.com/raspberrypi/pico-sdk.git
44 | export PICO_SDK_PATH=$PWD/pico-sdk
45 | cmake -S . -B build -DPICO_SDK_PATH=$PWD/pico-sdk
46 | cmake --build build --config Debug
47 | popd
48 | - name: Build stm32 examples
49 | run: |
50 | pushd examples/stm32
51 | cmake -S . -B build
52 | cmake --build build --config Debug
53 | popd
54 | Test:
55 | runs-on: ubuntu-latest
56 | needs: Build # run after Build job
57 | steps:
58 | - uses: actions/checkout@v3
59 | - name: Download Build Directory
60 | uses: actions/download-artifact@v4
61 | with:
62 | name: build
63 | - name: Extract Build Directory
64 | run: |
65 | mkdir -p build
66 | tar -xzf build.tar.gz -C .
67 | - name: List Build Files
68 | run: ls -R .
69 | - name: test
70 | run: |
71 | cd build
72 | ctest
73 |
74 |
--------------------------------------------------------------------------------
/examples/win32/modbus_cli.c:
--------------------------------------------------------------------------------
1 | /*
2 | * This example application uses the win32 API to read a single modbus
3 | * register from a server.
4 | */
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "..\..\nanomodbus.h"
12 | #include "comm.h"
13 |
14 | #define NMBS_DEBUG 1
15 | #define RTU_SERVER_ADDRESS 1
16 |
17 | HANDLE hComm;
18 | int reg_to_read;
19 | int commPort_In;
20 |
21 | void parseCmdLine(int argc, char** argv) {
22 |
23 | if (argc > 1) {
24 | commPort_In = atoi(argv[1]);
25 | reg_to_read = atoi(argv[2]);
26 | }
27 |
28 | if (reg_to_read == 0 || commPort_In == 0) {
29 | printf("please specify both a comm port and a register to read\n");
30 | exit(0);
31 | }
32 | }
33 |
34 | int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
35 | return ReadCommPort(hComm, buf, count, byte_timeout_ms);
36 | }
37 |
38 | int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
39 | return WriteToCommPort(hComm, buf, count);
40 | }
41 |
42 | void onError(nmbs_error err) {
43 | printf("error: %d\n", err);
44 | exit(0);
45 | }
46 |
47 | void ReadRegister(uint16_t reg) {
48 |
49 | nmbs_platform_conf platform_conf;
50 | nmbs_platform_conf_create(&platform_conf);
51 | platform_conf.transport = NMBS_TRANSPORT_RTU;
52 | platform_conf.read = read_serial;
53 | platform_conf.write = write_serial;
54 |
55 | nmbs_t nmbs;
56 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
57 | if (err != NMBS_ERROR_NONE)
58 | onError(err);
59 |
60 | nmbs_set_read_timeout(&nmbs, 1000);
61 | nmbs_set_byte_timeout(&nmbs, 100);
62 |
63 | nmbs_set_destination_rtu_address(&nmbs, RTU_SERVER_ADDRESS);
64 |
65 | uint16_t r_regs[2];
66 | err = nmbs_read_holding_registers(&nmbs, reg, 1, r_regs);
67 | if (err != NMBS_ERROR_NONE)
68 | onError(err);
69 |
70 | printf("register %d is set to: %d\n", reg, r_regs[0]);
71 | }
72 |
73 | int main(int argc, char** argv) {
74 |
75 | printf("modbus_cli - CLI to read modbus registers\n");
76 | printf("Usage: modbus_cli comport register\n\n");
77 |
78 | parseCmdLine(argc, argv);
79 |
80 | if (!InitCommPort(&hComm, commPort_In)) {
81 | printf("error opening output comm port %d\n", commPort_In);
82 | exit(0);
83 | }
84 |
85 | ReadRegister(reg_to_read);
86 | }
--------------------------------------------------------------------------------
/examples/arduino/client-rtu/client-rtu.ino:
--------------------------------------------------------------------------------
1 | /*
2 | This example client application connects via RTU to a server and sends some requests to it.
3 | */
4 |
5 | #include "nanomodbus.h"
6 |
7 | // The server address
8 | #define RTU_SERVER_ADDRESS 1
9 |
10 |
11 | int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
12 | Serial.setTimeout(byte_timeout_ms);
13 | return Serial.readBytes(buf, count);
14 | }
15 |
16 |
17 | int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
18 | Serial.setTimeout(byte_timeout_ms);
19 | return Serial.write(buf, count);
20 | }
21 |
22 |
23 | void onError() {
24 | // Make the LED blink on error
25 | while (true) {
26 | digitalWrite(LED_BUILTIN, HIGH);
27 | delay(1000);
28 | digitalWrite(LED_BUILTIN, LOW);
29 | delay(1000);
30 | }
31 | }
32 |
33 |
34 | void setup() {
35 | pinMode (LED_BUILTIN, OUTPUT);
36 | digitalWrite(LED_BUILTIN, LOW);
37 |
38 | Serial.begin(9600);
39 | while (!Serial);
40 | }
41 |
42 |
43 | void loop() {
44 | nmbs_platform_conf platform_conf;
45 | nmbs_platform_conf_create(&platform_conf);
46 | platform_conf.transport = NMBS_TRANSPORT_RTU;
47 | platform_conf.read = read_serial;
48 | platform_conf.write = write_serial;
49 |
50 | nmbs_t nmbs;
51 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
52 | if (err != NMBS_ERROR_NONE)
53 | onError();
54 |
55 | nmbs_set_read_timeout(&nmbs, 1000);
56 | nmbs_set_byte_timeout(&nmbs, 100);
57 |
58 | nmbs_set_destination_rtu_address(&nmbs, RTU_SERVER_ADDRESS);
59 |
60 | // Write 2 coils from address 64
61 | nmbs_bitfield coils = {0};
62 | nmbs_bitfield_write(coils, 0, 1);
63 | nmbs_bitfield_write(coils, 1, 1);
64 | err = nmbs_write_multiple_coils(&nmbs, 64, 2, coils);
65 | if (err != NMBS_ERROR_NONE)
66 | onError();
67 |
68 | // Read 3 coils from address 64
69 | nmbs_bitfield_reset(coils); // Reset whole bitfield to zero
70 | err = nmbs_read_coils(&nmbs, 64, 3, coils);
71 | if (err != NMBS_ERROR_NONE)
72 | onError();
73 |
74 | // Write 2 holding registers at address 26
75 | uint16_t w_regs[2] = {123, 124};
76 | err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs);
77 | if (err != NMBS_ERROR_NONE)
78 | onError();
79 |
80 | // Read 2 holding registers from address 26
81 | uint16_t r_regs[2];
82 | err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs);
83 | if (err != NMBS_ERROR_NONE)
84 | onError();
85 |
86 | // On success, keep the led on
87 | digitalWrite(LED_BUILTIN, HIGH);
88 |
89 | // No need to destroy the nmbs instance, terminate the program
90 | exit(0);
91 | }
92 |
--------------------------------------------------------------------------------
/examples/stm32/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "cppbuild",
6 | "label": "Build project",
7 | "command": "cmake",
8 | "args": ["--build", "${command:cmake.buildDirectory}", "-j", "8"],
9 | "options": {
10 | "cwd": "${workspaceFolder}"
11 | },
12 | "problemMatcher": ["$gcc"],
13 | "group": {
14 | "kind": "build",
15 | "isDefault": true
16 | }
17 | },
18 | {
19 | "type": "shell",
20 | "label": "Re-build project",
21 | "command": "cmake",
22 | "args": ["--build", "${command:cmake.buildDirectory}", "--clean-first", "-v", "-j", "8"],
23 | "options": {
24 | "cwd": "${workspaceFolder}"
25 | },
26 | "problemMatcher": ["$gcc"],
27 | },
28 | {
29 | "type": "shell",
30 | "label": "Clean project",
31 | "command": "cmake",
32 | "args": ["--build", "${command:cmake.buildDirectory}", "--target", "clean"],
33 | "options": {
34 | "cwd": "${workspaceFolder}"
35 | },
36 | "problemMatcher": []
37 | },
38 | {
39 | "type": "shell",
40 | "label": "CubeProg: Flash project (SWD)",
41 | "command": "STM32_Programmer_CLI",
42 | "args": [
43 | "--connect",
44 | "port=swd",
45 | "--download", "${command:cmake.launchTargetPath}",
46 | "-hardRst", // Hardware reset - if rst pin is connected
47 | "-rst", // Software reset (backup)
48 | "--start" // Start execution
49 | ],
50 | "options": {
51 | "cwd": "${workspaceFolder}"
52 | },
53 | "problemMatcher": []
54 | },
55 | {
56 | "type": "shell",
57 | "label": "CubeProg: Flash project with defined serial number (SWD) - you must set serial number first",
58 | "command": "STM32_Programmer_CLI",
59 | "args": [
60 | "--connect",
61 | "port=swd",
62 | "sn=",
63 | "--download", "${command:cmake.launchTargetPath}",
64 | "-hardRst", // Hardware reset - if rst pin is connected
65 | "-rst", // Software reset (backup)
66 | "--start" // Start execution
67 | ],
68 | "options": {
69 | "cwd": "${workspaceFolder}"
70 | },
71 | "problemMatcher": []
72 | },
73 | {
74 | "type": "shell",
75 | "label": "CubeProg: List all available communication interfaces",
76 | "command": "STM32_Programmer_CLI",
77 | "args": [
78 | "--list",
79 | ],
80 | "options": {
81 | "cwd": "${workspaceFolder}"
82 | },
83 | "problemMatcher": []
84 | },
85 | ]
86 | }
--------------------------------------------------------------------------------
/examples/stm32/modbus_tcp.c:
--------------------------------------------------------------------------------
1 | #include "blackpill/blackpill.h"
2 | #include "wizchip.h"
3 |
4 | #include
5 | #include
6 |
7 | #include "FreeRTOS.h"
8 | #include "nanomodbus.h"
9 | #include "nmbs/port.h"
10 | #include "task.h"
11 | #include
12 |
13 | extern SPI_HandleTypeDef hspi1;
14 |
15 | wiz_NetInfo net_info = {
16 | .mac = {0xEA, 0x11, 0x22, 0x33, 0x44, 0xEA},
17 | .ip = {192, 168, 137, 100}, // You can find this ip if you set your ethernet port ip as 192.168.137.XXX
18 | .sn = {255, 255, 255, 0},
19 | .gw = {192, 168, 137, 1},
20 | .dns = {0, 0, 0, 0},
21 | }; // Network information.
22 |
23 | uint8_t transaction_buf_sizes[] = {2, 2, 2, 2, 2, 2, 2, 2}; // All 2kB buffer setting for each sockets
24 |
25 | #if USE_HAL_SPI_REGISTER_CALLBACKS == 0
26 |
27 | void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef* hspi) {
28 | wizchip_dma_rx_cplt((void*) hspi);
29 | }
30 |
31 | void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef* hspi) {
32 | wizchip_dma_tx_cplt((void*) hspi);
33 | }
34 |
35 | #endif
36 |
37 | uint32_t HAL_GetTick(void) {
38 | return xTaskGetTickCount();
39 | }
40 |
41 | static void blink(void* args);
42 | static void modbus(void* args);
43 |
44 | int main(void) {
45 | BSP_Init();
46 | wizchip_register_hal(&hspi1, (fHalSpiTransaction) HAL_SPI_Receive, (fHalSpiTransaction) HAL_SPI_Transmit,
47 | (fHalSpiTransactionDma) HAL_SPI_Receive_DMA, (fHalSpiTransactionDma) HAL_SPI_Transmit_DMA,
48 | GPIOA, GPIO_PIN_15, (fHalGpioWritePin) HAL_GPIO_WritePin);
49 |
50 | wizchip_init(transaction_buf_sizes, transaction_buf_sizes);
51 | setSHAR(net_info.mac);
52 | setSIPR(net_info.ip);
53 | setSUBR(net_info.sn);
54 | setGAR(net_info.gw);
55 |
56 | xTaskCreate(blink, "blink", 128, NULL, 4, NULL);
57 | xTaskCreate(modbus, "modbus", 128 * 16, NULL, 2, NULL);
58 |
59 | vTaskStartScheduler();
60 |
61 | while (true) {}
62 | }
63 |
64 | static void blink(void* args) {
65 | for (;;) {
66 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
67 | vTaskDelay(500);
68 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
69 | vTaskDelay(500);
70 | }
71 | }
72 |
73 | static nmbs_t nmbs;
74 | static nmbs_server_t nmbs_server = {
75 | .id = 0x01,
76 | .coils =
77 | {
78 | 0,
79 | },
80 | .regs =
81 | {
82 | 0,
83 | },
84 | };
85 | static void modbus(void* args) {
86 | int status;
87 |
88 | nmbs_server_init(&nmbs, &nmbs_server);
89 |
90 | for (;;) {
91 | switch ((status = getSn_SR(MB_SOCKET))) {
92 | case SOCK_ESTABLISHED:
93 | nmbs_server_poll(&nmbs);
94 | break;
95 |
96 | case SOCK_INIT:
97 | listen(MB_SOCKET);
98 | break;
99 |
100 | case SOCK_CLOSED:
101 | socket(MB_SOCKET, Sn_MR_TCP, 502, 0);
102 | break;
103 |
104 | case SOCK_CLOSE_WAIT:
105 | disconnect(MB_SOCKET);
106 | break;
107 |
108 | default:
109 | taskYIELD();
110 | break;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/examples/stm32/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.16)
2 | set(PROJ_NAME stm32-blackpill)
3 |
4 | include(FetchContent)
5 | FetchContent_Declare(stm32_cmake
6 | GIT_REPOSITORY https://github.com/ObKo/stm32-cmake
7 | GIT_TAG v2.1.0
8 | GIT_SHALLOW TRUE
9 | )
10 | FetchContent_Populate(stm32_cmake)
11 | set(CMAKE_TOOLCHAIN_FILE ${stm32_cmake_SOURCE_DIR}/cmake/stm32_gcc.cmake)
12 |
13 | project(${PROJ_NAME} CXX C ASM)
14 | set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
15 |
16 | stm32_fetch_cmsis(F4)
17 | stm32_fetch_hal(F4)
18 |
19 | find_package(CMSIS COMPONENTS STM32F401CC REQUIRED)
20 | find_package(HAL COMPONENTS STM32F4 REQUIRED)
21 |
22 | add_compile_options(
23 | -mcpu=cortex-m4
24 | -mfpu=fpv4-sp-d16
25 | -mfloat-abi=hard
26 | )
27 |
28 | set(CMAKE_INCLUDE_CURRENT_DIR ON)
29 | set(FREERTOS_HEAP "4")
30 | set(FREERTOS_PORT "GCC_ARM_CM4F")
31 |
32 | add_library(freertos_config INTERFACE)
33 | target_include_directories(freertos_config SYSTEM
34 | INTERFACE
35 | ${CMAKE_CURRENT_SOURCE_DIR}
36 | )
37 | FetchContent_Declare(freertos_kernel
38 | GIT_REPOSITORY https://github.com/FreeRTOS/FreeRTOS-Kernel.git
39 | GIT_TAG V11.1.0
40 | GIT_SHALLOW TRUE
41 | )
42 |
43 | FetchContent_MakeAvailable(freertos_kernel)
44 |
45 | set(WIZ_CHIP W5500)
46 |
47 | FetchContent_Declare(
48 | wizchip
49 | GIT_REPOSITORY https://github.com/donghoonpark/wizchip-cmake
50 | GIT_SHALLOW TRUE
51 | )
52 |
53 | FetchContent_MakeAvailable(wizchip)
54 |
55 | FetchContent_Declare(
56 | nanomodbus
57 | GIT_REPOSITORY https://github.com/debevv/nanoMODBUS
58 | GIT_TAG master
59 | GIT_SHALLOW TRUE
60 | )
61 |
62 | FetchContent_GetProperties(nanomodbus)
63 | if (NOT nanomodbus_POPULATED)
64 | FetchContent_Populate(nanomodbus)
65 | endif ()
66 |
67 | add_library(nanomodbus ${nanomodbus_SOURCE_DIR}/nanomodbus.c)
68 | target_include_directories(nanomodbus PUBLIC ${nanomodbus_SOURCE_DIR})
69 |
70 | # FetchContent_MakeAvailable(nanomodbus)
71 |
72 | set(TARGET_NAMES modbus_rtu modbus_tcp)
73 |
74 | foreach (TARGET_NAME ${TARGET_NAMES})
75 |
76 | add_executable(${TARGET_NAME}
77 | ${TARGET_NAME}.c
78 | bsp/blackpill/blackpill.c
79 | nmbs/port.c
80 | )
81 |
82 | target_include_directories(
83 | ${TARGET_NAME} PRIVATE
84 | ${CMAKE_CURRENT_SOURCE_DIR}/bsp
85 | ${CMAKE_CURRENT_SOURCE_DIR}/nmbs
86 | )
87 |
88 | if (${TARGET_NAME} STREQUAL "modbus_rtu")
89 | target_compile_definitions(
90 | ${TARGET_NAME} PRIVATE
91 | NMBS_RTU
92 | )
93 | elseif (${TARGET_NAME} STREQUAL "modbus_tcp")
94 | target_compile_definitions(
95 | ${TARGET_NAME} PRIVATE
96 | NMBS_TCP
97 | )
98 | endif ()
99 |
100 | target_link_libraries(
101 | ${TARGET_NAME}
102 | STM32::NoSys
103 | CMSIS::STM32::F401CC
104 | HAL::STM32::F4::CORTEX
105 | HAL::STM32::F4::RCC
106 | HAL::STM32::F4::PWR
107 | HAL::STM32::F4::GPIO
108 | HAL::STM32::F4::TIM
109 | HAL::STM32::F4::UART
110 | HAL::STM32::F4::USART
111 | HAL::STM32::F4::SPI
112 | HAL::STM32::F4::DMA
113 | wizchip
114 | freertos_kernel
115 | nanomodbus
116 | )
117 |
118 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
119 | COMMAND ${CMAKE_SIZE} $
120 | )
121 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
122 | COMMAND ${CMAKE_OBJCOPY} -O ihex $ ${TARGET_NAME}.hex
123 | )
124 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
125 | COMMAND ${CMAKE_OBJCOPY} -O binary $ ${TARGET_NAME}.bin
126 | )
127 |
128 | endforeach ()
--------------------------------------------------------------------------------
/examples/win32/comm.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | UINT WriteToCommPort(HANDLE hComm, uint8_t* data_ptr, int data_length) {
7 |
8 | int actual_length;
9 |
10 | PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR);
11 |
12 | bool Status = WriteFile(hComm, // Handle to the Serialport
13 | data_ptr, // Data to be written to the port
14 | data_length,
15 | &actual_length, // No of bytes written to the port
16 | NULL);
17 |
18 | if (!Status)
19 | printf("\nWriteFile() failed with error %d.\n", GetLastError());
20 |
21 | if (data_length != actual_length)
22 | printf("\nWriteFile() failed to write all bytes.\n");
23 |
24 | return data_length;
25 | }
26 |
27 | bool SetLocalBaudRate(HANDLE hComm, DWORD baudrate) {
28 | bool Status;
29 | DCB dcb = {0};
30 |
31 | // Setting the Parameters for the SerialPort
32 | // but first grab the current settings
33 | dcb.DCBlength = sizeof(dcb);
34 |
35 | Status = GetCommState(hComm, &dcb);
36 | if (!Status)
37 | return false;
38 |
39 | dcb.BaudRate = baudrate;
40 | dcb.ByteSize = 8; // ByteSize = 8
41 | dcb.StopBits = ONESTOPBIT; // StopBits = 1
42 | dcb.Parity = NOPARITY; // Parity = None
43 |
44 | Status = SetCommState(hComm, &dcb);
45 |
46 | return Status;
47 | }
48 |
49 | bool InitCommPort(HANDLE* hComm, int PortNumber) {
50 | uint8_t SerialBuffer[2048] = {0};
51 | COMMTIMEOUTS timeouts = {0};
52 | bool Status;
53 | char commName[50] = {0};
54 | int serialPort = 9;
55 |
56 | // special syntax needed for comm ports > 10, but syntax also works for comm ports < 10
57 | sprintf_s(commName, sizeof(commName), "\\\\.\\COM%d", PortNumber);
58 |
59 | // Open the serial com port
60 | *hComm = CreateFileA(commName,
61 | GENERIC_READ | GENERIC_WRITE, // Read/Write Access
62 | 0, // No Sharing, ports cant be shared
63 | NULL, // No Security
64 | OPEN_EXISTING, // Open existing port only
65 | 0,
66 | NULL); // Null for Comm Devices
67 |
68 | if (*hComm == INVALID_HANDLE_VALUE)
69 | return false;
70 |
71 | Status = SetLocalBaudRate(*hComm, 9600);
72 | if (!Status)
73 | return false;
74 |
75 | // setup the timeouts for the SerialPort
76 | // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
77 |
78 | timeouts.ReadIntervalTimeout = MAXDWORD;
79 | timeouts.ReadTotalTimeoutMultiplier = 0;
80 | timeouts.ReadTotalTimeoutConstant = 0;
81 |
82 | timeouts.WriteTotalTimeoutConstant = 0;
83 | timeouts.WriteTotalTimeoutMultiplier = 0;
84 |
85 | if (!SetCommTimeouts(*hComm, &timeouts))
86 | return false;
87 |
88 | Status = PurgeComm(*hComm, PURGE_RXCLEAR | PURGE_TXCLEAR); // clear the buffers before we start
89 | if (!Status)
90 | return false;
91 |
92 | return true;
93 | }
94 |
95 | bool CloseCommPort(HANDLE* hComm) {
96 | if (*hComm != INVALID_HANDLE_VALUE)
97 | CloseHandle(*hComm);
98 | else
99 | return false;
100 |
101 | return true;
102 | }
103 |
104 | int32_t ReadCommPort(HANDLE hComm, uint8_t* buf, uint16_t count, int32_t byte_timeout_ms) {
105 | int TotalBytesRead = 0;
106 | bool Status = false;
107 | ULONGLONG StartTime = 0;
108 | uint8_t b;
109 | int tmpByteCount;
110 |
111 | StartTime = GetTickCount64();
112 |
113 | do {
114 | // read one byte
115 | Status = ReadFile(hComm, &b, 1, &tmpByteCount, NULL);
116 |
117 | // can't read from port at all??
118 | if (!Status)
119 | return false;
120 |
121 | // put one byte into our buffer
122 | if (tmpByteCount == 1) {
123 | buf[TotalBytesRead++] = b;
124 | }
125 |
126 | // did we time out yet??
127 | if (GetTickCount64() - StartTime > byte_timeout_ms) {
128 | break;
129 | }
130 |
131 | } while (TotalBytesRead < count);
132 |
133 | return TotalBytesRead;
134 | }
135 |
--------------------------------------------------------------------------------
/examples/arduino/server-rtu/server-rtu.ino:
--------------------------------------------------------------------------------
1 | /*
2 | This example application sets up an RTU server and handles modbus requests
3 |
4 | This server supports the following function codes:
5 | FC 01 (0x01) Read Coils
6 | FC 03 (0x03) Read Holding Registers
7 | FC 15 (0x0F) Write Multiple Coils
8 | FC 16 (0x10) Write Multiple registers
9 | */
10 |
11 | #include "nanomodbus.h"
12 |
13 | // The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
14 | #define COILS_ADDR_MAX 100
15 | #define REGS_ADDR_MAX 32
16 |
17 | // Our RTU address
18 | #define RTU_SERVER_ADDRESS 1
19 |
20 | // A single nmbs_bitfield variable can keep 2000 coils
21 | nmbs_bitfield server_coils = {0};
22 | uint16_t server_registers[REGS_ADDR_MAX + 1] = {0};
23 |
24 |
25 | int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
26 | Serial.setTimeout(byte_timeout_ms);
27 | return Serial.readBytes(buf, count);
28 | }
29 |
30 |
31 | int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
32 | Serial.setTimeout(byte_timeout_ms);
33 | return Serial.write(buf, count);
34 | }
35 |
36 |
37 | void onError() {
38 | // Set the led ON on error
39 | while (true) {
40 | digitalWrite(LED_BUILTIN, HIGH);
41 | }
42 | }
43 |
44 |
45 | nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
46 | if (address + quantity > COILS_ADDR_MAX + 1)
47 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
48 |
49 | // Read our coils values into coils_out
50 | for (int i = 0; i < quantity; i++) {
51 | bool value = nmbs_bitfield_read(server_coils, address + i);
52 | nmbs_bitfield_write(coils_out, i, value);
53 | }
54 |
55 | return NMBS_ERROR_NONE;
56 | }
57 |
58 |
59 | nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
60 | void* arg) {
61 | if (address + quantity > COILS_ADDR_MAX + 1)
62 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
63 |
64 | // Write coils values to our server_coils
65 | for (int i = 0; i < quantity; i++) {
66 | nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils, i));
67 | }
68 |
69 | return NMBS_ERROR_NONE;
70 | }
71 |
72 |
73 | nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
74 | void* arg) {
75 | if (address + quantity > REGS_ADDR_MAX + 1)
76 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
77 |
78 | // Read our registers values into registers_out
79 | for (int i = 0; i < quantity; i++)
80 | registers_out[i] = server_registers[address + i];
81 |
82 | return NMBS_ERROR_NONE;
83 | }
84 |
85 |
86 | nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
87 | uint8_t unit_id, void* arg) {
88 | if (address + quantity > REGS_ADDR_MAX + 1)
89 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
90 |
91 | // Write registers values to our server_registers
92 | for (int i = 0; i < quantity; i++)
93 | server_registers[address + i] = registers[i];
94 |
95 | return NMBS_ERROR_NONE;
96 | }
97 |
98 |
99 | void setup() {
100 | pinMode(LED_BUILTIN, OUTPUT);
101 |
102 | Serial.begin(9600);
103 | while (!Serial)
104 | ;
105 | }
106 |
107 |
108 | void loop() {
109 | nmbs_platform_conf platform_conf;
110 | nmbs_platform_conf_create(&platform_conf);
111 | platform_conf.transport = NMBS_TRANSPORT_RTU;
112 | platform_conf.read = read_serial;
113 | platform_conf.write = write_serial;
114 | platform_conf.arg = NULL;
115 |
116 | nmbs_callbacks callbacks;
117 | nmbs_callbacks_create(&callbacks);
118 | callbacks.read_coils = handle_read_coils;
119 | callbacks.write_multiple_coils = handle_write_multiple_coils;
120 | callbacks.read_holding_registers = handler_read_holding_registers;
121 | callbacks.write_multiple_registers = handle_write_multiple_registers;
122 |
123 | // Create the modbus server
124 | nmbs_t nmbs;
125 | nmbs_error err = nmbs_server_create(&nmbs, RTU_SERVER_ADDRESS, &platform_conf, &callbacks);
126 | if (err != NMBS_ERROR_NONE) {
127 | onError();
128 | }
129 |
130 | nmbs_set_read_timeout(&nmbs, 1000);
131 | nmbs_set_byte_timeout(&nmbs, 100);
132 |
133 | bool led = false;
134 |
135 | while (true) {
136 | err = nmbs_server_poll(&nmbs);
137 | // This will probably never happen, since we don't return < 0 in our platform funcs
138 | if (err == NMBS_ERROR_TRANSPORT)
139 | break;
140 |
141 | digitalWrite(LED_BUILTIN, led);
142 | led = !led;
143 | }
144 |
145 | // No need to destroy the nmbs instance, terminate the program
146 | exit(0);
147 | }
148 |
--------------------------------------------------------------------------------
/examples/rp2040/rtu-client.c:
--------------------------------------------------------------------------------
1 | #include "hardware/gpio.h"
2 | #include "hardware/timer.h"
3 | #include "hardware/uart.h"
4 | #include "nanomodbus.h"
5 | #include "pico/stdlib.h"
6 | #include
7 |
8 | // Define UART pins and settings
9 | #define UART_ID uart0
10 | #define BAUD_RATE 19200
11 | #define DATA_BITS 8
12 | #define STOP_BITS 1
13 | #define PARITY UART_PARITY_ODD
14 |
15 | // We are using pins 0 and 1, but see the GPIO function select table in the
16 | // datasheet for information on which other pins can be used.
17 | #define UART_TX_PIN 0
18 | #define UART_RX_PIN 1
19 | #define UART_DE_PIN 2 // Data Enable pin for RS485
20 |
21 | #define PICO_LED_PIN 25 // Onboard LED pin for the Pico
22 |
23 | // The server address
24 | #define RTU_SERVER_ADDRESS 1
25 |
26 | // Function prototypes
27 | void onError();
28 | int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
29 | int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
30 |
31 | void onError() {
32 | // Make the LED blink on error
33 | const uint LED_PIN = PICO_LED_PIN;
34 | gpio_init(LED_PIN);
35 | gpio_set_dir(LED_PIN, GPIO_OUT);
36 | while (true) {
37 | gpio_put(LED_PIN, 1);
38 | sleep_ms(1000);
39 | gpio_put(LED_PIN, 0);
40 | sleep_ms(1000);
41 | }
42 | }
43 |
44 | int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
45 | uint64_t start_time = time_us_64();
46 | int32_t bytes_read = 0;
47 | uint64_t timeout_us = (uint64_t) byte_timeout_ms * 1000;
48 |
49 | while (time_us_64() - start_time < timeout_us && bytes_read < count) {
50 | if (uart_is_readable(UART_ID)) {
51 | buf[bytes_read++] = uart_getc(UART_ID);
52 | start_time = time_us_64(); // Reset start time after a successful read
53 | }
54 | }
55 |
56 | return bytes_read;
57 | }
58 |
59 | int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
60 | uart_write_blocking(UART_ID, buf, count);
61 | return count;
62 | }
63 |
64 | void pico_setup() {
65 | printf("Initializing UART...\n");
66 | // Initialize the UART
67 | uart_init(UART_ID, BAUD_RATE);
68 | gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
69 | gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
70 |
71 | // Actually, we want a different speed
72 | // The call will return the actual baud rate selected, which will be as close as
73 | // possible to that requested
74 | int __unused actual = uart_set_baudrate(UART_ID, BAUD_RATE);
75 |
76 | // Set our data format
77 | uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
78 |
79 | // Initialize the DE pin as output for RS485
80 | gpio_init(UART_DE_PIN);
81 | gpio_set_dir(UART_DE_PIN, GPIO_OUT);
82 | gpio_put(UART_DE_PIN, 0); // Set DE low to enable receiving initially
83 |
84 | // Initialize the onboard LED
85 | const uint LED_PIN = PICO_LED_PIN;
86 | gpio_init(LED_PIN);
87 | gpio_set_dir(LED_PIN, GPIO_OUT);
88 | gpio_put(LED_PIN, 0);
89 | }
90 |
91 | int main() {
92 | stdio_init_all();
93 | sleep_ms(5000); // Initial pause to catch prints
94 |
95 | printf("Starting Pico setup...\n");
96 | pico_setup();
97 |
98 | printf("Setting up Modbus platform configuration...\n");
99 | nmbs_platform_conf platform_conf;
100 | nmbs_platform_conf_create(&platform_conf);
101 | platform_conf.transport = NMBS_TRANSPORT_RTU;
102 | platform_conf.read = read_serial;
103 | platform_conf.write = write_serial;
104 |
105 | printf("Creating Modbus client...\n");
106 | nmbs_t nmbs;
107 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
108 | if (err != NMBS_ERROR_NONE) {
109 | printf("Error creating Modbus client: %d\n", err);
110 | onError();
111 | }
112 |
113 | printf("Setting Modbus timeouts...\n");
114 | nmbs_set_read_timeout(&nmbs, 1000);
115 | nmbs_set_byte_timeout(&nmbs, 100);
116 |
117 | printf("Setting Modbus destination RTU address...\n");
118 | nmbs_set_destination_rtu_address(&nmbs, RTU_SERVER_ADDRESS);
119 |
120 | printf("Writing 2 coils from address 64...\n");
121 | nmbs_bitfield coils = {0};
122 | nmbs_bitfield_write(coils, 0, 1);
123 | nmbs_bitfield_write(coils, 1, 1);
124 | err = nmbs_write_multiple_coils(&nmbs, 64, 2, coils);
125 | if (err != NMBS_ERROR_NONE) {
126 | printf("Error writing multiple coils: %d\n", err);
127 | onError();
128 | }
129 |
130 | printf("Reading 3 coils from address 64...\n");
131 | nmbs_bitfield_reset(coils); // Reset whole bitfield to zero
132 | err = nmbs_read_coils(&nmbs, 64, 3, coils);
133 | if (err != NMBS_ERROR_NONE) {
134 | printf("Error reading coils: %d\n", err);
135 | onError();
136 | }
137 |
138 | printf("Writing 2 holding registers at address 26...\n");
139 | uint16_t w_regs[2] = {123, 124};
140 | err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs);
141 | if (err != NMBS_ERROR_NONE) {
142 | printf("Error writing multiple registers: %d\n", err);
143 | onError();
144 | }
145 |
146 | printf("Reading 2 holding registers from address 26...\n");
147 | uint16_t r_regs[2];
148 | err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs);
149 | if (err != NMBS_ERROR_NONE) {
150 | printf("Error reading holding registers: %d\n", err);
151 | onError();
152 | }
153 |
154 | // On success, keep the LED on
155 | const uint LED_PIN = PICO_LED_PIN;
156 | gpio_put(LED_PIN, 1);
157 |
158 | printf("Modbus operations completed successfully.\n");
159 |
160 | return 0;
161 | }
162 |
--------------------------------------------------------------------------------
/tests/multi_server_rtu.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "nanomodbus.h"
8 |
9 | #define WIRE_SIZE 1024
10 | #define UNUSED_PARAM(x) ((x) = (x))
11 |
12 | uint32_t run = 1;
13 |
14 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
15 |
16 | nmbs_t server1 = {0};
17 | nmbs_t server2 = {0};
18 |
19 | uint32_t index_w = 0;
20 | uint32_t indices_r[3] = {0};
21 |
22 | uint8_t wire[WIRE_SIZE];
23 |
24 |
25 | int32_t read_wire(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
26 | while (1) {
27 | pthread_mutex_lock(&mutex);
28 |
29 | uint64_t index = (uint64_t) arg;
30 | uint32_t read = 0;
31 | for (int i = 0; i < count; i++) {
32 | if (indices_r[index] == index_w)
33 | break;
34 |
35 | indices_r[index] = (indices_r[index] + 1) % WIRE_SIZE;
36 | buf[i] = wire[indices_r[index]];
37 | read++;
38 | }
39 |
40 | pthread_mutex_unlock(&mutex);
41 |
42 | if (read != 0)
43 | return (int32_t) read;
44 |
45 | if (timeout_ms != 0)
46 | usleep(timeout_ms * 1000);
47 | else
48 | return 0;
49 |
50 | timeout_ms = 0;
51 | }
52 | }
53 |
54 | int32_t write_wire(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
55 | UNUSED_PARAM(timeout_ms);
56 | pthread_mutex_lock(&mutex);
57 |
58 | uint64_t index = (uint64_t) arg;
59 | uint32_t written = 0;
60 | for (int i = 0; i < count; i++) {
61 | index_w = (index_w + 1) % WIRE_SIZE;
62 | indices_r[index] = index_w;
63 | wire[index_w] = buf[i];
64 | written++;
65 | }
66 |
67 | pthread_mutex_unlock(&mutex);
68 |
69 | return (int32_t) written;
70 | }
71 |
72 | nmbs_error read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
73 | UNUSED_PARAM(arg);
74 | UNUSED_PARAM(unit_id);
75 | for (int i = 0; i < quantity; i++)
76 | nmbs_bitfield_write(coils_out, address + i, 1);
77 |
78 | return NMBS_ERROR_NONE;
79 | }
80 |
81 | void* poll_server1(void* arg) {
82 | UNUSED_PARAM(arg);
83 | while (run) {
84 | nmbs_server_poll(&server1);
85 | }
86 |
87 | return NULL;
88 | }
89 |
90 | void* poll_server2(void* arg) {
91 | UNUSED_PARAM(arg);
92 | while (run) {
93 | nmbs_server_poll(&server2);
94 | }
95 |
96 | return NULL;
97 | }
98 |
99 | int main(int argc, char* argv[]) {
100 | UNUSED_PARAM(argc);
101 | UNUSED_PARAM(argv);
102 |
103 | nmbs_platform_conf c_conf;
104 | nmbs_platform_conf_create(&c_conf);
105 | c_conf.arg = wire;
106 | c_conf.transport = NMBS_TRANSPORT_RTU;
107 | c_conf.read = read_wire;
108 | c_conf.write = write_wire;
109 | c_conf.arg = 0;
110 |
111 | nmbs_platform_conf s1_conf = c_conf;
112 | s1_conf.arg = (void*) 1;
113 |
114 | nmbs_platform_conf s2_conf = c_conf;
115 | s2_conf.arg = (void*) 2;
116 |
117 | nmbs_t client = {0};
118 | nmbs_error err = nmbs_client_create(&client, &c_conf);
119 | if (err != NMBS_ERROR_NONE) {
120 | fprintf(stderr, "Error creating modbus client\n");
121 | return 1;
122 | }
123 |
124 | nmbs_set_read_timeout(&client, 5000);
125 | nmbs_set_byte_timeout(&client, 100);
126 |
127 | nmbs_callbacks callbacks;
128 | nmbs_callbacks_create(&callbacks);
129 | callbacks.read_coils = read_coils;
130 |
131 | err = nmbs_server_create(&server1, 33, &s1_conf, &callbacks);
132 | if (err != NMBS_ERROR_NONE) {
133 | fprintf(stderr, "Error creating modbus server 1\n");
134 | return 1;
135 | }
136 |
137 | nmbs_set_read_timeout(&server1, 100);
138 | nmbs_set_byte_timeout(&server1, 100);
139 |
140 | err = nmbs_server_create(&server2, 99, &s2_conf, &callbacks);
141 | if (err != NMBS_ERROR_NONE) {
142 | fprintf(stderr, "Error creating modbus server 2\n");
143 | return 1;
144 | }
145 |
146 | nmbs_set_read_timeout(&server2, 100);
147 | nmbs_set_byte_timeout(&server2, 100);
148 |
149 | pthread_t thread1, thread2;
150 | int ret = pthread_create(&thread1, NULL, poll_server1, NULL);
151 | if (ret != 0) {
152 | fprintf(stderr, "Error creating thread 1\n");
153 | return 1;
154 | }
155 |
156 | ret = pthread_create(&thread2, NULL, poll_server2, NULL);
157 | if (ret != 0) {
158 | fprintf(stderr, "Error creating thread 2\n");
159 | return 1;
160 | }
161 |
162 | sleep(1);
163 |
164 | nmbs_bitfield coils;
165 | for (uint32_t c = 0; c < 10; c++) {
166 | nmbs_bitfield_write(coils, c, rand() % 1);
167 | }
168 |
169 | nmbs_set_destination_rtu_address(&client, 33);
170 |
171 | err = nmbs_write_multiple_coils(&client, 0, 10, coils);
172 | if (err != NMBS_ERROR_NONE) {
173 | fprintf(stderr, "Error writing coils to %d %s\n", 33, nmbs_strerror(err));
174 | }
175 |
176 | nmbs_bitfield coils_read;
177 | err = nmbs_read_coils(&client, 0, 10, coils_read);
178 | if (err != NMBS_ERROR_NONE) {
179 | fprintf(stderr, "Error reading coils from %d %s\n", 33, nmbs_strerror(err));
180 | }
181 |
182 | if (memcmp(coils, coils, sizeof(nmbs_bitfield)) != 0) {
183 | fprintf(stderr, "Coils mismatch from %d\n", 33);
184 | }
185 |
186 | for (uint32_t c = 0; c < 10; c++) {
187 | nmbs_bitfield_write(coils, c, rand() % 1);
188 | }
189 |
190 | nmbs_set_destination_rtu_address(&client, 99);
191 |
192 | err = nmbs_write_multiple_coils(&client, 0, 10, coils);
193 | if (err != NMBS_ERROR_NONE) {
194 | fprintf(stderr, "Error writing coils to %d %s\n", 99, nmbs_strerror(err));
195 | }
196 |
197 | err = nmbs_read_coils(&client, 0, 10, coils_read);
198 | if (err != NMBS_ERROR_NONE) {
199 | fprintf(stderr, "Error reading coils from %d %s\n", 99, nmbs_strerror(err));
200 | }
201 |
202 | if (memcmp(coils, coils, sizeof(nmbs_bitfield)) != 0) {
203 | fprintf(stderr, "Coils mismatch from %d\n", 99);
204 | }
205 |
206 | sleep(5);
207 |
208 | run = 0;
209 | pthread_join(thread1, NULL);
210 | pthread_join(thread2, NULL);
211 |
212 | return 0;
213 | }
214 |
--------------------------------------------------------------------------------
/examples/linux/client-tcp.c:
--------------------------------------------------------------------------------
1 | /*
2 | * This example application connects via TCP to a modbus server at the specified address and port, and sends some
3 | * modbus requests to it.
4 | *
5 | * Since the platform for this example is linux, the platform arg is used to pass (to the linux file descriptor
6 | * read/write functions) a pointer to the file descriptor of our TCP connection
7 | *
8 | */
9 |
10 | #include
11 |
12 | #include "nanomodbus.h"
13 | #include "platform.h"
14 |
15 | int main(int argc, char* argv[]) {
16 | if (argc < 3) {
17 | fprintf(stderr, "Usage: client-tcp [address] [port]\n");
18 | return 1;
19 | }
20 |
21 | // Set up the TCP connection
22 | void* conn = connect_tcp(argv[1], argv[2]);
23 | if (!conn) {
24 | fprintf(stderr, "Error connecting to server\n");
25 | return 1;
26 | }
27 |
28 | nmbs_platform_conf platform_conf;
29 | nmbs_platform_conf_create(&platform_conf);
30 | platform_conf.transport = NMBS_TRANSPORT_TCP;
31 | platform_conf.read = read_fd_linux;
32 | platform_conf.write = write_fd_linux;
33 | platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions
34 |
35 | // Create the modbus client
36 | nmbs_t nmbs;
37 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
38 | if (err != NMBS_ERROR_NONE) {
39 | fprintf(stderr, "Error creating modbus client\n");
40 | if (!nmbs_error_is_exception(err))
41 | return 1;
42 | }
43 |
44 | // Set only the response timeout. Byte timeout will be handled by the TCP connection
45 | nmbs_set_read_timeout(&nmbs, 1000);
46 |
47 | // Write 2 coils from address 64
48 | nmbs_bitfield coils = {0};
49 | nmbs_bitfield_write(coils, 0, 1);
50 | nmbs_bitfield_write(coils, 1, 1);
51 | err = nmbs_write_multiple_coils(&nmbs, 64, 2, coils);
52 | if (err != NMBS_ERROR_NONE) {
53 | fprintf(stderr, "Error writing coils at address 64 - %s\n", nmbs_strerror(err));
54 | if (!nmbs_error_is_exception(err))
55 | return 1;
56 | }
57 |
58 | // Read 3 coils from address 64
59 | nmbs_bitfield_reset(coils); // Reset whole bitfield to zero
60 | err = nmbs_read_coils(&nmbs, 64, 3, coils);
61 | if (err != NMBS_ERROR_NONE) {
62 | fprintf(stderr, "Error reading coils at address 64 - %s\n", nmbs_strerror(err));
63 | if (!nmbs_error_is_exception(err))
64 | return 1;
65 | }
66 | else {
67 | printf("Coil at address 64 value: %d\n", nmbs_bitfield_read(coils, 0));
68 | printf("Coil at address 65 value: %d\n", nmbs_bitfield_read(coils, 1));
69 | printf("Coil at address 66 value: %d\n", nmbs_bitfield_read(coils, 2));
70 | }
71 |
72 | // Write 2 holding registers at address 26
73 | uint16_t w_regs[2] = {123, 124};
74 | err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs);
75 | if (err != NMBS_ERROR_NONE) {
76 | fprintf(stderr, "Error writing register at address 26 - %s\n", nmbs_strerror(err));
77 | if (!nmbs_error_is_exception(err))
78 | return 1;
79 | }
80 |
81 | // Read 2 holding registers from address 26
82 | uint16_t r_regs[2];
83 | err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs);
84 | if (err != NMBS_ERROR_NONE) {
85 | fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", nmbs_strerror(err));
86 | if (!nmbs_error_is_exception(err))
87 | return 1;
88 | }
89 | else {
90 | printf("Register at address 26: %d\n", r_regs[0]);
91 | printf("Register at address 27: %d\n", r_regs[1]);
92 | }
93 |
94 | // Write file
95 | uint16_t file[4] = {0x0000, 0x00AA, 0x5500, 0xFFFF};
96 | err = nmbs_write_file_record(&nmbs, 1, 0, file, 4);
97 | if (err != NMBS_ERROR_NONE) {
98 | fprintf(stderr, "Error writing file - %s\n", nmbs_strerror(err));
99 | if (!nmbs_error_is_exception(err))
100 | return 1;
101 | }
102 | else {
103 | printf("Write file registers: 0x%04X 0x%04X 0x%04X 0x%04X\n", file[0], file[1], file[2], file[3]);
104 | }
105 |
106 | // Read file
107 | memset(file, 0, sizeof(file));
108 | err = nmbs_read_file_record(&nmbs, 1, 0, file, 4);
109 | if (err != NMBS_ERROR_NONE) {
110 | fprintf(stderr, "Error writing file - %s\n", nmbs_strerror(err));
111 | if (!nmbs_error_is_exception(err))
112 | return 1;
113 | }
114 | else {
115 | printf("Read file registers: 0x%04X 0x%04X 0x%04X 0x%04X\n", file[0], file[1], file[2], file[3]);
116 | }
117 |
118 | // Read basic device identification
119 | char vendor_name[128], product_code[128], major_minor_revision[128];
120 | err = nmbs_read_device_identification_basic(&nmbs, vendor_name, product_code, major_minor_revision, 128);
121 | if (err != NMBS_ERROR_NONE) {
122 | fprintf(stderr, "Error reading basic device identification - %s\n", nmbs_strerror(err));
123 | if (!nmbs_error_is_exception(err))
124 | return 1;
125 | }
126 | else {
127 | printf("Read basic device identification: %s %s %s\n", vendor_name, product_code, major_minor_revision);
128 | }
129 |
130 | // Read basic extended device identification
131 | char mem[8 * 128];
132 | char* buffers[8];
133 | for (int i = 0; i < 8; i++)
134 | buffers[i] = &mem[i * 128];
135 | uint8_t ids[8];
136 | uint8_t objects_count = 0;
137 | err = nmbs_read_device_identification_extended(&nmbs, 0x80, ids, buffers, 8, 128, &objects_count);
138 | if (err != NMBS_ERROR_NONE) {
139 | // If err == NMBS_INVALID_ARGUMENT, the length of the ids and buffers arrays (8 here) is not enough for all
140 | // the read objects from the server
141 | fprintf(stderr, "Error reading extended device identification - %s\n", nmbs_strerror(err));
142 | if (!nmbs_error_is_exception(err))
143 | return 1;
144 | }
145 | else {
146 | for (int i = 0; i < objects_count; i++)
147 | printf("Read extended device identification: ID 0x%02x value %s\n", ids[i], buffers[i]);
148 | }
149 |
150 | // Close the TCP connection
151 | disconnect(conn);
152 |
153 | // No need to destroy the nmbs instance, bye bye
154 | return 0;
155 | }
156 |
--------------------------------------------------------------------------------
/examples/linux/platform.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "nanomodbus.h"
16 |
17 |
18 | #define UNUSED_PARAM(x) ((x) = (x))
19 |
20 |
21 | // Connection management
22 |
23 | int client_connection = -1;
24 |
25 | void* connect_tcp(const char* address, const char* port) {
26 | struct addrinfo ainfo = {0};
27 | struct addrinfo* results;
28 | struct addrinfo* rp;
29 | int fd;
30 |
31 | ainfo.ai_family = AF_INET;
32 | ainfo.ai_socktype = SOCK_STREAM;
33 | int ret = getaddrinfo(address, port, &ainfo, &results);
34 | if (ret != 0)
35 | return NULL;
36 |
37 | for (rp = results; rp != NULL; rp = rp->ai_next) {
38 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
39 | if (fd == -1)
40 | continue;
41 |
42 | ret = connect(fd, rp->ai_addr, rp->ai_addrlen);
43 | if (ret == 0)
44 | break;
45 |
46 | close(fd);
47 | }
48 |
49 | freeaddrinfo(results);
50 |
51 | if (rp == NULL)
52 | return NULL;
53 |
54 | client_connection = fd;
55 | return &client_connection;
56 | }
57 |
58 |
59 | int server_fd = -1;
60 | int client_read_fd = -1;
61 | fd_set client_connections;
62 |
63 | void close_tcp_server() {
64 | if (server_fd != -1) {
65 | close(server_fd);
66 | printf("Server closed\n");
67 | }
68 | }
69 |
70 |
71 | int create_tcp_server(const char* address, const char* port) {
72 | struct addrinfo ainfo = {0};
73 | struct addrinfo* results;
74 | struct addrinfo* rp;
75 | int fd = -1;
76 |
77 | ainfo.ai_family = AF_INET;
78 | ainfo.ai_socktype = SOCK_STREAM;
79 | int ret = getaddrinfo(address, port, &ainfo, &results);
80 | if (ret != 0)
81 | return -1;
82 |
83 | for (rp = results; rp != NULL; rp = rp->ai_next) {
84 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
85 | if (fd == -1)
86 | continue;
87 |
88 | ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
89 | if (ret != 0)
90 | return -1;
91 |
92 | ret = bind(fd, rp->ai_addr, rp->ai_addrlen);
93 | if (ret != 0) {
94 | close(fd);
95 | fd = -1;
96 | continue;
97 | }
98 |
99 | ret = listen(fd, 1);
100 | if (ret != 0) {
101 | close(fd);
102 | fd = -1;
103 | continue;
104 | }
105 |
106 | break;
107 | }
108 |
109 | freeaddrinfo(results);
110 |
111 | if (fd < 0) {
112 | return errno;
113 | }
114 |
115 | server_fd = fd;
116 | FD_ZERO(&client_connections);
117 |
118 | return 0;
119 | }
120 |
121 |
122 | void* server_poll(void) {
123 | fd_set read_fd_set;
124 | FD_ZERO(&read_fd_set);
125 |
126 | while (true) {
127 | read_fd_set = client_connections;
128 | FD_SET(server_fd, &read_fd_set);
129 |
130 | int ret = select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL);
131 | if (ret < 0)
132 | return NULL;
133 |
134 | for (int i = 0; i < FD_SETSIZE; ++i) {
135 | if (FD_ISSET(i, &read_fd_set)) {
136 | if (i == server_fd) {
137 | struct sockaddr_in client_addr;
138 | socklen_t client_addr_size = sizeof(client_addr);
139 |
140 | int client = accept(server_fd, (struct sockaddr*) &client_addr, &client_addr_size);
141 | if (client < 0) {
142 | fprintf(stderr, "Error accepting client connection from %s - %s\n",
143 | inet_ntoa(client_addr.sin_addr), strerror(errno));
144 | continue;
145 | }
146 |
147 | FD_SET(client, &client_connections);
148 | printf("Accepted connection %d from %s\n", client, inet_ntoa(client_addr.sin_addr));
149 | }
150 | else {
151 | client_read_fd = i;
152 | return &client_read_fd;
153 | }
154 | }
155 | }
156 | }
157 | }
158 |
159 |
160 | void disconnect(void* conn) {
161 | int fd = *(int*) conn;
162 | FD_CLR(fd, &client_connections);
163 | close(fd);
164 | printf("Closed connection %d\n", fd);
165 | }
166 |
167 |
168 | // Read/write platform functions
169 |
170 | int32_t read_fd_linux(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
171 | if (!arg) {
172 | return -1;
173 | }
174 |
175 | int fd = *(int*) arg;
176 |
177 | uint16_t total = 0;
178 | while (total != count) {
179 | fd_set rfds;
180 | FD_ZERO(&rfds);
181 | FD_SET(fd, &rfds);
182 |
183 | struct timeval* tv_p = NULL;
184 | struct timeval tv;
185 | if (timeout_ms >= 0) {
186 | tv_p = &tv;
187 | tv.tv_sec = timeout_ms / 1000;
188 | tv.tv_usec = (int64_t) (timeout_ms % 1000) * 1000;
189 | }
190 |
191 | int ret = select(fd + 1, &rfds, NULL, NULL, tv_p);
192 | if (ret == 0) {
193 | return total;
194 | }
195 |
196 | if (ret == 1) {
197 | ssize_t r = read(fd, buf + total, 1);
198 | if (r == 0) {
199 | disconnect(arg);
200 | return 0;
201 | }
202 |
203 | if (r < 0)
204 | return -1;
205 |
206 | total += r;
207 | }
208 | else
209 | return -1;
210 | }
211 |
212 | return total;
213 | }
214 |
215 |
216 | int32_t write_fd_linux(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
217 | int fd = *(int*) arg;
218 |
219 | uint16_t total = 0;
220 | while (total != count) {
221 | fd_set wfds;
222 | FD_ZERO(&wfds);
223 | FD_SET(fd, &wfds);
224 |
225 | struct timeval* tv_p = NULL;
226 | struct timeval tv;
227 | if (timeout_ms >= 0) {
228 | tv_p = &tv;
229 | tv.tv_sec = timeout_ms / 1000;
230 | tv.tv_usec = (int64_t) (timeout_ms % 1000) * 1000;
231 | }
232 |
233 | int ret = select(fd + 1, NULL, &wfds, NULL, tv_p);
234 | if (ret == 0) {
235 | return 0;
236 | }
237 |
238 | if (ret == 1) {
239 | ssize_t w = write(fd, buf + total, count - total);
240 | if (w == 0) {
241 | disconnect(arg);
242 | return -1;
243 | }
244 |
245 | if (w <= 0)
246 | return -1;
247 |
248 | total += (int32_t) w;
249 | }
250 | else
251 | return -1;
252 | }
253 |
254 | return total;
255 | }
256 |
--------------------------------------------------------------------------------
/examples/stm32/FreeRTOSConfig.h:
--------------------------------------------------------------------------------
1 | /*
2 | * FreeRTOS Kernel V10.4.1
3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | * SOFTWARE.
22 | *
23 | * http://www.FreeRTOS.org
24 | * http://aws.amazon.com/freertos
25 | *
26 | * 1 tab == 4 spaces!
27 | */
28 |
29 | #ifndef FREERTOS_CONFIG_H
30 | #define FREERTOS_CONFIG_H
31 |
32 | /*-----------------------------------------------------------
33 | * Application specific definitions.
34 | *
35 | * These definitions should be adjusted for your particular hardware and
36 | * application requirements.
37 | *
38 | * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
39 | * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
40 | *
41 | * See http://www.freertos.org/a00110.html
42 | *----------------------------------------------------------*/
43 |
44 | #include
45 | extern uint32_t SystemCoreClock;
46 |
47 | #if defined STM32L5
48 | #define configENABLE_TRUSTZONE 0
49 | #if configENABLE_TRUSTZONE
50 | #define configMINIMAL_SECURE_STACK_SIZE ((uint16_t) 1024)
51 | #endif
52 | #define configRUN_FREERTOS_SECURE_ONLY 0
53 | #define configENABLE_FPU 1
54 | #define configENABLE_MPU 0
55 | #endif
56 |
57 | #define configUSE_PREEMPTION 1
58 | #define configUSE_IDLE_HOOK 0
59 | #define configUSE_TICK_HOOK 0
60 | #define configCPU_CLOCK_HZ (SystemCoreClock)
61 | #define configTICK_RATE_HZ ((TickType_t) 1000)
62 | #if !defined USE_CMSIS_RTOS_V2
63 | #define configMAX_PRIORITIES (5)
64 | #endif
65 | #define configMINIMAL_STACK_SIZE ((unsigned short) 50)
66 | #define configTOTAL_HEAP_SIZE (size_t)(32 * 1024)
67 | #define configMAX_TASK_NAME_LEN (10)
68 | #define configUSE_TRACE_FACILITY 1
69 | #define configUSE_16_BIT_TICKS 0
70 | #define configIDLE_SHOULD_YIELD 1
71 | #define configUSE_MUTEXES 1
72 | #define configQUEUE_REGISTRY_SIZE 8
73 | #define configCHECK_FOR_STACK_OVERFLOW 0
74 | #define configUSE_RECURSIVE_MUTEXES 1
75 | #define configUSE_MALLOC_FAILED_HOOK 0
76 | #define configUSE_APPLICATION_TASK_TAG 0
77 | #define configUSE_COUNTING_SEMAPHORES 1
78 | #define configGENERATE_RUN_TIME_STATS 0
79 |
80 | /* Co-routine definitions. */
81 | #define configUSE_CO_ROUTINES 0
82 | #define configMAX_CO_ROUTINE_PRIORITIES (2)
83 |
84 | /* Software timer definitions. */
85 | #define configUSE_TIMERS 1
86 | #define configTIMER_TASK_PRIORITY (2)
87 | #define configTIMER_QUEUE_LENGTH 10
88 | #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
89 |
90 | /* Set the following definitions to 1 to include the API function, or zero
91 | to exclude the API function. */
92 | #define INCLUDE_vTaskPrioritySet 1
93 | #define INCLUDE_uxTaskPriorityGet 1
94 | #define INCLUDE_vTaskDelete 1
95 | #define INCLUDE_vTaskCleanUpResources 1
96 | #define INCLUDE_vTaskSuspend 1
97 | #define INCLUDE_vTaskDelayUntil 1
98 | #define INCLUDE_vTaskDelay 1
99 |
100 | #if defined USE_CMSIS_RTOS_V2
101 |
102 | #ifndef CMSIS_RTOS_V2_DEVICE_HEADER
103 | #error "CMSIS device header needs to be passed by the build system"
104 | #endif
105 | #define CMSIS_device_header CMSIS_RTOS_V2_DEVICE_HEADER
106 |
107 | /* Needed for CMSIS RTOS_V2 */
108 | #define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
109 | #define configMAX_PRIORITIES 56
110 |
111 | #define INCLUDE_xSemaphoreGetMutexHolder 1
112 | #define INCLUDE_xTaskGetCurrentTaskHandle 1
113 | #define INCLUDE_xTaskGetSchedulerState 1
114 | #define INCLUDE_uxTaskGetStackHighWaterMark 1
115 | #define INCLUDE_eTaskGetState 1
116 | #define INCLUDE_xTimerPendFunctionCall 1
117 |
118 | #endif
119 |
120 | /* Cortex-M specific definitions. */
121 | #ifdef __NVIC_PRIO_BITS
122 | /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
123 | #define configPRIO_BITS __NVIC_PRIO_BITS
124 | #else
125 | #define configPRIO_BITS 4 /* 15 priority levels */
126 | #endif
127 |
128 | /* The lowest interrupt priority that can be used in a call to a "set priority"
129 | function. */
130 | #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
131 |
132 | /* The highest interrupt priority that can be used by any interrupt service
133 | routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
134 | INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
135 | PRIORITY THAN THIS! (higher priorities are lower numeric values. */
136 | #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
137 |
138 | /* Interrupt priorities used by the kernel port layer itself. These are generic
139 | to all Cortex-M ports, and do not rely on any particular library functions. */
140 | #define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
141 | /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
142 | See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
143 | #define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
144 |
145 | /* Normal assert() semantics without relying on the provision of an assert.h
146 | header file. */
147 | #define configASSERT(x) \
148 | if ((x) == 0) { \
149 | taskDISABLE_INTERRUPTS(); \
150 | for (;;) \
151 | ; \
152 | }
153 |
154 | /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
155 | standard names. */
156 | #define vPortSVCHandler SVC_Handler
157 | #define xPortPendSVHandler PendSV_Handler
158 |
159 | /* When using CMSIS RTOS V2, this define causes a multiple definition error */
160 | #if !defined USE_CMSIS_RTOS_V2
161 | #define xPortSysTickHandler SysTick_Handler
162 | #endif
163 |
164 | #endif /* FREERTOS_CONFIG_H */
165 |
--------------------------------------------------------------------------------
/examples/win32/vs2022/modbus_cli.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 16.0
31 | Win32Proj
32 | {cba78147-81c5-4ded-b716-f6363421298b}
33 | modbuscli
34 | 10.0
35 |
36 |
37 |
38 | Application
39 | true
40 | v143
41 | Unicode
42 |
43 |
44 | Application
45 | false
46 | v143
47 | true
48 | Unicode
49 |
50 |
51 | Application
52 | true
53 | v143
54 | Unicode
55 |
56 |
57 | Application
58 | false
59 | v143
60 | true
61 | Unicode
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | Level3
84 | true
85 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
86 | true
87 |
88 |
89 | Console
90 | true
91 |
92 |
93 |
94 |
95 | Level3
96 | true
97 | true
98 | true
99 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
100 | true
101 |
102 |
103 | Console
104 | true
105 | true
106 | true
107 |
108 |
109 |
110 |
111 | Level3
112 | true
113 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
114 | true
115 | false
116 |
117 |
118 | Console
119 | true
120 |
121 |
122 |
123 |
124 | Level3
125 | true
126 | true
127 | true
128 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
129 | true
130 |
131 |
132 | Console
133 | true
134 | true
135 | true
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nanoMODBUS - A compact MODBUS RTU/TCP C library for embedded/microcontrollers
2 |
3 | **If you found this library useful, buy me a coffee on**
4 | [
](https://ko-fi.com/B0B2LK779)
5 |
6 | nanoMODBUS is a small C library that implements the Modbus protocol. It is especially useful in embedded and
7 | resource-constrained systems like microcontrollers.
8 | Its main features are:
9 |
10 | - Compact size
11 | - Only ~2000 lines of code
12 | - Client and server code can be disabled, if not needed
13 | - No dynamic memory allocations
14 | - Transports:
15 | - RTU
16 | - TCP
17 | - Roles:
18 | - Client
19 | - Server
20 | - Function codes:
21 | - 01 (0x01) Read Coils
22 | - 02 (0x02) Read Discrete Inputs
23 | - 03 (0x03) Read Holding Registers
24 | - 04 (0x04) Read Input Registers
25 | - 05 (0x05) Write Single Coil
26 | - 06 (0x06) Write Single Register
27 | - 15 (0x0F) Write Multiple Coils
28 | - 16 (0x10) Write Multiple registers
29 | - 20 (0x14) Read File Record
30 | - 21 (0x15) Write File Record
31 | - 23 (0x17) Read/Write Multiple registers
32 | - 43/14 (0x2B/0x0E) Read Device Identification
33 | - Platform-agnostic
34 | - Requires only C99 and its standard library
35 | - Data transport read/write functions are implemented by the user
36 | - User-definable CRC function for better performance
37 | - Broadcast requests and responses
38 |
39 | ## At a glance
40 |
41 | ```C
42 | #include
43 |
44 | #include "nanomodbus.h"
45 | #include "my_platform_stuff.h"
46 |
47 | int main(int argc, char* argv[]) {
48 | // Set up the TCP connection
49 | void* conn = my_connect_tcp(argv[1], argv[2]);
50 | if (!conn) {
51 | fprintf(stderr, "Error connecting to server\n");
52 | return 1;
53 | }
54 |
55 | // my_transport_read() and my_transport_write() are implemented by the user
56 | nmbs_platform_conf platform_conf;
57 | nmbs_platform_conf_create(&platform_conf);
58 | platform_conf.transport = NMBS_TRANSPORT_TCP;
59 | platform_conf.read = my_transport_read;
60 | platform_conf.write = my_transport_write;
61 | platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions
62 |
63 | // Create the modbus client
64 | nmbs_t nmbs;
65 | nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
66 | if (err != NMBS_ERROR_NONE) {
67 | fprintf(stderr, "Error creating modbus client\n");
68 | return 1;
69 | }
70 |
71 | // Set only the response timeout. Byte timeout will be handled by the TCP connection
72 | nmbs_set_read_timeout(&nmbs, 1000);
73 |
74 | // Write 2 holding registers at address 26
75 | uint16_t w_regs[2] = {123, 124};
76 | err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs);
77 | if (err != NMBS_ERROR_NONE) {
78 | fprintf(stderr, "Error writing register at address 26 - %s", nmbs_strerror(err));
79 | return 1;
80 | }
81 |
82 | // Read 2 holding registers from address 26
83 | uint16_t r_regs[2];
84 | err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs);
85 | if (err != NMBS_ERROR_NONE) {
86 | fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", nmbs_strerror(err));
87 | return 1;
88 | }
89 |
90 | // Close the TCP connection
91 | my_disconnect(conn);
92 |
93 | return 0;
94 | }
95 | ```
96 |
97 | ## Installation
98 |
99 | ### Manual
100 |
101 | Just copy `nanomodbus.c` and `nanomodbus.h` inside your application codebase.
102 |
103 | ### CMake project
104 |
105 | nanomodbus supports library linking by using CMake.
106 |
107 | ```cmake
108 | FetchContent_Declare(
109 | nanomodbus
110 | GIT_REPOSITORY https://github.com/debevv/nanoMODBUS
111 | GIT_TAG master # or the version you want
112 | GIT_SHALLOW TRUE
113 | )
114 |
115 | FetchContent_MakeAvailable(nanomodbus)
116 |
117 | #...
118 |
119 | add_executable(your_program source_codes)
120 | target_link_libraries(your_program nanomodbus)
121 | ```
122 |
123 | ## API reference
124 |
125 | API reference is available in the repository's [GitHub Pages](https://debevv.github.io/nanoMODBUS/nanomodbus_8h.html).
126 |
127 | ## Platform functions
128 |
129 | nanoMODBUS requires the implementation of 2 platform-specific functions, defined as function pointers when creating a
130 | client/server instance.
131 |
132 | ### Transport read/write
133 |
134 | ```C
135 | int32_t read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
136 | int32_t write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
137 | ```
138 |
139 | These are your platform-specific functions that read/write data to/from a serial port or a TCP connection.
140 | Both methods should block until either:
141 |
142 | - `count` bytes of data are read/written
143 | - the byte timeout, with `byte_timeout_ms >= 0`, expires
144 |
145 | A value `< 0` for `byte_timeout_ms` means infinite timeout.
146 | With a value `== 0` for `byte_timeout_ms`, the method should read/write once in a non-blocking fashion and return
147 | immediately.
148 |
149 | Their return value should be the number of bytes actually read/written, or `< 0` in case of error.
150 | A return value between `0` and `count - 1` will be treated as if a timeout occurred on the transport side. All other
151 | values will be treated as transport errors.
152 |
153 | ### Callbacks and platform functions arguments
154 |
155 | Server callbacks and platform functions can access arbitrary user data through their `void* arg` argument. The argument
156 | is useful, for example, to pass the connection a function should operate on.
157 | Their initial values can be set via the `nmbs_set_callbacks_arg` and `nmbs_set_platform_arg` API methods.
158 |
159 | ## Tests and examples
160 |
161 | Tests and examples can be built and run on Linux with CMake:
162 |
163 | ```sh
164 | mkdir build && cd build
165 | cmake ..
166 | make
167 | ```
168 |
169 | Please refer to `examples/arduino/README.md` for more info about building and running Arduino examples.
170 |
171 | ## Misc
172 |
173 | - To reduce code size, you can define the following `#define`s:
174 | - `NMBS_CLIENT_DISABLED` to disable all client code
175 | - `NMBS_SERVER_DISABLED` to disable all server code
176 | - To disable individual server callbacks, define the following:
177 | - `NMBS_SERVER_READ_COILS_DISABLED`
178 | - `NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED`
179 | - `NMBS_SERVER_READ_HOLDING_REGISTERS_DISABLED`
180 | - `NMBS_SERVER_READ_INPUT_REGISTERS_DISABLED`
181 | - `NMBS_SERVER_WRITE_SINGLE_COIL_DISABLED`
182 | - `NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED`
183 | - `NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED`
184 | - `NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED`
185 | - `NMBS_SERVER_READ_FILE_RECORD_DISABLED`
186 | - `NMBS_SERVER_WRITE_FILE_RECORD_DISABLED`
187 | - `NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED`
188 | - `NMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLED`
189 | - `NMBS_STRERROR_DISABLED` to disable the code that converts `nmbs_error`s to strings
190 | - `NMBS_BITFIELD_MAX` to set the size of the `nmbs_bitfield` type, used to store coil values (default is `2000`)
191 | - Debug prints about received and sent messages can be enabled by defining `NMBS_DEBUG`
192 |
--------------------------------------------------------------------------------
/tests/nanomodbus_tests.h:
--------------------------------------------------------------------------------
1 | #include "nanomodbus.h"
2 | #undef NDEBUG
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #define expect(expr) assert(expr)
14 |
15 | #define check(err) (expect((err) == NMBS_ERROR_NONE))
16 |
17 | #define reset(nmbs) (memset(&(nmbs), 0, sizeof(nmbs_t)))
18 |
19 | #define test(f) (nesting++, (f), nesting--)
20 |
21 | #define UNUSED_PARAM(x) ((x) = (x))
22 |
23 |
24 | const uint8_t TEST_SERVER_ADDR = 1;
25 |
26 |
27 | unsigned int nesting = 0;
28 |
29 | int sockets[2] = {-1, -1};
30 |
31 | bool server_stopped = true;
32 | pthread_mutex_t server_stopped_m = PTHREAD_MUTEX_INITIALIZER;
33 | pthread_t server_thread;
34 | nmbs_t CLIENT, SERVER;
35 |
36 |
37 | #define should(s) \
38 | for (unsigned int i = 0; i < nesting; i++) { \
39 | printf("\t"); \
40 | } \
41 | printf("Should %s\n", (s))
42 |
43 |
44 | uint64_t now_ms(void) {
45 | struct timespec ts = {0, 0};
46 | clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
47 | return (uint64_t) (ts.tv_sec) * 1000 + (uint64_t) (ts.tv_nsec) / 1000000;
48 | }
49 |
50 |
51 | void reset_sockets(void) {
52 | if (sockets[0] != -1)
53 | close(sockets[0]);
54 |
55 | if (sockets[1] != -1)
56 | close(sockets[1]);
57 |
58 | expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
59 | }
60 |
61 |
62 | int32_t read_fd(int fd, uint8_t* buf, uint16_t count, int32_t timeout_ms) {
63 | uint16_t total = 0;
64 | while (total != count) {
65 | fd_set rfds;
66 | FD_ZERO(&rfds);
67 | FD_SET(fd, &rfds);
68 |
69 | struct timeval* tv_p = NULL;
70 | struct timeval tv;
71 | if (timeout_ms >= 0) {
72 | tv_p = &tv;
73 | tv.tv_sec = timeout_ms / 1000;
74 | tv.tv_usec = ((__suseconds_t) timeout_ms % 1000) * 1000;
75 | }
76 |
77 | int ret = select(fd + 1, &rfds, NULL, NULL, tv_p);
78 | if (ret == 0) {
79 | return total;
80 | }
81 |
82 | if (ret == 1) {
83 | ssize_t r = read(fd, buf + total, 1);
84 | if (r <= 0)
85 | return -1;
86 |
87 | total += r;
88 | }
89 | else
90 | return -1;
91 | }
92 |
93 | return total;
94 | }
95 |
96 |
97 | int32_t write_fd(int fd, const uint8_t* buf, uint16_t count, int32_t timeout_ms) {
98 | uint16_t total = 0;
99 | while (total != count) {
100 | fd_set wfds;
101 | FD_ZERO(&wfds);
102 | FD_SET(fd, &wfds);
103 |
104 | struct timeval* tv_p = NULL;
105 | struct timeval tv;
106 | if (timeout_ms >= 0) {
107 | tv_p = &tv;
108 | tv.tv_sec = timeout_ms / 1000;
109 | tv.tv_usec = ((__suseconds_t) timeout_ms % 1000) * 1000;
110 | }
111 |
112 | int ret = select(fd + 1, NULL, &wfds, NULL, tv_p);
113 | if (ret == 0) {
114 | return 0;
115 | }
116 |
117 | if (ret == 1) {
118 | ssize_t w = write(fd, buf + total, count);
119 | if (w <= 0)
120 | return -1;
121 |
122 | total += w;
123 | }
124 | else
125 | return -1;
126 | }
127 |
128 | return total;
129 | }
130 |
131 |
132 | int32_t read_socket_server(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
133 | UNUSED_PARAM(arg);
134 | return read_fd(sockets[0], buf, count, timeout_ms);
135 | }
136 |
137 |
138 | int32_t write_socket_server(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
139 | UNUSED_PARAM(arg);
140 | return write_fd(sockets[0], buf, count, timeout_ms);
141 | }
142 |
143 |
144 | int32_t read_socket_client(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
145 | UNUSED_PARAM(arg);
146 | return read_fd(sockets[1], buf, count, timeout_ms);
147 | }
148 |
149 |
150 | int32_t write_socket_client(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
151 | UNUSED_PARAM(arg);
152 | return write_fd(sockets[1], buf, count, timeout_ms);
153 | }
154 |
155 |
156 | nmbs_platform_conf nmbs_platform_conf_server;
157 | nmbs_platform_conf* platform_conf_socket_server(nmbs_transport transport) {
158 | nmbs_platform_conf_create(&nmbs_platform_conf_server);
159 | nmbs_platform_conf_server.transport = transport;
160 | nmbs_platform_conf_server.read = read_socket_server;
161 | nmbs_platform_conf_server.write = write_socket_server;
162 | return &nmbs_platform_conf_server;
163 | }
164 |
165 |
166 | nmbs_platform_conf nmbs_platform_conf_client;
167 | nmbs_platform_conf* platform_conf_socket_client(nmbs_transport transport) {
168 | nmbs_platform_conf_create(&nmbs_platform_conf_client);
169 | nmbs_platform_conf_client.transport = transport;
170 | nmbs_platform_conf_client.read = read_socket_client;
171 | nmbs_platform_conf_client.write = write_socket_client;
172 | return &nmbs_platform_conf_client;
173 | }
174 |
175 |
176 | bool is_server_listen_thread_stopped(void) {
177 | bool stopped = false;
178 | expect(pthread_mutex_lock(&server_stopped_m) == 0);
179 | stopped = server_stopped;
180 | expect(pthread_mutex_unlock(&server_stopped_m) == 0);
181 | return stopped;
182 | }
183 |
184 |
185 | void* server_listen_thread(void* arg) {
186 | UNUSED_PARAM(arg);
187 | while (true) {
188 | if (is_server_listen_thread_stopped())
189 | break;
190 |
191 | check(nmbs_server_poll(&SERVER));
192 | }
193 |
194 | return NULL;
195 | }
196 |
197 | void stop_client_and_server(void) {
198 | if (!is_server_listen_thread_stopped()) {
199 | expect(pthread_mutex_lock(&server_stopped_m) == 0);
200 | server_stopped = true;
201 | expect(pthread_mutex_unlock(&server_stopped_m) == 0);
202 | expect(pthread_join(server_thread, NULL) == 0);
203 | }
204 | }
205 |
206 |
207 | void start_client_and_server(nmbs_transport transport, const nmbs_callbacks* server_callbacks) {
208 | expect(pthread_mutex_destroy(&server_stopped_m) == 0);
209 | expect(pthread_mutex_init(&server_stopped_m, NULL) == 0);
210 |
211 | reset_sockets();
212 |
213 | reset(SERVER);
214 | reset(CLIENT);
215 |
216 | check(nmbs_server_create(&SERVER, TEST_SERVER_ADDR, platform_conf_socket_server(transport), server_callbacks));
217 | check(nmbs_client_create(&CLIENT, platform_conf_socket_client(transport)));
218 |
219 | nmbs_set_destination_rtu_address(&CLIENT, TEST_SERVER_ADDR);
220 | nmbs_set_read_timeout(&SERVER, 500);
221 | nmbs_set_byte_timeout(&SERVER, 100);
222 |
223 | nmbs_set_read_timeout(&CLIENT, 1000);
224 | nmbs_set_byte_timeout(&CLIENT, 100);
225 |
226 | expect(pthread_mutex_lock(&server_stopped_m) == 0);
227 | server_stopped = false;
228 | expect(pthread_mutex_unlock(&server_stopped_m) == 0);
229 | expect(pthread_create(&server_thread, NULL, server_listen_thread, &SERVER) == 0);
230 | }
231 |
--------------------------------------------------------------------------------
/examples/linux/server-tcp.c:
--------------------------------------------------------------------------------
1 | /*
2 | * This example application sets up a TCP server at the specified address and port, and polls from modbus requests
3 | * from more than one modbus client (more specifically from maximum 1024 clients, since it uses select())
4 | *
5 | * Since the platform for this example is linux, the platform arg is used to pass (to the linux file descriptor
6 | * read/write functions) a pointer to the file descriptor of the current read client connection
7 | *
8 | */
9 |
10 | #include
11 |
12 | #include "nanomodbus.h"
13 | #include "platform.h"
14 |
15 |
16 | // The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
17 | #define COILS_ADDR_MAX 100
18 | #define REGS_ADDR_MAX 32
19 | #define FILE_SIZE_MAX 32
20 |
21 | // A single nmbs_bitfield variable can keep 2000 coils
22 | bool terminate = false;
23 | nmbs_bitfield server_coils = {0};
24 | uint16_t server_registers[REGS_ADDR_MAX + 1] = {0};
25 | uint16_t server_file[FILE_SIZE_MAX];
26 |
27 |
28 | void sighandler(int s) {
29 | UNUSED_PARAM(s);
30 | terminate = true;
31 | }
32 |
33 | nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
34 | UNUSED_PARAM(arg);
35 | UNUSED_PARAM(unit_id);
36 |
37 | if (address + quantity > COILS_ADDR_MAX + 1)
38 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
39 |
40 | // Read our coils values into coils_out
41 | for (int i = 0; i < quantity; i++) {
42 | bool value = nmbs_bitfield_read(server_coils, address + i);
43 | nmbs_bitfield_write(coils_out, i, value);
44 | }
45 |
46 | return NMBS_ERROR_NONE;
47 | }
48 |
49 |
50 | nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
51 | void* arg) {
52 | UNUSED_PARAM(arg);
53 | UNUSED_PARAM(unit_id);
54 |
55 | if (address + quantity > COILS_ADDR_MAX + 1)
56 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
57 |
58 | // Write coils values to our server_coils
59 | for (int i = 0; i < quantity; i++) {
60 | nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils, i));
61 | }
62 |
63 | return NMBS_ERROR_NONE;
64 | }
65 |
66 |
67 | nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
68 | void* arg) {
69 | UNUSED_PARAM(arg);
70 | UNUSED_PARAM(unit_id);
71 |
72 | if (address + quantity > REGS_ADDR_MAX + 1)
73 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
74 |
75 | // Read our registers values into registers_out
76 | for (int i = 0; i < quantity; i++)
77 | registers_out[i] = server_registers[address + i];
78 |
79 | return NMBS_ERROR_NONE;
80 | }
81 |
82 |
83 | nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
84 | uint8_t unit_id, void* arg) {
85 | UNUSED_PARAM(arg);
86 | UNUSED_PARAM(unit_id);
87 |
88 | if (address + quantity > REGS_ADDR_MAX + 1)
89 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
90 |
91 | // Write registers values to our server_registers
92 | for (int i = 0; i < quantity; i++)
93 | server_registers[address + i] = registers[i];
94 |
95 | return NMBS_ERROR_NONE;
96 | }
97 |
98 |
99 | nmbs_error handle_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
100 | uint8_t unit_id, void* arg) {
101 | UNUSED_PARAM(arg);
102 | UNUSED_PARAM(unit_id);
103 |
104 | if (file_number != 1)
105 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
106 |
107 | if ((record_number + count) > FILE_SIZE_MAX)
108 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
109 |
110 | memcpy(registers, server_file + record_number, count * sizeof(uint16_t));
111 |
112 | return NMBS_ERROR_NONE;
113 | }
114 |
115 |
116 | nmbs_error handle_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
117 | uint16_t count, uint8_t unit_id, void* arg) {
118 | UNUSED_PARAM(arg);
119 | UNUSED_PARAM(unit_id);
120 |
121 | if (file_number != 1)
122 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
123 |
124 | if ((record_number + count) > FILE_SIZE_MAX)
125 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
126 |
127 | memcpy(server_file + record_number, registers, count * sizeof(uint16_t));
128 |
129 | return NMBS_ERROR_NONE;
130 | }
131 |
132 | nmbs_error handle_read_device_identification_map(nmbs_bitfield_256 map) {
133 | // We support basic object ID and a couple of extended ones
134 | nmbs_bitfield_set(map, 0x00);
135 | nmbs_bitfield_set(map, 0x01);
136 | nmbs_bitfield_set(map, 0x02);
137 | nmbs_bitfield_set(map, 0x90);
138 | nmbs_bitfield_set(map, 0xA0);
139 | return NMBS_ERROR_NONE;
140 | }
141 |
142 | nmbs_error handle_read_device_identification(uint8_t object_id, char buffer[NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH]) {
143 | switch (object_id) {
144 | case 0x00:
145 | strcpy(buffer, "VendorName");
146 | break;
147 | case 0x01:
148 | strcpy(buffer, "ProductCode");
149 | break;
150 | case 0x02:
151 | strcpy(buffer, "MajorMinorRevision");
152 | break;
153 | case 0x90:
154 | strcpy(buffer, "Extended 1");
155 | break;
156 | case 0xA0:
157 | strcpy(buffer, "Extended 2");
158 | break;
159 | default:
160 | return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
161 | }
162 |
163 | return NMBS_ERROR_NONE;
164 | }
165 |
166 |
167 | int main(int argc, char* argv[]) {
168 | signal(SIGTERM, sighandler);
169 | signal(SIGSTOP, sighandler);
170 | signal(SIGINT, sighandler);
171 | signal(SIGQUIT, sighandler);
172 |
173 | if (argc < 3) {
174 | fprintf(stderr, "Usage: server-tcp [address] [port]\n");
175 | return 1;
176 | }
177 |
178 | // Set up the TCP server
179 | int ret = create_tcp_server(argv[1], argv[2]);
180 | if (ret != 0) {
181 | fprintf(stderr, "Error creating TCP server - %s\n", strerror(ret));
182 | return 1;
183 | }
184 |
185 | nmbs_platform_conf platform_conf;
186 | nmbs_platform_conf_create(&platform_conf);
187 | platform_conf.transport = NMBS_TRANSPORT_TCP;
188 | platform_conf.read = read_fd_linux;
189 | platform_conf.write = write_fd_linux;
190 | platform_conf.arg = NULL; // We will set the arg (socket fd) later
191 |
192 | nmbs_callbacks callbacks;
193 | nmbs_callbacks_create(&callbacks);
194 | callbacks.read_coils = handle_read_coils;
195 | callbacks.write_multiple_coils = handle_write_multiple_coils;
196 | callbacks.read_holding_registers = handler_read_holding_registers;
197 | callbacks.write_multiple_registers = handle_write_multiple_registers;
198 | callbacks.read_file_record = handle_read_file_record;
199 | callbacks.write_file_record = handle_write_file_record;
200 | callbacks.read_device_identification_map = handle_read_device_identification_map;
201 | callbacks.read_device_identification = handle_read_device_identification;
202 |
203 | // Create the modbus server. It's ok to set address_rtu to 0 since we are on TCP
204 | nmbs_t nmbs;
205 | nmbs_error err = nmbs_server_create(&nmbs, 0, &platform_conf, &callbacks);
206 | if (err != NMBS_ERROR_NONE) {
207 | fprintf(stderr, "Error creating modbus server\n");
208 | return 1;
209 | }
210 |
211 | // Set only the polling timeout. Byte timeout will be handled by the TCP connection
212 | nmbs_set_read_timeout(&nmbs, 1000);
213 |
214 | printf("Modbus TCP server started\n");
215 |
216 | // Our server supports requests from more than one client
217 | while (!terminate) {
218 | // Our server_poll() function will return the next client TCP connection to read from
219 | void* conn = server_poll();
220 | if (conn) {
221 | // Set the next connection handler used by the read/write platform functions
222 | nmbs_set_platform_arg(&nmbs, conn);
223 |
224 | err = nmbs_server_poll(&nmbs);
225 | if (err != NMBS_ERROR_NONE) {
226 | printf("Error on modbus connection - %s\n", nmbs_strerror(err));
227 | // In a more complete example, we would handle this error by checking its nmbs_error value
228 | }
229 | }
230 | }
231 |
232 | // Close the TCP server
233 | close_tcp_server();
234 |
235 | // No need to destroy the nmbs instance, bye bye
236 | return 0;
237 | }
238 |
--------------------------------------------------------------------------------
/examples/stm32/nmbs/port.c:
--------------------------------------------------------------------------------
1 | #include "nmbs/port.h"
2 | #include "FreeRTOS.h"
3 | #include
4 |
5 | #ifdef NMBS_TCP
6 | static int32_t read_socket(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
7 | static int32_t write_socket(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
8 | #endif
9 | #ifdef NMBS_RTU
10 | static int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
11 | static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg);
12 |
13 | #if MB_UART_DMA
14 | #include "queue.h"
15 | xQueueHandle rtu_rx_q;
16 | uint8_t rtu_rx_b[MB_RX_BUF_SIZE];
17 | #endif
18 |
19 | #endif
20 |
21 | static nmbs_server_t* server;
22 |
23 | static nmbs_error server_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id,
24 | void* arg);
25 | static nmbs_error server_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out,
26 | uint8_t unit_id, void* arg);
27 | static nmbs_error server_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg);
28 | static nmbs_error server_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils,
29 | uint8_t unit_id, void* arg);
30 | static nmbs_error server_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg);
31 | static nmbs_error server_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
32 | uint8_t unit_id, void* arg);
33 |
34 | nmbs_error nmbs_server_init(nmbs_t* nmbs, nmbs_server_t* _server) {
35 | nmbs_platform_conf conf;
36 | nmbs_callbacks cb;
37 |
38 | nmbs_platform_conf_create(&conf);
39 | #ifdef NMBS_TCP
40 | conf.transport = NMBS_TRANSPORT_TCP;
41 | conf.read = read_socket;
42 | conf.write = write_socket;
43 | #endif
44 | #ifdef NMBS_RTU
45 | conf.transport = NMBS_TRANSPORT_RTU;
46 | conf.read = read_serial;
47 | conf.write = write_serial;
48 | #endif
49 |
50 | server = _server;
51 |
52 | nmbs_callbacks_create(&cb);
53 | cb.read_coils = server_read_coils;
54 | cb.read_holding_registers = server_read_holding_registers;
55 | cb.write_single_coil = server_write_single_coil;
56 | cb.write_multiple_coils = server_write_multiple_coils;
57 | cb.write_single_register = server_write_single_register;
58 | cb.write_multiple_registers = server_write_multiple_registers;
59 |
60 | #if MB_UART_DMA
61 | rtu_rx_q = xQueueCreate(MB_RX_BUF_SIZE, sizeof(uint8_t));
62 | HAL_UARTEx_ReceiveToIdle_DMA(&MB_UART, rtu_rx_b, MB_RX_BUF_SIZE);
63 | #endif
64 |
65 | nmbs_error status = nmbs_server_create(nmbs, server->id, &conf, &cb);
66 | if (status != NMBS_ERROR_NONE) {
67 | return status;
68 | }
69 |
70 | nmbs_set_byte_timeout(nmbs, 100);
71 | nmbs_set_read_timeout(nmbs, 1000);
72 |
73 | return NMBS_ERROR_NONE;
74 | }
75 |
76 | nmbs_error nmbs_client_init(nmbs_t* nmbs) {
77 | nmbs_platform_conf conf;
78 |
79 | nmbs_platform_conf_create(&conf);
80 | #ifdef NMBS_TCP
81 | conf.transport = NMBS_TRANSPORT_TCP;
82 | conf.read = read_socket;
83 | conf.write = write_socket;
84 | #endif
85 | #ifdef NMBS_RTU
86 | conf.transport = NMBS_TRANSPORT_RTU;
87 | conf.read = read_serial;
88 | conf.write = write_serial;
89 | #endif
90 |
91 | nmbs_error status = nmbs_client_create(nmbs, &conf);
92 | if (status != NMBS_ERROR_NONE) {
93 | return status;
94 | }
95 |
96 | nmbs_set_byte_timeout(nmbs, 100);
97 | nmbs_set_read_timeout(nmbs, 1000);
98 |
99 | return NMBS_ERROR_NONE;
100 | }
101 |
102 |
103 | static nmbs_server_t* get_server(uint8_t id) {
104 | if (id == server->id) {
105 | return server;
106 | }
107 | else {
108 | return NULL;
109 | }
110 | }
111 |
112 | static nmbs_error server_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id,
113 | void* arg) {
114 | nmbs_server_t* server = get_server(unit_id);
115 |
116 | for (size_t i = 0; i < quantity; i++) {
117 | if ((address >> 3) > COIL_BUF_SIZE) {
118 | return NMBS_ERROR_INVALID_REQUEST;
119 | }
120 | nmbs_bitfield_write(coils_out, address, nmbs_bitfield_read(server->coils, address));
121 | address++;
122 | }
123 | return NMBS_ERROR_NONE;
124 | }
125 |
126 | static nmbs_error server_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out,
127 | uint8_t unit_id, void* arg) {
128 | nmbs_server_t* server = get_server(unit_id);
129 |
130 | for (size_t i = 0; i < quantity; i++) {
131 | if (address > REG_BUF_SIZE) {
132 | return NMBS_ERROR_INVALID_REQUEST;
133 | }
134 | registers_out[i] = server->regs[address++];
135 | }
136 | return NMBS_ERROR_NONE;
137 | }
138 |
139 | static nmbs_error server_write_single_coil(uint16_t address, bool value, uint8_t unit_id, void* arg) {
140 | uint8_t coil = 0;
141 | if (value) {
142 | coil |= 0x01;
143 | }
144 | return server_write_multiple_coils(address, 1, &coil, unit_id, arg);
145 | }
146 |
147 | static nmbs_error server_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils,
148 | uint8_t unit_id, void* arg) {
149 | nmbs_server_t* server = get_server(unit_id);
150 |
151 | for (size_t i = 0; i < quantity; i++) {
152 | if ((address >> 3) > COIL_BUF_SIZE) {
153 | return NMBS_ERROR_INVALID_REQUEST;
154 | }
155 | nmbs_bitfield_write(server->coils, address, nmbs_bitfield_read(coils, i));
156 | address++;
157 | }
158 | return NMBS_ERROR_NONE;
159 | }
160 |
161 | static nmbs_error server_write_single_register(uint16_t address, uint16_t value, uint8_t unit_id, void* arg) {
162 | uint16_t reg = value;
163 | return server_write_multiple_registers(address, 1, ®, unit_id, arg);
164 | }
165 |
166 | static nmbs_error server_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
167 | uint8_t unit_id, void* arg) {
168 | nmbs_server_t* server = get_server(unit_id);
169 |
170 | for (size_t i = 0; i < quantity; i++) {
171 | if (address > REG_BUF_SIZE) {
172 | return NMBS_ERROR_INVALID_REQUEST;
173 | }
174 | server->regs[address++] = registers[i];
175 | }
176 | return NMBS_ERROR_NONE;
177 | }
178 |
179 | #ifdef NMBS_TCP
180 | int32_t read_socket(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
181 | uint32_t tick_start = HAL_GetTick();
182 | while (recv(MB_SOCKET, buf, count) != count) {
183 | if (HAL_GetTick() - tick_start >= (uint32_t) byte_timeout_ms) {
184 | return 0;
185 | }
186 | }
187 | return count;
188 | }
189 |
190 | int32_t write_socket(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
191 | return send(MB_SOCKET, buf, count);
192 | }
193 | #endif
194 |
195 | #ifdef NMBS_RTU
196 | static int32_t read_serial(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
197 | #if MB_UART_DMA
198 | uint32_t tick_start = HAL_GetTick();
199 | while (uxQueueMessagesWaiting(rtu_rx_q) < count) {
200 | if (HAL_GetTick() - tick_start >= (uint32_t) byte_timeout_ms) {
201 | return 0;
202 | }
203 | }
204 | for (int i = 0; i < count; i++) {
205 | xQueueReceive(rtu_rx_q, buf + i, 1);
206 | }
207 | return count;
208 | #else
209 | HAL_StatusTypeDef status = HAL_UART_Receive(&MB_UART, buf, count, byte_timeout_ms);
210 | if (status == HAL_OK) {
211 | return count;
212 | }
213 | else {
214 | return 0;
215 | }
216 | #endif
217 | }
218 | static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
219 | #if MB_UART_DMA
220 | HAL_UART_Transmit_DMA(&MB_UART, buf, count);
221 | #else
222 | HAL_StatusTypeDef status = HAL_UART_Transmit(&MB_UART, buf, count, byte_timeout_ms);
223 | if (status == HAL_OK) {
224 | return count;
225 | }
226 | else {
227 | return 0;
228 | }
229 | #endif
230 | }
231 |
232 |
233 | #if MB_UART_DMA
234 | void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t Size) {
235 | BaseType_t xHigherPriorityTaskWoken = pdFALSE;
236 | if (huart == &MB_UART) {
237 | for (int i = 0; i < Size; i++) {
238 | xQueueSendFromISR(rtu_rx_q, rtu_rx_b + i, &xHigherPriorityTaskWoken);
239 | }
240 | HAL_UARTEx_ReceiveToIdle_DMA(huart, rtu_rx_b, MB_RX_BUF_SIZE);
241 | portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
242 | }
243 | // You may add your additional uart handler below
244 | }
245 | #endif
246 |
247 | #endif
--------------------------------------------------------------------------------
/examples/stm32/bsp/blackpill/blackpill.c:
--------------------------------------------------------------------------------
1 | /*
2 | * STM32F401CCU6 Board Support Package (BSP) Summary:
3 | *
4 | * 1. System Clock Configuration:
5 | * - External high-speed oscillator (HSE) enabled.
6 | * - PLL is configured with source from HSE, PLLM = 25, PLLN = 336, PLLP = 4,
7 | * PLLQ = 7.
8 | * - System clock (SYSCLK) sourced from PLL output at 84 MHz.
9 | * - AHB clock (HCLK) running at SYSCLK.
10 | * - APB1 clock (PCLK1) running at HCLK / 2 (42 MHz).
11 | * - APB2 clock (PCLK2) running at HCLK.
12 | *
13 | * 2. GPIO Configuration:
14 | * - GPIOC Pin 13: Configured as output (Push Pull), used for LED control,
15 | * low frequency.
16 | * - GPIOB Pin 7: Configured as input, no pull-up/pull-down, used as input
17 | * for interrupts.
18 | * - GPIOB Pin 6: Configured as open-drain output, low frequency, initially
19 | * set high.
20 | * - GPIOA Pin 15: Configured as output (Push Pull), used for NSS in SPI1
21 | * communication, very high frequency.
22 | * - GPIOA Pins 9 (TX), 10 (RX): Configured as alternate function (AF7) for
23 | * USART1 communication.
24 | * - GPIOB Pins 3 (SCLK), 4 (MISO), 5 (MOSI): Configured as alternate
25 | * function (AF5) for SPI1 communication.
26 | *
27 | * 3. SPI1 Configuration:
28 | * - Mode: Master.
29 | * - Data Direction: 2-line unidirectional.
30 | * - Data Size: 8-bit.
31 | * - Clock Polarity: Low when idle.
32 | * - Clock Phase: First edge capture.
33 | * - NSS (Chip Select): Software management.
34 | * - Baud Rate Prescaler: 2.
35 | * - First Bit: MSB.
36 | * - TI Mode: Disabled.
37 | * - CRC Calculation: Disabled.
38 | * - Pins: PB3 (SCLK), PB4 (MISO), PB5 (MOSI) configured as alternate
39 | * function.
40 | *
41 | * 4. USART1 Configuration:
42 | * - Baud Rate: 115200.
43 | * - Word Length: 8 bits.
44 | * - Stop Bits: 1.
45 | * - Parity: None.
46 | * - Mode: TX/RX.
47 | * - Hardware Flow Control: None.
48 | * - Oversampling: 16x.
49 | * - Pins: PA9 (TX), PA10 (RX) configured as alternate function.
50 | *
51 | * 5. DMA Configuration:
52 | * - DMA2_Stream3 (SPI1_TX): Used for SPI1 TX, configured for
53 | * memory-to-peripheral, channel 3.
54 | * - Memory increment enabled, peripheral increment disabled, normal mode,
55 | * low priority.
56 | * - Linked to SPI1_TX using __HAL_LINKDMA.
57 | * - Interrupt priority level 0, enabled.
58 | * - DMA2_Stream0 (SPI1_RX): Used for SPI1 RX, configured for
59 | * peripheral-to-memory, channel 3.
60 | * - Memory increment enabled, peripheral increment disabled, normal mode,
61 | * high priority.
62 | * - Linked to SPI1_RX using __HAL_LINKDMA.
63 | * - Interrupt priority level 0, enabled.
64 | * - DMA2_Stream7 (USART1_TX): Used for USART1 TX, configured for
65 | * memory-to-peripheral, channel 4.
66 | * - Memory increment enabled, peripheral increment disabled, normal mode,
67 | * low priority.
68 | * - Linked to USART1_TX using __HAL_LINKDMA.
69 | * - Interrupt priority level 0, enabled.
70 | * - DMA2_Stream2 (USART1_RX): Used for USART1 RX, configured for
71 | * peripheral-to-memory, channel 4.
72 | * - Memory increment enabled, peripheral increment disabled, normal mode,
73 | * high priority.
74 | * - Linked to USART1_RX using __HAL_LINKDMA.
75 | * - Interrupt priority level 0, enabled.
76 | *
77 | * 6. Peripheral Clocks:
78 | * - GPIOC, GPIOB, GPIOA clocks enabled for GPIO configuration.
79 | * - USART1 clock enabled for UART communication.
80 | * - SPI1 clock enabled for SPI communication.
81 | * - DMA2 clock enabled for DMA streams (used for SPI1 and USART1).
82 | *
83 | * 7. Interrupt Configuration:
84 | * - DMA2_Stream3 (SPI1_TX), DMA2_Stream0 (SPI1_RX), DMA2_Stream7
85 | * (USART1_TX), DMA2_Stream2 (USART1_RX).
86 | * - All configured with priority level 0 and interrupts enabled.
87 | *
88 | * 8. Error Handling:
89 | * - Error_Handler function enters an infinite loop to indicate an error
90 | * state.
91 | */
92 |
93 | #include "blackpill/blackpill.h"
94 | #include "blackpill.h"
95 |
96 | #include "stm32f4xx_hal.h"
97 |
98 | void SystemClock_Config(void);
99 | static void MX_GPIO_Init(void);
100 | static void MX_SPI1_Init(void);
101 | static void MX_DMA_Init(void);
102 | static void MX_USART1_UART_Init(void);
103 |
104 | SPI_HandleTypeDef hspi1;
105 | DMA_HandleTypeDef hdma_spi1_tx;
106 | DMA_HandleTypeDef hdma_spi1_rx;
107 | UART_HandleTypeDef huart1;
108 | DMA_HandleTypeDef hdma_usart1_tx;
109 | DMA_HandleTypeDef hdma_usart1_rx;
110 |
111 | void BSP_Init(void) {
112 | // Initialize the HAL Library
113 | HAL_Init();
114 |
115 | // Configure the system clock
116 | SystemClock_Config();
117 |
118 | // Initialize all configured peripherals (GPIO and SPI1)
119 | MX_GPIO_Init();
120 | MX_SPI1_Init();
121 | MX_DMA_Init();
122 | MX_USART1_UART_Init();
123 | }
124 |
125 | void SystemClock_Config(void) {
126 | // System Clock Configuration Code
127 | RCC_OscInitTypeDef RCC_OscInitStruct = {0};
128 | RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
129 |
130 | // Configure the main internal regulator output voltage
131 | RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
132 | RCC_OscInitStruct.HSEState = RCC_HSE_ON;
133 | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
134 | RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
135 | RCC_OscInitStruct.PLL.PLLM = 25;
136 | RCC_OscInitStruct.PLL.PLLN = 336;
137 | RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
138 | RCC_OscInitStruct.PLL.PLLQ = 7;
139 | HAL_RCC_OscConfig(&RCC_OscInitStruct);
140 |
141 | // Initialize the CPU, AHB, and APB buses clocks
142 | RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
143 | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
144 | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
145 | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
146 | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
147 | HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
148 | }
149 |
150 | static void MX_GPIO_Init(void) {
151 | GPIO_InitTypeDef GPIO_InitStruct = {0};
152 |
153 | // Enable GPIOC clock
154 | __HAL_RCC_GPIOC_CLK_ENABLE();
155 |
156 | // Configure GPIOC Pin 13 for LED output (Output Push Pull mode)
157 | GPIO_InitStruct.Pin = GPIO_PIN_13;
158 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
159 | GPIO_InitStruct.Pull = GPIO_NOPULL;
160 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
161 | HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
162 |
163 | // Enable GPIOB clock
164 | __HAL_RCC_GPIOB_CLK_ENABLE();
165 |
166 | // Configure GPIOB Pin 7 as input for interrupt (Input mode)
167 | GPIO_InitStruct.Pin = GPIO_PIN_7;
168 | GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
169 | GPIO_InitStruct.Pull = GPIO_NOPULL;
170 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
171 |
172 | // Configure GPIOB Pin 6 as output with Open Drain mode
173 | GPIO_InitStruct.Pin = GPIO_PIN_6;
174 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
175 | GPIO_InitStruct.Pull = GPIO_NOPULL;
176 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
177 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
178 |
179 | HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
180 |
181 | // Enable GPIOA clock
182 | __HAL_RCC_GPIOA_CLK_ENABLE();
183 | // Configure NSS pin (PA15) as Output Push Pull
184 | GPIO_InitStruct.Pin = GPIO_PIN_15;
185 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
186 | GPIO_InitStruct.Pull = GPIO_NOPULL;
187 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
188 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
189 | }
190 |
191 | static void MX_USART1_UART_Init(void) {
192 | // USART1 initialization settings
193 | __HAL_RCC_USART1_CLK_ENABLE();
194 |
195 | huart1.Instance = USART1;
196 | huart1.Init.BaudRate = 115200;
197 | huart1.Init.WordLength = UART_WORDLENGTH_8B;
198 | huart1.Init.StopBits = UART_STOPBITS_1;
199 | huart1.Init.Parity = UART_PARITY_NONE;
200 | huart1.Init.Mode = UART_MODE_TX_RX;
201 | huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
202 | huart1.Init.OverSampling = UART_OVERSAMPLING_16;
203 |
204 | if (HAL_UART_Init(&huart1) != HAL_OK) {
205 | // Initialization error handling
206 | Error_Handler();
207 | }
208 |
209 | // Enable USART1 interrupt
210 | // It must higher or equal than 5
211 | HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
212 | HAL_NVIC_EnableIRQ(USART1_IRQn);
213 |
214 | // USART1 Pin configuration: TX (PA9), RX (PA10)
215 | GPIO_InitTypeDef GPIO_InitStruct = {0};
216 |
217 | // Enable GPIOA clock
218 | __HAL_RCC_GPIOA_CLK_ENABLE();
219 |
220 | // Configure USART1 TX (PA9) and RX (PA10) pins as Alternate Function Push
221 | // Pull
222 | GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
223 | GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
224 | GPIO_InitStruct.Pull = GPIO_NOPULL;
225 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
226 | GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
227 | HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
228 | }
229 |
230 | static void MX_SPI1_Init(void) {
231 | __HAL_RCC_SPI1_CLK_ENABLE();
232 | // SPI1 initialization settings
233 | hspi1.Instance = SPI1;
234 | hspi1.Init.Mode = SPI_MODE_MASTER; // Set SPI1 as master
235 | hspi1.Init.Direction = SPI_DIRECTION_2LINES; // Set bidirectional data mode
236 | hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // Set data frame size to 8 bits
237 | hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // Clock polarity low when idle
238 | hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // First clock transition is the first data capture edge
239 | hspi1.Init.NSS = SPI_NSS_SOFT; // Hardware chip select management
240 | hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // Set baud rate prescaler to 2
241 | hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // Data is transmitted MSB first
242 | hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // Disable TI mode
243 | hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // Disable CRC calculation
244 | hspi1.Init.CRCPolynomial = 10; // CRC polynomial value
245 |
246 | if (HAL_SPI_Init(&hspi1) != HAL_OK) {
247 | // Initialization error handling
248 | Error_Handler();
249 | }
250 |
251 | // SPI1 Pin configuration: SCLK (PB3), MISO (PB4), MOSI (PB5)
252 | GPIO_InitTypeDef GPIO_InitStruct = {0};
253 |
254 | // Enable GPIOB clock
255 | __HAL_RCC_GPIOB_CLK_ENABLE();
256 |
257 | // Configure SPI1 SCLK, MISO, MOSI pins as Alternate Function Push Pull
258 | GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
259 | GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
260 | GPIO_InitStruct.Pull = GPIO_NOPULL;
261 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
262 | GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
263 | HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
264 | }
265 |
266 | static void MX_DMA_Init(void) {
267 | // DMA controller clock enable
268 | __HAL_RCC_DMA2_CLK_ENABLE();
269 |
270 | // Configure DMA request hdma_spi1_tx on DMA2_Stream3
271 | hdma_spi1_tx.Instance = DMA2_Stream3;
272 | hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
273 | hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
274 | hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
275 | hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
276 | hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
277 | hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
278 | hdma_spi1_tx.Init.Mode = DMA_NORMAL;
279 | hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
280 | hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
281 | if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK) {
282 | // Initialization error handling
283 | Error_Handler();
284 | }
285 |
286 | __HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);
287 |
288 | // Configure DMA request hdma_spi1_rx on DMA2_Stream0
289 | hdma_spi1_rx.Instance = DMA2_Stream0;
290 | hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
291 | hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
292 | hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
293 | hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
294 | hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
295 | hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
296 | hdma_spi1_rx.Init.Mode = DMA_NORMAL;
297 | hdma_spi1_rx.Init.Priority = DMA_PRIORITY_HIGH;
298 | hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
299 | if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK) {
300 | // Initialization error handling
301 | Error_Handler();
302 | }
303 |
304 | __HAL_LINKDMA(&hspi1, hdmarx, hdma_spi1_rx);
305 |
306 | // Configure DMA request hdma_usart1_tx on DMA2_Stream7
307 | hdma_usart1_tx.Instance = DMA2_Stream7;
308 | hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;
309 | hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
310 | hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
311 | hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
312 | hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
313 | hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
314 | hdma_usart1_tx.Init.Mode = DMA_NORMAL;
315 | hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
316 | hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
317 | if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) {
318 | // Initialization error handling
319 | Error_Handler();
320 | }
321 |
322 | __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);
323 |
324 | // Configure DMA request hdma_usart1_rx on DMA2_Stream2
325 | hdma_usart1_rx.Instance = DMA2_Stream2;
326 | hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
327 | hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
328 | hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
329 | hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
330 | hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
331 | hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
332 | hdma_usart1_rx.Init.Mode = DMA_NORMAL;
333 | hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
334 | hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
335 |
336 | if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) {
337 | // Initialization error handling
338 | Error_Handler();
339 | }
340 |
341 | __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
342 |
343 | // DMA2_Stream3 (SPI1_TX) Interrupt Configuration
344 | HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
345 | HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
346 |
347 | // DMA2_Stream0 (SPI1_RX) Interrupt Configuration
348 | HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
349 | HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
350 |
351 | // DMA2_Stream7 (USART1_TX) Interrupt Configuration
352 | HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);
353 | HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
354 |
355 | // DMA2_Stream2 (USART1_RX) Interrupt Configuration
356 | HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
357 | HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
358 | }
359 |
360 | void Error_Handler(void) {
361 | // If an error occurs, stay in infinite loop
362 | while (1) {}
363 | }
364 |
365 | void DMA2_Stream3_IRQHandler(void) {
366 | HAL_DMA_IRQHandler(&hdma_spi1_tx);
367 | }
368 |
369 | void DMA2_Stream0_IRQHandler(void) {
370 | HAL_DMA_IRQHandler(&hdma_spi1_rx);
371 | }
372 |
373 | void DMA2_Stream7_IRQHandler(void) {
374 | HAL_DMA_IRQHandler(&hdma_usart1_tx);
375 | }
376 |
377 | void DMA2_Stream2_IRQHandler(void) {
378 | HAL_DMA_IRQHandler(&hdma_usart1_rx);
379 | }
380 |
381 | void USART1_IRQHandler(void) {
382 | HAL_UART_IRQHandler(&huart1);
383 | }
384 |
--------------------------------------------------------------------------------
/examples/stm32/stm32f4xx_hal_conf.h:
--------------------------------------------------------------------------------
1 | /**
2 | ******************************************************************************
3 | * @file stm32f4xx_hal_conf_template.h
4 | * @author MCD Application Team
5 | * @brief HAL configuration template file.
6 | * This file should be copied to the application folder and renamed
7 | * to stm32f4xx_hal_conf.h.
8 | ******************************************************************************
9 | * @attention
10 | *
11 | * © Copyright (c) 2017 STMicroelectronics.
12 | * All rights reserved.
13 | *
14 | * This software component is licensed by ST under BSD 3-Clause license,
15 | * the "License"; You may not use this file except in compliance with the
16 | * License. You may obtain a copy of the License at:
17 | * opensource.org/licenses/BSD-3-Clause
18 | *
19 | ******************************************************************************
20 | */
21 |
22 | /* Define to prevent recursive inclusion -------------------------------------*/
23 | #ifndef __STM32F4xx_HAL_CONF_H
24 | #define __STM32F4xx_HAL_CONF_H
25 |
26 | #ifdef __cplusplus
27 | extern "C" {
28 | #endif
29 |
30 | /* Exported types ------------------------------------------------------------*/
31 | /* Exported constants --------------------------------------------------------*/
32 |
33 | /* ########################## Module Selection ############################## */
34 | /**
35 | * @brief This is the list of modules to be used in the HAL driver
36 | */
37 | #define HAL_MODULE_ENABLED
38 | // #define HAL_ADC_MODULE_ENABLED
39 | // #define HAL_CAN_MODULE_ENABLED
40 | // #define HAL_CAN_LEGACY_MODULE_ENABLED
41 | // #define HAL_CRC_MODULE_ENABLED
42 | // #define HAL_CEC_MODULE_ENABLED
43 | // #define HAL_CRYP_MODULE_ENABLED
44 | // #define HAL_DAC_MODULE_ENABLED
45 | // #define HAL_DCMI_MODULE_ENABLED
46 | #define HAL_DMA_MODULE_ENABLED
47 | // #define HAL_DMA2D_MODULE_ENABLED
48 | // #define HAL_ETH_MODULE_ENABLED
49 | #define HAL_FLASH_MODULE_ENABLED
50 | // #define HAL_NAND_MODULE_ENABLED
51 | // #define HAL_NOR_MODULE_ENABLED
52 | // #define HAL_PCCARD_MODULE_ENABLED
53 | // #define HAL_SRAM_MODULE_ENABLED
54 | // #define HAL_SDRAM_MODULE_ENABLED
55 | // #define HAL_HASH_MODULE_ENABLED
56 | #define HAL_GPIO_MODULE_ENABLED
57 | // #define HAL_EXTI_MODULE_ENABLED
58 | // #define HAL_I2C_MODULE_ENABLED
59 | // #define HAL_SMBUS_MODULE_ENABLED
60 | // #define HAL_I2S_MODULE_ENABLED
61 | // #define HAL_IWDG_MODULE_ENABLED
62 | // #define HAL_LTDC_MODULE_ENABLED
63 | // #define HAL_DSI_MODULE_ENABLED
64 | #define HAL_PWR_MODULE_ENABLED
65 | // #define HAL_QSPI_MODULE_ENABLED
66 | #define HAL_RCC_MODULE_ENABLED
67 | // #define HAL_RNG_MODULE_ENABLED
68 | // #define HAL_RTC_MODULE_ENABLED
69 | // #define HAL_SAI_MODULE_ENABLED
70 | // #define HAL_SD_MODULE_ENABLED
71 | #define HAL_SPI_MODULE_ENABLED
72 | // #define HAL_TIM_MODULE_ENABLED
73 | #define HAL_UART_MODULE_ENABLED
74 | #define HAL_USART_MODULE_ENABLED
75 | // #define HAL_IRDA_MODULE_ENABLED
76 | // #define HAL_SMARTCARD_MODULE_ENABLED
77 | // #define HAL_WWDG_MODULE_ENABLED
78 | #define HAL_CORTEX_MODULE_ENABLED
79 | // #define HAL_PCD_MODULE_ENABLED
80 | // #define HAL_HCD_MODULE_ENABLED
81 | // #define HAL_FMPI2C_MODULE_ENABLED
82 | // #define HAL_SPDIFRX_MODULE_ENABLED
83 | // #define HAL_DFSDM_MODULE_ENABLED
84 | // #define HAL_LPTIM_MODULE_ENABLED
85 | // #define HAL_MMC_MODULE_ENABLED
86 |
87 | /* ########################## HSE/HSI Values adaptation ##################### */
88 | /**
89 | * @brief Adjust the value of External High Speed oscillator (HSE) used in your
90 | * application. This value is used by the RCC HAL module to compute the system
91 | * frequency (when HSE is used as system clock source, directly or through the
92 | * PLL).
93 | */
94 | #if !defined(HSE_VALUE)
95 | #define HSE_VALUE 25000000U /*!< Value of the External oscillator in Hz */
96 | #endif /* HSE_VALUE */
97 |
98 | #if !defined(HSE_STARTUP_TIMEOUT)
99 | #define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */
100 | #endif /* HSE_STARTUP_TIMEOUT */
101 |
102 | /**
103 | * @brief Internal High Speed oscillator (HSI) value.
104 | * This value is used by the RCC HAL module to compute the system
105 | * frequency (when HSI is used as system clock source, directly or through the
106 | * PLL).
107 | */
108 | #if !defined(HSI_VALUE)
109 | #define HSI_VALUE 16000000U /*!< Value of the Internal oscillator in Hz */
110 | #endif /* HSI_VALUE */
111 |
112 | /**
113 | * @brief Internal Low Speed oscillator (LSI) value.
114 | */
115 | #if !defined(LSI_VALUE)
116 | #define LSI_VALUE 32000U /*!< LSI Typical Value in Hz */
117 | #endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz \
118 | The real value may vary depending on the \
119 | variations in voltage and temperature. */
120 | /**
121 | * @brief External Low Speed oscillator (LSE) value.
122 | */
123 | #if !defined(LSE_VALUE)
124 | #define LSE_VALUE 32768U /*!< Value of the External Low Speed oscillator in Hz */
125 | #endif /* LSE_VALUE */
126 |
127 | #if !defined(LSE_STARTUP_TIMEOUT)
128 | #define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */
129 | #endif /* LSE_STARTUP_TIMEOUT */
130 |
131 | /**
132 | * @brief External clock source for I2S peripheral
133 | * This value is used by the I2S HAL module to compute the I2S clock
134 | * source frequency, this source is inserted directly through I2S_CKIN pad.
135 | */
136 | #if !defined(EXTERNAL_CLOCK_VALUE)
137 | #define EXTERNAL_CLOCK_VALUE 12288000U /*!< Value of the External oscillator in Hz*/
138 | #endif /* EXTERNAL_CLOCK_VALUE */
139 |
140 | /* Tip: To avoid modifying this file each time you need to use different HSE,
141 | === you can define the HSE value in your toolchain compiler preprocessor. */
142 |
143 | /* ########################### System Configuration ######################### */
144 | /**
145 | * @brief This is the HAL system configuration section
146 | */
147 | #define VDD_VALUE 3300U /*!< Value of VDD in mv */
148 | #define TICK_INT_PRIORITY 0x0FU /*!< tick interrupt priority */
149 | #define USE_RTOS 0U
150 | #define PREFETCH_ENABLE 1U
151 | #define INSTRUCTION_CACHE_ENABLE 1U
152 | #define DATA_CACHE_ENABLE 1U
153 |
154 | #define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */
155 | #define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */
156 | #define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */
157 | #define USE_HAL_CRYP_REGISTER_CALLBACKS 0U /* CRYP register callback disabled */
158 | #define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */
159 | #define USE_HAL_DCMI_REGISTER_CALLBACKS 0U /* DCMI register callback disabled */
160 | #define USE_HAL_DFSDM_REGISTER_CALLBACKS 0U /* DFSDM register callback disabled */
161 | #define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U /* DMA2D register callback disabled */
162 | #define USE_HAL_DSI_REGISTER_CALLBACKS 0U /* DSI register callback disabled */
163 | #define USE_HAL_ETH_REGISTER_CALLBACKS 0U /* ETH register callback disabled */
164 | #define USE_HAL_HASH_REGISTER_CALLBACKS 0U /* HASH register callback disabled */
165 | #define USE_HAL_HCD_REGISTER_CALLBACKS 0U /* HCD register callback disabled */
166 | #define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */
167 | #define USE_HAL_FMPI2C_REGISTER_CALLBACKS 0U /* FMPI2C register callback disabled */
168 | #define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */
169 | #define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */
170 | #define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U /* LPTIM register callback disabled */
171 | #define USE_HAL_LTDC_REGISTER_CALLBACKS 0U /* LTDC register callback disabled */
172 | #define USE_HAL_MMC_REGISTER_CALLBACKS 0U /* MMC register callback disabled */
173 | #define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */
174 | #define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */
175 | #define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U /* PCCARD register callback disabled */
176 | #define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */
177 | #define USE_HAL_QSPI_REGISTER_CALLBACKS 0U /* QSPI register callback disabled */
178 | #define USE_HAL_RNG_REGISTER_CALLBACKS 0U /* RNG register callback disabled */
179 | #define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */
180 | #define USE_HAL_SAI_REGISTER_CALLBACKS 0U /* SAI register callback disabled */
181 | #define USE_HAL_SD_REGISTER_CALLBACKS 0U /* SD register callback disabled */
182 | #define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */
183 | #define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U /* SDRAM register callback disabled */
184 | #define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */
185 | #define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U /* SPDIFRX register callback disabled */
186 | #define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */
187 | #define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */
188 | #define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */
189 | #define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */
190 | #define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */
191 | #define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */
192 |
193 | /* ########################## Assert Selection ############################## */
194 | /**
195 | * @brief Uncomment the line below to expanse the "assert_param" macro in the
196 | * HAL drivers code
197 | */
198 | /* #define USE_FULL_ASSERT 1U */
199 |
200 | /* ################## Ethernet peripheral configuration ##################### */
201 |
202 | /* Section 1 : Ethernet peripheral configuration */
203 |
204 | /* MAC ADDRESS: MAC_ADDR0:MAC_ADDR1:MAC_ADDR2:MAC_ADDR3:MAC_ADDR4:MAC_ADDR5 */
205 | #define MAC_ADDR0 2U
206 | #define MAC_ADDR1 0U
207 | #define MAC_ADDR2 0U
208 | #define MAC_ADDR3 0U
209 | #define MAC_ADDR4 0U
210 | #define MAC_ADDR5 0U
211 |
212 | /* Definition of the Ethernet driver buffers size and count */
213 | #define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for receive */
214 | #define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for transmit */
215 | #define ETH_RXBUFNB 4U /* 4 Rx buffers of size ETH_RX_BUF_SIZE */
216 | #define ETH_TXBUFNB 4U /* 4 Tx buffers of size ETH_TX_BUF_SIZE */
217 |
218 | /* Section 2: PHY configuration section */
219 |
220 | /* DP83848 PHY Address*/
221 | #define DP83848_PHY_ADDRESS 0x01U
222 | /* PHY Reset delay these values are based on a 1 ms Systick interrupt*/
223 | #define PHY_RESET_DELAY 0x000000FFU
224 | /* PHY Configuration delay */
225 | #define PHY_CONFIG_DELAY 0x00000FFFU
226 |
227 | #define PHY_READ_TO 0x0000FFFFU
228 | #define PHY_WRITE_TO 0x0000FFFFU
229 |
230 | /* Section 3: Common PHY Registers */
231 |
232 | #define PHY_BCR ((uint16_t) 0x0000) /*!< Transceiver Basic Control Register */
233 | #define PHY_BSR ((uint16_t) 0x0001) /*!< Transceiver Basic Status Register */
234 |
235 | #define PHY_RESET ((uint16_t) 0x8000) /*!< PHY Reset */
236 | #define PHY_LOOPBACK ((uint16_t) 0x4000) /*!< Select loop-back mode */
237 | #define PHY_FULLDUPLEX_100M ((uint16_t) 0x2100) /*!< Set the full-duplex mode at 100 Mb/s */
238 | #define PHY_HALFDUPLEX_100M ((uint16_t) 0x2000) /*!< Set the half-duplex mode at 100 Mb/s */
239 | #define PHY_FULLDUPLEX_10M ((uint16_t) 0x0100) /*!< Set the full-duplex mode at 10 Mb/s */
240 | #define PHY_HALFDUPLEX_10M ((uint16_t) 0x0000) /*!< Set the half-duplex mode at 10 Mb/s */
241 | #define PHY_AUTONEGOTIATION ((uint16_t) 0x1000) /*!< Enable auto-negotiation function */
242 | #define PHY_RESTART_AUTONEGOTIATION ((uint16_t) 0x0200) /*!< Restart auto-negotiation function */
243 | #define PHY_POWERDOWN ((uint16_t) 0x0800) /*!< Select the power down mode */
244 | #define PHY_ISOLATE ((uint16_t) 0x0400) /*!< Isolate PHY from MII */
245 |
246 | #define PHY_AUTONEGO_COMPLETE ((uint16_t) 0x0020) /*!< Auto-Negotiation process completed */
247 | #define PHY_LINKED_STATUS ((uint16_t) 0x0004) /*!< Valid link established */
248 | #define PHY_JABBER_DETECTION ((uint16_t) 0x0002) /*!< Jabber condition detected */
249 |
250 | /* Section 4: Extended PHY Registers */
251 |
252 | #define PHY_SR ((uint16_t) 0x0010) /*!< PHY status register Offset */
253 | #define PHY_MICR ((uint16_t) 0x0011) /*!< MII Interrupt Control Register */
254 | #define PHY_MISR ((uint16_t) 0x0012) /*!< MII Interrupt Status and Misc. Control Register */
255 |
256 | #define PHY_LINK_STATUS ((uint16_t) 0x0001) /*!< PHY Link mask */
257 | #define PHY_SPEED_STATUS ((uint16_t) 0x0002) /*!< PHY Speed mask */
258 | #define PHY_DUPLEX_STATUS ((uint16_t) 0x0004) /*!< PHY Duplex mask */
259 |
260 | #define PHY_MICR_INT_EN ((uint16_t) 0x0002) /*!< PHY Enable interrupts */
261 | #define PHY_MICR_INT_OE ((uint16_t) 0x0001) /*!< PHY Enable output interrupt events */
262 |
263 | #define PHY_MISR_LINK_INT_EN ((uint16_t) 0x0020) /*!< Enable Interrupt on change of link status */
264 | #define PHY_LINK_INTERRUPT ((uint16_t) 0x2000) /*!< PHY link status interrupt mask */
265 |
266 | /* ################## SPI peripheral configuration ########################## */
267 |
268 | /* CRC FEATURE: Use to activate CRC feature inside HAL SPI Driver
269 | * Activated: CRC code is present inside driver
270 | * Deactivated: CRC code cleaned from driver
271 | */
272 |
273 | #define USE_SPI_CRC 1U
274 |
275 | /* Includes ------------------------------------------------------------------*/
276 | /**
277 | * @brief Include module's header file
278 | */
279 |
280 | #ifdef HAL_RCC_MODULE_ENABLED
281 | #include "stm32f4xx_hal_rcc.h"
282 | #endif /* HAL_RCC_MODULE_ENABLED */
283 |
284 | #ifdef HAL_GPIO_MODULE_ENABLED
285 | #include "stm32f4xx_hal_gpio.h"
286 | #endif /* HAL_GPIO_MODULE_ENABLED */
287 |
288 | #ifdef HAL_EXTI_MODULE_ENABLED
289 | #include "stm32f4xx_hal_exti.h"
290 | #endif /* HAL_EXTI_MODULE_ENABLED */
291 |
292 | #ifdef HAL_DMA_MODULE_ENABLED
293 | #include "stm32f4xx_hal_dma.h"
294 | #endif /* HAL_DMA_MODULE_ENABLED */
295 |
296 | #ifdef HAL_CORTEX_MODULE_ENABLED
297 | #include "stm32f4xx_hal_cortex.h"
298 | #endif /* HAL_CORTEX_MODULE_ENABLED */
299 |
300 | #ifdef HAL_ADC_MODULE_ENABLED
301 | #include "stm32f4xx_hal_adc.h"
302 | #endif /* HAL_ADC_MODULE_ENABLED */
303 |
304 | #ifdef HAL_CAN_MODULE_ENABLED
305 | #include "stm32f4xx_hal_can.h"
306 | #endif /* HAL_CAN_MODULE_ENABLED */
307 |
308 | #ifdef HAL_CAN_LEGACY_MODULE_ENABLED
309 | #include "stm32f4xx_hal_can_legacy.h"
310 | #endif /* HAL_CAN_LEGACY_MODULE_ENABLED */
311 |
312 | #ifdef HAL_CRC_MODULE_ENABLED
313 | #include "stm32f4xx_hal_crc.h"
314 | #endif /* HAL_CRC_MODULE_ENABLED */
315 |
316 | #ifdef HAL_CRYP_MODULE_ENABLED
317 | #include "stm32f4xx_hal_cryp.h"
318 | #endif /* HAL_CRYP_MODULE_ENABLED */
319 |
320 | #ifdef HAL_DMA2D_MODULE_ENABLED
321 | #include "stm32f4xx_hal_dma2d.h"
322 | #endif /* HAL_DMA2D_MODULE_ENABLED */
323 |
324 | #ifdef HAL_DAC_MODULE_ENABLED
325 | #include "stm32f4xx_hal_dac.h"
326 | #endif /* HAL_DAC_MODULE_ENABLED */
327 |
328 | #ifdef HAL_DCMI_MODULE_ENABLED
329 | #include "stm32f4xx_hal_dcmi.h"
330 | #endif /* HAL_DCMI_MODULE_ENABLED */
331 |
332 | #ifdef HAL_ETH_MODULE_ENABLED
333 | #include "stm32f4xx_hal_eth.h"
334 | #endif /* HAL_ETH_MODULE_ENABLED */
335 |
336 | #ifdef HAL_FLASH_MODULE_ENABLED
337 | #include "stm32f4xx_hal_flash.h"
338 | #endif /* HAL_FLASH_MODULE_ENABLED */
339 |
340 | #ifdef HAL_SRAM_MODULE_ENABLED
341 | #include "stm32f4xx_hal_sram.h"
342 | #endif /* HAL_SRAM_MODULE_ENABLED */
343 |
344 | #ifdef HAL_NOR_MODULE_ENABLED
345 | #include "stm32f4xx_hal_nor.h"
346 | #endif /* HAL_NOR_MODULE_ENABLED */
347 |
348 | #ifdef HAL_NAND_MODULE_ENABLED
349 | #include "stm32f4xx_hal_nand.h"
350 | #endif /* HAL_NAND_MODULE_ENABLED */
351 |
352 | #ifdef HAL_PCCARD_MODULE_ENABLED
353 | #include "stm32f4xx_hal_pccard.h"
354 | #endif /* HAL_PCCARD_MODULE_ENABLED */
355 |
356 | #ifdef HAL_SDRAM_MODULE_ENABLED
357 | #include "stm32f4xx_hal_sdram.h"
358 | #endif /* HAL_SDRAM_MODULE_ENABLED */
359 |
360 | #ifdef HAL_HASH_MODULE_ENABLED
361 | #include "stm32f4xx_hal_hash.h"
362 | #endif /* HAL_HASH_MODULE_ENABLED */
363 |
364 | #ifdef HAL_I2C_MODULE_ENABLED
365 | #include "stm32f4xx_hal_i2c.h"
366 | #endif /* HAL_I2C_MODULE_ENABLED */
367 |
368 | #ifdef HAL_SMBUS_MODULE_ENABLED
369 | #include "stm32f4xx_hal_smbus.h"
370 | #endif /* HAL_SMBUS_MODULE_ENABLED */
371 |
372 | #ifdef HAL_I2S_MODULE_ENABLED
373 | #include "stm32f4xx_hal_i2s.h"
374 | #endif /* HAL_I2S_MODULE_ENABLED */
375 |
376 | #ifdef HAL_IWDG_MODULE_ENABLED
377 | #include "stm32f4xx_hal_iwdg.h"
378 | #endif /* HAL_IWDG_MODULE_ENABLED */
379 |
380 | #ifdef HAL_LTDC_MODULE_ENABLED
381 | #include "stm32f4xx_hal_ltdc.h"
382 | #endif /* HAL_LTDC_MODULE_ENABLED */
383 |
384 | #ifdef HAL_PWR_MODULE_ENABLED
385 | #include "stm32f4xx_hal_pwr.h"
386 | #endif /* HAL_PWR_MODULE_ENABLED */
387 |
388 | #ifdef HAL_RNG_MODULE_ENABLED
389 | #include "stm32f4xx_hal_rng.h"
390 | #endif /* HAL_RNG_MODULE_ENABLED */
391 |
392 | #ifdef HAL_RTC_MODULE_ENABLED
393 | #include "stm32f4xx_hal_rtc.h"
394 | #endif /* HAL_RTC_MODULE_ENABLED */
395 |
396 | #ifdef HAL_SAI_MODULE_ENABLED
397 | #include "stm32f4xx_hal_sai.h"
398 | #endif /* HAL_SAI_MODULE_ENABLED */
399 |
400 | #ifdef HAL_SD_MODULE_ENABLED
401 | #include "stm32f4xx_hal_sd.h"
402 | #endif /* HAL_SD_MODULE_ENABLED */
403 |
404 | #ifdef HAL_SPI_MODULE_ENABLED
405 | #include "stm32f4xx_hal_spi.h"
406 | #endif /* HAL_SPI_MODULE_ENABLED */
407 |
408 | #ifdef HAL_TIM_MODULE_ENABLED
409 | #include "stm32f4xx_hal_tim.h"
410 | #endif /* HAL_TIM_MODULE_ENABLED */
411 |
412 | #ifdef HAL_UART_MODULE_ENABLED
413 | #include "stm32f4xx_hal_uart.h"
414 | #endif /* HAL_UART_MODULE_ENABLED */
415 |
416 | #ifdef HAL_USART_MODULE_ENABLED
417 | #include "stm32f4xx_hal_usart.h"
418 | #endif /* HAL_USART_MODULE_ENABLED */
419 |
420 | #ifdef HAL_IRDA_MODULE_ENABLED
421 | #include "stm32f4xx_hal_irda.h"
422 | #endif /* HAL_IRDA_MODULE_ENABLED */
423 |
424 | #ifdef HAL_SMARTCARD_MODULE_ENABLED
425 | #include "stm32f4xx_hal_smartcard.h"
426 | #endif /* HAL_SMARTCARD_MODULE_ENABLED */
427 |
428 | #ifdef HAL_WWDG_MODULE_ENABLED
429 | #include "stm32f4xx_hal_wwdg.h"
430 | #endif /* HAL_WWDG_MODULE_ENABLED */
431 |
432 | #ifdef HAL_PCD_MODULE_ENABLED
433 | #include "stm32f4xx_hal_pcd.h"
434 | #endif /* HAL_PCD_MODULE_ENABLED */
435 |
436 | #ifdef HAL_HCD_MODULE_ENABLED
437 | #include "stm32f4xx_hal_hcd.h"
438 | #endif /* HAL_HCD_MODULE_ENABLED */
439 |
440 | #ifdef HAL_DSI_MODULE_ENABLED
441 | #include "stm32f4xx_hal_dsi.h"
442 | #endif /* HAL_DSI_MODULE_ENABLED */
443 |
444 | #ifdef HAL_QSPI_MODULE_ENABLED
445 | #include "stm32f4xx_hal_qspi.h"
446 | #endif /* HAL_QSPI_MODULE_ENABLED */
447 |
448 | #ifdef HAL_CEC_MODULE_ENABLED
449 | #include "stm32f4xx_hal_cec.h"
450 | #endif /* HAL_CEC_MODULE_ENABLED */
451 |
452 | #ifdef HAL_FMPI2C_MODULE_ENABLED
453 | #include "stm32f4xx_hal_fmpi2c.h"
454 | #endif /* HAL_FMPI2C_MODULE_ENABLED */
455 |
456 | #ifdef HAL_SPDIFRX_MODULE_ENABLED
457 | #include "stm32f4xx_hal_spdifrx.h"
458 | #endif /* HAL_SPDIFRX_MODULE_ENABLED */
459 |
460 | #ifdef HAL_DFSDM_MODULE_ENABLED
461 | #include "stm32f4xx_hal_dfsdm.h"
462 | #endif /* HAL_DFSDM_MODULE_ENABLED */
463 |
464 | #ifdef HAL_LPTIM_MODULE_ENABLED
465 | #include "stm32f4xx_hal_lptim.h"
466 | #endif /* HAL_LPTIM_MODULE_ENABLED */
467 |
468 | #ifdef HAL_MMC_MODULE_ENABLED
469 | #include "stm32f4xx_hal_mmc.h"
470 | #endif /* HAL_MMC_MODULE_ENABLED */
471 |
472 | /* Exported macro ------------------------------------------------------------*/
473 | #ifdef USE_FULL_ASSERT
474 | /**
475 | * @brief The assert_param macro is used for function's parameters check.
476 | * @param expr If expr is false, it calls assert_failed function
477 | * which reports the name of the source file and the source
478 | * line number of the call that failed.
479 | * If expr is true, it returns no value.
480 | * @retval None
481 | */
482 | #define assert_param(expr) ((expr) ? (void) 0U : assert_failed((uint8_t*) __FILE__, __LINE__))
483 | /* Exported functions ------------------------------------------------------- */
484 | void assert_failed(uint8_t* file, uint32_t line);
485 | #else
486 | #define assert_param(expr) ((void) 0U)
487 | #endif /* USE_FULL_ASSERT */
488 |
489 | #ifdef __cplusplus
490 | }
491 | #endif
492 |
493 | #endif /* __STM32F4xx_HAL_CONF_H */
494 |
495 | /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
496 |
--------------------------------------------------------------------------------
/nanomodbus.h:
--------------------------------------------------------------------------------
1 | /*
2 | nanoMODBUS - A compact MODBUS RTU/TCP C library for microcontrollers
3 |
4 | MIT License
5 |
6 | Copyright (c) 2024 Valerio De Benedetto (@debevv)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 |
28 | /** @file */
29 |
30 | /*! \mainpage nanoMODBUS - A compact MODBUS RTU/TCP C library for microcontrollers
31 | * nanoMODBUS is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained
32 | * system like microcontrollers.
33 | *
34 | * GtiHub: https://github.com/debevv/nanoMODBUS
35 | *
36 | * API reference: \link nanomodbus.h \endlink
37 | *
38 | */
39 |
40 | #ifndef NANOMODBUS_H
41 | #define NANOMODBUS_H
42 |
43 | #include
44 | #include
45 | #include
46 |
47 | #ifdef __cplusplus
48 | extern "C" {
49 | #endif
50 |
51 | /**
52 | * nanoMODBUS errors.
53 | * Values <= 0 are library errors, > 0 are modbus exceptions.
54 | */
55 | typedef enum nmbs_error {
56 | // Library errors
57 | NMBS_ERROR_INVALID_REQUEST = -8, /**< Received invalid request from client */
58 | NMBS_ERROR_INVALID_UNIT_ID = -7, /**< Received invalid unit ID in response from server */
59 | NMBS_ERROR_INVALID_TCP_MBAP = -6, /**< Received invalid TCP MBAP */
60 | NMBS_ERROR_CRC = -5, /**< Received invalid CRC */
61 | NMBS_ERROR_TRANSPORT = -4, /**< Transport error */
62 | NMBS_ERROR_TIMEOUT = -3, /**< Read/write timeout occurred */
63 | NMBS_ERROR_INVALID_RESPONSE = -2, /**< Received invalid response from server */
64 | NMBS_ERROR_INVALID_ARGUMENT = -1, /**< Invalid argument provided */
65 | NMBS_ERROR_NONE = 0, /**< No error */
66 |
67 | // Modbus exceptions
68 | NMBS_EXCEPTION_ILLEGAL_FUNCTION = 1, /**< Modbus exception 1 */
69 | NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2, /**< Modbus exception 2 */
70 | NMBS_EXCEPTION_ILLEGAL_DATA_VALUE = 3, /**< Modbus exception 3 */
71 | NMBS_EXCEPTION_SERVER_DEVICE_FAILURE = 4, /**< Modbus exception 4 */
72 | } nmbs_error;
73 |
74 |
75 | /**
76 | * Return whether the nmbs_error is a modbus exception
77 | * @e nmbs_error to check
78 | */
79 | #define nmbs_error_is_exception(e) ((e) > 0 && (e) < 5)
80 |
81 | #ifndef NMBS_BITFIELD_MAX
82 | #define NMBS_BITFIELD_MAX 2000
83 | #endif
84 |
85 | /* check coil count divisible by 8 */
86 | #if ((NMBS_BITFIELD_MAX & 7) > 0)
87 | #error "NMBS_BITFIELD_MAX must be divisible by 8"
88 | #endif
89 |
90 | #define NMBS_BITFIELD_BYTES_MAX (NMBS_BITFIELD_MAX / 8)
91 |
92 | /**
93 | * Bitfield consisting of 2000 coils/discrete inputs
94 | */
95 | typedef uint8_t nmbs_bitfield[NMBS_BITFIELD_BYTES_MAX];
96 |
97 | /**
98 | * Bitfield consisting of 256 values
99 | */
100 | typedef uint8_t nmbs_bitfield_256[32];
101 |
102 | /**
103 | * Read a bit from the nmbs_bitfield bf at position b
104 | */
105 | #define nmbs_bitfield_read(bf, b) ((bool) ((bf)[(b) >> 3] & (0x1 << ((b) & (8 - 1)))))
106 |
107 | /**
108 | * Set a bit of the nmbs_bitfield bf at position b
109 | */
110 | #define nmbs_bitfield_set(bf, b) (((bf)[(b) >> 3]) = (((bf)[(b) >> 3]) | (0x1 << ((b) & (8 - 1)))))
111 |
112 | /**
113 | * Reset a bit of the nmbs_bitfield bf at position b
114 | */
115 | #define nmbs_bitfield_unset(bf, b) (((bf)[(b) >> 3]) = (((bf)[(b) >> 3]) & ~(0x1 << ((b) & (8 - 1)))))
116 |
117 | /**
118 | * Write value v to the nmbs_bitfield bf at position b
119 | */
120 | #define nmbs_bitfield_write(bf, b, v) ((bf)[(b) >> 3] = ((bf)[(b) >> 3] & ~(1 << ((b) & 7))) | ((v) << ((b) & 7)))
121 | /**
122 | * Reset (zero) the whole bitfield
123 | */
124 | #define nmbs_bitfield_reset(bf) memset(bf, 0, sizeof(bf))
125 |
126 | /**
127 | * Modbus transport type.
128 | */
129 | typedef enum nmbs_transport {
130 | NMBS_TRANSPORT_RTU = 1,
131 | NMBS_TRANSPORT_TCP = 2,
132 | } nmbs_transport;
133 |
134 |
135 | /**
136 | * nanoMODBUS platform configuration struct.
137 | * Passed to nmbs_server_create() and nmbs_client_create().
138 | *
139 | * read() and write() are the platform-specific methods that read/write data to/from a serial port or a TCP connection.
140 | *
141 | * Both methods should block until either:
142 | * - `count` bytes of data are read/written
143 | * - the byte timeout, with `byte_timeout_ms >= 0`, expires
144 | *
145 | * A value `< 0` for `byte_timeout_ms` means infinite timeout.
146 | * With a value `== 0` for `byte_timeout_ms`, the method should read/write once in a non-blocking fashion and return immediately.
147 | *
148 | *
149 | * Their return value should be the number of bytes actually read/written, or `< 0` in case of error.
150 | * A return value between `0` and `count - 1` will be treated as if a timeout occurred on the transport side. All other
151 | * values will be treated as transport errors.
152 | *
153 | * Additionally, an optional crc_calc() function can be defined to override the default nanoMODBUS CRC calculation function.
154 | *
155 | * These methods accept a pointer to arbitrary user-data, which is the arg member of this struct.
156 | * After the creation of an instance it can be changed with nmbs_set_platform_arg().
157 | */
158 | typedef struct nmbs_platform_conf {
159 | nmbs_transport transport; /*!< Transport type */
160 | int32_t (*read)(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,
161 | void* arg); /*!< Bytes read transport function pointer */
162 | int32_t (*write)(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,
163 | void* arg); /*!< Bytes write transport function pointer */
164 | uint16_t (*crc_calc)(const uint8_t* data, uint32_t length,
165 | void* arg); /*!< CRC calculation function pointer. Optional */
166 | void* arg; /*!< User data, will be passed to functions above */
167 | uint32_t initialized; /*!< Reserved, workaround for older user code not calling nmbs_platform_conf_create() */
168 | } nmbs_platform_conf;
169 |
170 |
171 | /**
172 | * Modbus server request callbacks. Passed to nmbs_server_create().
173 | *
174 | * These methods accept a pointer to arbitrary user data, which is the arg member of the nmbs_platform_conf that was passed
175 | * to nmbs_server_create together with this struct.
176 | *
177 | * `unit_id` is the RTU unit ID of the request sender. It is always 0 on TCP.
178 | */
179 | typedef struct nmbs_callbacks {
180 | #ifndef NMBS_SERVER_DISABLED
181 | #ifndef NMBS_SERVER_READ_COILS_DISABLED
182 | nmbs_error (*read_coils)(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg);
183 | #endif
184 |
185 | #ifndef NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED
186 | nmbs_error (*read_discrete_inputs)(uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out, uint8_t unit_id,
187 | void* arg);
188 | #endif
189 |
190 | #if !defined(NMBS_SERVER_READ_HOLDING_REGISTERS_DISABLED) || !defined(NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED)
191 | nmbs_error (*read_holding_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
192 | void* arg);
193 | #endif
194 |
195 | #ifndef NMBS_SERVER_READ_INPUT_REGISTERS_DISABLED
196 | nmbs_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
197 | void* arg);
198 | #endif
199 |
200 | #ifndef NMBS_SERVER_WRITE_SINGLE_COIL_DISABLED
201 | nmbs_error (*write_single_coil)(uint16_t address, bool value, uint8_t unit_id, void* arg);
202 | #endif
203 |
204 | #ifndef NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED
205 | nmbs_error (*write_single_register)(uint16_t address, uint16_t value, uint8_t unit_id, void* arg);
206 | #endif
207 |
208 | #ifndef NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED
209 | nmbs_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
210 | void* arg);
211 | #endif
212 |
213 | #if !defined(NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED) || !defined(NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED)
214 | nmbs_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers,
215 | uint8_t unit_id, void* arg);
216 | #endif
217 |
218 | #ifndef NMBS_SERVER_READ_FILE_RECORD_DISABLED
219 | nmbs_error (*read_file_record)(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
220 | uint8_t unit_id, void* arg);
221 | #endif
222 |
223 | #ifndef NMBS_SERVER_WRITE_FILE_RECORD_DISABLED
224 | nmbs_error (*write_file_record)(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
225 | uint16_t count, uint8_t unit_id, void* arg);
226 | #endif
227 |
228 | #ifndef NMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLED
229 | #define NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH 128
230 | nmbs_error (*read_device_identification)(uint8_t object_id, char buffer[NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH]);
231 | nmbs_error (*read_device_identification_map)(nmbs_bitfield_256 map);
232 | #endif
233 | #endif
234 |
235 | void* arg; // User data, will be passed to functions above
236 | uint32_t initialized; // Reserved, workaround for older user code not calling nmbs_callbacks_create()
237 | } nmbs_callbacks;
238 |
239 |
240 | /**
241 | * nanoMODBUS client/server instance type. All struct members are to be considered private,
242 | * it is not advisable to read/write them directly.
243 | */
244 | typedef struct nmbs_t {
245 | struct {
246 | uint8_t buf[260];
247 | uint16_t buf_idx;
248 |
249 | uint8_t unit_id;
250 | uint8_t fc;
251 | uint16_t transaction_id;
252 | bool broadcast;
253 | bool ignored;
254 | bool complete;
255 | } msg;
256 |
257 | nmbs_callbacks callbacks;
258 |
259 | int32_t byte_timeout_ms;
260 | int32_t read_timeout_ms;
261 |
262 | nmbs_platform_conf platform;
263 |
264 | uint8_t address_rtu;
265 | uint8_t dest_address_rtu;
266 | uint16_t current_tid;
267 | } nmbs_t;
268 |
269 | /**
270 | * Modbus broadcast address. Can be passed to nmbs_set_destination_rtu_address().
271 | */
272 | static const uint8_t NMBS_BROADCAST_ADDRESS = 0;
273 |
274 | /** Set the request/response timeout.
275 | * If the target instance is a server, sets the timeout of the nmbs_server_poll() function.
276 | * If the target instance is a client, sets the response timeout after sending a request. In case of timeout,
277 | * the called method will return NMBS_ERROR_TIMEOUT.
278 | * @param nmbs pointer to the nmbs_t instance
279 | * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled.
280 | */
281 | void nmbs_set_read_timeout(nmbs_t* nmbs, int32_t timeout_ms);
282 |
283 | /** Set the timeout between the reception/transmission of two consecutive bytes.
284 | * @param nmbs pointer to the nmbs_t instance
285 | * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled.
286 | */
287 | void nmbs_set_byte_timeout(nmbs_t* nmbs, int32_t timeout_ms);
288 |
289 | /** Create a new nmbs_platform_conf struct.
290 | * @param platform_conf pointer to the nmbs_platform_conf instance
291 | */
292 | void nmbs_platform_conf_create(nmbs_platform_conf* platform_conf);
293 |
294 | /** Set the pointer to user data argument passed to platform functions.
295 | * @param nmbs pointer to the nmbs_t instance
296 | * @param arg user data argument
297 | */
298 | void nmbs_set_platform_arg(nmbs_t* nmbs, void* arg);
299 |
300 | #ifndef NMBS_SERVER_DISABLED
301 | /** Create a new nmbs_callbacks struct.
302 | * @param callbacks pointer to the nmbs_callbacks instance
303 | */
304 | void nmbs_callbacks_create(nmbs_callbacks* callbacks);
305 |
306 | /** Create a new Modbus server.
307 | * @param nmbs pointer to the nmbs_t instance where the client will be created.
308 | * @param address_rtu RTU address of this server. Can be 0 if transport is not RTU.
309 | * @param platform_conf nmbs_platform_conf struct with platform configuration. It may be discarded after calling this method.
310 | * @param callbacks nmbs_callbacks struct with server request callbacks. It may be discarded after calling this method.
311 | *
312 | * @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise.
313 | */
314 | nmbs_error nmbs_server_create(nmbs_t* nmbs, uint8_t address_rtu, const nmbs_platform_conf* platform_conf,
315 | const nmbs_callbacks* callbacks);
316 |
317 | /** Handle incoming requests to the server.
318 | * This function should be called in a loop in order to serve any incoming request. Its maximum duration, in case of no
319 | * received request, is the value set with nmbs_set_read_timeout() (unless set to < 0).
320 | * @param nmbs pointer to the nmbs_t instance
321 | *
322 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
323 | */
324 | nmbs_error nmbs_server_poll(nmbs_t* nmbs);
325 |
326 | /** Set the pointer to user data argument passed to server request callbacks.
327 | * @param nmbs pointer to the nmbs_t instance
328 | * @param arg user data argument
329 | */
330 | void nmbs_set_callbacks_arg(nmbs_t* nmbs, void* arg);
331 | #endif
332 |
333 | #ifndef NMBS_CLIENT_DISABLED
334 | /** Create a new Modbus client.
335 | * @param nmbs pointer to the nmbs_t instance where the client will be created.
336 | * @param platform_conf nmbs_platform_conf struct with platform configuration. It may be discarded after calling this method.
337 | *
338 | * @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise.
339 | */
340 | nmbs_error nmbs_client_create(nmbs_t* nmbs, const nmbs_platform_conf* platform_conf);
341 |
342 | /** Set the recipient server address of the next request on RTU transport.
343 | * @param nmbs pointer to the nmbs_t instance
344 | * @param address server address
345 | */
346 | void nmbs_set_destination_rtu_address(nmbs_t* nmbs, uint8_t address);
347 |
348 | /** Send a FC 01 (0x01) Read Coils request
349 | * @param nmbs pointer to the nmbs_t instance
350 | * @param address starting address
351 | * @param quantity quantity of coils
352 | * @param coils_out nmbs_bitfield where the coils will be stored
353 | *
354 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
355 | */
356 | nmbs_error nmbs_read_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield coils_out);
357 |
358 | /** Send a FC 02 (0x02) Read Discrete Inputs request
359 | * @param nmbs pointer to the nmbs_t instance
360 | * @param address starting address
361 | * @param quantity quantity of inputs
362 | * @param inputs_out nmbs_bitfield where the discrete inputs will be stored
363 | *
364 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
365 | */
366 | nmbs_error nmbs_read_discrete_inputs(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out);
367 |
368 | /** Send a FC 03 (0x03) Read Holding Registers request
369 | * @param nmbs pointer to the nmbs_t instance
370 | * @param address starting address
371 | * @param quantity quantity of registers
372 | * @param registers_out array where the registers will be stored
373 | *
374 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
375 | */
376 | nmbs_error nmbs_read_holding_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out);
377 |
378 | /** Send a FC 04 (0x04) Read Input Registers request
379 | * @param nmbs pointer to the nmbs_t instance
380 | * @param address starting address
381 | * @param quantity quantity of registers
382 | * @param registers_out array where the registers will be stored
383 | *
384 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
385 | */
386 | nmbs_error nmbs_read_input_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out);
387 |
388 | /** Send a FC 05 (0x05) Write Single Coil request
389 | * @param nmbs pointer to the nmbs_t instance
390 | * @param address coil address
391 | * @param value coil value
392 | *
393 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
394 | */
395 | nmbs_error nmbs_write_single_coil(nmbs_t* nmbs, uint16_t address, bool value);
396 |
397 | /** Send a FC 06 (0x06) Write Single Register request
398 | * @param nmbs pointer to the nmbs_t instance
399 | * @param address register address
400 | * @param value register value
401 | *
402 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
403 | */
404 | nmbs_error nmbs_write_single_register(nmbs_t* nmbs, uint16_t address, uint16_t value);
405 |
406 | /** Send a FC 15 (0x0F) Write Multiple Coils
407 | * @param nmbs pointer to the nmbs_t instance
408 | * @param address starting address
409 | * @param quantity quantity of coils
410 | * @param coils bitfield of coils values
411 | *
412 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
413 | */
414 | nmbs_error nmbs_write_multiple_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const nmbs_bitfield coils);
415 |
416 | /** Send a FC 16 (0x10) Write Multiple Registers
417 | * @param nmbs pointer to the nmbs_t instance
418 | * @param address starting address
419 | * @param quantity quantity of registers
420 | * @param registers array of registers values
421 | *
422 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
423 | */
424 | nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers);
425 |
426 | /** Send a FC 20 (0x14) Read File Record
427 | * @param nmbs pointer to the nmbs_t instance
428 | * @param file_number file number (1 to 65535)
429 | * @param record_number record number from file (0000 to 9999)
430 | * @param registers array of registers to read
431 | * @param count count of registers
432 | *
433 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
434 | */
435 | nmbs_error nmbs_read_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, uint16_t* registers,
436 | uint16_t count);
437 |
438 | /** Send a FC 21 (0x15) Write File Record
439 | * @param nmbs pointer to the nmbs_t instance
440 | * @param file_number file number (1 to 65535)
441 | * @param record_number record number from file (0000 to 9999)
442 | * @param registers array of registers to write
443 | * @param count count of registers
444 | *
445 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
446 | */
447 | nmbs_error nmbs_write_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, const uint16_t* registers,
448 | uint16_t count);
449 |
450 | /** Send a FC 23 (0x17) Read Write Multiple registers
451 | * @param nmbs pointer to the nmbs_t instance
452 | * @param read_address starting read address
453 | * @param read_quantity quantity of registers to read
454 | * @param registers_out array where the read registers will be stored
455 | * @param write_address starting write address
456 | * @param write_quantity quantity of registers to write
457 | * @param registers array of registers values to write
458 | *
459 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
460 | */
461 | nmbs_error nmbs_read_write_registers(nmbs_t* nmbs, uint16_t read_address, uint16_t read_quantity,
462 | uint16_t* registers_out, uint16_t write_address, uint16_t write_quantity,
463 | const uint16_t* registers);
464 |
465 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Basic Object Id values (Read Device ID code 1)
466 | * @param nmbs pointer to the nmbs_t instance
467 | * @param vendor_name char array where the read VendorName value will be stored
468 | * @param product_code char array where the read ProductCode value will be stored
469 | * @param major_minor_revision char array where the read MajorMinorRevision value will be stored
470 | * @param buffers_length length of every char array
471 | *
472 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
473 | */
474 | nmbs_error nmbs_read_device_identification_basic(nmbs_t* nmbs, char* vendor_name, char* product_code,
475 | char* major_minor_revision, uint8_t buffers_length);
476 |
477 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Regular Object Id values (Read Device ID code 2)
478 | * @param nmbs pointer to the nmbs_t instance
479 | * @param vendor_url char array where the read VendorUrl value will be stored
480 | * @param product_name char array where the read ProductName value will be stored
481 | * @param model_name char array where the read ModelName value will be stored
482 | * @param user_application_name char array where the read UserApplicationName value will be stored
483 | * @param buffers_length length of every char array
484 | *
485 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
486 | */
487 | nmbs_error nmbs_read_device_identification_regular(nmbs_t* nmbs, char* vendor_url, char* product_name, char* model_name,
488 | char* user_application_name, uint8_t buffers_length);
489 |
490 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Extended Object Id values (Read Device ID code 3)
491 | * @param nmbs pointer to the nmbs_t instance
492 | * @param object_id_start Object Id to start reading from
493 | * @param ids array where the read Object Ids will be stored
494 | * @param buffers array of char arrays where the read values will be stored
495 | * @param ids_length length of the ids array and buffers array
496 | * @param buffer_length length of each char array
497 | * @param objects_count_out retrieved Object Ids count
498 | *
499 | * @return NMBS_ERROR_NONE if successful, NMBS_INVALID_ARGUMENT if buffers_count is less than retrieved Object Ids count,
500 | * other errors otherwise.
501 | */
502 | nmbs_error nmbs_read_device_identification_extended(nmbs_t* nmbs, uint8_t object_id_start, uint8_t* ids, char** buffers,
503 | uint8_t ids_length, uint8_t buffer_length,
504 | uint8_t* objects_count_out);
505 |
506 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to retrieve a single Object Id value (Read Device ID code 4)
507 | * @param nmbs pointer to the nmbs_t instance
508 | * @param object_id requested Object Id
509 | * @param buffer char array where the resulting value will be stored
510 | * @param buffer_length length of the char array
511 | *
512 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
513 | */
514 | nmbs_error nmbs_read_device_identification(nmbs_t* nmbs, uint8_t object_id, char* buffer, uint8_t buffer_length);
515 |
516 | /** Send a raw Modbus PDU.
517 | * CRC on RTU will be calculated and sent by this function.
518 | * @param nmbs pointer to the nmbs_t instance
519 | * @param fc request function code
520 | * @param data request data. It's up to the caller to convert this data to network byte order
521 | * @param data_len length of the data parameter
522 | *
523 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
524 | */
525 | nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const uint8_t* data, uint16_t data_len);
526 |
527 | /** Receive a raw response Modbus PDU.
528 | * @param nmbs pointer to the nmbs_t instance
529 | * @param data_out response data. It's up to the caller to convert this data to host byte order. Can be NULL.
530 | * @param data_out_len number of bytes to receive
531 | *
532 | * @return NMBS_ERROR_NONE if successful, other errors otherwise.
533 | */
534 | nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, uint8_t* data_out, uint8_t data_out_len);
535 | #endif
536 |
537 | /** Calculate the Modbus CRC of some data.
538 | * @param data Data
539 | * @param length Length of the data
540 | */
541 | uint16_t nmbs_crc_calc(const uint8_t* data, uint32_t length, void* arg);
542 |
543 | #ifndef NMBS_STRERROR_DISABLED
544 | /** Convert a nmbs_error to string
545 | * @param error error to be converted
546 | *
547 | * @return string representation of the error
548 | */
549 | const char* nmbs_strerror(nmbs_error error);
550 | #endif
551 |
552 | #ifdef __cplusplus
553 | } // extern "C"
554 | #endif
555 |
556 | #endif //NANOMODBUS_H
557 |
--------------------------------------------------------------------------------