├── LICENSE ├── README.md ├── examples ├── ModbusRTUSlaveExample │ ├── ModbusRTUSlaveExample.ino │ └── README.md └── NotModbusRTUSlaveExample │ └── NotModbusRTUSlaveExample.ino ├── keywords.txt ├── library.properties └── src ├── ModbusRTUSlave.cpp └── ModbusRTUSlave.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christopher Bulliner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModbusRTUSlave 2 | 3 | Modbus is an industrial communication protocol. The RTU variant communicates over serial lines such as UART, RS-232, or RS-485. The full details of the Modbus protocol can be found at [modbus.org](https://modbus.org). A good summary can also be found on [Wikipedia](https://en.wikipedia.org/wiki/Modbus). 4 | 5 | This is an Arduino library that implements the slave/server logic of the Modbus RTU protocol. It enables an Arduino, or arduino compatible, board to respond to Modbus RTU requests from a Modbus master/client. 6 | This library is able to service the following function codes: 7 | - 1 (Read Coils) 8 | - 2 (Read Discrete Inputs) 9 | - 3 (Read Holding Registers) 10 | - 4 (Read Input Registers) 11 | - 5 (Write Single Coil) 12 | - 6 (Write Single Holding Register) 13 | - 15 (Write Multiple Coils) 14 | - 16 (Write Multiple Holding Registers). 15 | 16 | This library will work with any `Stream` object, like `Serial`. A driver enable pin can be set up, enabling a half-duplex RS-485 transceiver to be used. Only `SERIAL_8N1`, `SERIAL_8E1`, `SERIAL_8O1`, `SERIAL_8N2`, `SERIAL_8E2`, and `SERIAL_8O2` configurations are supported; attempting to use any other configuration will cause the library to default to timings for `SERIAL_8N1`. 17 | 18 | This library updates `coil`, `descrete input`, `holding register`, and `input register` arrays based on Modbus requests. It does not give indication of what has changed, but it does give indication if a valid Modbus request has been received. 19 | 20 | 21 | 22 | ## Version Note 23 | ### 1.x.x to 2.x.x 24 | Version 2.x.x of this library is not backward compatible with version 1.x.x. Any sketches that were written to use a 1.x.x version of this library will not work with later versions, at least not without modification. 25 | 26 | ### 2.x.x to 3.x.x 27 | The main change going from version 2.x.x to 3.x.x is that `begin()` for the Serial object used needs to be run before running `begin()` for the library itself, e.g.: 28 | ```C++ 29 | Serial1.begin(38400); 30 | modbus.begin(38400); 31 | ``` 32 | This library is also now dependent on [ModbusADU](https://github.com/CMB27/ModbusADU) and [ModbusRTUComm](https://github.com/CMB27/ModbusRTUComm), and as of version 3.1.0, it is also dependent on [ModbusSlaveLogic](https://github.com/CMB27/ModbusSlaveLogic). 33 | 34 | 35 | 36 | ## Compatibility 37 | This library has been tested with the following boards and cores: 38 | 39 | | Board Name | Core | Works | 40 | | :-------------------------- | :------------------------------------------------------------------- | :------: | 41 | | Arduino Due | **Arduino SAM Boards (32-bits ARM Cortex-M3)** by Arduino `1.6.12` | Yes | 42 | | Arduino Giga | **Arduino Mbed OS GIGA Boards** by Arduino `4.2.1` | Yes | 43 | | Arduino Leonardo | **Arduino AVR Boards** by Arduino `1.8.6` | Yes | 44 | | Arduino Make Your UNO | **Arduino AVR Boards** by Arduino `1.8.6` | Yes [^1] | 45 | | Arduino Mega 2560 | **Arduino AVR Boards** by Arduino `1.8.6` | Yes | 46 | | Arduino Nano | **Arduino AVR Boards** by Arduino `1.8.6` | Yes | 47 | | Arduino Nano 33 BLE | **Arduino Mbed OS Nano Boards** by Arduino `4.2.1` | Yes | 48 | | Arduino Nano 33 IoT | **Arduino SAMD Boards (32-bits ARM Cortex-M0+)** by Arduino `1.8.14` | Yes | 49 | | Arduino Nano ESP32 | **Arduino ESP32 Boards** by Arduino `2.0.13` | Yes | 50 | | Arduino Nano ESP32 | **esp32** by Espressif Systems `3.0.7` | Yes | 51 | | Arduino Nano Every | **Arduino megaAVR Boards** by Arduino `1.8.8` | Yes | 52 | | Arduino Nano Matter | **Silicon Labs** by Silicon Labs `2.2.0` | Yes | 53 | | Arduino Nano RP2040 Connect | **Arduino Mbed OS Nano Boards** by Arduino `4.2.1` | No [^2] | 54 | | Arduino Nano RP2040 Connect | **Raspberry Pi Pico/RP2040** by Earle F. Philhower, III `4.4.0` | Yes | 55 | | Arduino UNO R3 SMD | **Arduino AVR Boards** by Arduino `1.8.6` | Yes | 56 | | Arduino UNO R4 Minima | **Arduino UNO R4 Boards** by Arduino `1.3.2` | Yes | 57 | | Arduino UNO R4 WiFi | **Arduino UNO R4 Boards** by Arduino `1.3.2` | Yes | 58 | | ST NUCLEO-F103RB | **STM32 MCU based boards** by STMicroelectronics `2.9.0` | Yes | 59 | | ST NUCLEO-F411RE | **STM32 MCU based boards** by STMicroelectronics `2.9.0` | Yes | 60 | 61 | [^1]: **Arduino Make Your UNO** 62 | The example program does not work with this board when connected to a computer via USB. 63 | However, it does work when it is powered through the barrel jack. 64 | 65 | [^2]: **Arduino Nano RP2040 Connect** 66 | This board has trouble receiving Modbus messages when using the `Arduino Mbed OS Nano Boards` core by Arduino. 67 | It seems that there is some issue with how the timing of `Serial.read()` works with this core. 68 | 69 | 70 | 71 | ## Example 72 | - [ModbusRTUSlaveExample](https://github.com/CMB27/ModbusRTUSlave/blob/main/examples/ModbusRTUSlaveExample/ModbusRTUSlaveExample.ino) 73 | - [NotModbusRTUSlaveExample](https://github.com/CMB27/ModbusRTUSlave/blob/main/examples/NotModbusRTUSlaveExample/NotModbusRTUSlaveExample.ino) 74 | 75 | 76 | ## Methods 77 | 78 | 79 | 80 |
ModbusRTUSlave() 81 |
82 | 83 | ### Description 84 | Creates a ModbusRTUSlave object and sets the serial port to use for data transmission. 85 | Optionally sets a driver enable pin. This pin will go `HIGH` when the library is transmitting. This is primarily intended for use with an RS-485 transceiver, but it can also be a handy diagnostic when connected to an LED. 86 | 87 | ### Syntax 88 | - `ModbusRTUSlave(serial)` 89 | - `ModbusRTUSlave(serial, dePin)` 90 | - `ModbusRTUSlave(serial, dePin, rePin)` 91 | 92 | ### Parameters 93 | - `serial`: the `Stream` object to use for Modbus communication. Usually something like `Serial1`. 94 | - `dePin`: the driver enable pin. This pin is set HIGH when transmitting. If this parameter is set to `-1`, this feature will be disabled. The default value is `-1`. Allowed data types are `int8_t` or `char`. 95 | - `rePin`: is always set `LOW`. If this parameter is set to `-1`, this feature will be disabled. 96 | 97 | ### Example 98 | ``` C++ 99 | # include 100 | 101 | const int8_t dePin = A6; 102 | const int8_t rePin = A5; 103 | 104 | ModbusRTUSlave modbus(Serial, dePin, rePin); 105 | ``` 106 | 107 |
108 |
109 | 110 | 111 | 112 |
configureCoils() 113 |
114 | 115 | ### Description 116 | Tells the library where coil data is stored and the number of coils. 117 | If this function is not run, the library will assume there are no coils. 118 | 119 | ### Syntax 120 | `modbus.configureCoils(coils, numCoils)` 121 | 122 | ### Parameters 123 | - `modbus`: a `ModbusRTUSlave` object. 124 | - `coils`: an array of coil values. Allowed data types: array of `bool`. 125 | - `numCoils`: the number of coils. This value must not be larger than the size of the array. Allowed data types: `uint16_t`. 126 | 127 |
128 |
129 | 130 | 131 | 132 |
configureDiscreteInputs() 133 |
134 | 135 | ### Description 136 | Tells the library where to read discrete input data and the number of discrete inputs. 137 | If this function is not run, the library will assume there are no discrete inputs. 138 | 139 | ### Syntax 140 | `modbus.configureDiscreteInputs(discreteInputs, numDiscreteInputs)` 141 | 142 | ### Parameters 143 | - `modbus`: a `ModbusRTUSlave` object. 144 | - `discreteInputs`: an array of discrete input values. Allowed data types: array of `bool`. 145 | - `numDiscreteInputs`: the number of discrete inputs. This value must not be larger than the size of the array. Allowed data types: `uint16_t`. 146 | 147 |
148 |
149 | 150 | 151 | 152 |
configureHoldingRegisters() 153 |
154 | 155 | ### Description 156 | Tells the library where holding register data is stored and the number of holding registers. 157 | If this function is not run, the library will assume there are no holding registers. 158 | 159 | ### Syntax 160 | `modbus.configureHoldingRegisters(holdingRegisters, numHoldingRegisters)` 161 | 162 | ### Parameters 163 | - `modbus`: a `ModbusRTUSlave` object. 164 | - `holdingRegisters`: an array of holding register values. Allowed data types: array of `uint16_t`. 165 | - `numHoldingRegisters`: the number of holding registers. This value must not be larger than the size of the array. Allowed data types: `uint16_t`. 166 | 167 |
168 |
169 | 170 | 171 | 172 |
configureInputRegisters() 173 |
174 | 175 | ### Description 176 | Tells the library where to read input register data and the number of input registers. 177 | If this function is not run, the library will assume there are no input registers. 178 | 179 | ### Syntax 180 | `modbus.configureInputRegisters(inputRegisters, numInputRegisters)` 181 | 182 | ### Parameters 183 | - `modbus`: a `ModbusRTUSlave` object. 184 | - `inputRegisters`: an array of input register values. Allowed data types: array of `uint16_t`. 185 | - `numInputRegisters`: the number of input registers. This value must not be larger than the size of the array. Allowed data types: `uint16_t`. 186 | 187 |
188 |
189 | 190 | 191 | 192 |
setResponseDelay() 193 |
194 | 195 | ### Description 196 | Sets an optional delay in milliseconds between when a request from a master device has been processed and when the slave device sends its response. 197 | By default this value is `0`. 198 | This may be useful when tight control over the DE pin of an RS-485 transceiver on a master device is not possible. 199 | Adding a delay will give the master more time to set the DE pin `LOW` and avoid issues with multiple active drivers on the RS-485 bus. 200 | 201 | ### Syntax 202 | `modbus.setResponseDelay(responseDelay)` 203 | 204 | ### Parameters 205 | - `modbus`: a `ModbusRTUSlave` object. 206 | - `responseDelay`: number of milliseconds to wait before responding to requests. Allowed data types: `unsigned long`. 207 | 208 | *This function should only be used as a last resort.* 209 | 210 |
211 |
212 | 213 | 214 | 215 |
begin() 216 |
217 | 218 | ### Description 219 | Sets the slave/server id and the data rate in bits per second (baud) for serial transmission. 220 | Optionally it also sets the data configuration. Note, there must be 8 data bits for Modbus RTU communication. The default configuration is 8 data bits, no parity, and one stop bit. 221 | 222 | ### Syntax 223 | - `modbus.begin(slaveId, baud)` 224 | - `modbus.begin(slaveId, baud, config)` 225 | 226 | ### Parameters 227 | - `modbus`: a `ModbusRTUSlave` object. 228 | - `slaveId`: the number used to itentify this device on the Modbus network. Allowed data types: `uint8_t` or `byte`. 229 | - `baud`: the baud rate to use for Modbus communication. Common values are: `1200`, `2400`, `4800`, `9600`, `16200`, `38400`, `57600`, and `115200`. Allowed data types: `unsigned long`. 230 | - `config`: the serial port configuration to use. Valid values are: 231 | `SERIAL_8N1`: no parity (default) 232 | `SERIAL_8N2` 233 | `SERIAL_8E1`: even parity 234 | `SERIAL_8E2` 235 | `SERIAL_8O1`: odd parity 236 | `SERIAL_8O2` 237 | 238 |
239 |
240 | 241 | 242 | 243 |
poll() 244 |
245 | 246 | ### Description 247 | Checks if any Modbus requests are available. 248 | If a valid write request has been received, it will update the appropriate data array, and send an acknowledgment response. 249 | If a valid read request has been received, it will send a response with the requested data. 250 | If an invalid request has been received, it will either respond with an exception response or not at all, as per the Modbus specification. 251 | This function should be called frequently. 252 | 253 | ### Syntax 254 | `modbus.poll()` 255 | 256 | ### Parameters 257 | - `modbus`: a `ModbusRTUSlave` object. 258 | 259 | ### Returns 260 | - `true` if a valid Modbus request was received and processed. 261 | - `false` if no valid Modbus request was received. 262 | 263 | *It is not essential that this value be read.* 264 | 265 | ### Example 266 | ``` C++ 267 | # include 268 | 269 | const uint8_t coilPins[2] = {4, 5}; 270 | const uint8_t discreteInputPins[2] = {2, 3}; 271 | 272 | ModbusRTUSlave modbus(Serial); 273 | 274 | bool coils[2]; 275 | bool discreteInputs[2]; 276 | 277 | void setup() { 278 | pinMode(coilPins[0], OUTPUT); 279 | pinMode(coilPins[1], OUTPUT); 280 | pinMode(discreteInputPins[0], INPUT); 281 | pinMode(discreteInputPins[1], INPUT); 282 | 283 | modbus.configureCoils(coils, 2); 284 | modbus.configureDiscreteInputs(discreteInputs, 2); 285 | Serial.begin(38400); 286 | modbus.begin(1, 38400); 287 | } 288 | 289 | void loop() { 290 | discreteInputs[0] = digitalRead(discreteInputPins[0]); 291 | discreteInputs[1] = digitalRead(discreteInputPins[1]); 292 | 293 | modbus.poll(); 294 | 295 | digitalWrite(coilPins[0], coils[0]); 296 | digitalWrite(coilPins[1], coils[1]); 297 | } 298 | 299 | ``` 300 | 301 |
302 |
303 | -------------------------------------------------------------------------------- /examples/ModbusRTUSlaveExample/ModbusRTUSlaveExample.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRTUSlaveExample 3 | 4 | This example demonstrates how to setup and use the ModbusRTUSlave library (https://github.com/CMB27/ModbusRTUSlave). 5 | See README.md (https://github.com/CMB27/ModbusRTUMaster/blob/main/examples/ModbusRTUSlaveExample/README.md) for hardware setup details. 6 | 7 | Created: 2023-07-22 8 | By: C. M. Bulliner 9 | Last Modified: 2025-01-03 10 | By: C. M. Bulliner 11 | 12 | */ 13 | 14 | #include 15 | 16 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) || defined(ARDUINO_SAM_DUE) 17 | // The ATmega328P and ATmega168 are used in the Ardunino UNO and similar boards. 18 | // The ATmega2560 and ATmega1280 are used in the Arduino Mega and similar. 19 | #define MODBUS_SERIAL Serial 20 | #elif defined(ARDUINO_NANO_ESP32) 21 | // On the Arduino Nano ESP32, the HardwareSerial port on pins 0 and 1 is Serial0. 22 | #define MODBUS_SERIAL Serial0 23 | #elif defined(ARDUINO_ARCH_STM32) 24 | // On ST Nucleo-64 Boards, the HardwareSerial port on pins 0 and 1 is Serial2. 25 | #define MODBUS_SERIAL Serial2 26 | #else 27 | // On the majority of Arduino boards, the HardwareSerial port on pins 0 and 1 is Serial1. 28 | #define MODBUS_SERIAL Serial1 29 | #endif 30 | // You can change the baud, config, and unit id values if you like. 31 | // Just make sure they match the settings you use in ModbusRTUMasterExample. 32 | #define MODBUS_BAUD 38400 33 | #define MODBUS_CONFIG SERIAL_8N1 34 | #define MODBUS_UNIT_ID 1 35 | 36 | #if (defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_ARCH_MBED)) || defined(ARDUINO_NANO_ESP32) 37 | // These boards operate unsing GPIO numbers that don't correspond to the numbers on the boards. 38 | // However they do have D# values #defined to correct this. 39 | const int16_t buttonPins[2] = {D2, D3}; 40 | const int16_t ledPins[4] = {D5, D6, D7, D8}; 41 | const int16_t dePin = D13; 42 | #else 43 | // Other boards do not have D# values, and will throw an error if you try to use them. 44 | const int16_t buttonPins[2] = {2, 3}; 45 | const int16_t ledPins[4] = {5, 6, 7, 8}; 46 | const int16_t dePin = 13; 47 | #endif 48 | const int16_t knobPins[2] = {A0, A1}; 49 | 50 | ModbusRTUSlave modbus(MODBUS_SERIAL, dePin); 51 | 52 | const uint8_t numCoils = 2; 53 | const uint8_t numDiscreteInputs = 2; 54 | const uint8_t numHoldingRegisters = 2; 55 | const uint8_t numInputRegisters = 2; 56 | 57 | bool coils[numCoils]; 58 | bool discreteInputs[numDiscreteInputs]; 59 | uint16_t holdingRegisters[numHoldingRegisters]; 60 | uint16_t inputRegisters[numInputRegisters]; 61 | 62 | 63 | 64 | void setup() { 65 | pinMode(knobPins[0], INPUT); 66 | pinMode(knobPins[1], INPUT); 67 | pinMode(buttonPins[0], INPUT_PULLUP); 68 | pinMode(buttonPins[1], INPUT_PULLUP); 69 | pinMode(ledPins[0], OUTPUT); 70 | pinMode(ledPins[1], OUTPUT); 71 | pinMode(ledPins[2], OUTPUT); 72 | pinMode(ledPins[3], OUTPUT); 73 | 74 | #if defined(ARDUINO_NANO_ESP32) || defined(ARDUINO_NANO_MATTER) 75 | analogReadResolution(10); 76 | #endif 77 | 78 | modbus.configureCoils(coils, numCoils); 79 | modbus.configureDiscreteInputs(discreteInputs, numDiscreteInputs); 80 | modbus.configureHoldingRegisters(holdingRegisters, numHoldingRegisters); 81 | modbus.configureInputRegisters(inputRegisters, numInputRegisters); 82 | 83 | MODBUS_SERIAL.begin(MODBUS_BAUD, MODBUS_CONFIG); 84 | modbus.begin(MODBUS_UNIT_ID, MODBUS_BAUD, MODBUS_CONFIG); 85 | } 86 | 87 | void loop() { 88 | inputRegisters[0] = map(analogRead(knobPins[0]), 0, 1023, 0, 255); 89 | inputRegisters[1] = map(analogRead(knobPins[1]), 0, 1023, 0, 255); 90 | discreteInputs[0] = !digitalRead(buttonPins[0]); 91 | discreteInputs[1] = !digitalRead(buttonPins[1]); 92 | 93 | modbus.poll(); 94 | 95 | analogWrite(ledPins[0], holdingRegisters[0]); 96 | analogWrite(ledPins[1], holdingRegisters[1]); 97 | digitalWrite(ledPins[2], coils[0]); 98 | digitalWrite(ledPins[3], coils[1]); 99 | } 100 | -------------------------------------------------------------------------------- /examples/ModbusRTUSlaveExample/README.md: -------------------------------------------------------------------------------- 1 | # ModbusRTUSlaveExample 2 | 3 | This example demonstrates how to setup and use the [ModbusRTUSlave](https://github.com/CMB27/ModbusRTUSlave) library. 4 | 5 | > [!IMPORTANT] 6 | > This is a communications library, so in order to test it, you will need something to communicate with. 7 | > A second board running ModbusRTUMasterExample from the [ModbusRTUMaster](https://github.com/CMB27/ModbusRTUMaster) library will be needed. 8 | > This second board will also need to be setup with the [circuit](#circuit) shown below. 9 | 10 | 11 | ## Circuit: 12 | ``` 13 | VCC 0.1µF Capacitor 14 | ^ Arduino Board / 15 | | / | | 16 | +--------------------o---------------------------o--------------------------/-------------------------------o---| |---+ 17 | | | / | | | | 18 | | | +------+ +------+ / RS-485 | | 19 | | | +--| |-------------------| |-------+ Transceiver | | 20 | | | | | | +------+ | \ | | 120 Ω Resistor 21 | | | | | | | +-------v-------+ | | _____ / 22 | | | | +------+ SCL [ ] | +-------| RO VCC |---+ | +---|_____|---+ 23 | | | | SDA [ ] | | | | | | | 24 | | | | AREF [ ] | | +---| RE B |-------------|---o-------------|---<> RS485_D- 25 | | | | GND [ ] | | | | | | | 26 | | | | [ ] BOOT 13 [ ] |------|---o---| DE A |-------------|-----------------o---<> RS485_D+ 27 | | +------| [ ] IOREF 12 [ ] | | | | | 28 | | | [ ] RESET ~11 [ ] | | +---| DI GND |-------------o 29 | | | [ ] 3.3V ~10 [ ] | | | +---------------+ | 30 | | | [ ] 5V ~9 [ ] | | | _____ | 31 | | | [ ] GND 8 [ ] |------|---|----------------->|---|_____|----o 32 | | +------| [ ] GND | | | _____ | 33 | | | | [ ] VIN 7 [ ] |------|---|----------------->|---|_____|----o 34 | | | | ~6 [ ] |------|---|------------+ | 4x LEDs 35 | | +--------------------| [ ] A0 ~5 [ ] |------|---|---------+ | _____ | 4x 1K Ω Resistors 36 | | | +-------------| [ ] A1 4 [ ] | | | | +---->|---|_____|----o 37 | | __v__ | | | [ ] A2 ~3 [ ] |------|---|------+ | _____ | 38 | o---|_____|---|------o | [ ] A3 2 [ ] |------|---|---+ | +------->|---|_____|----o 39 | | | | | [ ] A4 TX→1 [ ] |------|---+ | | | 40 | | __v__ | | [ ] A5 RX←0 [ ] |------+ | | | 41 | +----------|_____|---o | __________/ | | __|__ | 42 | | \_______________________________/ | +-------------o o--------o 43 | 2x 10K Ω | | | 2x Pushbutton Switches 44 | Potentiometers | | __|__ | 45 | | +----------------o o--------o 46 | | | 47 | +------------------------------------------------------------------------------------------------o---------------------<> GND 48 | ``` 49 | 50 | **Components:** 51 | - Arduino Board 52 | - Half-Duplex RS-485 Transceiver (Must be able to operate at your arduino board's logic level) 53 | - 120 Ω Resistor (At least 1/4W recommended) 54 | - 4x 1K Ω Resistors 55 | - 2x 10K Ω Potentiometers 56 | - 0.1µF Capacitor 57 | - 4x LEDs 58 | 59 | **Connect the following points together from this circuit and the one for ModbusRTUSlaveExample:** 60 | - RS485_D- 61 | - RS485_D+ 62 | - GND 63 | -------------------------------------------------------------------------------- /examples/NotModbusRTUSlaveExample/NotModbusRTUSlaveExample.ino: -------------------------------------------------------------------------------- 1 | /* 2 | NotModbusRTUSlaveExample 3 | 4 | This example should do the same thing as ModbusRTUSlaveExample, but without actually using the ModbusRTUSlave library. 5 | This adds some complexity, but it exposes more of the process and more information that can be used for debugging or advanced applications. 6 | 7 | See ModbusRTUSlaveExample README (https://github.com/CMB27/ModbusRTUMaster/blob/main/examples/ModbusRTUSlaveExample/README.md) for hardware setup details. 8 | 9 | See https://github.com/CMB27/ModbusADU for details on ModbusADU. 10 | See https://github.com/CMB27/ModbusSlaveLogic for details on ModbusSlaveLogic. 11 | See https://github.com/CMB27/ModbusRTUComm for details on ModbusRTUComm. 12 | 13 | Created: 2024-12-30 14 | By: C. M. Bulliner 15 | Last Modified: 2025-01-04 16 | By: C. M. Bulliner 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) || defined(ARDUINO_SAM_DUE) 25 | // The ATmega328P and ATmega168 are used in the Ardunino UNO and similar boards. 26 | // The ATmega2560 and ATmega1280 are used in the Arduino Mega and similar. 27 | #define MODBUS_SERIAL Serial 28 | #elif defined(ARDUINO_NANO_ESP32) 29 | // On the Arduino Nano ESP32, the HardwareSerial port on pins 0 and 1 is Serial0. 30 | #define MODBUS_SERIAL Serial0 31 | #elif defined(ARDUINO_ARCH_STM32) 32 | // On ST Nucleo-64 Boards, the HardwareSerial port on pins 0 and 1 is Serial2. 33 | #define MODBUS_SERIAL Serial2 34 | #else 35 | // On the majority of Arduino boards, the HardwareSerial port on pins 0 and 1 is Serial1. 36 | #define MODBUS_SERIAL Serial1 37 | #endif 38 | // You can change the baud, config, and unit id values if you like. 39 | // Just make sure they match the settings you use in ModbusRTUMasterExample. 40 | #define MODBUS_BAUD 38400 41 | #define MODBUS_CONFIG SERIAL_8N1 42 | #define MODBUS_UNIT_ID 1 43 | 44 | #if (defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_ARCH_MBED)) || defined(ARDUINO_NANO_ESP32) 45 | // These boards operate unsing GPIO numbers that don't correspond to the numbers on the boards. 46 | // However they do have D# values #defined to correct this. 47 | const int16_t buttonPins[2] = {D2, D3}; 48 | const int16_t ledPins[4] = {D5, D6, D7, D8}; 49 | const int16_t dePin = D13; 50 | #else 51 | // Other boards do not have D# values, and will throw an error if you try to use them. 52 | const int16_t buttonPins[2] = {2, 3}; 53 | const int16_t ledPins[4] = {5, 6, 7, 8}; 54 | const int16_t dePin = 13; 55 | #endif 56 | const int16_t knobPins[2] = {A0, A1}; 57 | 58 | ModbusRTUComm rtuComm(MODBUS_SERIAL, dePin); 59 | ModbusSlaveLogic modbusLogic; 60 | 61 | const uint8_t numCoils = 2; 62 | const uint8_t numDiscreteInputs = 2; 63 | const uint8_t numHoldingRegisters = 2; 64 | const uint8_t numInputRegisters = 2; 65 | 66 | bool coils[numCoils]; 67 | bool discreteInputs[numDiscreteInputs]; 68 | uint16_t holdingRegisters[numHoldingRegisters]; 69 | uint16_t inputRegisters[numInputRegisters]; 70 | 71 | 72 | 73 | void setup() { 74 | pinMode(knobPins[0], INPUT); 75 | pinMode(knobPins[1], INPUT); 76 | pinMode(buttonPins[0], INPUT_PULLUP); 77 | pinMode(buttonPins[1], INPUT_PULLUP); 78 | pinMode(ledPins[0], OUTPUT); 79 | pinMode(ledPins[1], OUTPUT); 80 | pinMode(ledPins[2], OUTPUT); 81 | pinMode(ledPins[3], OUTPUT); 82 | 83 | #if defined(ARDUINO_NANO_ESP32) || defined(ARDUINO_NANO_MATTER) 84 | analogReadResolution(10); 85 | #endif 86 | 87 | modbusLogic.configureCoils(coils, numCoils); 88 | modbusLogic.configureDiscreteInputs(discreteInputs, numDiscreteInputs); 89 | modbusLogic.configureHoldingRegisters(holdingRegisters, numHoldingRegisters); 90 | modbusLogic.configureInputRegisters(inputRegisters, numInputRegisters); 91 | 92 | MODBUS_SERIAL.begin(MODBUS_BAUD, MODBUS_CONFIG); 93 | rtuComm.begin(MODBUS_BAUD, MODBUS_CONFIG); 94 | } 95 | 96 | void loop() { 97 | inputRegisters[0] = map(analogRead(knobPins[0]), 0, 1023, 0, 255); 98 | inputRegisters[1] = map(analogRead(knobPins[1]), 0, 1023, 0, 255); 99 | discreteInputs[0] = !digitalRead(buttonPins[0]); 100 | discreteInputs[1] = !digitalRead(buttonPins[1]); 101 | 102 | ModbusADU adu; // creates a ModbusADU object to hold the modbus data frame 103 | uint8_t error = rtuComm.readAdu(adu); // reads any available data and checks for errors 104 | if (error) return; // skips the rest of loop if there are any errors 105 | uint8_t unitId = adu.getUnitId(); // gets the unit id from the modbus data frame 106 | if (unitId != MODBUS_UNIT_ID && unitId != 0) return; // skips the rest of loop if the data frame is not addressed to us and is not a broadcast message 107 | modbusLogic.processPdu(adu); // processes the data frame, possibly updating the modbus data arrays and formulating the response 108 | if (unitId != 0) rtuComm.writeAdu(adu); // send the response if the request was not a broadcast message 109 | 110 | analogWrite(ledPins[0], holdingRegisters[0]); 111 | analogWrite(ledPins[1], holdingRegisters[1]); 112 | digitalWrite(ledPins[2], coils[0]); 113 | digitalWrite(ledPins[3], coils[1]); 114 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ModbusRTUSlave KEYWORD1 2 | configureCoils KEYWORD2 3 | configureDiscreteInputs KEYWORD2 4 | configureHoldingRegisters KEYWORD2 5 | configureInputRegisters KEYWORD2 6 | setResponseDelay KEYWORD2 7 | begin KEYWORD2 8 | poll KEYWORD2 9 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ModbusRTUSlave 2 | version=3.1.1 3 | author=C. M. Bulliner 4 | maintainer=C. M. Bulliner 5 | sentence=This is an Arduino library that implements the slave/server logic of the Modbus RTU protocol. 6 | paragraph=This library implements function codes 1 (Read Coils), 2 (Read Discrete Inputs), 3 (Read Holding Registers), 4 (Read Input Registers), 5 (Write Single Coil), 6 (Write Single Holding Register), 15 (Write Multiple Coils), and 16 (Write Multiple Holding Registers). Version 2.x.x of this library is not backward compatible with version 1.x.x. Any sketches that were written to use a 1.x.x version of this library will not work with later versions, at least not without modification. 7 | category=Communication 8 | url=https://github.com/CMB27/ModbusRTUSlave 9 | architectures=* 10 | depends=ModbusADU (>=1.0.1), ModbusRTUComm (>=1.3.1), ModbusSlaveLogic (>=1.0.1) 11 | -------------------------------------------------------------------------------- /src/ModbusRTUSlave.cpp: -------------------------------------------------------------------------------- 1 | #include "ModbusRTUSlave.h" 2 | 3 | ModbusRTUSlave::ModbusRTUSlave(Stream& serial, int8_t dePin, int8_t rePin) : _rtuComm(serial, dePin, rePin) { 4 | 5 | } 6 | 7 | void ModbusRTUSlave::setResponseDelay(unsigned long responseDelay) { 8 | _responseDelay = responseDelay; 9 | } 10 | 11 | void ModbusRTUSlave::begin(uint8_t localUnitId, unsigned long baud, uint32_t config) { 12 | if (localUnitId >= 1 && localUnitId <= 247) _localUnitId = localUnitId; 13 | _rtuComm.begin(baud, config); 14 | } 15 | 16 | bool ModbusRTUSlave::poll() { 17 | ModbusADU adu; 18 | ModbusRTUCommError error = _rtuComm.readAdu(adu); 19 | if (error) return false; 20 | uint8_t unitId = adu.getUnitId(); 21 | if (unitId != _localUnitId && unitId != 0) return false; 22 | processPdu(adu); 23 | if (unitId != 0) { 24 | delay(_responseDelay); 25 | _rtuComm.writeAdu(adu); 26 | } 27 | return true; 28 | } 29 | -------------------------------------------------------------------------------- /src/ModbusRTUSlave.h: -------------------------------------------------------------------------------- 1 | #ifndef ModbusRTUSlave_h 2 | #define ModbusRTUSlave_h 3 | 4 | #include "Arduino.h" 5 | #include "ModbusADU.h" 6 | #include "ModbusSlaveLogic.h" 7 | #include "ModbusRTUComm.h" 8 | 9 | class ModbusRTUSlave : public ModbusSlaveLogic { 10 | public: 11 | ModbusRTUSlave(Stream& serial, int8_t dePin = -1, int8_t rePin = -1); 12 | void setResponseDelay(unsigned long responseDelay); 13 | void begin(uint8_t localUnitId, unsigned long baud, uint32_t config = SERIAL_8N1); 14 | bool poll(); 15 | 16 | private: 17 | ModbusRTUComm _rtuComm; 18 | uint8_t _localUnitId = 0; 19 | unsigned long _responseDelay = 0; 20 | using ModbusSlaveLogic::processPdu; 21 | 22 | }; 23 | 24 | #endif 25 | --------------------------------------------------------------------------------