├── 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 |
--------------------------------------------------------------------------------