├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── ci.yml │ ├── clang-format-check.yml │ └── repository-traffic.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── README.md ├── examples ├── arduino │ ├── README.md │ ├── client-rtu │ │ └── client-rtu.ino │ ├── compile-examples.sh │ └── server-rtu │ │ └── server-rtu.ino ├── linux │ ├── client-tcp.c │ ├── platform.h │ └── server-tcp.c ├── rp2040 │ ├── CMakeLists.txt │ ├── README.md │ └── rtu-client.c ├── stm32 │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── CMakeLists.txt │ ├── FreeRTOSConfig.h │ ├── bsp │ │ └── blackpill │ │ │ ├── blackpill.c │ │ │ └── blackpill.h │ ├── modbus_rtu.c │ ├── modbus_tcp.c │ ├── nmbs │ │ ├── port.c │ │ └── port.h │ ├── readme.md │ └── stm32f4xx_hal_conf.h └── win32 │ ├── comm.c │ ├── comm.h │ ├── modbus_cli.c │ └── vs2022 │ ├── .gitignore │ ├── modbus_cli.sln │ ├── modbus_cli.vcxproj │ ├── modbus_cli.vcxproj.filters │ └── modbus_cli.vcxproj.user ├── nanomodbus.c ├── nanomodbus.h └── tests ├── client_disabled.c ├── multi_server_rtu.c ├── nanomodbus_tests.c ├── nanomodbus_tests.h └── server_disabled.c /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | Build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Clone repo 12 | uses: actions/checkout@v3 13 | - name: configure 14 | run: | 15 | cmake -S . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug 16 | - name: build 17 | run: | 18 | cmake --build build --config Debug 19 | - name: Compress Build Directory 20 | run: tar -czf build.tar.gz build/ 21 | - name: Upload Build Artifact 22 | uses: actions/upload-artifact@v4 23 | with: 24 | name: build 25 | path: build.tar.gz 26 | Embedded: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Clone repo 30 | uses: actions/checkout@v3 31 | - name: Build Arduino examples 32 | run: | 33 | mkdir -p build 34 | pushd build 35 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh 36 | popd 37 | export PATH="build/bin:$PATH" 38 | ./examples/arduino/compile-examples.sh 39 | - name: Install ARM dependencies 40 | run: | 41 | sudo apt update 42 | sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential 43 | - name: Build rp2040 examples 44 | run: | 45 | pushd examples/rp2040 46 | git clone --depth=1 https://github.com/raspberrypi/pico-sdk.git 47 | export PICO_SDK_PATH=$PWD/pico-sdk 48 | cmake -S . -B build -DPICO_SDK_PATH=$PWD/pico-sdk 49 | cmake --build build --config Debug 50 | popd 51 | - name: Build stm32 examples 52 | run: | 53 | pushd examples/stm32 54 | cmake -S . -B build 55 | cmake --build build --config Debug 56 | popd 57 | Test: 58 | runs-on: ubuntu-latest 59 | needs: Build # run after Build job 60 | steps: 61 | - uses: actions/checkout@v3 62 | - name: Download Build Directory 63 | uses: actions/download-artifact@v4 64 | with: 65 | name: build 66 | - name: Extract Build Directory 67 | run: | 68 | mkdir -p build 69 | tar -xzf build.tar.gz -C . 70 | - name: List Build Files 71 | run: ls -R . 72 | - name: test 73 | run: | 74 | cd build 75 | ctest 76 | 77 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/repository-traffic.yml: -------------------------------------------------------------------------------- 1 | name: repository traffic 2 | on: 3 | schedule: 4 | - cron: "55 23 * * 0" 5 | 6 | jobs: 7 | traffic: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 12 | - uses: actions/checkout@v2 13 | with: 14 | ref: "traffic" 15 | 16 | # Calculates traffic and clones and stores in CSV file 17 | - name: GitHub traffic 18 | uses: sangonzal/repository-traffic-action@v.0.1.6 19 | env: 20 | TRAFFIC_ACTION_TOKEN: ${{ secrets.TRAFFIC_ACTION_TOKEN }} 21 | 22 | # Commits files to repository 23 | - name: Commit data 24 | uses: EndBug/add-and-commit@v4 25 | with: 26 | author_name: debevv 27 | message: "GitHub traffic" 28 | add: "./traffic/*" 29 | ref: "traffic" # commits to branch "traffic" 30 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "llvm-vs-code-extensions.vscode-clangd", 4 | "ms-vscode.cmake-tools", 5 | "twxs.cmake", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | // Use the CMake panel to launch/debug 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /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() -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | - Debug prints about received and sent messages can be enabled by defining `NMBS_DEBUG` 191 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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) -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /examples/stm32/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | Core/* 3 | Drivers/* 4 | .cproject 5 | .mxproject 6 | .project 7 | *.ld 8 | .vscode/* -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 -------------------------------------------------------------------------------- /examples/stm32/readme.md: -------------------------------------------------------------------------------- 1 | # STM32 nanomodbus porting 2 | 3 | ## Target hardware 4 | 5 | ![Blackpill board image](https://dfimg.dfrobot.com/enshop/image/data/DFR0864/Pinout-Diagram.png) 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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); -------------------------------------------------------------------------------- /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/win32/vs2022/.gitignore: -------------------------------------------------------------------------------- 1 | x64/ 2 | Debug/ 3 | .vs/ 4 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 82 | /** 83 | * Bitfield consisting of 2000 coils/discrete inputs 84 | */ 85 | typedef uint8_t nmbs_bitfield[250]; 86 | 87 | /** 88 | * Bitfield consisting of 256 values 89 | */ 90 | typedef uint8_t nmbs_bitfield_256[32]; 91 | 92 | /** 93 | * Read a bit from the nmbs_bitfield bf at position b 94 | */ 95 | #define nmbs_bitfield_read(bf, b) ((bool) ((bf)[(b) / 8] & (0x1 << ((b) % 8)))) 96 | 97 | /** 98 | * Set a bit of the nmbs_bitfield bf at position b 99 | */ 100 | #define nmbs_bitfield_set(bf, b) (((bf)[(b) / 8]) = (((bf)[(b) / 8]) | (0x1 << ((b) % 8)))) 101 | 102 | /** 103 | * Reset a bit of the nmbs_bitfield bf at position b 104 | */ 105 | #define nmbs_bitfield_unset(bf, b) (((bf)[(b) / 8]) = (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8)))) 106 | 107 | /** 108 | * Write value v to the nmbs_bitfield bf at position b 109 | */ 110 | #define nmbs_bitfield_write(bf, b, v) \ 111 | (((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8))))) 112 | 113 | /** 114 | * Reset (zero) the whole bitfield 115 | */ 116 | #define nmbs_bitfield_reset(bf) memset(bf, 0, sizeof(bf)) 117 | 118 | /** 119 | * Modbus transport type. 120 | */ 121 | typedef enum nmbs_transport { 122 | NMBS_TRANSPORT_RTU = 1, 123 | NMBS_TRANSPORT_TCP = 2, 124 | } nmbs_transport; 125 | 126 | 127 | /** 128 | * nanoMODBUS platform configuration struct. 129 | * Passed to nmbs_server_create() and nmbs_client_create(). 130 | * 131 | * read() and write() are the platform-specific methods that read/write data to/from a serial port or a TCP connection. 132 | * 133 | * Both methods should block until either: 134 | * - `count` bytes of data are read/written 135 | * - the byte timeout, with `byte_timeout_ms >= 0`, expires 136 | * 137 | * A value `< 0` for `byte_timeout_ms` means infinite timeout. 138 | * With a value `== 0` for `byte_timeout_ms`, the method should read/write once in a non-blocking fashion and return immediately. 139 | * 140 | * 141 | * Their return value should be the number of bytes actually read/written, or `< 0` in case of error. 142 | * A return value between `0` and `count - 1` will be treated as if a timeout occurred on the transport side. All other 143 | * values will be treated as transport errors. 144 | * 145 | * Additionally, an optional crc_calc() function can be defined to override the default nanoMODBUS CRC calculation function. 146 | * 147 | * These methods accept a pointer to arbitrary user-data, which is the arg member of this struct. 148 | * After the creation of an instance it can be changed with nmbs_set_platform_arg(). 149 | */ 150 | typedef struct nmbs_platform_conf { 151 | nmbs_transport transport; /*!< Transport type */ 152 | int32_t (*read)(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, 153 | void* arg); /*!< Bytes read transport function pointer */ 154 | int32_t (*write)(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, 155 | void* arg); /*!< Bytes write transport function pointer */ 156 | uint16_t (*crc_calc)(const uint8_t* data, uint32_t length, 157 | void* arg); /*!< CRC calculation function pointer. Optional */ 158 | void* arg; /*!< User data, will be passed to functions above */ 159 | uint32_t initialized; /*!< Reserved, workaround for older user code not calling nmbs_platform_conf_create() */ 160 | } nmbs_platform_conf; 161 | 162 | 163 | /** 164 | * Modbus server request callbacks. Passed to nmbs_server_create(). 165 | * 166 | * These methods accept a pointer to arbitrary user data, which is the arg member of the nmbs_platform_conf that was passed 167 | * to nmbs_server_create together with this struct. 168 | * 169 | * `unit_id` is the RTU unit ID of the request sender. It is always 0 on TCP. 170 | */ 171 | typedef struct nmbs_callbacks { 172 | #ifndef NMBS_SERVER_DISABLED 173 | #ifndef NMBS_SERVER_READ_COILS_DISABLED 174 | nmbs_error (*read_coils)(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg); 175 | #endif 176 | 177 | #ifndef NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED 178 | nmbs_error (*read_discrete_inputs)(uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out, uint8_t unit_id, 179 | void* arg); 180 | #endif 181 | 182 | #if !defined(NMBS_SERVER_READ_HOLDING_REGISTERS_DISABLED) || !defined(NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED) 183 | nmbs_error (*read_holding_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, 184 | void* arg); 185 | #endif 186 | 187 | #ifndef NMBS_SERVER_READ_INPUT_REGISTERS_DISABLED 188 | nmbs_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, 189 | void* arg); 190 | #endif 191 | 192 | #ifndef NMBS_SERVER_WRITE_SINGLE_COIL_DISABLED 193 | nmbs_error (*write_single_coil)(uint16_t address, bool value, uint8_t unit_id, void* arg); 194 | #endif 195 | 196 | #ifndef NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED 197 | nmbs_error (*write_single_register)(uint16_t address, uint16_t value, uint8_t unit_id, void* arg); 198 | #endif 199 | 200 | #ifndef NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED 201 | nmbs_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id, 202 | void* arg); 203 | #endif 204 | 205 | #if !defined(NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED) || !defined(NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED) 206 | nmbs_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers, 207 | uint8_t unit_id, void* arg); 208 | #endif 209 | 210 | #ifndef NMBS_SERVER_READ_FILE_RECORD_DISABLED 211 | nmbs_error (*read_file_record)(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count, 212 | uint8_t unit_id, void* arg); 213 | #endif 214 | 215 | #ifndef NMBS_SERVER_WRITE_FILE_RECORD_DISABLED 216 | nmbs_error (*write_file_record)(uint16_t file_number, uint16_t record_number, const uint16_t* registers, 217 | uint16_t count, uint8_t unit_id, void* arg); 218 | #endif 219 | 220 | #ifndef NMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLED 221 | #define NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH 128 222 | nmbs_error (*read_device_identification)(uint8_t object_id, char buffer[NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH]); 223 | nmbs_error (*read_device_identification_map)(nmbs_bitfield_256 map); 224 | #endif 225 | #endif 226 | 227 | void* arg; // User data, will be passed to functions above 228 | uint32_t initialized; // Reserved, workaround for older user code not calling nmbs_callbacks_create() 229 | } nmbs_callbacks; 230 | 231 | 232 | /** 233 | * nanoMODBUS client/server instance type. All struct members are to be considered private, 234 | * it is not advisable to read/write them directly. 235 | */ 236 | typedef struct nmbs_t { 237 | struct { 238 | uint8_t buf[260]; 239 | uint16_t buf_idx; 240 | 241 | uint8_t unit_id; 242 | uint8_t fc; 243 | uint16_t transaction_id; 244 | bool broadcast; 245 | bool ignored; 246 | bool complete; 247 | } msg; 248 | 249 | nmbs_callbacks callbacks; 250 | 251 | int32_t byte_timeout_ms; 252 | int32_t read_timeout_ms; 253 | 254 | nmbs_platform_conf platform; 255 | 256 | uint8_t address_rtu; 257 | uint8_t dest_address_rtu; 258 | uint16_t current_tid; 259 | } nmbs_t; 260 | 261 | /** 262 | * Modbus broadcast address. Can be passed to nmbs_set_destination_rtu_address(). 263 | */ 264 | static const uint8_t NMBS_BROADCAST_ADDRESS = 0; 265 | 266 | /** Set the request/response timeout. 267 | * If the target instance is a server, sets the timeout of the nmbs_server_poll() function. 268 | * If the target instance is a client, sets the response timeout after sending a request. In case of timeout, 269 | * the called method will return NMBS_ERROR_TIMEOUT. 270 | * @param nmbs pointer to the nmbs_t instance 271 | * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. 272 | */ 273 | void nmbs_set_read_timeout(nmbs_t* nmbs, int32_t timeout_ms); 274 | 275 | /** Set the timeout between the reception/transmission of two consecutive bytes. 276 | * @param nmbs pointer to the nmbs_t instance 277 | * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. 278 | */ 279 | void nmbs_set_byte_timeout(nmbs_t* nmbs, int32_t timeout_ms); 280 | 281 | /** Create a new nmbs_platform_conf struct. 282 | * @param platform_conf pointer to the nmbs_platform_conf instance 283 | */ 284 | void nmbs_platform_conf_create(nmbs_platform_conf* platform_conf); 285 | 286 | /** Set the pointer to user data argument passed to platform functions. 287 | * @param nmbs pointer to the nmbs_t instance 288 | * @param arg user data argument 289 | */ 290 | void nmbs_set_platform_arg(nmbs_t* nmbs, void* arg); 291 | 292 | #ifndef NMBS_SERVER_DISABLED 293 | /** Create a new nmbs_callbacks struct. 294 | * @param callbacks pointer to the nmbs_callbacks instance 295 | */ 296 | void nmbs_callbacks_create(nmbs_callbacks* callbacks); 297 | 298 | /** Create a new Modbus server. 299 | * @param nmbs pointer to the nmbs_t instance where the client will be created. 300 | * @param address_rtu RTU address of this server. Can be 0 if transport is not RTU. 301 | * @param platform_conf nmbs_platform_conf struct with platform configuration. It may be discarded after calling this method. 302 | * @param callbacks nmbs_callbacks struct with server request callbacks. It may be discarded after calling this method. 303 | * 304 | * @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise. 305 | */ 306 | nmbs_error nmbs_server_create(nmbs_t* nmbs, uint8_t address_rtu, const nmbs_platform_conf* platform_conf, 307 | const nmbs_callbacks* callbacks); 308 | 309 | /** Handle incoming requests to the server. 310 | * This function should be called in a loop in order to serve any incoming request. Its maximum duration, in case of no 311 | * received request, is the value set with nmbs_set_read_timeout() (unless set to < 0). 312 | * @param nmbs pointer to the nmbs_t instance 313 | * 314 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 315 | */ 316 | nmbs_error nmbs_server_poll(nmbs_t* nmbs); 317 | 318 | /** Set the pointer to user data argument passed to server request callbacks. 319 | * @param nmbs pointer to the nmbs_t instance 320 | * @param arg user data argument 321 | */ 322 | void nmbs_set_callbacks_arg(nmbs_t* nmbs, void* arg); 323 | #endif 324 | 325 | #ifndef NMBS_CLIENT_DISABLED 326 | /** Create a new Modbus client. 327 | * @param nmbs pointer to the nmbs_t instance where the client will be created. 328 | * @param platform_conf nmbs_platform_conf struct with platform configuration. It may be discarded after calling this method. 329 | * 330 | * @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise. 331 | */ 332 | nmbs_error nmbs_client_create(nmbs_t* nmbs, const nmbs_platform_conf* platform_conf); 333 | 334 | /** Set the recipient server address of the next request on RTU transport. 335 | * @param nmbs pointer to the nmbs_t instance 336 | * @param address server address 337 | */ 338 | void nmbs_set_destination_rtu_address(nmbs_t* nmbs, uint8_t address); 339 | 340 | /** Send a FC 01 (0x01) Read Coils request 341 | * @param nmbs pointer to the nmbs_t instance 342 | * @param address starting address 343 | * @param quantity quantity of coils 344 | * @param coils_out nmbs_bitfield where the coils will be stored 345 | * 346 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 347 | */ 348 | nmbs_error nmbs_read_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield coils_out); 349 | 350 | /** Send a FC 02 (0x02) Read Discrete Inputs request 351 | * @param nmbs pointer to the nmbs_t instance 352 | * @param address starting address 353 | * @param quantity quantity of inputs 354 | * @param inputs_out nmbs_bitfield where the discrete inputs will be stored 355 | * 356 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 357 | */ 358 | nmbs_error nmbs_read_discrete_inputs(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out); 359 | 360 | /** Send a FC 03 (0x03) Read Holding Registers request 361 | * @param nmbs pointer to the nmbs_t instance 362 | * @param address starting address 363 | * @param quantity quantity of registers 364 | * @param registers_out array where the registers will be stored 365 | * 366 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 367 | */ 368 | nmbs_error nmbs_read_holding_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out); 369 | 370 | /** Send a FC 04 (0x04) Read Input Registers request 371 | * @param nmbs pointer to the nmbs_t instance 372 | * @param address starting address 373 | * @param quantity quantity of registers 374 | * @param registers_out array where the registers will be stored 375 | * 376 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 377 | */ 378 | nmbs_error nmbs_read_input_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out); 379 | 380 | /** Send a FC 05 (0x05) Write Single Coil request 381 | * @param nmbs pointer to the nmbs_t instance 382 | * @param address coil address 383 | * @param value coil value 384 | * 385 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 386 | */ 387 | nmbs_error nmbs_write_single_coil(nmbs_t* nmbs, uint16_t address, bool value); 388 | 389 | /** Send a FC 06 (0x06) Write Single Register request 390 | * @param nmbs pointer to the nmbs_t instance 391 | * @param address register address 392 | * @param value register value 393 | * 394 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 395 | */ 396 | nmbs_error nmbs_write_single_register(nmbs_t* nmbs, uint16_t address, uint16_t value); 397 | 398 | /** Send a FC 15 (0x0F) Write Multiple Coils 399 | * @param nmbs pointer to the nmbs_t instance 400 | * @param address starting address 401 | * @param quantity quantity of coils 402 | * @param coils bitfield of coils values 403 | * 404 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 405 | */ 406 | nmbs_error nmbs_write_multiple_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const nmbs_bitfield coils); 407 | 408 | /** Send a FC 16 (0x10) Write Multiple Registers 409 | * @param nmbs pointer to the nmbs_t instance 410 | * @param address starting address 411 | * @param quantity quantity of registers 412 | * @param registers array of registers values 413 | * 414 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 415 | */ 416 | nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers); 417 | 418 | /** Send a FC 20 (0x14) Read File Record 419 | * @param nmbs pointer to the nmbs_t instance 420 | * @param file_number file number (1 to 65535) 421 | * @param record_number record number from file (0000 to 9999) 422 | * @param registers array of registers to read 423 | * @param count count of registers 424 | * 425 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 426 | */ 427 | nmbs_error nmbs_read_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, uint16_t* registers, 428 | uint16_t count); 429 | 430 | /** Send a FC 21 (0x15) Write File Record 431 | * @param nmbs pointer to the nmbs_t instance 432 | * @param file_number file number (1 to 65535) 433 | * @param record_number record number from file (0000 to 9999) 434 | * @param registers array of registers to write 435 | * @param count count of registers 436 | * 437 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 438 | */ 439 | nmbs_error nmbs_write_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, const uint16_t* registers, 440 | uint16_t count); 441 | 442 | /** Send a FC 23 (0x17) Read Write Multiple registers 443 | * @param nmbs pointer to the nmbs_t instance 444 | * @param read_address starting read address 445 | * @param read_quantity quantity of registers to read 446 | * @param registers_out array where the read registers will be stored 447 | * @param write_address starting write address 448 | * @param write_quantity quantity of registers to write 449 | * @param registers array of registers values to write 450 | * 451 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 452 | */ 453 | nmbs_error nmbs_read_write_registers(nmbs_t* nmbs, uint16_t read_address, uint16_t read_quantity, 454 | uint16_t* registers_out, uint16_t write_address, uint16_t write_quantity, 455 | const uint16_t* registers); 456 | 457 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Basic Object Id values (Read Device ID code 1) 458 | * @param nmbs pointer to the nmbs_t instance 459 | * @param vendor_name char array where the read VendorName value will be stored 460 | * @param product_code char array where the read ProductCode value will be stored 461 | * @param major_minor_revision char array where the read MajorMinorRevision value will be stored 462 | * @param buffers_length length of every char array 463 | * 464 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 465 | */ 466 | nmbs_error nmbs_read_device_identification_basic(nmbs_t* nmbs, char* vendor_name, char* product_code, 467 | char* major_minor_revision, uint8_t buffers_length); 468 | 469 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Regular Object Id values (Read Device ID code 2) 470 | * @param nmbs pointer to the nmbs_t instance 471 | * @param vendor_url char array where the read VendorUrl value will be stored 472 | * @param product_name char array where the read ProductName value will be stored 473 | * @param model_name char array where the read ModelName value will be stored 474 | * @param user_application_name char array where the read UserApplicationName value will be stored 475 | * @param buffers_length length of every char array 476 | * 477 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 478 | */ 479 | nmbs_error nmbs_read_device_identification_regular(nmbs_t* nmbs, char* vendor_url, char* product_name, char* model_name, 480 | char* user_application_name, uint8_t buffers_length); 481 | 482 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to read all Extended Object Id values (Read Device ID code 3) 483 | * @param nmbs pointer to the nmbs_t instance 484 | * @param object_id_start Object Id to start reading from 485 | * @param ids array where the read Object Ids will be stored 486 | * @param buffers array of char arrays where the read values will be stored 487 | * @param ids_length length of the ids array and buffers array 488 | * @param buffer_length length of each char array 489 | * @param objects_count_out retrieved Object Ids count 490 | * 491 | * @return NMBS_ERROR_NONE if successful, NMBS_INVALID_ARGUMENT if buffers_count is less than retrieved Object Ids count, 492 | * other errors otherwise. 493 | */ 494 | nmbs_error nmbs_read_device_identification_extended(nmbs_t* nmbs, uint8_t object_id_start, uint8_t* ids, char** buffers, 495 | uint8_t ids_length, uint8_t buffer_length, 496 | uint8_t* objects_count_out); 497 | 498 | /** Send a FC 43 / 14 (0x2B / 0x0E) Read Device Identification to retrieve a single Object Id value (Read Device ID code 4) 499 | * @param nmbs pointer to the nmbs_t instance 500 | * @param object_id requested Object Id 501 | * @param buffer char array where the resulting value will be stored 502 | * @param buffer_length length of the char array 503 | * 504 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 505 | */ 506 | nmbs_error nmbs_read_device_identification(nmbs_t* nmbs, uint8_t object_id, char* buffer, uint8_t buffer_length); 507 | 508 | /** Send a raw Modbus PDU. 509 | * CRC on RTU will be calculated and sent by this function. 510 | * @param nmbs pointer to the nmbs_t instance 511 | * @param fc request function code 512 | * @param data request data. It's up to the caller to convert this data to network byte order 513 | * @param data_len length of the data parameter 514 | * 515 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 516 | */ 517 | nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const uint8_t* data, uint16_t data_len); 518 | 519 | /** Receive a raw response Modbus PDU. 520 | * @param nmbs pointer to the nmbs_t instance 521 | * @param data_out response data. It's up to the caller to convert this data to host byte order. Can be NULL. 522 | * @param data_out_len number of bytes to receive 523 | * 524 | * @return NMBS_ERROR_NONE if successful, other errors otherwise. 525 | */ 526 | nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, uint8_t* data_out, uint8_t data_out_len); 527 | #endif 528 | 529 | /** Calculate the Modbus CRC of some data. 530 | * @param data Data 531 | * @param length Length of the data 532 | */ 533 | uint16_t nmbs_crc_calc(const uint8_t* data, uint32_t length, void* arg); 534 | 535 | #ifndef NMBS_STRERROR_DISABLED 536 | /** Convert a nmbs_error to string 537 | * @param error error to be converted 538 | * 539 | * @return string representation of the error 540 | */ 541 | const char* nmbs_strerror(nmbs_error error); 542 | #endif 543 | 544 | #ifdef __cplusplus 545 | } // extern "C" 546 | #endif 547 | 548 | #endif //NANOMODBUS_H 549 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, 5000); 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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------