├── .github └── workflows │ └── action.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── SConscript ├── doc ├── Modbus_Application_Protocol_V1_1b3.pdf └── doxygen │ ├── Agile_Modbus.chm │ ├── Doxyfile │ ├── figures │ ├── ModbusPollRTUConnection.jpg │ ├── ModbusPollSetup.jpg │ ├── ModbusPollTCPConnection.jpg │ ├── ModbusProtocol.jpg │ ├── ModbusSlaveRTUConnection.jpg │ ├── ModbusSlaveSetup.jpg │ ├── ModbusSlaveShow.jpg │ ├── ModbusSlaveTCPConnection.jpg │ ├── ModbusSlaveTimeoutShow.jpg │ ├── RTUMaster.jpg │ ├── TCPMaster.jpg │ ├── VirtualCom.jpg │ ├── VirtualComGroup.jpg │ ├── agile_modbus_slave_util_callback.png │ ├── rtu_broadcast.gif │ ├── rtu_p2p.gif │ └── zanshang.jpg │ └── output │ ├── figures │ ├── ModbusPollRTUConnection.jpg │ ├── ModbusPollSetup.jpg │ ├── ModbusPollTCPConnection.jpg │ ├── ModbusProtocol.jpg │ ├── ModbusSlaveRTUConnection.jpg │ ├── ModbusSlaveSetup.jpg │ ├── ModbusSlaveShow.jpg │ ├── ModbusSlaveTCPConnection.jpg │ ├── ModbusSlaveTimeoutShow.jpg │ ├── RTUMaster.jpg │ ├── TCPMaster.jpg │ ├── VirtualCom.jpg │ ├── VirtualComGroup.jpg │ ├── agile_modbus_slave_util_callback.png │ ├── rtu_broadcast.gif │ ├── rtu_p2p.gif │ └── zanshang.jpg │ └── index.html ├── examples ├── CMakeLists.txt ├── README.md ├── common │ ├── dbg_log.h │ ├── ringbuffer.c │ ├── ringbuffer.h │ ├── rt_tick.c │ ├── rt_tick.h │ ├── rtservice.h │ ├── serial.c │ ├── serial.h │ ├── tcp.c │ └── tcp.h ├── figures │ ├── ModbusPollRTUConnection.jpg │ ├── ModbusPollSetup.jpg │ ├── ModbusPollTCPConnection.jpg │ ├── ModbusProtocol.jpg │ ├── ModbusSlaveRTUConnection.jpg │ ├── ModbusSlaveSetup.jpg │ ├── ModbusSlaveShow.jpg │ ├── ModbusSlaveTCPConnection.jpg │ ├── ModbusSlaveTimeoutShow.jpg │ ├── RTUMaster.jpg │ ├── TCPMaster.jpg │ ├── VirtualCom.jpg │ ├── VirtualComGroup.jpg │ ├── rtu_broadcast.gif │ └── rtu_p2p.gif ├── rtu_broadcast │ ├── CMakeLists.txt │ ├── broadcast_master.c │ └── broadcast_slave.c ├── rtu_master │ ├── CMakeLists.txt │ └── rtu_master.c ├── rtu_p2p │ ├── CMakeLists.txt │ ├── p2p_master.c │ └── p2p_slave.c ├── slave │ ├── CMakeLists.txt │ ├── bits.c │ ├── input_bits.c │ ├── input_registers.c │ ├── registers.c │ ├── rtu_slave.c │ ├── slave.c │ ├── slave.h │ └── tcp_slave.c └── tcp_master │ ├── CMakeLists.txt │ └── tcp_master.c ├── figures ├── ModbusProtocol.jpg ├── agile_modbus_slave_util_callback.png └── zanshang.jpg ├── inc ├── agile_modbus.h ├── agile_modbus_rtu.h └── agile_modbus_tcp.h ├── src ├── agile_modbus.c ├── agile_modbus_rtu.c └── agile_modbus_tcp.c └── util ├── agile_modbus_slave_util.c └── agile_modbus_slave_util.h /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: Agile_Modbus 2 | 3 | on: 4 | push: 5 | branches: 6 | # Make sure this is the branch name you are using 7 | - master 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | example-build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | # If your document requires Git submodules, uncomment the next line 22 | # submodules: true 23 | 24 | - name: Install Tools 25 | shell: bash 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install cmake 29 | 30 | - name: Example Compile 31 | shell: bash 32 | run: | 33 | cd examples 34 | cmake -B build 35 | cmake --build build 36 | 37 | - name: Package Binaries 38 | shell: bash 39 | run: | 40 | cd examples/build/bin 41 | tar -czvf binaries.tar.gz * 42 | 43 | - name: Upload examples bin 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: examples-bin 47 | path: examples/build/bin/binaries.tar.gz 48 | 49 | deploy-gh-pages: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | with: 55 | fetch-depth: 0 56 | # If your document requires Git submodules, uncomment the next line 57 | # submodules: true 58 | 59 | - name: Install Tools 60 | shell: bash 61 | run: | 62 | sudo apt-get update 63 | sudo apt-get -qq install doxygen graphviz 64 | 65 | - name: generat doxygen html 66 | shell: bash 67 | run: | 68 | ls 69 | cd doc/doxygen 70 | doxygen Doxyfile 71 | 72 | - name: Deployment documentation 73 | uses: JamesIves/github-pages-deploy-action@v4 74 | with: 75 | # This is the branch name to which the document is deployed 76 | branch: gh-pages 77 | folder: doc/doxygen/output 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | .vscode 45 | build 46 | 47 | # Kernel Module Compile Results 48 | *.mod* 49 | *.cmd 50 | .tmp_versions/ 51 | modules.order 52 | Module.symvers 53 | Mkfile.old 54 | dkms.conf 55 | 56 | # Documentation 57 | doc/doxygen/output/* 58 | !doc/doxygen/output/index.html 59 | !doc/doxygen/output/figures 60 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Update record 2 | 3 | ## Agile Modbus 1.1.0 released 4 | 5 | ### New function 6 | 7 | 2021-12-02: Ma Longwei 8 | 9 | * Add Doxygen comments and generate documentation 10 | 11 | 2021-12-28: Ma Longwei 12 | 13 | * Add RTU and TCP host examples 14 | * Add RTU and TCP slave examples 15 | * Add sample document 16 | 17 | 2022-01-08: Ma Longwei 18 | 19 | * Add RTU point-to-point transmission file example 20 | * Add RTU broadcast transmission file example 21 | 22 | ### Revise 23 | 24 | 2022-01-06: Ma Longwei 25 | 26 | * Modify the slave example, RTU and TCP use the same slave callback 27 | * TCP slave supports up to 5 client access 28 | * The TCP slave will automatically disconnect if it does not receive the correct message within 10s. 29 | 30 | 2022-01-08: Ma Longwei 31 | 32 | * Remove the length limit in receiving data judgment 33 | * Remove `agile_modbus_serialize_raw_request`'s length limit on raw data 34 | 35 | ## Agile Modbus 1.1.1 released 36 | 37 | ### Revise 38 | 39 | 2022-06-22: Ma Longwei 40 | 41 | * README.md adds a bootloader link that supports Modbus firmware upgrade based on RT-Thread on AT32F437 42 | * Add HPM6750_Boot link 43 | * Change LICENSE to `Apache-2.0` 44 | 45 | ## Agile Modbus 1.1.2 released 46 | 47 | ### New function 48 | 49 | 2022-07-28: Ma Longwei 50 | 51 | * Provide simple slave access `agile_modbus_slave_util_callback` interface 52 | 53 | ### Revise 54 | 55 | 2022-07-28: Ma Longwei 56 | 57 | * `agile_modbus_slave_handle` adds `slave callback private data` parameter 58 | * `agile_modbus_slave_callback_t` adds `private data` parameter 59 | * The slave example in `examples` uses the `agile_modbus_slave_util_callback` interface to implement register reading and writing 60 | 61 | ## Agile Modbus 1.1.3 released 62 | 63 | ### Revise 64 | 65 | 2022-11-22: Ma Longwei 66 | 67 | * Writing a single register in `agile_modbus_slave_handle` will point the `slave_info.buf` pointer to the local variable address. This address will be used by other variables after turning on compiler optimization. Modify it to point to the global variable address within the function. 68 | 69 | ## Agile Modbus 1.1.4 released 70 | 71 | ### Revise 72 | 73 | * fixed some warnings for some compiltion IDE such as ses 74 | 75 | ### New function 76 | 77 | * Add API `agile_modbus_compute_response_length_from_request`: Obtain the length of the response from the slave based on the request 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agile Modbus 2 | 3 | ## 1. Introduction 4 | 5 | Agile Modbus is a lightweight modbus protocol stack that meets the needs of users in any scenario. 6 | 7 | ![ModbusProtocol](./figures/ModbusProtocol.jpg) 8 | 9 | - Online documentation: [API Manual](https://loogg.github.io/agile_modbus/) 10 | 11 | - The `examples` folder provides examples on PC 12 | 13 | - See examples on MCU [mcu_demos](https://github.com/loogg/agile_modbus_mcu_demos) 14 | 15 | - Bootloader based on RT-Thread on AT32F437 that supports Modbus firmware upgrade: [AT32F437_Boot](https://github.com/loogg/AT32F437_Boot) 16 | 17 | - Bootloader based on RT-Thread on HPM6750 that supports Modbus firmware upgrade: [HPM6750_Boot](https://github.com/loogg/HPM6750_Boot) 18 | 19 | ### 1.1. Features 20 | 21 | 1. Supports rtu and tcp protocols, is developed using pure C, does not involve any hardware interface, and can be used directly on any form of hardware. 22 | 2. Since it is developed using pure C and does not involve hardware, it can run the tcp protocol on the serial port and the rtu protocol on the network. 23 | 3. Support custom protocols compliant with modbus format. 24 | 4. Supports multiple masters and multiple slaves at the same time. 25 | 5. It is easy to use. You only need to initialize the rtu or tcp handle and call the corresponding API to package and unpack. 26 | 27 | ### 1.2. Directory structure 28 | 29 | | Name | Description | 30 | | ---- | ---- | 31 | | doc | documentation | 32 | | examples | examples | 33 | | figures | materials | 34 | | inc | header file | 35 | | src | source code | 36 | | util | Provides simple and practical components | 37 | 38 | ### 1.3. License 39 | 40 | Agile Modbus complies with the `Apache-2.0` license, see the `LICENSE` file for details. 41 | 42 | ## 2. Use Agile Modbus 43 | 44 | Please view the help document [doc/doxygen/Agile_Modbus.chm](./doc/doxygen/Agile_Modbus.chm) 45 | 46 | ### 2.1. Transplantation 47 | 48 | - Users need to implement the `send data`, `wait for data reception to end` and `clear the receive buffer` functions of the hardware interface 49 | 50 | Regarding `waiting for data reception to end`, the following ideas are provided: 51 | 52 | 1. General method 53 | 54 | Every 20 /50 ms (this time can be set according to the baud rate and hardware, here is just a reference value) reads data from the hardware interface and stores it in the buffer and updates the offset until it cannot be read or the buffer is full. , exit reading. 55 | 56 | This applies to both bare metal and operating systems, which can accomplish blocking via `select` or `semaphore`. 57 | 58 | 2. Serial port `DMA + IDLE` interrupt mode 59 | 60 | Configure the `DMA + IDLE` interrupt, enable the flag in the interrupt, and determine whether the flag is set in the application program. 61 | 62 | However, this solution is prone to problems. If the data bytes are slightly staggered, it will not be a frame. The first option is recommended. 63 | 64 | - Host: 65 | 66 | 1. `agile_modbus_rtu_init` / `agile_modbus_tcp_init` initializes `RTU/TCP` environment 67 | 2. `agile_modbus_set_slave` sets the slave address 68 | 3. `Clear the receive cache` 69 | 4. `agile_modbus_serialize_xxx` package request data 70 | 5. `Send data` 71 | 6. `Waiting for data reception to end` 72 | 7. `agile_modbus_deserialize_xxx` Parse response data 73 | 8. Data processed by users 74 | 75 | - Slave machine: 76 | 77 | 1. Implement the `agile_modbus_slave_callback_t` type callback function 78 | 2. `agile_modbus_rtu_init` / `agile_modbus_tcp_init` initializes `RTU/TCP` environment 79 | 3. `agile_modbus_set_slave` sets the slave address 80 | 4. `Waiting for data reception to end` 81 | 5. `agile_modbus_slave_handle` processes request data 82 | 6. `Clear the receive buffer` (optional) 83 | 7. `Send data` 84 | 85 | - Special function code 86 | 87 | You need to call the `agile_modbus_set_compute_meta_length_after_function_cb` and `agile_modbus_set_compute_data_length_after_meta_cb` APIs to set the callbacks for special function codes to be processed in master-slave mode. 88 | 89 | - `agile_modbus_set_compute_meta_length_after_function_cb` 90 | 91 | `msg_type == AGILE_MODBUS_MSG_INDICATION`: Returns the data element length of the host request message (uint8_t type). If it is not a special function code, 0 must be returned. 92 | 93 | `msg_type == MSG_CONFIRMATION`: Returns the data element length (uint8_t type) of the slave response message. If it is not a special function code, 1 must be returned. 94 | 95 | - `agile_modbus_set_compute_data_length_after_meta_cb` 96 | 97 | `msg_type == AGILE_MODBUS_MSG_INDICATION`: Returns the data length after the data element of the host request message. If it is not a special function code, 0 must be returned. 98 | 99 | `msg_type == MSG_CONFIRMATION`: Returns the data length after the data element of the slave response message. If it is not a special function code, it must return 0. 100 | 101 | - `agile_modbus_rtu_init` / `agile_modbus_tcp_init` 102 | 103 | When initializing the `RTU/TCP` environment, the user needs to pass in the `send buffer` and `receive buffer`. It is recommended that the size of both buffers is `AGILE_MODBUS_MAX_ADU_LENGTH` (260) bytes. `Special function code` is determined by the user according to the agreement. 104 | 105 | But for small memory MCUs, these two buffers can also be set small, and all APIs will judge the buffer size: 106 | 107 | Send buffer setting: If `expected request data length` or `expected response data length` is greater than `set send buffer size`, an exception is returned. 108 | 109 | Receive buffer setting: If the `message length requested by the host` is greater than the `set receive buffer size`, an exception will be returned. This is reasonable. When a small memory MCU is used as a slave, certain function codes must be restricted. 110 | 111 | ### 2.2. Host 112 | 113 | See `2.1. Transplantation`. 114 | 115 | ### 2.3. Slave machine 116 | 117 | #### 2.3.1. Interface description 118 | 119 | - Introduction to `agile_modbus_slave_handle` 120 | 121 | ```c 122 | 123 | int agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave_strict, 124 | agile_modbus_slave_callback_t slave_cb, const void *slave_data, int *frame_length) 125 | 126 | ``` 127 | 128 | msg_length: The length of data received after `waiting for the end of data reception`. 129 | 130 | slave_strict: slave address strictness check (0: Do not judge whether the address is consistent, it will be processed by user callback; 1: The address must be consistent, otherwise the callback will not be called and the response data will not be packaged). 131 | 132 | slave_cb: `agile_modbus_slave_callback_t` type callback function, implemented and passed in by the user. If it is NULL, all function codes can respond and are successful, but the register data is still 0. 133 | 134 | slave_data: slave callback function private data. 135 | 136 | frame_length: Get the length of the parsed modbus data frame. The meaning of this parameter is: 137 | 1. There is dirty data at the end: it can still be parsed successfully and tells the user the real modbus frame length, which the user can process. 138 | 2. Data sticky packet: The data consists of a complete frame of modbus data + a partial modbus data frame. After the user obtains the real modbus frame length, he can remove the processed modbus data frame and read the hardware interface data and the current one again. Part of the modbus data frame forms a new frame 139 | 3. This parameter is often used when modbus broadcast transmits big data (such as custom function code broadcast to upgrade firmware). Ordinary slave responses are one question and one answer, and only the complete data frame is processed. It is recommended to Execute `clear receive cache` 140 | 141 | - Introduction to `agile_modbus_slave_callback_t` 142 | 143 | ```c 144 | 145 | /** 146 | * @brief slave callback function 147 | * @param ctx modbus handle 148 | * @param slave_info slave information body 149 | * @param data private data 150 | * @return =0: normal; 151 | * <0: Abnormal 152 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 153 | * (Other negative exception codes: package exception response data from the opportunity) 154 | */ 155 | typedef int (*agile_modbus_slave_callback_t)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data); 156 | 157 | ``` 158 | 159 | `agile_modbus_slave_info`: 160 | 161 | sft: Contains slave address and function code attributes, which can be used in callbacks 162 | 163 | rsp_length: response data length pointer, its value needs to be updated when processing `special function code` in the callback, otherwise **not allowed to change** 164 | 165 | address: register address (not used by all function codes) 166 | 167 | nb: number (not used by all function codes) 168 | 169 | buf: data field required by different function codes (not used by all function codes) 170 | 171 | send_index: the current index of the send buffer (not used by all function codes) 172 | 173 | - `agile_modbus_slave_info` used by different function codes 174 | 175 | - AGILE_MODBUS_FC_READ_COILS、AGILE_MODBUS_FC_READ_DISCRETE_INPUTS 176 | 177 | The `address`, `nb`, and `send_index` attributes need to be used, and the `agile_modbus_slave_io_set` API needs to be called to store the IO data in the data area starting from `ctx->send_buf + send_index`. 178 | 179 | - AGILE_MODBUS_FC_READ_HOLDING_REGISTERS、AGILE_MODBUS_FC_READ_INPUT_REGISTERS 180 | 181 | The `address`, `nb`, and `send_index` attributes need to be used, and the `agile_modbus_slave_register_set` API needs to be called to store the register data in the data area starting from `ctx->send_buf + send_index`. 182 | 183 | - AGILE_MODBUS_FC_WRITE_SINGLE_COIL、AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER 184 | 185 | You need to use the `address` and `buf` attributes, force `buf` to the `int *` type, and get the value and store it in a register. 186 | 187 | - AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS 188 | 189 | The `address`, `nb`, `buf` attributes need to be used, and the `agile_modbus_slave_io_get` API needs to be called to obtain the IO data to be written. 190 | 191 | - AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS 192 | 193 | The `address`, `nb`, and `buf` attributes need to be used, and the `agile_modbus_slave_register_get` API needs to be called to obtain the register data to be written. 194 | 195 | - AGILE_MODBUS_FC_MASK_WRITE_REGISTER 196 | 197 | You need to use the `address` and `buf` attributes, pass `(buf[0] << 8) + buf[1]` to get the `and` value, pass `(buf[2] << 8) + buf[3 ]` Gets the `or` value. Get the register value `data`, perform the `data = (data & and) | (or & (~and))` operation to update the `data` value, and write it to the register. 198 | 199 | - AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS 200 | 201 | You need to use the `address`, `buf`, `send_index` attributes, pass `(buf[0] << 8) + buf[1]` to get the number of registers to be read, pass `(buf[2] << 8) + buf[3]` Get the register address to be written, and use `(buf[4] << 8) + buf[5]` to get the number of registers to be written. You need to call the `agile_modbus_slave_register_get` API to obtain the register data to be written, and call the `agile_modbus_slave_register_set` API to store the register data in the data area starting from `ctx->send_buf + send_index`. 202 | 203 | - Custom function code 204 | 205 | You need to use the `send_index`, `nb`, and `buf` attributes, and the user processes the data in the callback. 206 | 207 | send_index: current index of send buffer 208 | 209 | nb: PUD - 1, which is the modbus data field length 210 | 211 | buf: starting position of modbus data field 212 | 213 | **Note**: After the user fills data into the send buffer in the callback, the `rsp_length` value of `agile_modbus_slave_info` needs to be updated. 214 | 215 | #### 2.3.2. Simple slave access interface 216 | 217 | Agile Modbus provides an implementation of `agile_modbus_slave_callback_t`, allowing users to access it simply and conveniently. 218 | 219 | See [examples/slave](./examples/slave) for examples of usage. 220 | 221 | How to use: 222 | 223 | ```c 224 | 225 | #include "agile_modbus.h" 226 | #include "agile_modbus_slave_util.h" 227 | 228 | const agile_modbus_slave_util_t slave_util = { 229 | /* User implementation */ 230 | 231 | }; 232 | 233 | agile_modbus_slave_handle(ctx, read_len, 0, agile_modbus_slave_util_callback, &slave_util, NULL); 234 | 235 | ``` 236 | 237 | - Introduction to `agile_modbus_slave_util_callback` 238 | 239 | - An implementation of `agile_modbus_slave_callback_t` provided by Agile Modbus, which requires `agile_modbus_slave_util_t` type variable pointer as private data. 240 | 241 | - The private data is NULL, all function codes can respond and are successful, but the register data is still 0. 242 | 243 | - Introduction to `agile_modbus_slave_util_t` 244 | 245 | ```c 246 | 247 | typedef struct agile_modbus_slave_util { 248 | const agile_modbus_slave_util_map_t *tab_bits; /**< Coil register definition array */ 249 | int nb_bits; /**< Number of coil register definition arrays */ 250 | const agile_modbus_slave_util_map_t *tab_input_bits; /**< Discrete input register definition array */ 251 | int nb_input_bits; /**< Number of discrete input register definition arrays */ 252 | const agile_modbus_slave_util_map_t *tab_registers; /**< Holding register definition array */ 253 | int nb_registers; /**< Number of holding register definition arrays */ 254 | const agile_modbus_slave_util_map_t *tab_input_registers; /**< Input register definition array */ 255 | int nb_input_registers; /**< Number of input register definition arrays */ 256 | int (*addr_check)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info); /**< Address check interface */ 257 | int (*special_function)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info); /**< Special function code processing interface */ 258 | int (*done)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, int ret); /**< Processing end interface */ 259 | } agile_modbus_slave_util_t; 260 | 261 | ``` 262 | 263 | - Register related 264 | 265 | Users need to implement the definitions of `bits`, `input_bits`, `registers` and `input_registers`. If a register is defined as NULL, the function code corresponding to the register can respond and is successful, but the register data is all 0. 266 | 267 | - Interface calling process 268 | 269 | ![agile_modbus_slave_util_callback](./figures/agile_modbus_slave_util_callback.png) 270 | 271 | - Introduction to `agile_modbus_slave_util_map` 272 | 273 | ```c 274 | 275 | typedef struct agile_modbus_slave_util_map { 276 | int start_addr; /**< starting address */ 277 | int end_addr; /**< end address */ 278 | int (*get)(void *buf, int bufsz); /**< Get register data interface */ 279 | int (*set)(int index, int len, void *buf, int bufsz); /**< Set register data interface */ 280 | } agile_modbus_slave_util_map_t; 281 | 282 | ``` 283 | 284 | - **Precautions**: 285 | 286 | - The number of registers determined by the start address and end address is limited. Changing the size of the `map_buf` array inside the function can make it larger. 287 | 288 | - bit register < 250 289 | 290 | - register register < 125 291 | 292 | - The interface function is NULL, and the function code corresponding to the register can respond and is successful. 293 | 294 | - `get` interface 295 | 296 | Copy all data in the address field to `buf`. 297 | 298 | - `set` interface 299 | 300 | - `index`: offset within the address field 301 | 302 | - `len`: length 303 | 304 | Modify data based on `index` and `len`. 305 | 306 | ### 2.4. Example 307 | 308 | - Examples on PC are provided in the [examples](./examples) folder, which can be compiled and run under `WSL` or `Linux`. 309 | 310 | - Examples of RTU/TCP master and slave 311 | 312 | - Examples of special function codes 313 | 314 | RTU point-to-point transmission of files: Demonstrates the use of special function codes 315 | 316 | RTU broadcast transmission file: Demonstrates the use of `frame_length` in `agile_modbus_slave_handle` 317 | 318 | - [mcu_demos](https://github.com/loogg/agile_modbus_mcu_demos) provides examples on MCU. 319 | 320 | - [AT32F437_Boot](https://github.com/loogg/AT32F437_Boot) A Bootloader based on RT-Thread implemented on AT32F437 that supports Modbus firmware upgrade. 321 | 322 | - [HPM6750_Boot](https://github.com/loogg/HPM6750_Boot) A Bootloader based on RT-Thread implemented on HPM6750 that supports Modbus firmware upgrade. 323 | 324 | ### 2.5. Doxygen document generation 325 | 326 | - Use `Doxywizard` to open [Doxyfile](./doc/doxygen/Doxyfile) and run it. The generated file will be under [doxygen/output](./doc/doxygen/output). 327 | - `Graphviz` path needs to be changed. 328 | - `HTML` is generated without using `chm` format. If it is enabled, you need to change the `hhc.exe` path. 329 | 330 | ## 3. Support 331 | 332 | ![zanshang](./figures/zanshang.jpg) 333 | 334 | If Agile Modbus solves your problem, you might as well scan the QR code above and invite me for a **cup of coffee** ~ 335 | 336 | ## 4. Contact information & thanks 337 | 338 | - Maintenance: Ma Longwei 339 | - Home page: 340 | - Email: <2544047213@qq.com> 341 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | # RT-Thread building script for bridge 2 | 3 | from building import * 4 | 5 | cwd = GetCurrentDir() 6 | src = Glob('src/*.c') 7 | src += Glob('util/*.c') 8 | 9 | CPPPATH = [cwd + '/inc'] 10 | CPPPATH += [cwd + '/util'] 11 | 12 | group = DefineGroup('agile_modbus', src, depend = ['PKG_USING_AGILE_MODBUS'], CPPPATH = CPPPATH) 13 | 14 | Return('group') 15 | -------------------------------------------------------------------------------- /doc/Modbus_Application_Protocol_V1_1b3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/Modbus_Application_Protocol_V1_1b3.pdf -------------------------------------------------------------------------------- /doc/doxygen/Agile_Modbus.chm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/Agile_Modbus.chm -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusPollRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusPollRTUConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusPollSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusPollSetup.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusPollTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusPollTCPConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusProtocol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusProtocol.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusSlaveRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusSlaveRTUConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusSlaveSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusSlaveSetup.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusSlaveShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusSlaveShow.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusSlaveTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusSlaveTCPConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/ModbusSlaveTimeoutShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/ModbusSlaveTimeoutShow.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/RTUMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/RTUMaster.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/TCPMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/TCPMaster.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/VirtualCom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/VirtualCom.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/VirtualComGroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/VirtualComGroup.jpg -------------------------------------------------------------------------------- /doc/doxygen/figures/agile_modbus_slave_util_callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/agile_modbus_slave_util_callback.png -------------------------------------------------------------------------------- /doc/doxygen/figures/rtu_broadcast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/rtu_broadcast.gif -------------------------------------------------------------------------------- /doc/doxygen/figures/rtu_p2p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/rtu_p2p.gif -------------------------------------------------------------------------------- /doc/doxygen/figures/zanshang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/figures/zanshang.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusPollRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusPollRTUConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusPollSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusPollSetup.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusPollTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusPollTCPConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusProtocol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusProtocol.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusSlaveRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusSlaveRTUConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusSlaveSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusSlaveSetup.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusSlaveShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusSlaveShow.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusSlaveTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusSlaveTCPConnection.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/ModbusSlaveTimeoutShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/ModbusSlaveTimeoutShow.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/RTUMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/RTUMaster.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/TCPMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/TCPMaster.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/VirtualCom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/VirtualCom.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/VirtualComGroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/VirtualComGroup.jpg -------------------------------------------------------------------------------- /doc/doxygen/output/figures/agile_modbus_slave_util_callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/agile_modbus_slave_util_callback.png -------------------------------------------------------------------------------- /doc/doxygen/output/figures/rtu_broadcast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/rtu_broadcast.gif -------------------------------------------------------------------------------- /doc/doxygen/output/figures/rtu_p2p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/rtu_p2p.gif -------------------------------------------------------------------------------- /doc/doxygen/output/figures/zanshang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/doc/doxygen/output/figures/zanshang.jpg -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(example) 4 | 5 | set(LIB_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib) 6 | set(EXEC_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin) 7 | set(COMMON_HEAD_PATH ../inc ../util common) 8 | 9 | include_directories(${COMMON_HEAD_PATH}) 10 | 11 | 12 | set(MODBUS_LIB modbus) 13 | set(COMMON_LIB common) 14 | 15 | set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) 16 | file(GLOB MODBUS_SRCS ../src/*.c ../util/*.c) 17 | file(GLOB COMMON_SRCS common/*.c) 18 | 19 | add_library(${MODBUS_LIB} STATIC ${MODBUS_SRCS}) 20 | add_library(${COMMON_LIB} STATIC ${COMMON_SRCS}) 21 | 22 | 23 | set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) 24 | link_directories(${LIB_PATH}) 25 | link_libraries(${MODBUS_LIB} ${COMMON_LIB}) 26 | 27 | find_package(Threads REQUIRED) 28 | 29 | add_subdirectory(rtu_broadcast) 30 | add_subdirectory(rtu_master) 31 | add_subdirectory(rtu_p2p) 32 | add_subdirectory(slave) 33 | add_subdirectory(tcp_master) 34 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example description 2 | 3 | ## 1. Introduction 4 | 5 | This example provides functional demonstration of RTU / TCP master and slave. 6 | 7 | Using `cmake` under `WSL` or `Linux`, you can directly compile all examples and run the test program on your computer. 8 | 9 | Directory Structure: 10 | 11 | | Name | Description | 12 | | ---- | ---- | 13 | | common | public source code | 14 | | figures | materials | 15 | | rtu_master | RTU master example | 16 | | tcp_master | TCP master example | 17 | | slave | RTU + TCP slave example | 18 | | rtu_p2p | RTU peer-to-peer transfer file | 19 | | rtu_broadcast | RTU broadcast transmission file (sticky packet processing example) | 20 | 21 | ## 2. Use 22 | 23 | The tools you need to prepare are as follows: 24 | 25 | - Virtual serial port software 26 | - Modbus Poll 27 | - Modbus Slave 28 | 29 | Type `cmake -B build` 、 `cmake --build build` on the command line. 30 | 31 | ### 2.1. Host 32 | 33 | - RTU (rtu_master) 34 | 35 | - Use virtual serial port software to virtualize a pair of serial ports 36 | 37 | ![VirtualCom](./figures/VirtualCom.jpg) 38 | 39 | - Open `Modbus Slave` and set as shown below 40 | 41 | ![ModbusSlaveSetup](./figures/ModbusSlaveSetup.jpg) 42 | 43 | - `Modbus Slave` connection, set as shown below 44 | 45 | ![ModbusSlaveRTUConnection](./figures/ModbusSlaveRTUConnection.jpg) 46 | 47 | - Enter the `build/bin` directory, `./RtuMaster /dev/ttySX` to run the `RTU` host example, `ttySX` is the other of a pair of virtual serial ports 48 | 49 | ![RTUMaster](./figures/RTUMaster.jpg) 50 | 51 | - TCP (tcp_master) 52 | 53 | - Open `Modbus Slave`, `SetUp` settings are consistent with `RTU` 54 | 55 | - `Modbus Slave` connection, set as shown below 56 | 57 | ![ModbusSlaveTCPConnection](./figures/ModbusSlaveTCPConnection.jpg) 58 | 59 | - Enter the `build/bin` directory, `./TcpMaster 127.0.0.1 502` and run the `TCP` host example 60 | 61 | ![TCPMaster](./figures/TCPMaster.jpg) 62 | 63 | ### 2.2. Slave machine 64 | 65 | - This example (slave) provides both `RTU` and `TCP` slave function demonstrations, controlling the same memory. `TCP` can connect to a maximum of 5 clients. Each client has a no-data timeout of 10s and will be automatically disconnected after 10s. 66 | 67 | - The example supports all function codes (except function code 0x07). 68 | 69 | - `bit`, `input_bit`, `register`, `input_register` registers are defined separately for each file. 70 | 71 | - Use `agile_modbus_slave_util_callback`. 72 | 73 | - Register address field: 74 | 75 | | Register | Address range | 76 | | --- | --- | 77 | | Coil register | 0x041A ~ 0x0423 (1050 ~ 1059) | 78 | | Discrete input register | 0x041A ~ 0x0423 (1050 ~ 1059) | 79 | | Holding register | 0xFFF6 ~ 0xFFFF (65526 ~ 65535) | 80 | | Input register | 0xFFF6 ~ 0xFFFF (65526 ~ 65535) | 81 | 82 | **Note**: Reading and writing other address registers can be successful, but the values ​​are all 0. 83 | 84 | use: 85 | 86 | - Use virtual serial port software to virtualize a pair of serial ports 87 | 88 | ![VirtualCom](./figures/VirtualCom.jpg) 89 | 90 | - Enter the `build/bin` directory, `./ModbusSlave /dev/ttyS2 1025` to run the example 91 | 92 | /dev/ttySX: One of the virtual serial ports 93 | 94 | 1025: Listening port number. If you do not have `root` permissions, the port number must be greater than `1024` 95 | 96 | - Open `Modbus Poll` and set and connect RTU as shown below 97 | 98 | ![ModbusPollSetup](./figures/ModbusPollSetup.jpg) 99 | 100 | ![ModbusPollRTUConnection](./figures/ModbusPollRTUConnection.jpg) 101 | 102 | - Open 5 `Modbus Poll`, set the same as RTU, and connect as shown below 103 | 104 | ![ModbusPollTCPConnection](./figures/ModbusPollTCPConnection.jpg) 105 | 106 | - Effect demonstration 107 | 108 | ![ModbusSlaveShow](./figures/ModbusSlaveShow.jpg) 109 | 110 | - Timeout disconnect demonstration 111 | 112 | Close the poll interface of `Modbus Poll` and look at the console print to see the close message. 113 | 114 | ![ModbusSlaveTimeoutShow](./figures/ModbusSlaveTimeoutShow.jpg) 115 | 116 | ### 2.3. RTU transmission file 117 | 118 | ![ModbusProtocol](./figures/ModbusProtocol.jpg) 119 | 120 | Use `0x50` as the special function code for transferring files. 121 | 122 | File data is transferred in packets, with a maximum size of 1024 bytes per packet. 123 | 124 | `Data` field protocol definition: 125 | 126 | - Host request 127 | 128 | | command | number of bytes | data | 129 | | ---- | ---- | ---- | 130 | | 2 Bytes | 2 Bytes | N Bytes | 131 | 132 | Order: 133 | 134 | | Command | Description | Data | 135 | | ---- | ---- | ---- | 136 | | 0x0001 | Start sending | File size (4 Bytes) + file name (string) | 137 | | 0x0002 | Transmission data | Flag (1 Byte) + file data | 138 | 139 | Logo: 140 | 141 | | Status | Description | 142 | | ---- | ---- | 143 | | 0x00 | The last packet of data | 144 | | 0x01 | Not the last packet of data | 145 | 146 | - Slave response 147 | 148 | | Command | Status | 149 | | ---- | ---- | 150 | | 2 Bytes | 1 Byte | 151 | 152 | state: 153 | 154 | | Status | Description | 155 | | ---- | ---- | 156 | | 0x00 | Failure | 157 | | 0x01 | Success | 158 | 159 | - Use virtual serial port software to virtualize 3 serial ports to form a serial port group 160 | 161 | Here I use the MX virtual serial port 162 | 163 | ![VirtualComGroup](./figures/VirtualComGroup.jpg) 164 | 165 | #### 2.3.1. Point-to-point transmission 166 | 167 | - Enter the `build/bin` directory and open `Linux Shell`. The demonstration effect is as follows 168 | 169 | **Notice**: 170 | 171 | - The transferred files must be general files. Executable files, directories, etc. are not supported. 172 | 173 | - The file path must be the path in `Linux` environment 174 | 175 | - After the slave receives the data, the file name is modified (slave address_original file name) and written in the current directory. 176 | 177 | ![rtu_p2p](./figures/rtu_p2p.gif) 178 | 179 | #### 2.3.2. Broadcast transmission 180 | 181 | This example mainly demonstrates the use of `frame_length` in `agile_modbus_slave_handle`. 182 | 183 | In `broadcast_master`, use broadcast address 0 and send data packets every 5ms. At the same time, 100 bytes of dirty data are sent after each packet of data. 184 | 185 | ```c 186 | 187 | int send_len = agile_modbus_serialize_raw_request(ctx, raw_req, raw_req_len); 188 | serial_send(_fd, ctx->send_buf, send_len); 189 | 190 | //dirty data 191 | serial_send(_fd, _dirty_buf, sizeof(_dirty_buf)); 192 | 193 | ``` 194 | 195 | Under such a fast data flow, `broadcast_slave` must use the `frame_length` parameter in `agile_modbus_slave_handle` to handle sticky packets. 196 | 197 | - Enter the `build/bin` directory and open `Linux Shell`. The demonstration effect is as follows 198 | 199 | **Notice**: 200 | 201 | - The transferred files must be general files. Executable files, directories, etc. are not supported. 202 | 203 | - The file path must be the path in `Linux` environment 204 | 205 | - After the slave receives the data, the file name is modified (slave address_original file name) and written in the current directory. 206 | 207 | ![rtu_broadcast](./figures/rtu_broadcast.gif) 208 | -------------------------------------------------------------------------------- /examples/common/dbg_log.h: -------------------------------------------------------------------------------- 1 | #ifndef __DBG_LOG_H 2 | #define __DBG_LOG_H 3 | 4 | #include 5 | 6 | /* DEBUG level */ 7 | #define DBG_ERROR 0 8 | #define DBG_WARNING 1 9 | #define DBG_INFO 2 10 | #define DBG_LOG 3 11 | 12 | #ifndef DBG_SECTION_NAME 13 | #define DBG_SECTION_NAME "DBG" 14 | #endif 15 | 16 | #ifdef DBG_ENABLE 17 | 18 | #ifndef DBG_LEVEL 19 | #define DBG_LEVEL DBG_WARNING 20 | #endif 21 | 22 | /* 23 | * The color for terminal (foreground) 24 | * BLACK 30 25 | * RED 31 26 | * GREEN 32 27 | * YELLOW 33 28 | * BLUE 34 29 | * PURPLE 35 30 | * CYAN 36 31 | * WHITE 37 32 | */ 33 | #ifdef DBG_COLOR 34 | #define _DBG_COLOR(n) printf("\033[" #n "m") 35 | #define _DBG_LOG_HDR(lvl_name, color_n) \ 36 | printf("\033[" #color_n "m[" lvl_name "/" DBG_SECTION_NAME "] ") 37 | #define _DBG_LOG_X_END \ 38 | printf("\033[0m\n") 39 | #else 40 | #define _DBG_COLOR(n) 41 | #define _DBG_LOG_HDR(lvl_name, color_n) \ 42 | printf("[" lvl_name "/" DBG_SECTION_NAME "] ") 43 | #define _DBG_LOG_X_END \ 44 | printf("\n") 45 | #endif /* DBG_COLOR */ 46 | 47 | /* 48 | * static debug routine 49 | * NOTE: This is a NOT RECOMMENDED API. Please using LOG_X API. 50 | * It will be DISCARDED later. Because it will take up more resources. 51 | */ 52 | #define dbg_log(level, fmt, ...) \ 53 | if ((level) <= DBG_LEVEL) { \ 54 | switch (level) { \ 55 | case DBG_ERROR: \ 56 | _DBG_LOG_HDR("E", 31); \ 57 | break; \ 58 | case DBG_WARNING: \ 59 | _DBG_LOG_HDR("W", 33); \ 60 | break; \ 61 | case DBG_INFO: \ 62 | _DBG_LOG_HDR("I", 32); \ 63 | break; \ 64 | case DBG_LOG: \ 65 | _DBG_LOG_HDR("D", 0); \ 66 | break; \ 67 | default: \ 68 | break; \ 69 | } \ 70 | printf(fmt, ##__VA_ARGS__); \ 71 | _DBG_COLOR(0); \ 72 | } 73 | 74 | #define dbg_here \ 75 | if ((DBG_LEVEL) <= DBG_LOG) { \ 76 | printf(DBG_SECTION_NAME " Here %s:%d\n", \ 77 | __FUNCTION__, __LINE__); \ 78 | } 79 | 80 | #define dbg_enter \ 81 | if ((DBG_LEVEL) <= DBG_LOG) { \ 82 | _DBG_COLOR(32); \ 83 | printf(DBG_SECTION_NAME " Enter %s\n", \ 84 | __FUNCTION__); \ 85 | _DBG_COLOR(0); \ 86 | } 87 | 88 | #define dbg_exit \ 89 | if ((DBG_LEVEL) <= DBG_LOG) { \ 90 | _DBG_COLOR(32); \ 91 | printf(DBG_SECTION_NAME " Exit %s:%d\n", \ 92 | __FUNCTION__); \ 93 | _DBG_COLOR(0); \ 94 | } 95 | 96 | #define dbg_log_line(lvl, color_n, fmt, ...) \ 97 | do { \ 98 | _DBG_LOG_HDR(lvl, color_n); \ 99 | printf(fmt, ##__VA_ARGS__); \ 100 | _DBG_LOG_X_END; \ 101 | } while (0) 102 | 103 | #define dbg_raw(...) printf(__VA_ARGS__); 104 | 105 | #else 106 | #define dbg_log(level, fmt, ...) 107 | #define dbg_here 108 | #define dbg_enter 109 | #define dbg_exit 110 | #define dbg_log_line(lvl, color_n, fmt, ...) 111 | #define dbg_raw(...) 112 | #endif /* DBG_ENABLE */ 113 | 114 | #if (DBG_LEVEL >= DBG_LOG) 115 | #define LOG_D(fmt, ...) dbg_log_line("D", 0, fmt, ##__VA_ARGS__) 116 | #else 117 | #define LOG_D(...) 118 | #endif 119 | 120 | #if (DBG_LEVEL >= DBG_INFO) 121 | #define LOG_I(fmt, ...) dbg_log_line("I", 32, fmt, ##__VA_ARGS__) 122 | #else 123 | #define LOG_I(...) 124 | #endif 125 | 126 | #if (DBG_LEVEL >= DBG_WARNING) 127 | #define LOG_W(fmt, ...) dbg_log_line("W", 33, fmt, ##__VA_ARGS__) 128 | #else 129 | #define LOG_W(...) 130 | #endif 131 | 132 | #if (DBG_LEVEL >= DBG_ERROR) 133 | #define LOG_E(fmt, ...) dbg_log_line("E", 31, fmt, ##__VA_ARGS__) 134 | #else 135 | #define LOG_E(...) 136 | #endif 137 | 138 | #define LOG_RAW(...) dbg_raw(__VA_ARGS__) 139 | 140 | #endif /* __DBG_LOG_H */ 141 | -------------------------------------------------------------------------------- /examples/common/ringbuffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2018, RT-Thread Development Team 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2012-09-30 Bernard first version. 9 | * 2013-05-08 Grissiom reimplement 10 | * 2016-08-18 heyuanjie add interface 11 | * 2021-01-26 Loogg Move to Linux 12 | */ 13 | 14 | #include "ringbuffer.h" 15 | #include 16 | 17 | static __inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb) 18 | { 19 | if (rb->read_index == rb->write_index) { 20 | if (rb->read_mirror == rb->write_mirror) 21 | return RT_RINGBUFFER_EMPTY; 22 | else 23 | return RT_RINGBUFFER_FULL; 24 | } 25 | return RT_RINGBUFFER_HALFFULL; 26 | } 27 | 28 | void rt_ringbuffer_init(struct rt_ringbuffer *rb, 29 | uint8_t *pool, 30 | int16_t size) 31 | { 32 | assert(rb != NULL); 33 | assert(size > 0); 34 | 35 | /* initialize read and write index */ 36 | rb->read_mirror = rb->read_index = 0; 37 | rb->write_mirror = rb->write_index = 0; 38 | 39 | /* set buffer pool and size */ 40 | rb->buffer_ptr = pool; 41 | rb->buffer_size = RT_ALIGN_DOWN(size, __SIZEOF_POINTER__); 42 | } 43 | 44 | /** 45 | * put a block of data into ring buffer 46 | */ 47 | uint32_t rt_ringbuffer_put(struct rt_ringbuffer *rb, 48 | const uint8_t *ptr, 49 | uint16_t length) 50 | { 51 | uint16_t size; 52 | 53 | assert(rb != NULL); 54 | 55 | /* whether has enough space */ 56 | size = rt_ringbuffer_space_len(rb); 57 | 58 | /* no space */ 59 | if (size == 0) 60 | return 0; 61 | 62 | /* drop some data */ 63 | if (size < length) 64 | length = size; 65 | 66 | if (rb->buffer_size - rb->write_index > length) { 67 | /* read_index - write_index = empty space */ 68 | memcpy(&rb->buffer_ptr[rb->write_index], ptr, length); 69 | /* this should not cause overflow because there is enough space for 70 | * length of data in current mirror */ 71 | rb->write_index += length; 72 | return length; 73 | } 74 | 75 | memcpy(&rb->buffer_ptr[rb->write_index], 76 | &ptr[0], 77 | rb->buffer_size - rb->write_index); 78 | memcpy(&rb->buffer_ptr[0], 79 | &ptr[rb->buffer_size - rb->write_index], 80 | length - (rb->buffer_size - rb->write_index)); 81 | 82 | /* we are going into the other side of the mirror */ 83 | rb->write_mirror = ~rb->write_mirror; 84 | rb->write_index = length - (rb->buffer_size - rb->write_index); 85 | 86 | return length; 87 | } 88 | 89 | /** 90 | * put a block of data into ring buffer 91 | * 92 | * When the buffer is full, it will discard the old data. 93 | */ 94 | uint32_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, 95 | const uint8_t *ptr, 96 | uint16_t length) 97 | { 98 | uint16_t space_length; 99 | 100 | assert(rb != NULL); 101 | 102 | space_length = rt_ringbuffer_space_len(rb); 103 | 104 | if (length > rb->buffer_size) { 105 | ptr = &ptr[length - rb->buffer_size]; 106 | length = rb->buffer_size; 107 | } 108 | 109 | if (rb->buffer_size - rb->write_index > length) { 110 | /* read_index - write_index = empty space */ 111 | memcpy(&rb->buffer_ptr[rb->write_index], ptr, length); 112 | /* this should not cause overflow because there is enough space for 113 | * length of data in current mirror */ 114 | rb->write_index += length; 115 | 116 | if (length > space_length) 117 | rb->read_index = rb->write_index; 118 | 119 | return length; 120 | } 121 | 122 | memcpy(&rb->buffer_ptr[rb->write_index], 123 | &ptr[0], 124 | rb->buffer_size - rb->write_index); 125 | memcpy(&rb->buffer_ptr[0], 126 | &ptr[rb->buffer_size - rb->write_index], 127 | length - (rb->buffer_size - rb->write_index)); 128 | 129 | /* we are going into the other side of the mirror */ 130 | rb->write_mirror = ~rb->write_mirror; 131 | rb->write_index = length - (rb->buffer_size - rb->write_index); 132 | 133 | if (length > space_length) { 134 | rb->read_mirror = ~rb->read_mirror; 135 | rb->read_index = rb->write_index; 136 | } 137 | 138 | return length; 139 | } 140 | 141 | /** 142 | * get data from ring buffer 143 | */ 144 | uint32_t rt_ringbuffer_get(struct rt_ringbuffer *rb, 145 | uint8_t *ptr, 146 | uint16_t length) 147 | { 148 | uint32_t size; 149 | 150 | assert(rb != NULL); 151 | 152 | /* whether has enough data */ 153 | size = rt_ringbuffer_data_len(rb); 154 | 155 | /* no data */ 156 | if (size == 0) 157 | return 0; 158 | 159 | /* less data */ 160 | if (size < length) 161 | length = size; 162 | 163 | if (rb->buffer_size - rb->read_index > length) { 164 | /* copy all of data */ 165 | memcpy(ptr, &rb->buffer_ptr[rb->read_index], length); 166 | /* this should not cause overflow because there is enough space for 167 | * length of data in current mirror */ 168 | rb->read_index += length; 169 | return length; 170 | } 171 | 172 | memcpy(&ptr[0], 173 | &rb->buffer_ptr[rb->read_index], 174 | rb->buffer_size - rb->read_index); 175 | memcpy(&ptr[rb->buffer_size - rb->read_index], 176 | &rb->buffer_ptr[0], 177 | length - (rb->buffer_size - rb->read_index)); 178 | 179 | /* we are going into the other side of the mirror */ 180 | rb->read_mirror = ~rb->read_mirror; 181 | rb->read_index = length - (rb->buffer_size - rb->read_index); 182 | 183 | return length; 184 | } 185 | 186 | /** 187 | * peak data from ring buffer 188 | */ 189 | uint32_t rt_ringbuffer_peak(struct rt_ringbuffer *rb, uint8_t **ptr) 190 | { 191 | assert(rb != NULL); 192 | 193 | *ptr = NULL; 194 | 195 | /* whether has enough data */ 196 | uint32_t size = rt_ringbuffer_data_len(rb); 197 | 198 | /* no data */ 199 | if (size == 0) 200 | return 0; 201 | 202 | *ptr = &rb->buffer_ptr[rb->read_index]; 203 | 204 | if (rb->buffer_size - rb->read_index > size) { 205 | rb->read_index += size; 206 | return size; 207 | } 208 | 209 | size = rb->buffer_size - rb->read_index; 210 | 211 | /* we are going into the other side of the mirror */ 212 | rb->read_mirror = ~rb->read_mirror; 213 | rb->read_index = 0; 214 | 215 | return size; 216 | } 217 | 218 | /** 219 | * put a character into ring buffer 220 | */ 221 | uint32_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const uint8_t ch) 222 | { 223 | assert(rb != NULL); 224 | 225 | /* whether has enough space */ 226 | if (!rt_ringbuffer_space_len(rb)) 227 | return 0; 228 | 229 | rb->buffer_ptr[rb->write_index] = ch; 230 | 231 | /* flip mirror */ 232 | if (rb->write_index == rb->buffer_size - 1) { 233 | rb->write_mirror = ~rb->write_mirror; 234 | rb->write_index = 0; 235 | } else { 236 | rb->write_index++; 237 | } 238 | 239 | return 1; 240 | } 241 | 242 | /** 243 | * put a character into ring buffer 244 | * 245 | * When the buffer is full, it will discard one old data. 246 | */ 247 | uint32_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const uint8_t ch) 248 | { 249 | enum rt_ringbuffer_state old_state; 250 | 251 | assert(rb != NULL); 252 | 253 | old_state = rt_ringbuffer_status(rb); 254 | 255 | rb->buffer_ptr[rb->write_index] = ch; 256 | 257 | /* flip mirror */ 258 | if (rb->write_index == rb->buffer_size - 1) { 259 | rb->write_mirror = ~rb->write_mirror; 260 | rb->write_index = 0; 261 | if (old_state == RT_RINGBUFFER_FULL) { 262 | rb->read_mirror = ~rb->read_mirror; 263 | rb->read_index = rb->write_index; 264 | } 265 | } else { 266 | rb->write_index++; 267 | if (old_state == RT_RINGBUFFER_FULL) 268 | rb->read_index = rb->write_index; 269 | } 270 | 271 | return 1; 272 | } 273 | 274 | /** 275 | * get a character from a ringbuffer 276 | */ 277 | uint32_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, uint8_t *ch) 278 | { 279 | assert(rb != NULL); 280 | 281 | /* ringbuffer is empty */ 282 | if (!rt_ringbuffer_data_len(rb)) 283 | return 0; 284 | 285 | /* put character */ 286 | *ch = rb->buffer_ptr[rb->read_index]; 287 | 288 | if (rb->read_index == rb->buffer_size - 1) { 289 | rb->read_mirror = ~rb->read_mirror; 290 | rb->read_index = 0; 291 | } else { 292 | rb->read_index++; 293 | } 294 | 295 | return 1; 296 | } 297 | 298 | /** 299 | * get the size of data in rb 300 | */ 301 | uint32_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb) 302 | { 303 | switch (rt_ringbuffer_status(rb)) { 304 | case RT_RINGBUFFER_EMPTY: 305 | return 0; 306 | case RT_RINGBUFFER_FULL: 307 | return rb->buffer_size; 308 | case RT_RINGBUFFER_HALFFULL: 309 | default: 310 | if (rb->write_index > rb->read_index) 311 | return rb->write_index - rb->read_index; 312 | else 313 | return rb->buffer_size - (rb->read_index - rb->write_index); 314 | }; 315 | } 316 | 317 | /** 318 | * empty the rb 319 | */ 320 | void rt_ringbuffer_reset(struct rt_ringbuffer *rb) 321 | { 322 | assert(rb != NULL); 323 | 324 | rb->read_mirror = 0; 325 | rb->read_index = 0; 326 | rb->write_mirror = 0; 327 | rb->write_index = 0; 328 | } 329 | 330 | struct rt_ringbuffer *rt_ringbuffer_create(uint16_t size) 331 | { 332 | struct rt_ringbuffer *rb; 333 | uint8_t *pool; 334 | 335 | assert(size > 0); 336 | 337 | size = RT_ALIGN_DOWN(size, __SIZEOF_POINTER__); 338 | 339 | rb = (struct rt_ringbuffer *)malloc(sizeof(struct rt_ringbuffer)); 340 | if (rb == NULL) 341 | goto exit; 342 | 343 | pool = (uint8_t *)malloc(size); 344 | if (pool == NULL) { 345 | free(rb); 346 | rb = NULL; 347 | goto exit; 348 | } 349 | rt_ringbuffer_init(rb, pool, size); 350 | 351 | exit: 352 | return rb; 353 | } 354 | 355 | void rt_ringbuffer_destroy(struct rt_ringbuffer *rb) 356 | { 357 | assert(rb != NULL); 358 | 359 | free(rb->buffer_ptr); 360 | free(rb); 361 | } 362 | -------------------------------------------------------------------------------- /examples/common/ringbuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2018, RT-Thread Development Team 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2021-01-26 Loogg Move to Linux 9 | */ 10 | #ifndef RINGBUFFER_H__ 11 | #define RINGBUFFER_H__ 12 | 13 | #include 14 | #include 15 | #include 16 | #include "rtservice.h" 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | /* ring buffer */ 23 | struct rt_ringbuffer { 24 | uint8_t *buffer_ptr; 25 | /* use the msb of the {read,write}_index as mirror bit. You can see this as 26 | * if the buffer adds a virtual mirror and the pointers point either to the 27 | * normal or to the mirrored buffer. If the write_index has the same value 28 | * with the read_index, but in a different mirror, the buffer is full. 29 | * While if the write_index and the read_index are the same and within the 30 | * same mirror, the buffer is empty. The ASCII art of the ringbuffer is: 31 | * 32 | * mirror = 0 mirror = 1 33 | * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ 34 | * | 0 | 1 | 2 | 3 | 4 | 5 | 6 ||| 0 | 1 | 2 | 3 | 4 | 5 | 6 | Full 35 | * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ 36 | * read_idx-^ write_idx-^ 37 | * 38 | * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ 39 | * | 0 | 1 | 2 | 3 | 4 | 5 | 6 ||| 0 | 1 | 2 | 3 | 4 | 5 | 6 | Empty 40 | * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ 41 | * read_idx-^ ^-write_idx 42 | * 43 | * The tradeoff is we could only use 32KiB of buffer for 16 bit of index. 44 | * But it should be enough for most of the cases. 45 | * 46 | * Ref: http://en.wikipedia.org/wiki/Circular_buffer#Mirroring */ 47 | uint16_t read_mirror : 1; 48 | uint16_t read_index : 15; 49 | uint16_t write_mirror : 1; 50 | uint16_t write_index : 15; 51 | /* as we use msb of index as mirror bit, the size should be signed and 52 | * could only be positive. */ 53 | int16_t buffer_size; 54 | }; 55 | 56 | enum rt_ringbuffer_state { 57 | RT_RINGBUFFER_EMPTY, 58 | RT_RINGBUFFER_FULL, 59 | /* half full is neither full nor empty */ 60 | RT_RINGBUFFER_HALFFULL, 61 | }; 62 | 63 | /** 64 | * RingBuffer for DeviceDriver 65 | * 66 | * Please note that the ring buffer implementation of RT-Thread 67 | * has no thread wait or resume feature. 68 | */ 69 | void rt_ringbuffer_init(struct rt_ringbuffer *rb, uint8_t *pool, int16_t size); 70 | void rt_ringbuffer_reset(struct rt_ringbuffer *rb); 71 | uint32_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const uint8_t *ptr, uint16_t length); 72 | uint32_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, const uint8_t *ptr, uint16_t length); 73 | uint32_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const uint8_t ch); 74 | uint32_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const uint8_t ch); 75 | uint32_t rt_ringbuffer_get(struct rt_ringbuffer *rb, uint8_t *ptr, uint16_t length); 76 | uint32_t rt_ringbuffer_peak(struct rt_ringbuffer *rb, uint8_t **ptr); 77 | uint32_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, uint8_t *ch); 78 | uint32_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb); 79 | 80 | struct rt_ringbuffer *rt_ringbuffer_create(uint16_t size); 81 | void rt_ringbuffer_destroy(struct rt_ringbuffer *rb); 82 | 83 | static __inline uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb) 84 | { 85 | assert(rb != NULL); 86 | return rb->buffer_size; 87 | } 88 | 89 | /** return the size of empty space in rb */ 90 | #define rt_ringbuffer_space_len(rb) ((rb)->buffer_size - rt_ringbuffer_data_len(rb)) 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /examples/common/rt_tick.c: -------------------------------------------------------------------------------- 1 | #include "rt_tick.h" 2 | #include 3 | #include 4 | 5 | static uint32_t rt_tick = 0; 6 | static uint8_t is_initialized = 0; 7 | 8 | static void *rt_tick_entry(void *param) 9 | { 10 | int us = 1000000 / RT_TICK_PER_SECOND; 11 | while (1) { 12 | usleep(us); 13 | rt_tick++; 14 | } 15 | 16 | pthread_exit(NULL); 17 | } 18 | 19 | void rt_tick_init(void) 20 | { 21 | if (is_initialized) 22 | return; 23 | 24 | pthread_t tid; 25 | pthread_create(&tid, NULL, rt_tick_entry, NULL); 26 | pthread_detach(tid); 27 | 28 | is_initialized = 1; 29 | } 30 | 31 | uint32_t rt_tick_get(void) 32 | { 33 | return rt_tick; 34 | } 35 | 36 | uint32_t rt_tick_from_millisecond(int32_t ms) 37 | { 38 | uint32_t tick; 39 | 40 | if (ms < 0) { 41 | tick = (uint32_t)RT_WAITING_FOREVER; 42 | } else { 43 | tick = RT_TICK_PER_SECOND * (ms / 1000); 44 | tick += (RT_TICK_PER_SECOND * (ms % 1000) + 999) / 1000; 45 | } 46 | 47 | /* return the calculated tick */ 48 | return tick; 49 | } 50 | -------------------------------------------------------------------------------- /examples/common/rt_tick.h: -------------------------------------------------------------------------------- 1 | #ifndef __RT_TICK_H 2 | #define __RT_TICK_H 3 | #include 4 | 5 | #define RT_TICK_MAX 0xffffffff 6 | #define RT_TICK_PER_SECOND 200 7 | #define RT_WAITING_FOREVER -1 8 | 9 | void rt_tick_init(void); 10 | uint32_t rt_tick_get(void); 11 | uint32_t rt_tick_from_millisecond(int32_t ms); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /examples/common/rtservice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006-2018, RT-Thread Development Team 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2006-03-16 Bernard the first version 9 | * 2006-09-07 Bernard move the kservice APIs to rtthread.h 10 | * 2007-06-27 Bernard fix the rt_list_remove bug 11 | * 2012-03-22 Bernard rename kservice.h to rtservice.h 12 | * 2017-11-15 JasonJia Modify rt_slist_foreach to rt_slist_for_each_entry. 13 | * Make code cleanup. 14 | * 2021-01-26 Loogg Move to Linux 15 | */ 16 | 17 | #ifndef __RT_SERVICE_H__ 18 | #define __RT_SERVICE_H__ 19 | 20 | #include 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | /** 27 | * @addtogroup KernelService 28 | */ 29 | 30 | /**@{*/ 31 | 32 | /** 33 | * @ingroup BasicDef 34 | * 35 | * @def RT_ALIGN(size, align) 36 | * Return the most contiguous size aligned at specified width. RT_ALIGN(13, 4) 37 | * would return 16. 38 | */ 39 | #define RT_ALIGN(size, align) (((size) + (align)-1) & ~((align)-1)) 40 | 41 | /** 42 | * @ingroup BasicDef 43 | * 44 | * @def RT_ALIGN_DOWN(size, align) 45 | * Return the down number of aligned at specified width. RT_ALIGN_DOWN(13, 4) 46 | * would return 12. 47 | */ 48 | #define RT_ALIGN_DOWN(size, align) ((size) & ~((align)-1)) 49 | 50 | /** 51 | * Double List structure 52 | */ 53 | struct rt_list_node { 54 | struct rt_list_node *next; /**< point to next node. */ 55 | struct rt_list_node *prev; /**< point to prev node. */ 56 | }; 57 | typedef struct rt_list_node rt_list_t; /**< Type for lists. */ 58 | 59 | /** 60 | * Single List structure 61 | */ 62 | struct rt_slist_node { 63 | struct rt_slist_node *next; /**< point to next node. */ 64 | }; 65 | typedef struct rt_slist_node rt_slist_t; /**< Type for single list. */ 66 | 67 | /** 68 | * rt_container_of - return the member address of ptr, if the type of ptr is the 69 | * struct type. 70 | */ 71 | #define rt_container_of(ptr, type, member) \ 72 | ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member))) 73 | 74 | /** 75 | * @brief initialize a list object 76 | */ 77 | #define RT_LIST_OBJECT_INIT(object) \ 78 | { \ 79 | &(object), &(object) \ 80 | } 81 | 82 | /** 83 | * @brief initialize a list 84 | * 85 | * @param l list to be initialized 86 | */ 87 | static __inline void rt_list_init(rt_list_t *l) 88 | { 89 | l->next = l->prev = l; 90 | } 91 | 92 | /** 93 | * @brief insert a node after a list 94 | * 95 | * @param l list to insert it 96 | * @param n new node to be inserted 97 | */ 98 | static __inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n) 99 | { 100 | l->next->prev = n; 101 | n->next = l->next; 102 | 103 | l->next = n; 104 | n->prev = l; 105 | } 106 | 107 | /** 108 | * @brief insert a node before a list 109 | * 110 | * @param n new node to be inserted 111 | * @param l list to insert it 112 | */ 113 | static __inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n) 114 | { 115 | l->prev->next = n; 116 | n->prev = l->prev; 117 | 118 | l->prev = n; 119 | n->next = l; 120 | } 121 | 122 | /** 123 | * @brief remove node from list. 124 | * @param n the node to remove from the list. 125 | */ 126 | static __inline void rt_list_remove(rt_list_t *n) 127 | { 128 | n->next->prev = n->prev; 129 | n->prev->next = n->next; 130 | 131 | n->next = n->prev = n; 132 | } 133 | 134 | /** 135 | * @brief tests whether a list is empty 136 | * @param l the list to test. 137 | */ 138 | static __inline int rt_list_isempty(const rt_list_t *l) 139 | { 140 | return l->next == l; 141 | } 142 | 143 | /** 144 | * @brief get the list length 145 | * @param l the list to get. 146 | */ 147 | static __inline unsigned int rt_list_len(const rt_list_t *l) 148 | { 149 | unsigned int len = 0; 150 | const rt_list_t *p = l; 151 | while (p->next != l) { 152 | p = p->next; 153 | len++; 154 | } 155 | 156 | return len; 157 | } 158 | 159 | /** 160 | * @brief get the struct for this entry 161 | * @param node the entry point 162 | * @param type the type of structure 163 | * @param member the name of list in structure 164 | */ 165 | #define rt_list_entry(node, type, member) \ 166 | rt_container_of(node, type, member) 167 | 168 | /** 169 | * rt_list_for_each - iterate over a list 170 | * @pos: the rt_list_t * to use as a loop cursor. 171 | * @head: the head for your list. 172 | */ 173 | #define rt_list_for_each(pos, head) \ 174 | for (pos = (head)->next; pos != (head); pos = pos->next) 175 | 176 | /** 177 | * rt_list_for_each_safe - iterate over a list safe against removal of list entry 178 | * @pos: the rt_list_t * to use as a loop cursor. 179 | * @n: another rt_list_t * to use as temporary storage 180 | * @head: the head for your list. 181 | */ 182 | #define rt_list_for_each_safe(pos, n, head) \ 183 | for (pos = (head)->next, n = pos->next; pos != (head); \ 184 | pos = n, n = pos->next) 185 | 186 | /** 187 | * rt_list_for_each_entry - iterate over list of given type 188 | * @pos: the type * to use as a loop cursor. 189 | * @head: the head for your list. 190 | * @member: the name of the list_struct within the struct. 191 | */ 192 | #define rt_list_for_each_entry(pos, head, member) \ 193 | for (pos = rt_list_entry((head)->next, typeof(*pos), member); \ 194 | &pos->member != (head); \ 195 | pos = rt_list_entry(pos->member.next, typeof(*pos), member)) 196 | 197 | /** 198 | * rt_list_for_each_entry_safe - iterate over list of given type safe against removal of list entry 199 | * @pos: the type * to use as a loop cursor. 200 | * @n: another type * to use as temporary storage 201 | * @head: the head for your list. 202 | * @member: the name of the list_struct within the struct. 203 | */ 204 | #define rt_list_for_each_entry_safe(pos, n, head, member) \ 205 | for (pos = rt_list_entry((head)->next, typeof(*pos), member), \ 206 | n = rt_list_entry(pos->member.next, typeof(*pos), member); \ 207 | &pos->member != (head); \ 208 | pos = n, n = rt_list_entry(n->member.next, typeof(*n), member)) 209 | 210 | /** 211 | * rt_list_first_entry - get the first element from a list 212 | * @ptr: the list head to take the element from. 213 | * @type: the type of the struct this is embedded in. 214 | * @member: the name of the list_struct within the struct. 215 | * 216 | * Note, that list is expected to be not empty. 217 | */ 218 | #define rt_list_first_entry(ptr, type, member) \ 219 | rt_list_entry((ptr)->next, type, member) 220 | 221 | #define RT_SLIST_OBJECT_INIT(object) \ 222 | { \ 223 | NULL \ 224 | } 225 | 226 | /** 227 | * @brief initialize a single list 228 | * 229 | * @param l the single list to be initialized 230 | */ 231 | static __inline void rt_slist_init(rt_slist_t *l) 232 | { 233 | l->next = NULL; 234 | } 235 | 236 | static __inline void rt_slist_append(rt_slist_t *l, rt_slist_t *n) 237 | { 238 | struct rt_slist_node *node; 239 | 240 | node = l; 241 | while (node->next) 242 | node = node->next; 243 | 244 | /* append the node to the tail */ 245 | node->next = n; 246 | n->next = NULL; 247 | } 248 | 249 | static __inline void rt_slist_insert(rt_slist_t *l, rt_slist_t *n) 250 | { 251 | n->next = l->next; 252 | l->next = n; 253 | } 254 | 255 | static __inline unsigned int rt_slist_len(const rt_slist_t *l) 256 | { 257 | unsigned int len = 0; 258 | const rt_slist_t *list = l->next; 259 | while (list != NULL) { 260 | list = list->next; 261 | len++; 262 | } 263 | 264 | return len; 265 | } 266 | 267 | static __inline rt_slist_t *rt_slist_remove(rt_slist_t *l, rt_slist_t *n) 268 | { 269 | /* remove slist head */ 270 | struct rt_slist_node *node = l; 271 | while (node->next && node->next != n) 272 | node = node->next; 273 | 274 | /* remove node */ 275 | if (node->next != (rt_slist_t *)0) 276 | node->next = node->next->next; 277 | 278 | return l; 279 | } 280 | 281 | static __inline rt_slist_t *rt_slist_first(rt_slist_t *l) 282 | { 283 | return l->next; 284 | } 285 | 286 | static __inline rt_slist_t *rt_slist_tail(rt_slist_t *l) 287 | { 288 | while (l->next) 289 | l = l->next; 290 | 291 | return l; 292 | } 293 | 294 | static __inline rt_slist_t *rt_slist_next(rt_slist_t *n) 295 | { 296 | return n->next; 297 | } 298 | 299 | static __inline int rt_slist_isempty(rt_slist_t *l) 300 | { 301 | return l->next == NULL; 302 | } 303 | 304 | /** 305 | * @brief get the struct for this single list node 306 | * @param node the entry point 307 | * @param type the type of structure 308 | * @param member the name of list in structure 309 | */ 310 | #define rt_slist_entry(node, type, member) \ 311 | rt_container_of(node, type, member) 312 | 313 | /** 314 | * rt_slist_for_each - iterate over a single list 315 | * @pos: the rt_slist_t * to use as a loop cursor. 316 | * @head: the head for your single list. 317 | */ 318 | #define rt_slist_for_each(pos, head) \ 319 | for (pos = (head)->next; pos != NULL; pos = pos->next) 320 | 321 | /** 322 | * rt_slist_for_each_entry - iterate over single list of given type 323 | * @pos: the type * to use as a loop cursor. 324 | * @head: the head for your single list. 325 | * @member: the name of the list_struct within the struct. 326 | */ 327 | #define rt_slist_for_each_entry(pos, head, member) \ 328 | for (pos = rt_slist_entry((head)->next, typeof(*pos), member); \ 329 | &pos->member != (NULL); \ 330 | pos = rt_slist_entry(pos->member.next, typeof(*pos), member)) 331 | 332 | /** 333 | * rt_slist_first_entry - get the first element from a slist 334 | * @ptr: the slist head to take the element from. 335 | * @type: the type of the struct this is embedded in. 336 | * @member: the name of the slist_struct within the struct. 337 | * 338 | * Note, that slist is expected to be not empty. 339 | */ 340 | #define rt_slist_first_entry(ptr, type, member) \ 341 | rt_slist_entry((ptr)->next, type, member) 342 | 343 | /** 344 | * rt_slist_tail_entry - get the tail element from a slist 345 | * @ptr: the slist head to take the element from. 346 | * @type: the type of the struct this is embedded in. 347 | * @member: the name of the slist_struct within the struct. 348 | * 349 | * Note, that slist is expected to be not empty. 350 | */ 351 | #define rt_slist_tail_entry(ptr, type, member) \ 352 | rt_slist_entry(rt_slist_tail(ptr), type, member) 353 | 354 | /**@}*/ 355 | 356 | #ifdef __cplusplus 357 | } 358 | #endif 359 | 360 | #endif 361 | -------------------------------------------------------------------------------- /examples/common/serial.c: -------------------------------------------------------------------------------- 1 | #include "serial.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DBG_ENABLE 13 | #define DBG_COLOR 14 | #define DBG_SECTION_NAME "serial" 15 | #define DBG_LEVEL DBG_LOG 16 | #include "dbg_log.h" 17 | 18 | int serial_init(const char *device, 19 | int baud, char parity, int data_bit, 20 | int stop_bit, struct termios *old_tios) 21 | { 22 | 23 | struct termios tios; 24 | speed_t speed; 25 | int flags; 26 | 27 | /* The O_NOCTTY flag tells UNIX that this program doesn't want 28 | to be the "controlling terminal" for that port. If you 29 | don't specify this then any input (such as keyboard abort 30 | signals and so forth) will affect your process 31 | 32 | Timeouts are ignored in canonical input mode or when the 33 | NDELAY option is set on the file via open or fcntl */ 34 | flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL; 35 | #ifdef O_CLOEXEC 36 | flags |= O_CLOEXEC; 37 | #endif 38 | 39 | int s = open(device, flags); 40 | if (s == -1) { 41 | LOG_E("ERROR Can't open the device %s (%s)", device, strerror(errno)); 42 | return -1; 43 | } 44 | 45 | flags = fcntl(s, F_GETFL, 0); 46 | flags |= O_NONBLOCK; 47 | fcntl(s, F_SETFL, flags); 48 | 49 | flags = fcntl(s, F_GETFD); 50 | flags |= FD_CLOEXEC; 51 | fcntl(s, F_SETFD, flags); 52 | 53 | /* Save */ 54 | tcgetattr(s, old_tios); 55 | 56 | memset(&tios, 0, sizeof(struct termios)); 57 | 58 | /* C_ISPEED Input baud (new interface) 59 | C_OSPEED Output baud (new interface) 60 | */ 61 | switch (baud) { 62 | case 110: 63 | speed = B110; 64 | break; 65 | case 300: 66 | speed = B300; 67 | break; 68 | case 600: 69 | speed = B600; 70 | break; 71 | case 1200: 72 | speed = B1200; 73 | break; 74 | case 2400: 75 | speed = B2400; 76 | break; 77 | case 4800: 78 | speed = B4800; 79 | break; 80 | case 9600: 81 | speed = B9600; 82 | break; 83 | case 19200: 84 | speed = B19200; 85 | break; 86 | case 38400: 87 | speed = B38400; 88 | break; 89 | #ifdef B57600 90 | case 57600: 91 | speed = B57600; 92 | break; 93 | #endif 94 | #ifdef B115200 95 | case 115200: 96 | speed = B115200; 97 | break; 98 | #endif 99 | #ifdef B230400 100 | case 230400: 101 | speed = B230400; 102 | break; 103 | #endif 104 | #ifdef B460800 105 | case 460800: 106 | speed = B460800; 107 | break; 108 | #endif 109 | #ifdef B500000 110 | case 500000: 111 | speed = B500000; 112 | break; 113 | #endif 114 | #ifdef B576000 115 | case 576000: 116 | speed = B576000; 117 | break; 118 | #endif 119 | #ifdef B921600 120 | case 921600: 121 | speed = B921600; 122 | break; 123 | #endif 124 | #ifdef B1000000 125 | case 1000000: 126 | speed = B1000000; 127 | break; 128 | #endif 129 | #ifdef B1152000 130 | case 1152000: 131 | speed = B1152000; 132 | break; 133 | #endif 134 | #ifdef B1500000 135 | case 1500000: 136 | speed = B1500000; 137 | break; 138 | #endif 139 | #ifdef B2500000 140 | case 2500000: 141 | speed = B2500000; 142 | break; 143 | #endif 144 | #ifdef B3000000 145 | case 3000000: 146 | speed = B3000000; 147 | break; 148 | #endif 149 | #ifdef B3500000 150 | case 3500000: 151 | speed = B3500000; 152 | break; 153 | #endif 154 | #ifdef B4000000 155 | case 4000000: 156 | speed = B4000000; 157 | break; 158 | #endif 159 | default: 160 | speed = B9600; 161 | LOG_W("WARNING Unknown baud rate %d for %s (B9600 used)", baud, device); 162 | } 163 | 164 | /* Set the baud rate */ 165 | if ((cfsetispeed(&tios, speed) < 0) || 166 | (cfsetospeed(&tios, speed) < 0)) { 167 | close(s); 168 | s = -1; 169 | return -1; 170 | } 171 | 172 | /* C_CFLAG Control options 173 | CLOCAL Local line - do not change "owner" of port 174 | CREAD Enable receiver 175 | */ 176 | tios.c_cflag |= (CREAD | CLOCAL); 177 | /* CSIZE, HUPCL, CRTSCTS (hardware flow control) */ 178 | 179 | /* Set data bits (5, 6, 7, 8 bits) 180 | CSIZE Bit mask for data bits 181 | */ 182 | tios.c_cflag &= ~CSIZE; 183 | switch (data_bit) { 184 | case 5: 185 | tios.c_cflag |= CS5; 186 | break; 187 | case 6: 188 | tios.c_cflag |= CS6; 189 | break; 190 | case 7: 191 | tios.c_cflag |= CS7; 192 | break; 193 | case 8: 194 | default: 195 | tios.c_cflag |= CS8; 196 | break; 197 | } 198 | 199 | /* Stop bit (1 or 2) */ 200 | if (stop_bit == 1) 201 | tios.c_cflag &= ~CSTOPB; 202 | else /* 2 */ 203 | tios.c_cflag |= CSTOPB; 204 | 205 | /* PARENB Enable parity bit 206 | PARODD Use odd parity instead of even */ 207 | if (parity == 'N') { 208 | /* None */ 209 | tios.c_cflag &= ~PARENB; 210 | } else if (parity == 'E') { 211 | /* Even */ 212 | tios.c_cflag |= PARENB; 213 | tios.c_cflag &= ~PARODD; 214 | } else { 215 | /* Odd */ 216 | tios.c_cflag |= PARENB; 217 | tios.c_cflag |= PARODD; 218 | } 219 | 220 | /* Read the man page of termios if you need more information. */ 221 | 222 | /* This field isn't used on POSIX systems 223 | tios.c_line = 0; 224 | */ 225 | 226 | /* C_LFLAG Line options 227 | 228 | ISIG Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals 229 | ICANON Enable canonical input (else raw) 230 | XCASE Map uppercase \lowercase (obsolete) 231 | ECHO Enable echoing of input characters 232 | ECHOE Echo erase character as BS-SP-BS 233 | ECHOK Echo NL after kill character 234 | ECHONL Echo NL 235 | NOFLSH Disable flushing of input buffers after 236 | interrupt or quit characters 237 | IEXTEN Enable extended functions 238 | ECHOCTL Echo control characters as ^char and delete as ~? 239 | ECHOPRT Echo erased character as character erased 240 | ECHOKE BS-SP-BS entire line on line kill 241 | FLUSHO Output being flushed 242 | PENDIN Retype pending input at next read or input char 243 | TOSTOP Send SIGTTOU for background output 244 | 245 | Canonical input is line-oriented. Input characters are put 246 | into a buffer which can be edited interactively by the user 247 | until a CR (carriage return) or LF (line feed) character is 248 | received. 249 | 250 | Raw input is unprocessed. Input characters are passed 251 | through exactly as they are received, when they are 252 | received. Generally you'll deselect the ICANON, ECHO, 253 | ECHOE, and ISIG options when using raw input 254 | */ 255 | 256 | /* Raw input */ 257 | tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 258 | 259 | /* C_IFLAG Input options 260 | 261 | Constant Description 262 | INPCK Enable parity check 263 | IGNPAR Ignore parity errors 264 | PARMRK Mark parity errors 265 | ISTRIP Strip parity bits 266 | IXON Enable software flow control (outgoing) 267 | IXOFF Enable software flow control (incoming) 268 | IXANY Allow any character to start flow again 269 | IGNBRK Ignore break condition 270 | BRKINT Send a SIGINT when a break condition is detected 271 | INLCR Map NL to CR 272 | IGNCR Ignore CR 273 | ICRNL Map CR to NL 274 | IUCLC Map uppercase to lowercase 275 | IMAXBEL Echo BEL on input line too long 276 | */ 277 | if (parity == 'N') { 278 | /* None */ 279 | tios.c_iflag &= ~INPCK; 280 | } else { 281 | tios.c_iflag |= INPCK; 282 | } 283 | 284 | /* Software flow control is disabled */ 285 | tios.c_iflag &= ~(IXON | IXOFF | IXANY); 286 | 287 | /* C_OFLAG Output options 288 | OPOST Postprocess output (not set = raw output) 289 | ONLCR Map NL to CR-NL 290 | 291 | ONCLR ant others needs OPOST to be enabled 292 | */ 293 | 294 | /* Raw ouput */ 295 | tios.c_oflag &= ~OPOST; 296 | 297 | /* C_CC Control characters 298 | VMIN Minimum number of characters to read 299 | VTIME Time to wait for data (tenths of seconds) 300 | 301 | UNIX serial interface drivers provide the ability to 302 | specify character and packet timeouts. Two elements of the 303 | c_cc array are used for timeouts: VMIN and VTIME. Timeouts 304 | are ignored in canonical input mode or when the NDELAY 305 | option is set on the file via open or fcntl. 306 | 307 | VMIN specifies the minimum number of characters to read. If 308 | it is set to 0, then the VTIME value specifies the time to 309 | wait for every character read. Note that this does not mean 310 | that a read call for N bytes will wait for N characters to 311 | come in. Rather, the timeout will apply to the first 312 | character and the read call will return the number of 313 | characters immediately available (up to the number you 314 | request). 315 | 316 | If VMIN is non-zero, VTIME specifies the time to wait for 317 | the first character read. If a character is read within the 318 | time given, any read will block (wait) until all VMIN 319 | characters are read. That is, once the first character is 320 | read, the serial interface driver expects to receive an 321 | entire packet of characters (VMIN bytes total). If no 322 | character is read within the time allowed, then the call to 323 | read returns 0. This method allows you to tell the serial 324 | driver you need exactly N bytes and any read call will 325 | return 0 or N bytes. However, the timeout only applies to 326 | the first character read, so if for some reason the driver 327 | misses one character inside the N byte packet then the read 328 | call could block forever waiting for additional input 329 | characters. 330 | 331 | VTIME specifies the amount of time to wait for incoming 332 | characters in tenths of seconds. If VTIME is set to 0 (the 333 | default), reads will block (wait) indefinitely unless the 334 | NDELAY option is set on the port with open or fcntl. 335 | */ 336 | /* Unused because we use open with the NDELAY option */ 337 | tios.c_cc[VMIN] = 0; 338 | tios.c_cc[VTIME] = 0; 339 | 340 | if (tcsetattr(s, TCSANOW, &tios) < 0) { 341 | close(s); 342 | s = -1; 343 | return -1; 344 | } 345 | 346 | return s; 347 | } 348 | 349 | void serial_close(int s, struct termios *old_tios) 350 | { 351 | if (s != -1) { 352 | tcsetattr(s, TCSANOW, old_tios); 353 | close(s); 354 | } 355 | } 356 | 357 | int serial_send(int s, const uint8_t *buf, int length) 358 | { 359 | return write(s, buf, length); 360 | } 361 | 362 | int serial_receive(int s, uint8_t *buf, int bufsz, int timeout) 363 | { 364 | int len = 0; 365 | int rc = 0; 366 | fd_set rset; 367 | struct timeval tv; 368 | 369 | while (bufsz > 0) { 370 | FD_ZERO(&rset); 371 | FD_SET(s, &rset); 372 | 373 | tv.tv_sec = timeout / 1000; 374 | tv.tv_usec = (timeout % 1000) * 1000; 375 | rc = select(s + 1, &rset, NULL, NULL, &tv); 376 | if (rc == -1) { 377 | if (errno == EINTR) 378 | continue; 379 | } 380 | 381 | if (rc <= 0) { 382 | break; 383 | } 384 | 385 | rc = read(s, buf + len, bufsz); 386 | if (rc <= 0) { 387 | break; 388 | } 389 | len += rc; 390 | bufsz -= rc; 391 | 392 | timeout = 20; 393 | } 394 | 395 | if (rc >= 0) { 396 | rc = len; 397 | } 398 | 399 | return rc; 400 | } 401 | 402 | int serial_flush(int s) 403 | { 404 | if (s != -1) { 405 | tcflush(s, TCIOFLUSH); 406 | } 407 | 408 | return 0; 409 | } 410 | -------------------------------------------------------------------------------- /examples/common/serial.h: -------------------------------------------------------------------------------- 1 | #ifndef __SERIAL_H 2 | #define __SERIAL_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | int serial_init(const char *device, 12 | int baud, char parity, int data_bit, 13 | int stop_bit, struct termios *old_tios); 14 | void serial_close(int s, struct termios *old_tios); 15 | int serial_send(int s, const uint8_t *buf, int length); 16 | int serial_receive(int s, uint8_t *buf, int bufsz, int timeout); 17 | int serial_flush(int s); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /examples/common/tcp.c: -------------------------------------------------------------------------------- 1 | #include "tcp.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define DBG_ENABLE 18 | #define DBG_COLOR 19 | #define DBG_SECTION_NAME "tcp" 20 | #define DBG_LEVEL DBG_LOG 21 | #include "dbg_log.h" 22 | 23 | int tcp_listen(int port, int nb_connection) 24 | { 25 | int new_s; 26 | int enable; 27 | int flags; 28 | struct sockaddr_in addr; 29 | 30 | flags = SOCK_STREAM; 31 | 32 | #ifdef SOCK_CLOEXEC 33 | flags |= SOCK_CLOEXEC; 34 | #endif 35 | 36 | new_s = socket(PF_INET, flags, IPPROTO_TCP); 37 | if (new_s == -1) { 38 | return -1; 39 | } 40 | 41 | enable = 1; 42 | if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, 43 | (char *)&enable, sizeof(enable)) == -1) { 44 | close(new_s); 45 | return -1; 46 | } 47 | 48 | memset(&addr, 0, sizeof(addr)); 49 | addr.sin_family = AF_INET; 50 | /* If the modbus port is < to 1024, we need the setuid root. */ 51 | addr.sin_port = htons(port); 52 | /* Listen any addresses */ 53 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 54 | 55 | if (bind(new_s, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 56 | close(new_s); 57 | return -1; 58 | } 59 | 60 | if (listen(new_s, nb_connection) == -1) { 61 | close(new_s); 62 | return -1; 63 | } 64 | 65 | flags = fcntl(new_s, F_GETFL, 0); 66 | flags |= O_NONBLOCK; 67 | fcntl(new_s, F_SETFL, flags); 68 | 69 | flags = fcntl(new_s, F_GETFD); 70 | flags |= FD_CLOEXEC; 71 | fcntl(new_s, F_SETFD, flags); 72 | 73 | return new_s; 74 | } 75 | 76 | int tcp_accept(int s) 77 | { 78 | struct sockaddr_in addr; 79 | socklen_t addrlen; 80 | int new_s; 81 | 82 | addrlen = sizeof(addr); 83 | #ifdef HAVE_ACCEPT4 84 | /* Inherit socket flags and use accept4 call */ 85 | new_s = accept4(s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC); 86 | #else 87 | new_s = accept(s, (struct sockaddr *)&addr, &addrlen); 88 | #endif 89 | 90 | if (new_s == -1) { 91 | return -1; 92 | } 93 | 94 | LOG_I("The client connection from %s is accepted", inet_ntoa(addr.sin_addr)); 95 | 96 | return new_s; 97 | } 98 | 99 | void tcp_close(int s) 100 | { 101 | if (s != -1) { 102 | shutdown(s, SHUT_RDWR); 103 | close(s); 104 | } 105 | } 106 | 107 | int tcp_send(int s, const uint8_t *buf, int length) 108 | { 109 | return send(s, buf, length, MSG_NOSIGNAL); 110 | } 111 | 112 | int tcp_receive(int s, uint8_t *buf, int bufsz, int timeout) 113 | { 114 | int len = 0; 115 | int rc = 0; 116 | fd_set readset, exceptset; 117 | struct timeval tv; 118 | 119 | while (bufsz > 0) { 120 | FD_ZERO(&readset); 121 | FD_ZERO(&exceptset); 122 | FD_SET(s, &readset); 123 | FD_SET(s, &exceptset); 124 | 125 | tv.tv_sec = timeout / 1000; 126 | tv.tv_usec = (timeout % 1000) * 1000; 127 | rc = select(s + 1, &readset, NULL, &exceptset, &tv); 128 | if (rc == -1) { 129 | if (errno == EINTR) { 130 | continue; 131 | } 132 | } 133 | 134 | if (rc <= 0) 135 | break; 136 | 137 | if (FD_ISSET(s, &exceptset)) { 138 | rc = -1; 139 | break; 140 | } 141 | 142 | rc = recv(s, buf + len, bufsz, MSG_DONTWAIT); 143 | if (rc < 0) 144 | break; 145 | if (rc == 0) { 146 | if (len == 0) 147 | rc = -1; 148 | break; 149 | } 150 | 151 | len += rc; 152 | bufsz -= rc; 153 | 154 | timeout = 50; 155 | } 156 | 157 | if (rc >= 0) 158 | rc = len; 159 | 160 | return rc; 161 | } 162 | 163 | int tcp_flush(int s) 164 | { 165 | int rc; 166 | 167 | do { 168 | uint8_t devnull[100]; 169 | 170 | if (s == -1) 171 | break; 172 | 173 | rc = recv(s, devnull, sizeof(devnull), MSG_DONTWAIT); 174 | 175 | } while (rc == 100); 176 | 177 | return 0; 178 | } 179 | 180 | int tcp_connect(const char *ip, int port) 181 | { 182 | int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 183 | if (s == -1) 184 | return -1; 185 | 186 | int option = 1; 187 | int rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const void *)&option, sizeof(int)); 188 | if (rc == -1) { 189 | close(s); 190 | s = -1; 191 | return -1; 192 | } 193 | 194 | struct timeval tv; 195 | tv.tv_sec = 20; 196 | tv.tv_usec = 0; 197 | rc = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const void *)&tv, sizeof(struct timeval)); 198 | if (rc == -1) { 199 | close(s); 200 | s = -1; 201 | return -1; 202 | } 203 | 204 | struct sockaddr_in addr; 205 | memset(&addr, 0, sizeof(addr)); 206 | addr.sin_family = AF_INET; 207 | addr.sin_port = htons(port); 208 | addr.sin_addr.s_addr = inet_addr(ip); 209 | rc = connect(s, (struct sockaddr *)&addr, sizeof(addr)); 210 | if (rc == -1) { 211 | close(s); 212 | s = -1; 213 | return -1; 214 | } 215 | 216 | return s; 217 | } 218 | -------------------------------------------------------------------------------- /examples/common/tcp.h: -------------------------------------------------------------------------------- 1 | #ifndef __TCP_H 2 | #define __TCP_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | int tcp_listen(int port, int nb_connection); 11 | int tcp_accept(int s); 12 | void tcp_close(int s); 13 | int tcp_send(int s, const uint8_t *buf, int length); 14 | int tcp_receive(int s, uint8_t *buf, int bufsz, int timeout); 15 | int tcp_flush(int s); 16 | int tcp_connect(const char *ip, int port); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /examples/figures/ModbusPollRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusPollRTUConnection.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusPollSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusPollSetup.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusPollTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusPollTCPConnection.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusProtocol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusProtocol.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusSlaveRTUConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusSlaveRTUConnection.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusSlaveSetup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusSlaveSetup.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusSlaveShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusSlaveShow.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusSlaveTCPConnection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusSlaveTCPConnection.jpg -------------------------------------------------------------------------------- /examples/figures/ModbusSlaveTimeoutShow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/ModbusSlaveTimeoutShow.jpg -------------------------------------------------------------------------------- /examples/figures/RTUMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/RTUMaster.jpg -------------------------------------------------------------------------------- /examples/figures/TCPMaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/TCPMaster.jpg -------------------------------------------------------------------------------- /examples/figures/VirtualCom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/VirtualCom.jpg -------------------------------------------------------------------------------- /examples/figures/VirtualComGroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/VirtualComGroup.jpg -------------------------------------------------------------------------------- /examples/figures/rtu_broadcast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/rtu_broadcast.gif -------------------------------------------------------------------------------- /examples/figures/rtu_p2p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/examples/figures/rtu_p2p.gif -------------------------------------------------------------------------------- /examples/rtu_broadcast/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(broadcast) 4 | 5 | add_executable(broadcast_master broadcast_master.c) 6 | add_executable(broadcast_slave broadcast_slave.c) 7 | 8 | target_link_libraries(broadcast_master PRIVATE Threads::Threads) 9 | target_link_libraries(broadcast_slave PRIVATE Threads::Threads) 10 | -------------------------------------------------------------------------------- /examples/rtu_broadcast/broadcast_master.c: -------------------------------------------------------------------------------- 1 | #include "agile_modbus.h" 2 | #include "serial.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define DBG_ENABLE 11 | #define DBG_COLOR 12 | #define DBG_SECTION_NAME "broadcast_master" 13 | #define DBG_LEVEL DBG_LOG 14 | #include "dbg_log.h" 15 | 16 | static int _fd = -1; 17 | static struct termios _old_tios = {0}; 18 | static const uint8_t _dirty_buf[100] = {1, 2, 3, 4, 5}; 19 | 20 | #define AGILE_MODBUS_FC_TRANS_FILE 0x50 21 | #define TRANS_FILE_CMD_START 0x0001 22 | #define TRANS_FILE_CMD_DATA 0x0002 23 | #define TRANS_FILE_FLAG_END 0x00 24 | #define TRANS_FILE_FLAG_NOT_END 0x01 25 | 26 | static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function, 27 | agile_modbus_msg_type_t msg_type) 28 | { 29 | int length; 30 | 31 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 32 | length = 0; 33 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 34 | length = 4; 35 | } else { 36 | /* MSG_CONFIRMATION */ 37 | length = 1; 38 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 39 | length = 3; 40 | } 41 | 42 | return length; 43 | } 44 | 45 | static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg, 46 | int msg_length, agile_modbus_msg_type_t msg_type) 47 | { 48 | int function = msg[ctx->backend->header_length]; 49 | int length; 50 | 51 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 52 | length = 0; 53 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 54 | length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4]; 55 | } else { 56 | /* MSG_CONFIRMATION */ 57 | length = 0; 58 | } 59 | 60 | return length; 61 | } 62 | 63 | static void print_progress(size_t cur_size, size_t total_size) 64 | { 65 | static uint8_t progress_sign[100 + 1]; 66 | uint8_t i, per = cur_size * 100 / total_size; 67 | 68 | if (per > 100) { 69 | per = 100; 70 | } 71 | 72 | for (i = 0; i < 100; i++) { 73 | if (i < per) { 74 | progress_sign[i] = '='; 75 | } else if (per == i) { 76 | progress_sign[i] = '>'; 77 | } else { 78 | progress_sign[i] = ' '; 79 | } 80 | } 81 | 82 | progress_sign[sizeof(progress_sign) - 1] = '\0'; 83 | 84 | LOG_I("\033[2A"); 85 | LOG_I("Trans: [%s] %d%%", progress_sign, per); 86 | } 87 | 88 | static char *normalize_path(char *fullpath) 89 | { 90 | char *dst0, *dst, *src; 91 | 92 | src = fullpath; 93 | dst = fullpath; 94 | 95 | dst0 = dst; 96 | while (1) { 97 | char c = *src; 98 | 99 | if (c == '.') { 100 | if (!src[1]) 101 | src++; /* '.' and ends */ 102 | else if (src[1] == '/') { 103 | /* './' case */ 104 | src += 2; 105 | 106 | while ((*src == '/') && (*src != '\0')) 107 | src++; 108 | continue; 109 | } else if (src[1] == '.') { 110 | if (!src[2]) { 111 | /* '..' and ends case */ 112 | src += 2; 113 | goto up_one; 114 | } else if (src[2] == '/') { 115 | /* '../' case */ 116 | src += 3; 117 | 118 | while ((*src == '/') && (*src != '\0')) 119 | src++; 120 | goto up_one; 121 | } 122 | } 123 | } 124 | 125 | /* copy up the next '/' and erase all '/' */ 126 | while ((c = *src++) != '\0' && c != '/') 127 | *dst++ = c; 128 | 129 | if (c == '/') { 130 | *dst++ = '/'; 131 | while (c == '/') 132 | c = *src++; 133 | 134 | src--; 135 | } else if (!c) 136 | break; 137 | 138 | continue; 139 | 140 | up_one: 141 | dst--; 142 | if (dst < dst0) 143 | return NULL; 144 | while (dst0 < dst && dst[-1] != '/') 145 | dst--; 146 | } 147 | 148 | *dst = '\0'; 149 | 150 | /* remove '/' in the end of path if exist */ 151 | dst--; 152 | if ((dst != fullpath) && (*dst == '/')) 153 | *dst = '\0'; 154 | 155 | return fullpath; 156 | } 157 | 158 | static int trans_file(int slave, char *file_path) 159 | { 160 | uint8_t ctx_send_buf[2048]; 161 | uint8_t ctx_read_buf[50]; 162 | uint8_t raw_req[2048]; 163 | int raw_req_len = 0; 164 | 165 | agile_modbus_rtu_t ctx_rtu; 166 | agile_modbus_t *ctx = &ctx_rtu._ctx; 167 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 168 | agile_modbus_set_slave(ctx, slave); 169 | agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback); 170 | agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback); 171 | 172 | if (normalize_path(file_path) == NULL) 173 | return -1; 174 | 175 | const char *file_name = file_path; 176 | while (1) { 177 | const char *ptr = strchr(file_name, '/'); 178 | if (ptr == NULL) 179 | break; 180 | 181 | file_name = ptr + 1; 182 | } 183 | 184 | struct stat s; 185 | if (stat(file_path, &s) != 0) 186 | return -1; 187 | 188 | if (!S_ISREG(s.st_mode)) 189 | return -1; 190 | 191 | int file_size = s.st_size; 192 | 193 | LOG_I("file name:%s, file size:%d", file_name, file_size); 194 | printf("\r\n\r\n"); 195 | 196 | FILE *fp = fopen(file_path, "rb"); 197 | if (fp == NULL) 198 | return -1; 199 | 200 | int ret = 0; 201 | int write_file_size = 0; 202 | int step = 0; 203 | 204 | raw_req[0] = slave; 205 | raw_req[1] = AGILE_MODBUS_FC_TRANS_FILE; 206 | 207 | while (1) { 208 | usleep(5000); 209 | 210 | raw_req_len = 2; 211 | 212 | switch (step) { 213 | case 0: { 214 | step = 1; 215 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START >> 8); 216 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START & 0xFF); 217 | int nb = 5 + strlen(file_name); 218 | raw_req[raw_req_len++] = nb >> 8; 219 | raw_req[raw_req_len++] = nb & 0xFF; 220 | raw_req[raw_req_len++] = (file_size >> 24) & 0xFF; 221 | raw_req[raw_req_len++] = (file_size >> 16) & 0xFF; 222 | raw_req[raw_req_len++] = (file_size >> 8) & 0xFF; 223 | raw_req[raw_req_len++] = file_size & 0xFF; 224 | memcpy(raw_req + raw_req_len, file_name, strlen(file_name)); 225 | raw_req_len += strlen(file_name); 226 | raw_req[raw_req_len++] = '\0'; 227 | } break; 228 | 229 | case 1: { 230 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA >> 8); 231 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA & 0xFF); 232 | int nb_pos = raw_req_len; 233 | raw_req_len += 3; 234 | int recv_bytes = fread(raw_req + raw_req_len, 1, 1024, fp); 235 | raw_req_len += recv_bytes; 236 | write_file_size += recv_bytes; 237 | int nb = recv_bytes + 1; 238 | raw_req[nb_pos] = nb >> 8; 239 | raw_req[nb_pos + 1] = nb & 0xFF; 240 | if (recv_bytes < 1024) { 241 | raw_req[nb_pos + 2] = TRANS_FILE_FLAG_END; 242 | step = 2; 243 | } else 244 | raw_req[nb_pos + 2] = TRANS_FILE_FLAG_NOT_END; 245 | } break; 246 | 247 | default: 248 | break; 249 | } 250 | 251 | if (ret < 0) 252 | break; 253 | 254 | serial_flush(_fd); 255 | int send_len = agile_modbus_serialize_raw_request(ctx, raw_req, raw_req_len); 256 | serial_send(_fd, ctx->send_buf, send_len); 257 | 258 | //dirty data 259 | serial_send(_fd, _dirty_buf, sizeof(_dirty_buf)); 260 | 261 | print_progress(write_file_size, file_size); 262 | 263 | if (step == 2) 264 | break; 265 | } 266 | 267 | fclose(fp); 268 | fp = NULL; 269 | 270 | printf("\r\n\r\n"); 271 | 272 | return ret; 273 | } 274 | 275 | static void *cycle_entry(void *param) 276 | { 277 | char tmp[100]; 278 | 279 | while (1) { 280 | printf("please enter file_path:\r\n"); 281 | fgets(tmp, sizeof(tmp), stdin); 282 | for (int i = 0; i < strlen(tmp); i++) { 283 | if (tmp[i] == '\r' || tmp[i] == '\n') { 284 | tmp[i] = '\0'; 285 | break; 286 | } 287 | } 288 | 289 | trans_file(0, tmp); 290 | } 291 | } 292 | 293 | int main(int argc, char *argv[]) 294 | { 295 | if (argc < 2) { 296 | LOG_E("Please enter broadcast_master [dev]!"); 297 | return -1; 298 | } 299 | 300 | _fd = serial_init(argv[1], 115200, 'N', 8, 1, &_old_tios); 301 | if (_fd < 0) { 302 | LOG_E("Open %s failed!", argv[1]); 303 | return -1; 304 | } 305 | 306 | pthread_t tid; 307 | pthread_create(&tid, NULL, cycle_entry, NULL); 308 | pthread_join(tid, NULL); 309 | } 310 | -------------------------------------------------------------------------------- /examples/rtu_broadcast/broadcast_slave.c: -------------------------------------------------------------------------------- 1 | #include "serial.h" 2 | #include "agile_modbus.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "ringbuffer.h" 10 | 11 | #define DBG_ENABLE 12 | #define DBG_COLOR 13 | #define DBG_SECTION_NAME "broadcast_slave" 14 | #define DBG_LEVEL DBG_LOG 15 | #include "dbg_log.h" 16 | 17 | static int _fd = -1; 18 | static struct termios _old_tios = {0}; 19 | static FILE *_fp = NULL; 20 | static int _slave = 0; 21 | static int _file_size = 0; 22 | static int _write_file_size = 0; 23 | static pthread_mutex_t _mtx; 24 | static sem_t _notice; 25 | static struct rt_ringbuffer _recv_rb; 26 | static uint8_t _recv_rb_buf[20480]; 27 | 28 | #define AGILE_MODBUS_FC_TRANS_FILE 0x50 29 | #define TRANS_FILE_CMD_START 0x0001 30 | #define TRANS_FILE_CMD_DATA 0x0002 31 | #define TRANS_FILE_FLAG_END 0x00 32 | #define TRANS_FILE_FLAG_NOT_END 0x01 33 | 34 | static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function, 35 | agile_modbus_msg_type_t msg_type) 36 | { 37 | int length; 38 | 39 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 40 | length = 0; 41 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 42 | length = 4; 43 | } else { 44 | /* MSG_CONFIRMATION */ 45 | length = 1; 46 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 47 | length = 3; 48 | } 49 | 50 | return length; 51 | } 52 | 53 | static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg, 54 | int msg_length, agile_modbus_msg_type_t msg_type) 55 | { 56 | int function = msg[ctx->backend->header_length]; 57 | int length; 58 | 59 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 60 | length = 0; 61 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 62 | length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4]; 63 | } else { 64 | /* MSG_CONFIRMATION */ 65 | length = 0; 66 | } 67 | 68 | return length; 69 | } 70 | 71 | static void print_progress(size_t cur_size, size_t total_size) 72 | { 73 | static uint8_t progress_sign[100 + 1]; 74 | uint8_t i, per = cur_size * 100 / total_size; 75 | 76 | if (per > 100) { 77 | per = 100; 78 | } 79 | 80 | for (i = 0; i < 100; i++) { 81 | if (i < per) { 82 | progress_sign[i] = '='; 83 | } else if (per == i) { 84 | progress_sign[i] = '>'; 85 | } else { 86 | progress_sign[i] = ' '; 87 | } 88 | } 89 | 90 | progress_sign[sizeof(progress_sign) - 1] = '\0'; 91 | 92 | LOG_I("\033[2A"); 93 | LOG_I("Trans: [%s] %d%%", progress_sign, per); 94 | } 95 | 96 | /** 97 | * @brief Slave callback function 98 | * @param ctx modbus handle 99 | * @param slave_info slave information body 100 | * @param data private data 101 | * @return =0: normal; 102 | * <0: Abnormal 103 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 104 | * (Other negative exception codes: package exception response data from the opportunity) 105 | */ 106 | static int slave_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data) 107 | { 108 | int function = slave_info->sft->function; 109 | 110 | if (function != AGILE_MODBUS_FC_TRANS_FILE) 111 | return 0; 112 | 113 | int ret = 0; 114 | 115 | int send_index = slave_info->send_index; 116 | int data_len = slave_info->nb; 117 | uint8_t *data_ptr = slave_info->buf; 118 | int cmd = (data_ptr[0] << 8) + data_ptr[1]; 119 | int cmd_data_len = (data_ptr[2] << 8) + data_ptr[3]; 120 | uint8_t *cmd_data_ptr = data_ptr + 4; 121 | 122 | switch (cmd) { 123 | case TRANS_FILE_CMD_START: { 124 | if (_fp != NULL) { 125 | LOG_W("_fp is not NULL, now close _fp."); 126 | fclose(_fp); 127 | _fp = NULL; 128 | } 129 | 130 | if (cmd_data_len <= 4) { 131 | LOG_W("cmd start date_len must be greater than 4."); 132 | ret = -1; 133 | break; 134 | } 135 | 136 | _file_size = (((int)cmd_data_ptr[0] << 24) + 137 | ((int)cmd_data_ptr[1] << 16) + 138 | ((int)cmd_data_ptr[2] << 8) + 139 | (int)cmd_data_ptr[3]); 140 | 141 | _write_file_size = 0; 142 | 143 | char *file_name = (char *)(data_ptr + 8); 144 | if (strlen(file_name) >= 256) { 145 | LOG_W("file name must be less than 256."); 146 | ret = -1; 147 | break; 148 | } 149 | 150 | char own_file_name[300]; 151 | snprintf(own_file_name, sizeof(own_file_name), "%d_%s", ctx->slave, file_name); 152 | 153 | LOG_I("write to %s, file size is %d", own_file_name, _file_size); 154 | printf("\r\n\r\n"); 155 | 156 | _fp = fopen(own_file_name, "wb"); 157 | if (_fp == NULL) { 158 | LOG_W("open file %s error.", own_file_name); 159 | ret = -1; 160 | break; 161 | } 162 | } break; 163 | 164 | case TRANS_FILE_CMD_DATA: { 165 | if (_fp == NULL) { 166 | LOG_W("_fp is NULL."); 167 | ret = -1; 168 | break; 169 | } 170 | 171 | if (cmd_data_len <= 0) { 172 | LOG_W("cmd data data_len must be greater than 0"); 173 | ret = -1; 174 | break; 175 | } 176 | 177 | int flag = cmd_data_ptr[0]; 178 | int file_len = cmd_data_len - 1; 179 | if (file_len > 0) { 180 | if (fwrite(cmd_data_ptr + 1, file_len, 1, _fp) != 1) { 181 | LOG_W("write to file error."); 182 | ret = -1; 183 | break; 184 | } 185 | } 186 | _write_file_size += file_len; 187 | 188 | print_progress(_write_file_size, _file_size); 189 | 190 | if (flag == TRANS_FILE_FLAG_END) { 191 | fclose(_fp); 192 | _fp = NULL; 193 | printf("\r\n\r\n"); 194 | if (_write_file_size != _file_size) { 195 | LOG_W("_write_file_size (%d) != _file_size (%d)", _write_file_size, _file_size); 196 | ret = -1; 197 | break; 198 | } 199 | 200 | LOG_I("success."); 201 | } 202 | 203 | } break; 204 | 205 | default: 206 | ret = -1; 207 | break; 208 | } 209 | 210 | ctx->send_buf[send_index++] = data_ptr[0]; 211 | ctx->send_buf[send_index++] = data_ptr[1]; 212 | ctx->send_buf[send_index++] = (ret == 0) ? 0x01 : 0x00; 213 | *(slave_info->rsp_length) = send_index; 214 | 215 | return 0; 216 | } 217 | 218 | static void *recv_entry(void *param) 219 | { 220 | uint8_t tmp[4096]; 221 | while (1) { 222 | int read_len = serial_receive(_fd, tmp, sizeof(tmp), 1000); 223 | 224 | while (read_len > 0) { 225 | pthread_mutex_lock(&_mtx); 226 | int rb_recv_len = rt_ringbuffer_put(&_recv_rb, tmp, read_len); 227 | pthread_mutex_unlock(&_mtx); 228 | 229 | read_len -= rb_recv_len; 230 | 231 | if (rb_recv_len > 0) 232 | sem_post(&_notice); 233 | else 234 | usleep(1000); 235 | } 236 | } 237 | } 238 | 239 | static int rb_receive(uint8_t *buf, int bufsz, int timeout) 240 | { 241 | int len = 0; 242 | 243 | while (1) { 244 | while (sem_trywait(&_notice) == 0) 245 | ; 246 | pthread_mutex_lock(&_mtx); 247 | int read_len = rt_ringbuffer_get(&_recv_rb, buf + len, bufsz); 248 | pthread_mutex_unlock(&_mtx); 249 | 250 | if (read_len > 0) { 251 | len += read_len; 252 | bufsz -= read_len; 253 | if (bufsz == 0) 254 | break; 255 | 256 | continue; 257 | } 258 | 259 | struct timespec ts; 260 | clock_gettime(CLOCK_REALTIME, &ts); 261 | ts.tv_sec += timeout / 1000; 262 | ts.tv_nsec += (timeout % 1000) * 1000; 263 | if (sem_timedwait(&_notice, &ts) != 0) 264 | break; 265 | } 266 | 267 | return len; 268 | } 269 | 270 | static void *cycle_entry(void *param) 271 | { 272 | uint8_t ctx_send_buf[50]; 273 | uint8_t ctx_read_buf1[2048]; 274 | uint8_t ctx_read_buf2[2048]; 275 | uint8_t *used_buf_ptr = ctx_read_buf1; 276 | 277 | int frame_length = 0; 278 | int remain_length = 0; 279 | 280 | agile_modbus_rtu_t ctx_rtu; 281 | agile_modbus_t *ctx = &ctx_rtu._ctx; 282 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), used_buf_ptr, sizeof(ctx_read_buf1)); 283 | agile_modbus_set_slave(ctx, _slave); 284 | agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback); 285 | agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback); 286 | 287 | LOG_I("slave %d running.", _slave); 288 | 289 | while (1) { 290 | int read_len = rb_receive(ctx->read_buf + remain_length, ctx->read_bufsz - remain_length, 1000); 291 | 292 | int total_len = read_len + remain_length; 293 | 294 | int is_reset = 0; 295 | 296 | if (total_len && read_len == 0) 297 | is_reset = 1; 298 | 299 | // Unpacking. In order to prevent dirty data, it cannot be discarded directly. Move one byte back to continue parsing. 300 | while (total_len > 0) { 301 | int rc = agile_modbus_slave_handle(ctx, total_len, 1, slave_callback, NULL, &frame_length); 302 | if (rc >= 0) { 303 | ctx->read_buf = ctx->read_buf + frame_length; 304 | ctx->read_bufsz = ctx->read_bufsz - frame_length; 305 | 306 | remain_length = total_len - frame_length; 307 | total_len = remain_length; 308 | } else { 309 | 310 | if (total_len > 1600 || is_reset) { 311 | ctx->read_buf++; 312 | ctx->read_bufsz--; 313 | total_len--; 314 | continue; 315 | } 316 | 317 | if (used_buf_ptr == ctx_read_buf1) { 318 | memcpy(ctx_read_buf2, ctx->read_buf, total_len); 319 | 320 | ctx->read_buf = ctx_read_buf2; 321 | ctx->read_bufsz = sizeof(ctx_read_buf2); 322 | used_buf_ptr = ctx_read_buf2; 323 | } else { 324 | memcpy(ctx_read_buf1, ctx->read_buf, total_len); 325 | 326 | ctx->read_buf = ctx_read_buf1; 327 | ctx->read_bufsz = sizeof(ctx_read_buf1); 328 | used_buf_ptr = ctx_read_buf1; 329 | } 330 | 331 | remain_length = total_len; 332 | 333 | break; 334 | } 335 | } 336 | 337 | if (total_len == 0) { 338 | remain_length = 0; 339 | 340 | ctx->read_buf = ctx_read_buf1; 341 | ctx->read_bufsz = sizeof(ctx_read_buf1); 342 | used_buf_ptr = ctx_read_buf1; 343 | } 344 | } 345 | 346 | serial_close(_fd, &_old_tios); 347 | } 348 | 349 | int main(int argc, char *argv[]) 350 | { 351 | if (argc < 3) { 352 | LOG_E("Please enter broadcast_slave [dev] [slave]!"); 353 | return -1; 354 | } 355 | 356 | _slave = atoi(argv[2]); 357 | if (_slave <= 0) { 358 | LOG_E("slave must be greater than 0!"); 359 | return -1; 360 | } 361 | 362 | _fd = serial_init(argv[1], 115200, 'N', 8, 1, &_old_tios); 363 | if (_fd < 0) { 364 | LOG_E("Open %s failed!", argv[1]); 365 | return -1; 366 | } 367 | 368 | pthread_mutex_init(&_mtx, NULL); 369 | sem_init(&_notice, 0, 0); 370 | rt_ringbuffer_init(&_recv_rb, _recv_rb_buf, sizeof(_recv_rb_buf)); 371 | 372 | pthread_t tid1, tid2; 373 | pthread_create(&tid1, NULL, cycle_entry, NULL); 374 | pthread_create(&tid2, NULL, recv_entry, NULL); 375 | 376 | pthread_join(tid1, NULL); 377 | pthread_join(tid2, NULL); 378 | 379 | return 0; 380 | } 381 | -------------------------------------------------------------------------------- /examples/rtu_master/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(rtu_master) 4 | 5 | file(GLOB SRCS *.c) 6 | 7 | add_executable(RtuMaster ${SRCS}) 8 | 9 | target_link_libraries(RtuMaster PRIVATE Threads::Threads) 10 | -------------------------------------------------------------------------------- /examples/rtu_master/rtu_master.c: -------------------------------------------------------------------------------- 1 | #include "agile_modbus.h" 2 | #include "serial.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define DBG_ENABLE 9 | #define DBG_COLOR 10 | #define DBG_SECTION_NAME "rtu_master" 11 | #define DBG_LEVEL DBG_LOG 12 | #include "dbg_log.h" 13 | 14 | static int _fd = -1; 15 | static struct termios _old_tios = {0}; 16 | 17 | static void *cycle_entry(void *param) 18 | { 19 | uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 20 | uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 21 | uint16_t hold_register[10]; 22 | 23 | agile_modbus_rtu_t ctx_rtu; 24 | agile_modbus_t *ctx = &ctx_rtu._ctx; 25 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 26 | agile_modbus_set_slave(ctx, 1); 27 | 28 | LOG_I("Running."); 29 | 30 | while (1) { 31 | usleep(100000); 32 | 33 | serial_flush(_fd); 34 | int send_len = agile_modbus_serialize_read_registers(ctx, 0, 10); 35 | serial_send(_fd, ctx->send_buf, send_len); 36 | int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000); 37 | if (read_len < 0) { 38 | LOG_E("Receive error, now exit."); 39 | break; 40 | } 41 | 42 | if (read_len == 0) { 43 | LOG_W("Receive timeout."); 44 | continue; 45 | } 46 | 47 | int rc = agile_modbus_deserialize_read_registers(ctx, read_len, hold_register); 48 | if (rc < 0) { 49 | LOG_W("Receive failed."); 50 | if (rc != -1) 51 | LOG_W("Error code:%d", -128 - rc); 52 | 53 | continue; 54 | } 55 | 56 | LOG_I("Hold Registers:"); 57 | for (int i = 0; i < 10; i++) 58 | LOG_I("Register [%d]: 0x%04X", i, hold_register[i]); 59 | 60 | printf("\r\n\r\n\r\n"); 61 | } 62 | 63 | serial_close(_fd, &_old_tios); 64 | } 65 | 66 | int main(int argc, char *argv[]) 67 | { 68 | if (argc < 2) { 69 | LOG_E("Please enter RtuMaster [dev]!"); 70 | return -1; 71 | } 72 | 73 | _fd = serial_init(argv[1], 9600, 'N', 8, 1, &_old_tios); 74 | if (_fd < 0) { 75 | LOG_E("Open %s failed!", argv[1]); 76 | return -1; 77 | } 78 | 79 | pthread_t tid; 80 | pthread_create(&tid, NULL, cycle_entry, NULL); 81 | pthread_join(tid, NULL); 82 | } 83 | -------------------------------------------------------------------------------- /examples/rtu_p2p/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(p2p) 4 | 5 | add_executable(p2p_master p2p_master.c) 6 | add_executable(p2p_slave p2p_slave.c) 7 | 8 | target_link_libraries(p2p_master PRIVATE Threads::Threads) 9 | target_link_libraries(p2p_slave PRIVATE Threads::Threads) 10 | -------------------------------------------------------------------------------- /examples/rtu_p2p/p2p_master.c: -------------------------------------------------------------------------------- 1 | #include "agile_modbus.h" 2 | #include "serial.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define DBG_ENABLE 11 | #define DBG_COLOR 12 | #define DBG_SECTION_NAME "p2p_master" 13 | #define DBG_LEVEL DBG_LOG 14 | #include "dbg_log.h" 15 | 16 | static int _fd = -1; 17 | static struct termios _old_tios = {0}; 18 | 19 | #define AGILE_MODBUS_FC_TRANS_FILE 0x50 20 | #define TRANS_FILE_CMD_START 0x0001 21 | #define TRANS_FILE_CMD_DATA 0x0002 22 | #define TRANS_FILE_FLAG_END 0x00 23 | #define TRANS_FILE_FLAG_NOT_END 0x01 24 | 25 | static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function, 26 | agile_modbus_msg_type_t msg_type) 27 | { 28 | int length; 29 | 30 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 31 | length = 0; 32 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 33 | length = 4; 34 | } else { 35 | /* MSG_CONFIRMATION */ 36 | length = 1; 37 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 38 | length = 3; 39 | } 40 | 41 | return length; 42 | } 43 | 44 | static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg, 45 | int msg_length, agile_modbus_msg_type_t msg_type) 46 | { 47 | int function = msg[ctx->backend->header_length]; 48 | int length; 49 | 50 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 51 | length = 0; 52 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 53 | length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4]; 54 | } else { 55 | /* MSG_CONFIRMATION */ 56 | length = 0; 57 | } 58 | 59 | return length; 60 | } 61 | 62 | static void print_progress(size_t cur_size, size_t total_size) 63 | { 64 | static uint8_t progress_sign[100 + 1]; 65 | uint8_t i, per = cur_size * 100 / total_size; 66 | 67 | if (per > 100) { 68 | per = 100; 69 | } 70 | 71 | for (i = 0; i < 100; i++) { 72 | if (i < per) { 73 | progress_sign[i] = '='; 74 | } else if (per == i) { 75 | progress_sign[i] = '>'; 76 | } else { 77 | progress_sign[i] = ' '; 78 | } 79 | } 80 | 81 | progress_sign[sizeof(progress_sign) - 1] = '\0'; 82 | 83 | LOG_I("\033[2A"); 84 | LOG_I("Trans: [%s] %d%%", progress_sign, per); 85 | } 86 | 87 | static char *normalize_path(char *fullpath) 88 | { 89 | char *dst0, *dst, *src; 90 | 91 | src = fullpath; 92 | dst = fullpath; 93 | 94 | dst0 = dst; 95 | while (1) { 96 | char c = *src; 97 | 98 | if (c == '.') { 99 | if (!src[1]) 100 | src++; /* '.' and ends */ 101 | else if (src[1] == '/') { 102 | /* './' case */ 103 | src += 2; 104 | 105 | while ((*src == '/') && (*src != '\0')) 106 | src++; 107 | continue; 108 | } else if (src[1] == '.') { 109 | if (!src[2]) { 110 | /* '..' and ends case */ 111 | src += 2; 112 | goto up_one; 113 | } else if (src[2] == '/') { 114 | /* '../' case */ 115 | src += 3; 116 | 117 | while ((*src == '/') && (*src != '\0')) 118 | src++; 119 | goto up_one; 120 | } 121 | } 122 | } 123 | 124 | /* copy up the next '/' and erase all '/' */ 125 | while ((c = *src++) != '\0' && c != '/') 126 | *dst++ = c; 127 | 128 | if (c == '/') { 129 | *dst++ = '/'; 130 | while (c == '/') 131 | c = *src++; 132 | 133 | src--; 134 | } else if (!c) 135 | break; 136 | 137 | continue; 138 | 139 | up_one: 140 | dst--; 141 | if (dst < dst0) 142 | return NULL; 143 | while (dst0 < dst && dst[-1] != '/') 144 | dst--; 145 | } 146 | 147 | *dst = '\0'; 148 | 149 | /* remove '/' in the end of path if exist */ 150 | dst--; 151 | if ((dst != fullpath) && (*dst == '/')) 152 | *dst = '\0'; 153 | 154 | return fullpath; 155 | } 156 | 157 | static int trans_file(int slave, char *file_path) 158 | { 159 | uint8_t ctx_send_buf[2048]; 160 | uint8_t ctx_read_buf[50]; 161 | uint8_t raw_req[2048]; 162 | int raw_req_len = 0; 163 | 164 | agile_modbus_rtu_t ctx_rtu; 165 | agile_modbus_t *ctx = &ctx_rtu._ctx; 166 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 167 | agile_modbus_set_slave(ctx, slave); 168 | agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback); 169 | agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback); 170 | 171 | if (normalize_path(file_path) == NULL) 172 | return -1; 173 | 174 | const char *file_name = file_path; 175 | while (1) { 176 | const char *ptr = strchr(file_name, '/'); 177 | if (ptr == NULL) 178 | break; 179 | 180 | file_name = ptr + 1; 181 | } 182 | 183 | struct stat s; 184 | if (stat(file_path, &s) != 0) 185 | return -1; 186 | 187 | if (!S_ISREG(s.st_mode)) 188 | return -1; 189 | 190 | int file_size = s.st_size; 191 | 192 | LOG_I("file name:%s, file size:%d", file_name, file_size); 193 | printf("\r\n\r\n"); 194 | 195 | FILE *fp = fopen(file_path, "rb"); 196 | if (fp == NULL) 197 | return -1; 198 | 199 | int ret = 0; 200 | int write_file_size = 0; 201 | int step = 0; 202 | 203 | raw_req[0] = slave; 204 | raw_req[1] = AGILE_MODBUS_FC_TRANS_FILE; 205 | 206 | while (1) { 207 | usleep(10000); 208 | 209 | raw_req_len = 2; 210 | 211 | switch (step) { 212 | case 0: { 213 | step = 1; 214 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START >> 8); 215 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START & 0xFF); 216 | int nb = 5 + strlen(file_name); 217 | raw_req[raw_req_len++] = nb >> 8; 218 | raw_req[raw_req_len++] = nb & 0xFF; 219 | raw_req[raw_req_len++] = (file_size >> 24) & 0xFF; 220 | raw_req[raw_req_len++] = (file_size >> 16) & 0xFF; 221 | raw_req[raw_req_len++] = (file_size >> 8) & 0xFF; 222 | raw_req[raw_req_len++] = file_size & 0xFF; 223 | memcpy(raw_req + raw_req_len, file_name, strlen(file_name)); 224 | raw_req_len += strlen(file_name); 225 | raw_req[raw_req_len++] = '\0'; 226 | } break; 227 | 228 | case 1: { 229 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA >> 8); 230 | raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA & 0xFF); 231 | int nb_pos = raw_req_len; 232 | raw_req_len += 3; 233 | int recv_bytes = fread(raw_req + raw_req_len, 1, 1024, fp); 234 | raw_req_len += recv_bytes; 235 | write_file_size += recv_bytes; 236 | int nb = recv_bytes + 1; 237 | raw_req[nb_pos] = nb >> 8; 238 | raw_req[nb_pos + 1] = nb & 0xFF; 239 | if (recv_bytes < 1024) { 240 | raw_req[nb_pos + 2] = TRANS_FILE_FLAG_END; 241 | step = 2; 242 | } else 243 | raw_req[nb_pos + 2] = TRANS_FILE_FLAG_NOT_END; 244 | } break; 245 | 246 | default: 247 | break; 248 | } 249 | 250 | if (ret < 0) 251 | break; 252 | 253 | serial_flush(_fd); 254 | int send_len = agile_modbus_serialize_raw_request(ctx, raw_req, raw_req_len); 255 | serial_send(_fd, ctx->send_buf, send_len); 256 | int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000); 257 | if (read_len < 0) { 258 | LOG_E("Receive error."); 259 | ret = -1; 260 | break; 261 | } 262 | 263 | if (read_len == 0) { 264 | LOG_W("Receive timeout."); 265 | ret = -1; 266 | break; 267 | } 268 | 269 | int rc = agile_modbus_deserialize_raw_response(ctx, read_len); 270 | if (rc < 0) { 271 | LOG_W("Receive failed."); 272 | ret = -1; 273 | break; 274 | } 275 | 276 | int flag = ctx->read_buf[ctx->backend->header_length + 3]; 277 | if (flag != 0x01) { 278 | LOG_W("ack flag is failed."); 279 | ret = -1; 280 | break; 281 | } 282 | 283 | print_progress(write_file_size, file_size); 284 | 285 | if (step == 2) 286 | break; 287 | } 288 | 289 | fclose(fp); 290 | fp = NULL; 291 | 292 | return ret; 293 | } 294 | 295 | static void *cycle_entry(void *param) 296 | { 297 | int slave; 298 | char tmp[100]; 299 | 300 | while (1) { 301 | printf("please enter slave:\r\n"); 302 | fgets(tmp, sizeof(tmp), stdin); 303 | slave = atoi(tmp); 304 | if (slave <= 0) { 305 | LOG_W("slave must be greater than 0"); 306 | continue; 307 | } 308 | 309 | printf("please enter file_path:\r\n"); 310 | fgets(tmp, sizeof(tmp), stdin); 311 | for (int i = 0; i < strlen(tmp); i++) { 312 | if (tmp[i] == '\r' || tmp[i] == '\n') { 313 | tmp[i] = '\0'; 314 | break; 315 | } 316 | } 317 | 318 | trans_file(slave, tmp); 319 | } 320 | } 321 | 322 | int main(int argc, char *argv[]) 323 | { 324 | if (argc < 2) { 325 | LOG_E("Please enter p2p_master [dev]!"); 326 | return -1; 327 | } 328 | 329 | _fd = serial_init(argv[1], 115200, 'N', 8, 1, &_old_tios); 330 | if (_fd < 0) { 331 | LOG_E("Open %s failed!", argv[1]); 332 | return -1; 333 | } 334 | 335 | pthread_t tid; 336 | pthread_create(&tid, NULL, cycle_entry, NULL); 337 | pthread_join(tid, NULL); 338 | } 339 | -------------------------------------------------------------------------------- /examples/rtu_p2p/p2p_slave.c: -------------------------------------------------------------------------------- 1 | #include "serial.h" 2 | #include "agile_modbus.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define DBG_ENABLE 10 | #define DBG_COLOR 11 | #define DBG_SECTION_NAME "p2p_slave" 12 | #define DBG_LEVEL DBG_LOG 13 | #include "dbg_log.h" 14 | 15 | static int _fd = -1; 16 | static struct termios _old_tios = {0}; 17 | static FILE *_fp = NULL; 18 | static int _slave = 0; 19 | static int _file_size = 0; 20 | static int _write_file_size = 0; 21 | 22 | #define AGILE_MODBUS_FC_TRANS_FILE 0x50 23 | #define TRANS_FILE_CMD_START 0x0001 24 | #define TRANS_FILE_CMD_DATA 0x0002 25 | #define TRANS_FILE_FLAG_END 0x00 26 | #define TRANS_FILE_FLAG_NOT_END 0x01 27 | 28 | static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function, 29 | agile_modbus_msg_type_t msg_type) 30 | { 31 | int length; 32 | 33 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 34 | length = 0; 35 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 36 | length = 4; 37 | } else { 38 | /* MSG_CONFIRMATION */ 39 | length = 1; 40 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 41 | length = 3; 42 | } 43 | 44 | return length; 45 | } 46 | 47 | static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg, 48 | int msg_length, agile_modbus_msg_type_t msg_type) 49 | { 50 | int function = msg[ctx->backend->header_length]; 51 | int length; 52 | 53 | if (msg_type == AGILE_MODBUS_MSG_INDICATION) { 54 | length = 0; 55 | if (function == AGILE_MODBUS_FC_TRANS_FILE) 56 | length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4]; 57 | } else { 58 | /* MSG_CONFIRMATION */ 59 | length = 0; 60 | } 61 | 62 | return length; 63 | } 64 | 65 | static void print_progress(size_t cur_size, size_t total_size) 66 | { 67 | static uint8_t progress_sign[100 + 1]; 68 | uint8_t i, per = cur_size * 100 / total_size; 69 | 70 | if (per > 100) { 71 | per = 100; 72 | } 73 | 74 | for (i = 0; i < 100; i++) { 75 | if (i < per) { 76 | progress_sign[i] = '='; 77 | } else if (per == i) { 78 | progress_sign[i] = '>'; 79 | } else { 80 | progress_sign[i] = ' '; 81 | } 82 | } 83 | 84 | progress_sign[sizeof(progress_sign) - 1] = '\0'; 85 | 86 | LOG_I("\033[2A"); 87 | LOG_I("Trans: [%s] %d%%", progress_sign, per); 88 | } 89 | 90 | /** 91 | * @brief Slave callback function 92 | * @param ctx modbus handle 93 | * @param slave_info slave information body 94 | * @param data private data 95 | * @return =0: normal; 96 | * <0: Abnormal 97 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 98 | * (Other negative exception codes: package exception response data from the opportunity) 99 | */ 100 | static int slave_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data) 101 | { 102 | int function = slave_info->sft->function; 103 | 104 | if (function != AGILE_MODBUS_FC_TRANS_FILE) 105 | return 0; 106 | 107 | int ret = 0; 108 | 109 | int send_index = slave_info->send_index; 110 | int data_len = slave_info->nb; 111 | uint8_t *data_ptr = slave_info->buf; 112 | int cmd = (data_ptr[0] << 8) + data_ptr[1]; 113 | int cmd_data_len = (data_ptr[2] << 8) + data_ptr[3]; 114 | uint8_t *cmd_data_ptr = data_ptr + 4; 115 | 116 | switch (cmd) { 117 | case TRANS_FILE_CMD_START: { 118 | if (_fp != NULL) { 119 | LOG_W("_fp is not NULL, now close _fp."); 120 | fclose(_fp); 121 | _fp = NULL; 122 | ret = -1; 123 | break; 124 | } 125 | 126 | if (cmd_data_len <= 4) { 127 | LOG_W("cmd start date_len must be greater than 4."); 128 | ret = -1; 129 | break; 130 | } 131 | 132 | _file_size = (((int)cmd_data_ptr[0] << 24) + 133 | ((int)cmd_data_ptr[1] << 16) + 134 | ((int)cmd_data_ptr[2] << 8) + 135 | (int)cmd_data_ptr[3]); 136 | 137 | _write_file_size = 0; 138 | 139 | char *file_name = (char *)(data_ptr + 8); 140 | if (strlen(file_name) >= 256) { 141 | LOG_W("file name must be less than 256."); 142 | ret = -1; 143 | break; 144 | } 145 | 146 | char own_file_name[300]; 147 | snprintf(own_file_name, sizeof(own_file_name), "%d_%s", slave_info->sft->slave, file_name); 148 | 149 | LOG_I("write to %s, file size is %d", own_file_name, _file_size); 150 | printf("\r\n\r\n"); 151 | 152 | _fp = fopen(own_file_name, "wb"); 153 | if (_fp == NULL) { 154 | LOG_W("open file %s error.", own_file_name); 155 | ret = -1; 156 | break; 157 | } 158 | } break; 159 | 160 | case TRANS_FILE_CMD_DATA: { 161 | if (_fp == NULL) { 162 | LOG_W("_fp is NULL."); 163 | ret = -1; 164 | break; 165 | } 166 | 167 | if (cmd_data_len <= 0) { 168 | LOG_W("cmd data data_len must be greater than 0"); 169 | ret = -1; 170 | break; 171 | } 172 | 173 | int flag = cmd_data_ptr[0]; 174 | int file_len = cmd_data_len - 1; 175 | if (file_len > 0) { 176 | if (fwrite(cmd_data_ptr + 1, file_len, 1, _fp) != 1) { 177 | LOG_W("write to file error."); 178 | ret = -1; 179 | break; 180 | } 181 | } 182 | _write_file_size += file_len; 183 | 184 | print_progress(_write_file_size, _file_size); 185 | 186 | if (flag == TRANS_FILE_FLAG_END) { 187 | fclose(_fp); 188 | _fp = NULL; 189 | printf("\r\n\r\n"); 190 | if (_write_file_size != _file_size) { 191 | LOG_W("_write_file_size (%d) != _file_size (%d)", _write_file_size, _file_size); 192 | ret = -1; 193 | break; 194 | } 195 | 196 | LOG_I("success."); 197 | } 198 | 199 | } break; 200 | 201 | default: 202 | ret = -1; 203 | break; 204 | } 205 | 206 | ctx->send_buf[send_index++] = data_ptr[0]; 207 | ctx->send_buf[send_index++] = data_ptr[1]; 208 | ctx->send_buf[send_index++] = (ret == 0) ? 0x01 : 0x00; 209 | *(slave_info->rsp_length) = send_index; 210 | 211 | return 0; 212 | } 213 | 214 | static void *cycle_entry(void *param) 215 | { 216 | uint8_t ctx_send_buf[50]; 217 | uint8_t ctx_read_buf[2048]; 218 | 219 | agile_modbus_rtu_t ctx_rtu; 220 | agile_modbus_t *ctx = &ctx_rtu._ctx; 221 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 222 | agile_modbus_set_slave(ctx, _slave); 223 | agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback); 224 | agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback); 225 | 226 | LOG_I("slave %d running.", _slave); 227 | 228 | while (1) { 229 | int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000); 230 | if (read_len < 0) { 231 | LOG_E("Receive error, now exit."); 232 | break; 233 | } 234 | 235 | if (read_len == 0) 236 | continue; 237 | 238 | int send_len = agile_modbus_slave_handle(ctx, read_len, 1, slave_callback, NULL, NULL); 239 | serial_flush(_fd); 240 | if (send_len > 0) 241 | serial_send(_fd, ctx->send_buf, send_len); 242 | } 243 | 244 | serial_close(_fd, &_old_tios); 245 | } 246 | 247 | int main(int argc, char *argv[]) 248 | { 249 | if (argc < 3) { 250 | LOG_E("Please enter p2p_slave [dev] [slave]!"); 251 | return -1; 252 | } 253 | 254 | _slave = atoi(argv[2]); 255 | if (_slave <= 0) { 256 | LOG_E("slave must be greater than 0!"); 257 | return -1; 258 | } 259 | 260 | _fd = serial_init(argv[1], 115200, 'N', 8, 1, &_old_tios); 261 | if (_fd < 0) { 262 | LOG_E("Open %s failed!", argv[1]); 263 | return -1; 264 | } 265 | 266 | pthread_t tid; 267 | pthread_create(&tid, NULL, cycle_entry, NULL); 268 | pthread_join(tid, NULL); 269 | 270 | return 0; 271 | } 272 | -------------------------------------------------------------------------------- /examples/slave/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(slave) 4 | 5 | file(GLOB SRCS *.c) 6 | 7 | add_executable(ModbusSlave ${SRCS}) 8 | 9 | target_link_libraries(ModbusSlave PRIVATE Threads::Threads) 10 | -------------------------------------------------------------------------------- /examples/slave/bits.c: -------------------------------------------------------------------------------- 1 | #include "slave.h" 2 | 3 | static uint8_t _tab_bits[10] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1}; 4 | 5 | static int get_map_buf(void *buf, int bufsz) 6 | { 7 | uint8_t *ptr = (uint8_t *)buf; 8 | 9 | pthread_mutex_lock(&slave_mtx); 10 | for (int i = 0; i < sizeof(_tab_bits); i++) { 11 | ptr[i] = _tab_bits[i]; 12 | } 13 | pthread_mutex_unlock(&slave_mtx); 14 | 15 | return 0; 16 | } 17 | 18 | static int set_map_buf(int index, int len, void *buf, int bufsz) 19 | { 20 | uint8_t *ptr = (uint8_t *)buf; 21 | 22 | pthread_mutex_lock(&slave_mtx); 23 | for (int i = 0; i < len; i++) { 24 | _tab_bits[index + i] = ptr[index + i]; 25 | } 26 | pthread_mutex_unlock(&slave_mtx); 27 | 28 | return 0; 29 | } 30 | 31 | const agile_modbus_slave_util_map_t bit_maps[1] = { 32 | {0x041A, 0x0423, get_map_buf, set_map_buf}}; 33 | -------------------------------------------------------------------------------- /examples/slave/input_bits.c: -------------------------------------------------------------------------------- 1 | #include "slave.h" 2 | 3 | static uint8_t _tab_input_bits[10] = {0, 1, 1, 0, 0, 1, 1, 0, 0, 1}; 4 | 5 | static int get_map_buf(void *buf, int bufsz) 6 | { 7 | uint8_t *ptr = (uint8_t *)buf; 8 | 9 | pthread_mutex_lock(&slave_mtx); 10 | for (int i = 0; i < sizeof(_tab_input_bits); i++) { 11 | ptr[i] = _tab_input_bits[i]; 12 | } 13 | pthread_mutex_unlock(&slave_mtx); 14 | 15 | return 0; 16 | } 17 | 18 | const agile_modbus_slave_util_map_t input_bit_maps[1] = { 19 | {0x041A, 0x0423, get_map_buf, NULL}}; 20 | -------------------------------------------------------------------------------- /examples/slave/input_registers.c: -------------------------------------------------------------------------------- 1 | #include "slave.h" 2 | 3 | static uint16_t _tab_input_registers[10] = {0, 1, 2, 3, 4, 9, 8, 7, 6, 5}; 4 | 5 | static int get_map_buf(void *buf, int bufsz) 6 | { 7 | uint16_t *ptr = (uint16_t *)buf; 8 | 9 | pthread_mutex_lock(&slave_mtx); 10 | for (int i = 0; i < sizeof(_tab_input_registers) / sizeof(_tab_input_registers[0]); i++) { 11 | ptr[i] = _tab_input_registers[i]; 12 | } 13 | pthread_mutex_unlock(&slave_mtx); 14 | 15 | return 0; 16 | } 17 | 18 | const agile_modbus_slave_util_map_t input_register_maps[1] = { 19 | {0xFFF6, 0xFFFF, get_map_buf, NULL}}; 20 | -------------------------------------------------------------------------------- /examples/slave/registers.c: -------------------------------------------------------------------------------- 1 | #include "slave.h" 2 | 3 | static uint16_t _tab_registers[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 4 | 5 | static int get_map_buf(void *buf, int bufsz) 6 | { 7 | uint16_t *ptr = (uint16_t *)buf; 8 | 9 | pthread_mutex_lock(&slave_mtx); 10 | for (int i = 0; i < sizeof(_tab_registers) / sizeof(_tab_registers[0]); i++) { 11 | ptr[i] = _tab_registers[i]; 12 | } 13 | pthread_mutex_unlock(&slave_mtx); 14 | 15 | return 0; 16 | } 17 | 18 | static int set_map_buf(int index, int len, void *buf, int bufsz) 19 | { 20 | uint16_t *ptr = (uint16_t *)buf; 21 | 22 | pthread_mutex_lock(&slave_mtx); 23 | for (int i = 0; i < len; i++) { 24 | _tab_registers[index + i] = ptr[index + i]; 25 | } 26 | pthread_mutex_unlock(&slave_mtx); 27 | 28 | return 0; 29 | } 30 | 31 | const agile_modbus_slave_util_map_t register_maps[1] = { 32 | {0xFFF6, 0xFFFF, get_map_buf, set_map_buf}}; 33 | -------------------------------------------------------------------------------- /examples/slave/rtu_slave.c: -------------------------------------------------------------------------------- 1 | #include "serial.h" 2 | #include "slave.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define DBG_ENABLE 9 | #define DBG_COLOR 10 | #define DBG_SECTION_NAME "rtu_slave" 11 | #define DBG_LEVEL DBG_LOG 12 | #include "dbg_log.h" 13 | 14 | static int _fd = -1; 15 | static struct termios _old_tios = {0}; 16 | 17 | static void *rtu_entry(void *param) 18 | { 19 | uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 20 | uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 21 | 22 | agile_modbus_rtu_t ctx_rtu; 23 | agile_modbus_t *ctx = &ctx_rtu._ctx; 24 | agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 25 | agile_modbus_set_slave(ctx, 1); 26 | 27 | LOG_I("Running."); 28 | 29 | while (1) { 30 | int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000); 31 | if (read_len < 0) { 32 | LOG_E("Receive error, now exit."); 33 | break; 34 | } 35 | 36 | if (read_len == 0) 37 | continue; 38 | 39 | int send_len = agile_modbus_slave_handle(ctx, read_len, 0, agile_modbus_slave_util_callback, &slave_util, NULL); 40 | serial_flush(_fd); 41 | if (send_len > 0) 42 | serial_send(_fd, ctx->send_buf, send_len); 43 | } 44 | 45 | serial_close(_fd, &_old_tios); 46 | } 47 | 48 | int rtu_slave_init(const char *dev, pthread_t *tid) 49 | { 50 | _fd = serial_init(dev, 9600, 'N', 8, 1, &_old_tios); 51 | if (_fd < 0) { 52 | LOG_E("Open %s failed!", dev); 53 | return -1; 54 | } 55 | 56 | pthread_create(tid, NULL, rtu_entry, NULL); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/slave/slave.c: -------------------------------------------------------------------------------- 1 | #include "slave.h" 2 | #include 3 | #include 4 | #include "rt_tick.h" 5 | 6 | #define DBG_ENABLE 7 | #define DBG_COLOR 8 | #define DBG_SECTION_NAME "slave" 9 | #define DBG_LEVEL DBG_LOG 10 | #include "dbg_log.h" 11 | 12 | extern const agile_modbus_slave_util_map_t bit_maps[1]; 13 | extern const agile_modbus_slave_util_map_t input_bit_maps[1]; 14 | extern const agile_modbus_slave_util_map_t register_maps[1]; 15 | extern const agile_modbus_slave_util_map_t input_register_maps[1]; 16 | 17 | extern int rtu_slave_init(const char *dev, pthread_t *tid); 18 | extern int tcp_slave_init(int port, pthread_t *tid); 19 | 20 | pthread_mutex_t slave_mtx; 21 | 22 | static int addr_check(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info) 23 | { 24 | int slave = slave_info->sft->slave; 25 | if ((slave != ctx->slave) && (slave != AGILE_MODBUS_BROADCAST_ADDRESS) && (slave != 0xFF)) 26 | return -AGILE_MODBUS_EXCEPTION_UNKNOW; 27 | 28 | return 0; 29 | } 30 | 31 | const agile_modbus_slave_util_t slave_util = { 32 | bit_maps, 33 | sizeof(bit_maps) / sizeof(bit_maps[0]), 34 | input_bit_maps, 35 | sizeof(input_bit_maps) / sizeof(input_bit_maps[0]), 36 | register_maps, 37 | sizeof(register_maps) / sizeof(register_maps[0]), 38 | input_register_maps, 39 | sizeof(input_register_maps) / sizeof(input_register_maps[0]), 40 | addr_check, 41 | NULL, 42 | NULL}; 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | if (argc < 3) { 47 | LOG_E("Please enter ModbusSlave [dev] [port]!"); 48 | return -1; 49 | } 50 | 51 | pthread_mutex_init(&slave_mtx, NULL); 52 | 53 | rt_tick_init(); 54 | 55 | pthread_t rtu_tid; 56 | pthread_t tcp_tid; 57 | 58 | int rc1 = rtu_slave_init(argv[1], &rtu_tid); 59 | int rc2 = tcp_slave_init(atoi(argv[2]), &tcp_tid); 60 | 61 | if (rc1 == 0) 62 | pthread_join(rtu_tid, NULL); 63 | 64 | if (rc2 == 0) 65 | pthread_join(tcp_tid, NULL); 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /examples/slave/slave.h: -------------------------------------------------------------------------------- 1 | #ifndef __SLAVE_H 2 | #define __SLAVE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include "agile_modbus.h" 11 | #include "agile_modbus_slave_util.h" 12 | 13 | extern pthread_mutex_t slave_mtx; 14 | extern const agile_modbus_slave_util_t slave_util; 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif /* __SLAVE_H */ 21 | -------------------------------------------------------------------------------- /examples/slave/tcp_slave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "tcp.h" 13 | #include "rtservice.h" 14 | #include "rt_tick.h" 15 | #include "slave.h" 16 | 17 | #define DBG_ENABLE 18 | #define DBG_COLOR 19 | #define DBG_SECTION_NAME "tcp_slave" 20 | #define DBG_LEVEL DBG_LOG 21 | #include "dbg_log.h" 22 | 23 | struct mbtcp_session { 24 | int fd; 25 | uint32_t tick_timeout; 26 | rt_slist_t slist; 27 | }; 28 | 29 | #define MBTCP_SESSION_MAX_NUM 5 30 | #define MBTCP_SESSION_TIMEOUT 10 31 | 32 | static int _listen_port = 0; 33 | static pthread_mutex_t _mtx; 34 | static rt_slist_t _header = RT_SLIST_OBJECT_INIT(_header); 35 | 36 | static int mbtcp_session_get_num(void) 37 | { 38 | int num = 0; 39 | 40 | pthread_mutex_lock(&_mtx); 41 | num = rt_slist_len(&_header); 42 | pthread_mutex_unlock(&_mtx); 43 | 44 | return num; 45 | } 46 | 47 | static void mbtcp_session_delete(struct mbtcp_session *session) 48 | { 49 | pthread_mutex_lock(&_mtx); 50 | rt_slist_remove(&_header, &(session->slist)); 51 | pthread_mutex_unlock(&_mtx); 52 | 53 | tcp_close(session->fd); 54 | 55 | free(session); 56 | } 57 | 58 | static void *mbtcp_session_entry(void *param) 59 | { 60 | struct mbtcp_session *session = (struct mbtcp_session *)param; 61 | int option = 1; 62 | int rc = setsockopt(session->fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&option, sizeof(int)); 63 | if (rc < 0) 64 | goto _exit; 65 | 66 | int flags = fcntl(session->fd, F_GETFL, 0); 67 | flags |= O_NONBLOCK; 68 | fcntl(session->fd, F_SETFL, flags); 69 | 70 | flags = fcntl(session->fd, F_GETFD); 71 | flags |= FD_CLOEXEC; 72 | fcntl(session->fd, F_SETFD, flags); 73 | 74 | session->tick_timeout = rt_tick_get() + rt_tick_from_millisecond(MBTCP_SESSION_TIMEOUT * 1000); 75 | 76 | uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 77 | uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 78 | 79 | agile_modbus_tcp_t ctx_tcp; 80 | agile_modbus_t *ctx = &ctx_tcp._ctx; 81 | agile_modbus_tcp_init(&ctx_tcp, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 82 | agile_modbus_set_slave(ctx, 1); 83 | 84 | while (1) { 85 | rc = tcp_receive(session->fd, ctx->read_buf, ctx->read_bufsz, 1000); 86 | if (rc < 0) 87 | break; 88 | if (rc > 0) { 89 | int send_len = agile_modbus_slave_handle(ctx, rc, 0, agile_modbus_slave_util_callback, &slave_util, NULL); 90 | tcp_flush(session->fd); 91 | if (send_len > 0) { 92 | session->tick_timeout = rt_tick_get() + rt_tick_from_millisecond(MBTCP_SESSION_TIMEOUT * 1000); 93 | 94 | if (tcp_send(session->fd, ctx->send_buf, send_len) != send_len) 95 | break; 96 | } 97 | } 98 | 99 | if ((rt_tick_get() - session->tick_timeout) < (RT_TICK_MAX / 2)) 100 | break; 101 | } 102 | 103 | _exit: 104 | LOG_W("socket %d close.", session->fd); 105 | mbtcp_session_delete(session); 106 | } 107 | 108 | static int mbtcp_session_create(int fd) 109 | { 110 | if (fd < 0) 111 | return -1; 112 | 113 | if (mbtcp_session_get_num() >= MBTCP_SESSION_MAX_NUM) 114 | return -1; 115 | 116 | struct mbtcp_session *session = malloc(sizeof(struct mbtcp_session)); 117 | if (session == NULL) 118 | return -1; 119 | 120 | memset(session, 0, sizeof(struct mbtcp_session)); 121 | session->fd = fd; 122 | rt_slist_init(&(session->slist)); 123 | pthread_mutex_lock(&_mtx); 124 | rt_slist_append(&_header, &(session->slist)); 125 | pthread_mutex_unlock(&_mtx); 126 | 127 | pthread_t tid; 128 | pthread_create(&tid, NULL, mbtcp_session_entry, session); 129 | pthread_detach(tid); 130 | 131 | return 0; 132 | } 133 | 134 | static void *mbtcp_entry(void *param) 135 | { 136 | int server_fd = -1; 137 | // Select use 138 | fd_set readset, exceptset; 139 | // Select timeout 140 | struct timeval select_timeout; 141 | 142 | _tcp_start: 143 | LOG_I("mbtcp server running."); 144 | server_fd = tcp_listen(_listen_port, 1); 145 | if (server_fd < 0) 146 | goto _tcp_restart; 147 | 148 | while (1) { 149 | FD_ZERO(&readset); 150 | FD_ZERO(&exceptset); 151 | 152 | FD_SET(server_fd, &readset); 153 | FD_SET(server_fd, &exceptset); 154 | 155 | select_timeout.tv_sec = 1; 156 | select_timeout.tv_usec = 0; 157 | int rc = select(server_fd + 1, &readset, NULL, &exceptset, &select_timeout); 158 | if (rc == -1) { 159 | if (errno == EINTR) 160 | continue; 161 | } 162 | if (rc < 0) 163 | break; 164 | 165 | if (rc > 0) { 166 | if (FD_ISSET(server_fd, &exceptset)) 167 | break; 168 | if (FD_ISSET(server_fd, &readset)) { 169 | int client_fd = tcp_accept(server_fd); 170 | if (client_fd < 0) 171 | break; 172 | if (mbtcp_session_create(client_fd) < 0) 173 | tcp_close(client_fd); 174 | } 175 | } 176 | } 177 | 178 | _tcp_restart: 179 | LOG_W("mbtcp server go wrong, now wait restarting..."); 180 | if (server_fd >= 0) { 181 | tcp_close(server_fd); 182 | server_fd = -1; 183 | } 184 | 185 | sleep(1); 186 | goto _tcp_start; 187 | } 188 | 189 | int tcp_slave_init(int port, pthread_t *tid) 190 | { 191 | if (port <= 0) { 192 | LOG_E("Port must be greater than 0!"); 193 | return -1; 194 | } 195 | 196 | _listen_port = port; 197 | 198 | pthread_mutex_init(&_mtx, NULL); 199 | 200 | pthread_create(tid, NULL, mbtcp_entry, NULL); 201 | 202 | return 0; 203 | } 204 | -------------------------------------------------------------------------------- /examples/tcp_master/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(tcp_master) 4 | 5 | file(GLOB SRCS *.c) 6 | 7 | add_executable(TcpMaster ${SRCS}) 8 | 9 | target_link_libraries(TcpMaster PRIVATE Threads::Threads) 10 | -------------------------------------------------------------------------------- /examples/tcp_master/tcp_master.c: -------------------------------------------------------------------------------- 1 | #include "agile_modbus.h" 2 | #include "tcp.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define DBG_ENABLE 10 | #define DBG_COLOR 11 | #define DBG_SECTION_NAME "tcp_master" 12 | #define DBG_LEVEL DBG_LOG 13 | #include "dbg_log.h" 14 | 15 | static int _sock = -1; 16 | 17 | static void *cycle_entry(void *param) 18 | { 19 | uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 20 | uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH]; 21 | uint16_t hold_register[10]; 22 | 23 | agile_modbus_tcp_t ctx_tcp; 24 | agile_modbus_t *ctx = &ctx_tcp._ctx; 25 | agile_modbus_tcp_init(&ctx_tcp, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf)); 26 | agile_modbus_set_slave(ctx, 1); 27 | 28 | LOG_I("Running."); 29 | 30 | while (1) { 31 | usleep(100000); 32 | 33 | tcp_flush(_sock); 34 | int send_len = agile_modbus_serialize_read_registers(ctx, 0, 10); 35 | tcp_send(_sock, ctx->send_buf, send_len); 36 | int read_len = tcp_receive(_sock, ctx->read_buf, ctx->read_bufsz, 1000); 37 | if (read_len < 0) { 38 | LOG_E("Receive error, now exit."); 39 | break; 40 | } 41 | 42 | if (read_len == 0) { 43 | LOG_W("Receive timeout."); 44 | continue; 45 | } 46 | 47 | int rc = agile_modbus_deserialize_read_registers(ctx, read_len, hold_register); 48 | if (rc < 0) { 49 | LOG_W("Receive failed."); 50 | if (rc != -1) 51 | LOG_W("Error code:%d", -128 - rc); 52 | 53 | continue; 54 | } 55 | 56 | LOG_I("Hold Registers:"); 57 | for (int i = 0; i < 10; i++) 58 | LOG_I("Register [%d]: 0x%04X", i, hold_register[i]); 59 | 60 | printf("\r\n\r\n\r\n"); 61 | } 62 | 63 | tcp_close(_sock); 64 | } 65 | 66 | int main(int argc, char *argv[]) 67 | { 68 | if (argc < 3) { 69 | LOG_E("Please enter TcpMaster [ip] [port]!"); 70 | return -1; 71 | } 72 | 73 | _sock = tcp_connect(argv[1], atoi(argv[2])); 74 | if (_sock == -1) { 75 | LOG_E("Connect %s:%d failed!", argv[1], atoi(argv[2])); 76 | return -1; 77 | } 78 | 79 | pthread_t tid; 80 | pthread_create(&tid, NULL, cycle_entry, NULL); 81 | pthread_join(tid, NULL); 82 | } 83 | -------------------------------------------------------------------------------- /figures/ModbusProtocol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/figures/ModbusProtocol.jpg -------------------------------------------------------------------------------- /figures/agile_modbus_slave_util_callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/figures/agile_modbus_slave_util_callback.png -------------------------------------------------------------------------------- /figures/zanshang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loogg/agile_modbus/fc4226f3b6f93a94632d65ecdea2b8911b76d47d/figures/zanshang.jpg -------------------------------------------------------------------------------- /inc/agile_modbus.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus.h 3 | * @brief Agile Modbus software package common header file 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2022-07-28 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2021 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #ifndef __PKG_AGILE_MODBUS_H 15 | #define __PKG_AGILE_MODBUS_H 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | 23 | /** @addtogroup COMMON 24 | * @{ 25 | */ 26 | 27 | /** @defgroup Modbus_Module_Definition Modbus Module Definition 28 | * @{ 29 | */ 30 | #ifndef AGILE_MODBUS_USING_RTU 31 | #define AGILE_MODBUS_USING_RTU 1 32 | #endif /* AGILE_MODBUS_USING_RTU */ 33 | 34 | #ifndef AGILE_MODBUS_USING_TCP 35 | #define AGILE_MODBUS_USING_TCP 1 36 | #endif /* AGILE_MODBUS_USING_TCP */ 37 | /** 38 | * @} 39 | */ 40 | 41 | /** @defgroup COMMON_Exported_Constants Common Exported Constants 42 | * @{ 43 | */ 44 | 45 | /** @defgroup Modbus_Function_Codes Modbus Function Codes 46 | * @{ 47 | */ 48 | #define AGILE_MODBUS_FC_READ_COILS 0x01 49 | #define AGILE_MODBUS_FC_READ_DISCRETE_INPUTS 0x02 50 | #define AGILE_MODBUS_FC_READ_HOLDING_REGISTERS 0x03 51 | #define AGILE_MODBUS_FC_READ_INPUT_REGISTERS 0x04 52 | #define AGILE_MODBUS_FC_WRITE_SINGLE_COIL 0x05 53 | #define AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 54 | #define AGILE_MODBUS_FC_READ_EXCEPTION_STATUS 0x07 55 | #define AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F 56 | #define AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 57 | #define AGILE_MODBUS_FC_REPORT_SLAVE_ID 0x11 58 | #define AGILE_MODBUS_FC_MASK_WRITE_REGISTER 0x16 59 | #define AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 60 | /** 61 | * @} 62 | */ 63 | 64 | /** @defgroup Modbus_Constants Modbus Constants 65 | * @{ 66 | */ 67 | #define AGILE_MODBUS_VERSION_STRING "AMB_1.1.4" /**< Agile Modbus version number */ 68 | 69 | #define AGILE_MODBUS_BROADCAST_ADDRESS 0 /**< Modbus broadcast address */ 70 | 71 | /** @name Quantity limit of Coils 72 | @verbatim 73 | Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12) 74 | Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0) 75 | (chapter 6 section 11 page 29) 76 | Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0) 77 | 78 | @endverbatim 79 | * @{ 80 | */ 81 | #define AGILE_MODBUS_MAX_READ_BITS 2000 82 | #define AGILE_MODBUS_MAX_WRITE_BITS 1968 83 | /** 84 | * @} 85 | */ 86 | 87 | /** @name Quantity limit of Registers 88 | @verbatim 89 | Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) 90 | Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) 91 | (chapter 6 section 12 page 31) 92 | Quantity of Registers to write (2 bytes) 1 to 123 (0x7B) 93 | (chapter 6 section 17 page 38) 94 | Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79) 95 | 96 | @endverbatim 97 | * @{ 98 | */ 99 | #define AGILE_MODBUS_MAX_READ_REGISTERS 125 100 | #define AGILE_MODBUS_MAX_WRITE_REGISTERS 123 101 | #define AGILE_MODBUS_MAX_WR_WRITE_REGISTERS 121 102 | #define AGILE_MODBUS_MAX_WR_READ_REGISTERS 125 103 | /** 104 | * @} 105 | */ 106 | 107 | /** 108 | @verbatim 109 | The size of the MODBUS PDU is limited by the size constraint inherited from 110 | the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 111 | bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server 112 | address (1 byte) - CRC (2 bytes) = 253 bytes. 113 | 114 | @endverbatim 115 | */ 116 | #define AGILE_MODBUS_MAX_PDU_LENGTH 253 117 | 118 | /** 119 | @verbatim 120 | Consequently: 121 | - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 122 | bytes. 123 | - TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes. 124 | so the maximum of both backend in 260 bytes. This size can used to allocate 125 | an array of bytes to store responses and it will be compatible with the two 126 | backends. 127 | 128 | @endverbatim 129 | */ 130 | #define AGILE_MODBUS_MAX_ADU_LENGTH 260 131 | /** 132 | * @} 133 | */ 134 | 135 | /** 136 | * @} 137 | */ 138 | 139 | /** @defgroup COMMON_Exported_Types Common Exported Types 140 | * @{ 141 | */ 142 | 143 | /** 144 | * @brief Modbus exception code 145 | */ 146 | enum { 147 | AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, 148 | AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, 149 | AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, 150 | AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, 151 | AGILE_MODBUS_EXCEPTION_ACKNOWLEDGE, 152 | AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, 153 | AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, 154 | AGILE_MODBUS_EXCEPTION_MEMORY_PARITY, 155 | AGILE_MODBUS_EXCEPTION_NOT_DEFINED, 156 | AGILE_MODBUS_EXCEPTION_GATEWAY_PATH, 157 | AGILE_MODBUS_EXCEPTION_GATEWAY_TARGET, 158 | AGILE_MODBUS_EXCEPTION_UNKNOW = 0xff 159 | }; 160 | 161 | /** 162 | *@ brief Modbus backend type 163 | */ 164 | typedef enum { 165 | AGILE_MODBUS_BACKEND_TYPE_RTU = 0, /**< RTU */ 166 | AGILE_MODBUS_BACKEND_TYPE_TCP /**< TCP */ 167 | } agile_modbus_backend_type_t; 168 | 169 | /** 170 | * @brief Modbus received message type 171 | * 172 | @verbatim 173 | ---------- Request Indication ---------- 174 | | Client | ---------------------->| Server | 175 | ---------- Confirmation Response ---------- 176 | 177 | @endverbatim 178 | */ 179 | typedef enum { 180 | AGILE_MODBUS_MSG_INDICATION, /**< Host-side request message */ 181 | AGILE_MODBUS_MSG_CONFIRMATION /**< Server-side request message */ 182 | } agile_modbus_msg_type_t; 183 | 184 | /** 185 | * @brief contains the modbus header parameter structure 186 | */ 187 | typedef struct agile_modbus_sft { 188 | int slave; /**< slave address */ 189 | int function; /**< function code */ 190 | int t_id; /**< Transaction identifier */ 191 | } agile_modbus_sft_t; 192 | 193 | typedef struct agile_modbus agile_modbus_t; /**< Agile Modbus structure */ 194 | 195 | /** 196 | * @brief Agile Modbus backend interface structure 197 | */ 198 | typedef struct agile_modbus_backend { 199 | uint32_t backend_type; /**< Backend type */ 200 | uint32_t header_length; /**
© Copyright (c) 2021 Ma Longwei. 10 | * All rights reserved.
11 | * 12 | */ 13 | 14 | #ifndef __PKG_AGILE_MODBUS_RTU_H 15 | #define __PKG_AGILE_MODBUS_RTU_H 16 | 17 | #if AGILE_MODBUS_USING_RTU 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | 25 | /** @addtogroup RTU 26 | * @{ 27 | */ 28 | 29 | /** @defgroup RTU_Exported_Constants RTU Exported Constants 30 | * @{ 31 | */ 32 | #define AGILE_MODBUS_RTU_HEADER_LENGTH 1 33 | #define AGILE_MODBUS_RTU_PRESET_REQ_LENGTH 6 34 | #define AGILE_MODBUS_RTU_PRESET_RSP_LENGTH 2 35 | 36 | #define AGILE_MODBUS_RTU_CHECKSUM_LENGTH 2 37 | 38 | /** 39 | @verbatim 40 | Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 41 | RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes 42 | 43 | @endverbatim 44 | */ 45 | #define AGILE_MODBUS_RTU_MAX_ADU_LENGTH 256 46 | /** 47 | * @} 48 | */ 49 | 50 | /** @defgroup RTU_Exported_Types RTU Exported Types 51 | * @{ 52 | */ 53 | 54 | /** 55 | * @brief RTU structure 56 | */ 57 | typedef struct agile_modbus_rtu { 58 | agile_modbus_t _ctx; /**< modbus handle */ 59 | } agile_modbus_rtu_t; 60 | 61 | /** 62 | * @} 63 | */ 64 | 65 | /** @addtogroup RTU_Exported_Functions 66 | * @{ 67 | */ 68 | int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); 69 | /** 70 | * @} 71 | */ 72 | 73 | /** 74 | * @} 75 | */ 76 | 77 | #ifdef __cplusplus 78 | } 79 | #endif 80 | 81 | #endif /* AGILE_MODBUS_USING_RTU */ 82 | 83 | #endif /* __PKG_AGILE_MODBUS_RTU_H */ 84 | -------------------------------------------------------------------------------- /inc/agile_modbus_tcp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus_tcp.h 3 | * @brief Agile Modbus package TCP header file 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2021-12-02 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2021 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #ifndef __PKG_AGILE_MODBUS_TCP_H 15 | #define __PKG_AGILE_MODBUS_TCP_H 16 | 17 | #if AGILE_MODBUS_USING_TCP 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | 25 | /** @addtogroup TCP 26 | * @{ 27 | */ 28 | 29 | /** @defgroup TCP_Exported_Constants TCP Exported Constants 30 | * @{ 31 | */ 32 | #define AGILE_MODBUS_TCP_HEADER_LENGTH 7 33 | #define AGILE_MODBUS_TCP_PRESET_REQ_LENGTH 12 34 | #define AGILE_MODBUS_TCP_PRESET_RSP_LENGTH 8 35 | 36 | #define AGILE_MODBUS_TCP_CHECKSUM_LENGTH 0 37 | 38 | /** 39 | @verbatim 40 | Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 41 | TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes 42 | 43 | @endverbatim 44 | */ 45 | #define AGILE_MODBUS_TCP_MAX_ADU_LENGTH 260 46 | /** 47 | * @} 48 | */ 49 | 50 | /** @defgroup TCP_Exported_Types TCP Exported Types 51 | * @{ 52 | */ 53 | 54 | /** 55 | * @brief TCP structure 56 | */ 57 | typedef struct agile_modbus_tcp { 58 | agile_modbus_t _ctx; /**< modbus handle */ 59 | uint16_t t_id; /**< Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b 60 | (page 23/46): 61 | The transaction identifier is used to associate the future response 62 | with the request. This identifier is unique on each TCP connection. */ 63 | } agile_modbus_tcp_t; 64 | 65 | /** 66 | * @} 67 | */ 68 | 69 | /** @addtogroup TCP_Exported_Functions 70 | * @{ 71 | */ 72 | int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); 73 | /** 74 | * @} 75 | */ 76 | 77 | /** 78 | * @} 79 | */ 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif 84 | 85 | #endif /* AGILE_MODBUS_USING_TCP */ 86 | 87 | #endif /* __PKG_AGILE_MODBUS_TCP_H */ 88 | -------------------------------------------------------------------------------- /src/agile_modbus_rtu.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus_rtu.c 3 | * @brief Agile Modbus package RTU source file 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2021-12-02 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2021 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #include "agile_modbus.h" 15 | 16 | #if AGILE_MODBUS_USING_RTU 17 | 18 | #include "agile_modbus_rtu.h" 19 | 20 | /** @defgroup RTU RTU 21 | * @{ 22 | */ 23 | 24 | /** @defgroup RTU_Private_Constants RTU Private Constants 25 | * @{ 26 | */ 27 | /** Table of CRC values for high-order byte */ 28 | static const uint8_t _table_crc_hi[] = 29 | { 30 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 31 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 32 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 33 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 34 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 35 | 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 36 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 37 | 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 38 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 39 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 40 | 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 41 | 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 42 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 43 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 44 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 45 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 46 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 47 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 48 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 49 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 50 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 51 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 52 | 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 53 | 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 54 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 55 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40}; 56 | 57 | /** Table of CRC values for low-order byte */ 58 | static const uint8_t _table_crc_lo[] = 59 | { 60 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 61 | 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 62 | 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 63 | 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 64 | 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 65 | 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 66 | 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 67 | 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 68 | 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 69 | 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 70 | 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 71 | 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 72 | 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 73 | 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 74 | 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 75 | 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 76 | 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 77 | 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 78 | 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 79 | 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 80 | 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 81 | 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 82 | 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 83 | 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 84 | 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 85 | 0x43, 0x83, 0x41, 0x81, 0x80, 0x40}; 86 | /** 87 | * @} 88 | */ 89 | 90 | /** @defgroup RTU_Private_Functions RTU Private Functions 91 | * @{ 92 | */ 93 | 94 | /** 95 | * @brief RTU CRC16 calculation 96 | * @param buffer data pointer 97 | * @param buffer_length data length 98 | * @return CRC16 value 99 | */ 100 | static uint16_t agile_modbus_rtu_crc16(uint8_t *buffer, uint16_t buffer_length) 101 | { 102 | uint8_t crc_hi = 0xFF; /* high CRC byte initialized */ 103 | uint8_t crc_lo = 0xFF; /* low CRC byte initialized */ 104 | unsigned int i; /* will index into CRC lookup */ 105 | 106 | /* pass through message buffer */ 107 | while (buffer_length--) { 108 | i = crc_hi ^ *buffer++; /* calculate the CRC */ 109 | crc_hi = crc_lo ^ _table_crc_hi[i]; 110 | crc_lo = _table_crc_lo[i]; 111 | } 112 | 113 | return (crc_hi << 8 | crc_lo); 114 | } 115 | 116 | /** 117 | * @brief RTU sets the address interface 118 | * @param ctx modbus handle 119 | * @param slave slave address 120 | * @return 0: success 121 | */ 122 | static int agile_modbus_rtu_set_slave(agile_modbus_t *ctx, int slave) 123 | { 124 | ctx->slave = slave; 125 | return 0; 126 | } 127 | 128 | /** 129 | * @brief RTU builds the basic request message interface (header message) 130 | * @param ctx modbus handle 131 | * @param function function code 132 | * @param addr register address 133 | * @param nb number of registers 134 | * @param req data storage pointer 135 | * @return data length 136 | */ 137 | static int agile_modbus_rtu_build_request_basis(agile_modbus_t *ctx, int function, 138 | int addr, int nb, 139 | uint8_t *req) 140 | { 141 | req[0] = ctx->slave; 142 | req[1] = function; 143 | req[2] = addr >> 8; 144 | req[3] = addr & 0x00ff; 145 | req[4] = nb >> 8; 146 | req[5] = nb & 0x00ff; 147 | 148 | return AGILE_MODBUS_RTU_PRESET_REQ_LENGTH; 149 | } 150 | 151 | /** 152 | * @brief RTU builds the basic response message interface (header message) 153 | * @param sft modbus header parameter structure pointer 154 | * @param rsp data storage pointer 155 | * @return data length 156 | */ 157 | static int agile_modbus_rtu_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) 158 | { 159 | rsp[0] = sft->slave; 160 | rsp[1] = sft->function; 161 | 162 | return AGILE_MODBUS_RTU_PRESET_RSP_LENGTH; 163 | } 164 | 165 | /** 166 | * @brief RTU ready response interface 167 | * @note This API will automatically subtract the AGILE_MODBUS_RTU_CHECKSUM_LENGTH length from req_length 168 | * @param req request data pointer 169 | * @param req_length request data length 170 | * @return 0 (RTU has no transaction identifier) 171 | */ 172 | static int agile_modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) 173 | { 174 | (void)req; 175 | (*req_length) -= AGILE_MODBUS_RTU_CHECKSUM_LENGTH; 176 | /* No TID */ 177 | return 0; 178 | } 179 | 180 | /** 181 | * @brief RTU pre-send data interface 182 | * @note This API will calculate CRC16 and automatically fill in the tail 183 | * @param req data storage pointer 184 | * @param req_length existing data length 185 | * @return data length 186 | */ 187 | static int agile_modbus_rtu_send_msg_pre(uint8_t *req, int req_length) 188 | { 189 | uint16_t crc = agile_modbus_rtu_crc16(req, req_length); 190 | req[req_length++] = crc >> 8; 191 | req[req_length++] = crc & 0x00FF; 192 | 193 | return req_length; 194 | } 195 | 196 | /** 197 | * @brief RTU check received data integrity interface (CRC16 comparison) 198 | * @param ctx modbus handle 199 | * @param msg Receive data pointer 200 | * @param msg_length valid data length 201 | * @return >0: valid data length; others: exception 202 | */ 203 | static int agile_modbus_rtu_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) 204 | { 205 | uint16_t crc_calculated; 206 | uint16_t crc_received; 207 | (void)ctx; 208 | crc_calculated = agile_modbus_rtu_crc16(msg, msg_length - 2); 209 | crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1]; 210 | 211 | /* Check CRC of msg */ 212 | if (crc_calculated == crc_received) 213 | return msg_length; 214 | 215 | return -1; 216 | } 217 | 218 | /** 219 | * @brief RTU pre-check confirmation interface (request response address comparison) 220 | * @note If the request address is broadcast address 0, return success 221 | * @param ctx modbus handle 222 | * @param req request data pointer 223 | * @param rsp response data pointer 224 | * @param rsp_length response data length 225 | * @return 0: success; others: exception 226 | */ 227 | static int agile_modbus_rtu_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, 228 | const uint8_t *rsp, int rsp_length) 229 | { 230 | /* Check responding slave is the slave we requested (except for broacast 231 | * request) */ 232 | (void)ctx; 233 | (void)rsp_length; 234 | if (req[0] != rsp[0] && req[0] != AGILE_MODBUS_BROADCAST_ADDRESS) 235 | return -1; 236 | 237 | return 0; 238 | } 239 | 240 | /** 241 | * @} 242 | */ 243 | 244 | /** @addtogroup RTU_Private_Constants 245 | * @{ 246 | */ 247 | 248 | /** 249 | * @brief RTU backend interface 250 | */ 251 | static const agile_modbus_backend_t agile_modbus_rtu_backend = 252 | { 253 | AGILE_MODBUS_BACKEND_TYPE_RTU, 254 | AGILE_MODBUS_RTU_HEADER_LENGTH, 255 | AGILE_MODBUS_RTU_CHECKSUM_LENGTH, 256 | AGILE_MODBUS_RTU_MAX_ADU_LENGTH, 257 | agile_modbus_rtu_set_slave, 258 | agile_modbus_rtu_build_request_basis, 259 | agile_modbus_rtu_build_response_basis, 260 | agile_modbus_rtu_prepare_response_tid, 261 | agile_modbus_rtu_send_msg_pre, 262 | agile_modbus_rtu_check_integrity, 263 | agile_modbus_rtu_pre_check_confirmation}; 264 | 265 | /** 266 | * @} 267 | */ 268 | 269 | /** @defgroup RTU_Exported_Functions RTU Exported Functions 270 | * @{ 271 | */ 272 | 273 | /** 274 | * @brief RTU initialization 275 | * @param ctx RTU handle 276 | * @param send_buf send buffer 277 | * @param send_bufsz send buffer size 278 | * @param read_buf receive buffer 279 | * @param read_bufsz receive buffer size 280 | * @return 0: success 281 | */ 282 | int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) 283 | { 284 | agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); 285 | ctx->_ctx.backend = &agile_modbus_rtu_backend; 286 | ctx->_ctx.backend_data = ctx; 287 | 288 | return 0; 289 | } 290 | 291 | /** 292 | * @} 293 | */ 294 | 295 | /** 296 | * @} 297 | */ 298 | 299 | #endif /* AGILE_MODBUS_USING_RTU */ 300 | -------------------------------------------------------------------------------- /src/agile_modbus_tcp.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus_tcp.c 3 | * @brief Agile Modbus package TCP source file 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2021-12-02 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2021 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #include "agile_modbus.h" 15 | 16 | #if AGILE_MODBUS_USING_TCP 17 | 18 | #include "agile_modbus_tcp.h" 19 | 20 | /** @defgroup TCP TCP 21 | * @{ 22 | */ 23 | 24 | /** @defgroup TCP_Private_Functions TCP Private Functions 25 | * @{ 26 | */ 27 | 28 | /** 29 | * @brief TCP set address interface 30 | * @param ctx modbus handle 31 | * @param slave slave address 32 | * @return 0: success 33 | */ 34 | static int agile_modbus_tcp_set_slave(agile_modbus_t *ctx, int slave) 35 | { 36 | ctx->slave = slave; 37 | return 0; 38 | } 39 | 40 | /** 41 | *@brief TCP builds the basic request message interface (header message) 42 | *@param ctx modbus handle 43 | *@param function function code 44 | *@param addr register address 45 | *@param nb number of registers 46 | *@param req data storage pointer 47 | *@return data length 48 | */ 49 | static int agile_modbus_tcp_build_request_basis(agile_modbus_t *ctx, int function, 50 | int addr, int nb, 51 | uint8_t *req) 52 | { 53 | agile_modbus_tcp_t *ctx_tcp = ctx->backend_data; 54 | 55 | /* Increase transaction ID */ 56 | if (ctx_tcp->t_id < UINT16_MAX) 57 | ctx_tcp->t_id++; 58 | else 59 | ctx_tcp->t_id = 0; 60 | req[0] = ctx_tcp->t_id >> 8; 61 | req[1] = ctx_tcp->t_id & 0x00ff; 62 | 63 | /* Protocol Modbus */ 64 | req[2] = 0; 65 | req[3] = 0; 66 | 67 | /* Length will be defined later by set_req_length_tcp at offsets 4 68 | and 5 */ 69 | 70 | req[6] = ctx->slave; 71 | req[7] = function; 72 | req[8] = addr >> 8; 73 | req[9] = addr & 0x00ff; 74 | req[10] = nb >> 8; 75 | req[11] = nb & 0x00ff; 76 | 77 | return AGILE_MODBUS_TCP_PRESET_REQ_LENGTH; 78 | } 79 | 80 | /** 81 | * @brief TCP builds a basic response message interface (header message) 82 | * @param sft modbus header parameter structure pointer 83 | * @param rsp data storage pointer 84 | * @return data length 85 | */ 86 | static int agile_modbus_tcp_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) 87 | { 88 | /* Extract from MODBUS Messaging on TCP/IP Implementation 89 | Guide V1.0b (page 23/46): 90 | The transaction identifier is used to associate the future 91 | response with the request. */ 92 | rsp[0] = sft->t_id >> 8; 93 | rsp[1] = sft->t_id & 0x00ff; 94 | 95 | /* Protocol Modbus */ 96 | rsp[2] = 0; 97 | rsp[3] = 0; 98 | 99 | /* Length will be set later by send_msg (4 and 5) */ 100 | 101 | /* The slave ID is copied from the indication */ 102 | rsp[6] = sft->slave; 103 | rsp[7] = sft->function; 104 | 105 | return AGILE_MODBUS_TCP_PRESET_RSP_LENGTH; 106 | } 107 | 108 | /** 109 | * @brief TCP ready response interface 110 | * @param req request data pointer 111 | * @param req_length request data length 112 | * @return transaction identifier 113 | */ 114 | static int agile_modbus_tcp_prepare_response_tid(const uint8_t *req, int *req_length) 115 | { 116 | (void)req_length; 117 | return (req[0] << 8) + req[1]; 118 | } 119 | 120 | /** 121 | * @brief TCP pre-send data interface (calculate the value of the length field and store it) 122 | * @param req data storage pointer 123 | * @param req_length existing data length 124 | * @return data length 125 | */ 126 | static int agile_modbus_tcp_send_msg_pre(uint8_t *req, int req_length) 127 | { 128 | /* Substract the header length to the message length */ 129 | int mbap_length = req_length - 6; 130 | 131 | req[4] = mbap_length >> 8; 132 | req[5] = mbap_length & 0x00FF; 133 | 134 | return req_length; 135 | } 136 | 137 | /** 138 | * @brief TCP check receiving data integrity interface 139 | * @param ctx modbus handle 140 | * @param msg Receive data pointer 141 | * @param msg_length valid data length 142 | * @return valid data length 143 | */ 144 | static int agile_modbus_tcp_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) 145 | { 146 | (void)ctx; 147 | (void)msg; 148 | return msg_length; 149 | } 150 | 151 | /** 152 | * @brief TCP pre-check confirmation interface (compare transaction identifier and protocol identifier) 153 | * @param ctx modbus handle 154 | * @param req request data pointer 155 | * @param rsp response data pointer 156 | * @param rsp_length response data length 157 | * @return 0: success; others: exception 158 | */ 159 | static int agile_modbus_tcp_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, 160 | const uint8_t *rsp, int rsp_length) 161 | { 162 | (void)ctx; 163 | (void)rsp_length; 164 | /* Check transaction ID */ 165 | if (req[0] != rsp[0] || req[1] != rsp[1]) 166 | return -1; 167 | 168 | /* Check protocol ID */ 169 | if (rsp[2] != 0x0 && rsp[3] != 0x0) 170 | return -1; 171 | 172 | return 0; 173 | } 174 | 175 | /** 176 | * @} 177 | */ 178 | 179 | /** @defgroup TCP_Private_Constants TCP Private Constants 180 | * @{ 181 | */ 182 | 183 | /** 184 | * @brief TCP backend interface 185 | */ 186 | static const agile_modbus_backend_t agile_modbus_tcp_backend = 187 | { 188 | AGILE_MODBUS_BACKEND_TYPE_TCP, 189 | AGILE_MODBUS_TCP_HEADER_LENGTH, 190 | AGILE_MODBUS_TCP_CHECKSUM_LENGTH, 191 | AGILE_MODBUS_TCP_MAX_ADU_LENGTH, 192 | agile_modbus_tcp_set_slave, 193 | agile_modbus_tcp_build_request_basis, 194 | agile_modbus_tcp_build_response_basis, 195 | agile_modbus_tcp_prepare_response_tid, 196 | agile_modbus_tcp_send_msg_pre, 197 | agile_modbus_tcp_check_integrity, 198 | agile_modbus_tcp_pre_check_confirmation}; 199 | 200 | /** 201 | * @} 202 | */ 203 | 204 | /** @defgroup TCP_Exported_Functions TCP Exported Functions 205 | * @{ 206 | */ 207 | 208 | /** 209 | * @brief TCP initialization 210 | * @param ctx TCP handle 211 | * @param send_buf send buffer 212 | * @param send_bufsz send buffer size 213 | * @param read_buf receive buffer 214 | * @param read_bufsz receive buffer size 215 | * @return 0: success 216 | */ 217 | int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) 218 | { 219 | agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); 220 | ctx->_ctx.backend = &agile_modbus_tcp_backend; 221 | ctx->_ctx.backend_data = ctx; 222 | 223 | ctx->t_id = 0; 224 | 225 | return 0; 226 | } 227 | 228 | /** 229 | * @} 230 | */ 231 | 232 | /** 233 | * @} 234 | */ 235 | 236 | #endif /* AGILE_MODBUS_USING_TCP */ 237 | -------------------------------------------------------------------------------- /util/agile_modbus_slave_util.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus_slave_util.c 3 | * @brief Agile Modbus software package provides simple slave access source files 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2022-07-28 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2022 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #include "agile_modbus.h" 15 | #include "agile_modbus_slave_util.h" 16 | #include 17 | 18 | /** @addtogroup UTIL 19 | * @{ 20 | */ 21 | 22 | /** @defgroup SLAVE_UTIL Slave Util 23 | * @{ 24 | */ 25 | 26 | /** @defgroup SLAVE_UTIL_Private_Functions Slave Util Private Functions 27 | * @{ 28 | */ 29 | 30 | /** 31 | * @brief Get the mapping object from the mapping object array according to the register address 32 | * @param maps mapping object array 33 | * @param nb_maps number of arrays 34 | * @param address register address 35 | * @return !=NULL: mapping object; =NULL: failure 36 | */ 37 | static const agile_modbus_slave_util_map_t *get_map_by_addr(const agile_modbus_slave_util_map_t *maps, int nb_maps, int address) 38 | { 39 | for (int i = 0; i < nb_maps; i++) { 40 | const agile_modbus_slave_util_map_t *map = &maps[i]; 41 | if (address >= map->start_addr && address <= map->end_addr) 42 | return map; 43 | } 44 | 45 | return NULL; 46 | } 47 | 48 | /** 49 | * @brief read register 50 | * @param ctx modbus handle 51 | * @param slave_info slave information body 52 | * @param slave_util slave function structure 53 | * @return =0: normal; 54 | * <0: Abnormal 55 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 56 | * (Other negative exception codes: package exception response data from the opportunity) 57 | */ 58 | static int read_registers(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const agile_modbus_slave_util_t *slave_util) 59 | { 60 | uint8_t map_buf[AGILE_MODBUS_MAX_PDU_LENGTH]; 61 | int function = slave_info->sft->function; 62 | int address = slave_info->address; 63 | int nb = slave_info->nb; 64 | int send_index = slave_info->send_index; 65 | const agile_modbus_slave_util_map_t *maps = NULL; 66 | int nb_maps = 0; 67 | 68 | switch (function) { 69 | case AGILE_MODBUS_FC_READ_COILS: { 70 | maps = slave_util->tab_bits; 71 | nb_maps = slave_util->nb_bits; 72 | } break; 73 | 74 | case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: { 75 | maps = slave_util->tab_input_bits; 76 | nb_maps = slave_util->nb_input_bits; 77 | } break; 78 | 79 | case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: { 80 | maps = slave_util->tab_registers; 81 | nb_maps = slave_util->nb_registers; 82 | } break; 83 | 84 | case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: { 85 | maps = slave_util->tab_input_registers; 86 | nb_maps = slave_util->nb_input_registers; 87 | } break; 88 | 89 | default: 90 | return -AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION; 91 | } 92 | 93 | if (maps == NULL) 94 | return 0; 95 | 96 | for (int now_address = address, i = 0; now_address < address + nb; now_address++, i++) { 97 | const agile_modbus_slave_util_map_t *map = get_map_by_addr(maps, nb_maps, now_address); 98 | if (map == NULL) 99 | continue; 100 | 101 | int map_len = map->end_addr - now_address + 1; 102 | if (map->get) { 103 | memset(map_buf, 0, sizeof(map_buf)); 104 | map->get(map_buf, sizeof(map_buf)); 105 | int index = now_address - map->start_addr; 106 | int need_len = address + nb - now_address; 107 | if (need_len > map_len) { 108 | need_len = map_len; 109 | } 110 | 111 | if (function == AGILE_MODBUS_FC_READ_COILS || function == AGILE_MODBUS_FC_READ_DISCRETE_INPUTS) { 112 | uint8_t *ptr = map_buf; 113 | for (int j = 0; j < need_len; j++) { 114 | agile_modbus_slave_io_set(ctx->send_buf + send_index, i + j, ptr[index + j]); 115 | } 116 | } else { 117 | uint16_t *ptr = (uint16_t *)map_buf; 118 | for (int j = 0; j < need_len; j++) { 119 | agile_modbus_slave_register_set(ctx->send_buf + send_index, i + j, ptr[index + j]); 120 | } 121 | } 122 | } 123 | 124 | now_address += map_len - 1; 125 | i += map_len - 1; 126 | } 127 | 128 | return 0; 129 | } 130 | 131 | /** 132 | * @brief write register 133 | * @param ctx modbus handle 134 | * @param slave_info slave information body 135 | * @param slave_util slave function structure 136 | * @return =0: normal; 137 | * <0: Abnormal 138 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 139 | * (Other negative exception codes: package exception response data from the opportunity) 140 | */ 141 | static int write_registers(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const agile_modbus_slave_util_t *slave_util) 142 | { 143 | uint8_t map_buf[AGILE_MODBUS_MAX_PDU_LENGTH]; 144 | int function = slave_info->sft->function; 145 | int address = slave_info->address; 146 | int nb = 0; 147 | const agile_modbus_slave_util_map_t *maps = NULL; 148 | int nb_maps = 0; 149 | (void)ctx; 150 | switch (function) { 151 | case AGILE_MODBUS_FC_WRITE_SINGLE_COIL: 152 | case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: { 153 | maps = slave_util->tab_bits; 154 | nb_maps = slave_util->nb_bits; 155 | if (function == AGILE_MODBUS_FC_WRITE_SINGLE_COIL) { 156 | nb = 1; 157 | } else { 158 | nb = slave_info->nb; 159 | } 160 | } break; 161 | 162 | case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER: 163 | case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { 164 | maps = slave_util->tab_registers; 165 | nb_maps = slave_util->nb_registers; 166 | if (function == AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER) { 167 | nb = 1; 168 | } else { 169 | nb = slave_info->nb; 170 | } 171 | } break; 172 | 173 | default: 174 | return -AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION; 175 | } 176 | 177 | if (maps == NULL) 178 | return 0; 179 | 180 | for (int now_address = address, i = 0; now_address < address + nb; now_address++, i++) { 181 | const agile_modbus_slave_util_map_t *map = get_map_by_addr(maps, nb_maps, now_address); 182 | if (map == NULL) 183 | continue; 184 | 185 | int map_len = map->end_addr - now_address + 1; 186 | if (map->set) { 187 | memset(map_buf, 0, sizeof(map_buf)); 188 | if (map->get) { 189 | map->get(map_buf, sizeof(map_buf)); 190 | } 191 | 192 | int index = now_address - map->start_addr; 193 | int need_len = address + nb - now_address; 194 | if (need_len > map_len) { 195 | need_len = map_len; 196 | } 197 | 198 | if (function == AGILE_MODBUS_FC_WRITE_SINGLE_COIL || function == AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS) { 199 | uint8_t *ptr = map_buf; 200 | if (function == AGILE_MODBUS_FC_WRITE_SINGLE_COIL) { 201 | int data = *((int *)slave_info->buf); 202 | ptr[index] = data; 203 | } else { 204 | for (int j = 0; j < need_len; j++) { 205 | uint8_t data = agile_modbus_slave_io_get(slave_info->buf, i + j); 206 | ptr[index + j] = data; 207 | } 208 | } 209 | } else { 210 | uint16_t *ptr = (uint16_t *)map_buf; 211 | if (function == AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER) { 212 | int data = *((int *)slave_info->buf); 213 | ptr[index] = data; 214 | } else { 215 | for (int j = 0; j < need_len; j++) { 216 | uint16_t data = agile_modbus_slave_register_get(slave_info->buf, i + j); 217 | ptr[index + j] = data; 218 | } 219 | } 220 | } 221 | 222 | int rc = map->set(index, need_len, map_buf, sizeof(map_buf)); 223 | if (rc != 0) 224 | return rc; 225 | } 226 | 227 | now_address += map_len - 1; 228 | i += map_len - 1; 229 | } 230 | 231 | return 0; 232 | } 233 | 234 | /** 235 | * @brief mask write register 236 | * @param ctx modbus handle 237 | * @param slave_info slave information body 238 | * @param slave_util slave function structure 239 | * @return =0: normal; 240 | * <0: Abnormal 241 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 242 | * (Other negative exception codes: package exception response data from the opportunity) 243 | */ 244 | static int mask_write_register(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const agile_modbus_slave_util_t *slave_util) 245 | { 246 | uint8_t map_buf[AGILE_MODBUS_MAX_PDU_LENGTH]; 247 | int address = slave_info->address; 248 | const agile_modbus_slave_util_map_t *maps = slave_util->tab_registers; 249 | int nb_maps = slave_util->nb_registers; 250 | (void)ctx; 251 | if (maps == NULL) 252 | return 0; 253 | 254 | const agile_modbus_slave_util_map_t *map = get_map_by_addr(maps, nb_maps, address); 255 | if (map == NULL) 256 | return 0; 257 | 258 | if (map->set) { 259 | memset(map_buf, 0, sizeof(map_buf)); 260 | if (map->get) { 261 | map->get(map_buf, sizeof(map_buf)); 262 | } 263 | 264 | int index = address - map->start_addr; 265 | uint16_t *ptr = (uint16_t *)map_buf; 266 | uint16_t data = ptr[index]; 267 | uint16_t and = (slave_info->buf[0] << 8) + slave_info->buf[1]; 268 | uint16_t or = (slave_info->buf[2] << 8) + slave_info->buf[3]; 269 | 270 | data = (data & and) | (or &(~and)); 271 | ptr[index] = data; 272 | 273 | int rc = map->set(index, 1, map_buf, sizeof(map_buf)); 274 | if (rc != 0) 275 | return rc; 276 | } 277 | 278 | return 0; 279 | } 280 | 281 | /** 282 | * @brief Write and read registers 283 | * @param ctx modbus handle 284 | * @param slave_info slave information body 285 | * @param slave_util slave function structure 286 | * @return =0: normal; 287 | * <0: Abnormal 288 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 289 | * (Other negative exception codes: package exception response data from the opportunity) 290 | */ 291 | static int write_read_registers(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const agile_modbus_slave_util_t *slave_util) 292 | { 293 | uint8_t map_buf[AGILE_MODBUS_MAX_PDU_LENGTH]; 294 | int address = slave_info->address; 295 | int nb = (slave_info->buf[0] << 8) + slave_info->buf[1]; 296 | int address_write = (slave_info->buf[2] << 8) + slave_info->buf[3]; 297 | int nb_write = (slave_info->buf[4] << 8) + slave_info->buf[5]; 298 | int send_index = slave_info->send_index; 299 | 300 | const agile_modbus_slave_util_map_t *maps = slave_util->tab_registers; 301 | int nb_maps = slave_util->nb_registers; 302 | 303 | if (maps == NULL) 304 | return 0; 305 | 306 | /* Write first. 7 is the offset of the first values to write */ 307 | for (int now_address = address_write, i = 0; now_address < address_write + nb_write; now_address++, i++) { 308 | const agile_modbus_slave_util_map_t *map = get_map_by_addr(maps, nb_maps, now_address); 309 | if (map == NULL) 310 | continue; 311 | 312 | int map_len = map->end_addr - now_address + 1; 313 | if (map->set) { 314 | memset(map_buf, 0, sizeof(map_buf)); 315 | if (map->get) { 316 | map->get(map_buf, sizeof(map_buf)); 317 | } 318 | 319 | int index = now_address - map->start_addr; 320 | uint16_t *ptr = (uint16_t *)map_buf; 321 | int need_len = address_write + nb_write - now_address; 322 | if (need_len > map_len) { 323 | need_len = map_len; 324 | } 325 | 326 | for (int j = 0; j < need_len; j++) { 327 | uint16_t data = agile_modbus_slave_register_get(slave_info->buf + 7, i + j); 328 | ptr[index + j] = data; 329 | } 330 | 331 | int rc = map->set(index, need_len, map_buf, sizeof(map_buf)); 332 | if (rc != 0) 333 | return rc; 334 | } 335 | 336 | now_address += map_len - 1; 337 | i += map_len - 1; 338 | } 339 | 340 | /* and read the data for the response */ 341 | for (int now_address = address, i = 0; now_address < address + nb; now_address++, i++) { 342 | const agile_modbus_slave_util_map_t *map = get_map_by_addr(maps, nb_maps, now_address); 343 | if (map == NULL) 344 | continue; 345 | 346 | int map_len = map->end_addr - now_address + 1; 347 | if (map->get) { 348 | memset(map_buf, 0, sizeof(map_buf)); 349 | map->get(map_buf, sizeof(map_buf)); 350 | int index = now_address - map->start_addr; 351 | uint16_t *ptr = (uint16_t *)map_buf; 352 | int need_len = address + nb - now_address; 353 | if (need_len > map_len) { 354 | need_len = map_len; 355 | } 356 | 357 | for (int j = 0; j < need_len; j++) { 358 | agile_modbus_slave_register_set(ctx->send_buf + send_index, i + j, ptr[index + j]); 359 | } 360 | } 361 | 362 | now_address += map_len - 1; 363 | i += map_len - 1; 364 | } 365 | 366 | return 0; 367 | } 368 | 369 | /** 370 | * @} 371 | */ 372 | 373 | /** @defgroup SLAVE_UTIL_Exported_Functions Slave Util Exported Functions 374 | * @{ 375 | */ 376 | 377 | /** 378 | * @brief Slave callback function 379 | * @param ctx modbus handle 380 | * @param slave_info slave information body 381 | * @param data private data 382 | * @return =0: normal; 383 | * <0: Abnormal 384 | * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): Unknown exception, the slave will not package the response data) 385 | * (Other negative exception codes: package exception response data from the opportunity) 386 | */ 387 | int agile_modbus_slave_util_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data) 388 | { 389 | int function = slave_info->sft->function; 390 | int ret = 0; 391 | const agile_modbus_slave_util_t *slave_util = (const agile_modbus_slave_util_t *)data; 392 | 393 | if (slave_util == NULL) 394 | return 0; 395 | 396 | if (slave_util->addr_check) { 397 | ret = slave_util->addr_check(ctx, slave_info); 398 | if (ret != 0) 399 | return ret; 400 | } 401 | 402 | switch (function) { 403 | case AGILE_MODBUS_FC_READ_COILS: 404 | case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: 405 | case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: 406 | case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: 407 | ret = read_registers(ctx, slave_info, slave_util); 408 | break; 409 | 410 | case AGILE_MODBUS_FC_WRITE_SINGLE_COIL: 411 | case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: 412 | case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER: 413 | case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: 414 | ret = write_registers(ctx, slave_info, slave_util); 415 | break; 416 | 417 | case AGILE_MODBUS_FC_MASK_WRITE_REGISTER: 418 | ret = mask_write_register(ctx, slave_info, slave_util); 419 | break; 420 | 421 | case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: 422 | ret = write_read_registers(ctx, slave_info, slave_util); 423 | break; 424 | 425 | default: { 426 | if (slave_util->special_function) { 427 | ret = slave_util->special_function(ctx, slave_info); 428 | } else { 429 | ret = -AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION; 430 | } 431 | } break; 432 | } 433 | 434 | if (slave_util->done) { 435 | slave_util->done(ctx, slave_info, ret); 436 | } 437 | 438 | return ret; 439 | } 440 | 441 | /** 442 | * @} 443 | */ 444 | 445 | /** 446 | * @} 447 | */ 448 | 449 | /** 450 | * @} 451 | */ 452 | -------------------------------------------------------------------------------- /util/agile_modbus_slave_util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file agile_modbus_slave_util.h 3 | * @brief The simple slave access header file provided by the Agile Modbus software package 4 | * @author Ma Longwei (2544047213@qq.com) 5 | * @date 2022-07-28 6 | * 7 | * @attention 8 | * 9 | *

© Copyright (c) 2022 Ma Longwei. 10 | * All rights reserved.

11 | * 12 | */ 13 | 14 | #ifndef __PKG_AGILE_MODBUS_SLAVE_UTIL_H 15 | #define __PKG_AGILE_MODBUS_SLAVE_UTIL_H 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | 23 | /** @addtogroup UTIL 24 | * @{ 25 | */ 26 | 27 | /** @addtogroup SLAVE_UTIL 28 | * @{ 29 | */ 30 | 31 | /** @defgroup SLAVE_UTIL_Exported_Types Slave Util Exported Types 32 | * @{ 33 | */ 34 | 35 | /** 36 | * @brief slave register mapping structure 37 | */ 38 | typedef struct agile_modbus_slave_util_map { 39 | int start_addr; /**