Skip to content
Newer
Older
100644 302 lines (250 sloc) 7.37 KB
8641bf4 @stephane Apply LGPL v3 license
authored Apr 12, 2011
1 /*
4a56466 @stephane Change license from LGPL to ISC
authored Sep 16, 2012
2 * Copyright © 2011-2012 Stéphane Raimbault <stephane.raimbault@gmail.com>
8641bf4 @stephane Apply LGPL v3 license
authored Apr 12, 2011
3 *
4a56466 @stephane Change license from LGPL to ISC
authored Sep 16, 2012
4 * License ISC, see LICENSE for more details.
8641bf4 @stephane Apply LGPL v3 license
authored Apr 12, 2011
5 *
6 * This library implements the Modbus protocol.
7 * http://libmodbus.org/
c37f3cf @GregRadion Change a couple of 8-bit data types to 16-bit
GregRadion authored Sep 11, 2012
8 *
8641bf4 @stephane Apply LGPL v3 license
authored Apr 12, 2011
9 */
10
e3fdcdf @stephane Initial import
authored Apr 10, 2011
11 #include <inttypes.h>
12
c37f3cf @GregRadion Change a couple of 8-bit data types to 16-bit
GregRadion authored Sep 11, 2012
13 #if defined(ARDUINO) && ARDUINO >= 100
14 #include "Arduino.h"
15 #else
16 #include "WProgram.h"
17 #include <pins_arduino.h>
18 #endif
e3fdcdf @stephane Initial import
authored Apr 10, 2011
19 #include "Modbusino.h"
20
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
21 #define _MODBUS_RTU_SLAVE 0
22 #define _MODBUS_RTU_FUNCTION 1
e3fdcdf @stephane Initial import
authored Apr 10, 2011
23 #define _MODBUS_RTU_PRESET_REQ_LENGTH 6
24 #define _MODBUS_RTU_PRESET_RSP_LENGTH 2
25
26 #define _MODBUS_RTU_CHECKSUM_LENGTH 2
27
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
28 #define _MODBUSINO_RTU_MAX_ADU_LENGTH 128
29
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
30 /* Supported function codes */
e3fdcdf @stephane Initial import
authored Apr 10, 2011
31 #define _FC_READ_HOLDING_REGISTERS 0x03
32 #define _FC_WRITE_MULTIPLE_REGISTERS 0x10
33
793d8cb @stephane Replace step defines by an enum
authored Apr 11, 2011
34 enum {
35 _STEP_FUNCTION = 0x01,
36 _STEP_META,
37 _STEP_DATA
38 };
e3fdcdf @stephane Initial import
authored Apr 10, 2011
39
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
40 static uint16_t crc16(uint8_t *req, uint8_t req_length)
e3fdcdf @stephane Initial import
authored Apr 10, 2011
41 {
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
42 uint8_t j;
d53ee3f @stephane Changing fast CRC code by a slow one, frees 512 bytes of RAM!
authored Apr 12, 2011
43 uint16_t crc;
44
45 crc = 0xFFFF;
46 while (req_length--) {
47 crc = crc ^ *req++;
48 for (j=0; j < 8; j++) {
49 if (crc & 0x0001)
50 crc = (crc >> 1) ^ 0xA001;
51 else
52 crc = crc >> 1;
53 }
e3fdcdf @stephane Initial import
authored Apr 10, 2011
54 }
55
d53ee3f @stephane Changing fast CRC code by a slow one, frees 512 bytes of RAM!
authored Apr 12, 2011
56 return (crc << 8 | crc >> 8);
e3fdcdf @stephane Initial import
authored Apr 10, 2011
57 }
58
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
59 ModbusinoSlave::ModbusinoSlave(uint8_t slave) {
d9b593c @stephane Check the slave argument
authored Apr 22, 2011
60 if (slave >= 0 & slave <= 247) {
61 _slave = slave;
62 }
e3fdcdf @stephane Initial import
authored Apr 10, 2011
63 }
64
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
65 void ModbusinoSlave::setup(long baud) {
66 Serial.begin(baud);
e3fdcdf @stephane Initial import
authored Apr 10, 2011
67 }
68
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
69 static int check_integrity(uint8_t *msg, uint8_t msg_length)
e3fdcdf @stephane Initial import
authored Apr 10, 2011
70 {
71 uint16_t crc_calculated;
72 uint16_t crc_received;
73
aa052c6 @stephane Defensive programming in check_integrity
authored Apr 14, 2011
74 if (msg_length < 2)
75 return -1;
76
e3fdcdf @stephane Initial import
authored Apr 10, 2011
77 crc_calculated = crc16(msg, msg_length - 2);
78 crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1];
79
80 /* Check CRC of msg */
81 if (crc_calculated == crc_received) {
82 return msg_length;
83 } else {
84 return -1;
85 }
86 }
87
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
88 static int build_response_basis(uint8_t slave, uint8_t function,
89 uint8_t* rsp)
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
90 {
91 rsp[0] = slave;
92 rsp[1] = function;
93
94 return _MODBUS_RTU_PRESET_RSP_LENGTH;
95 }
96
8aa80d7 @stephane Fix void return
authored Apr 14, 2011
97 static void send_msg(uint8_t *msg, uint8_t msg_length)
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
98 {
99 uint16_t crc = crc16(msg, msg_length);
100
101 msg[msg_length++] = crc >> 8;
102 msg[msg_length++] = crc & 0x00FF;
103
104 Serial.write(msg, msg_length);
105 }
106
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
107 static uint8_t response_exception(uint8_t slave, uint8_t function,
108 uint8_t exception_code,
109 uint8_t *rsp)
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
110 {
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
111 uint8_t rsp_length;
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
112
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
113 rsp_length = build_response_basis(slave, function + 0x80, rsp);
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
114
115 /* Positive exception code */
116 rsp[rsp_length++] = exception_code;
117
118 return rsp_length;
119 }
120
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
121 static void flush(void)
122 {
2766954 @stephane Avoid getting stuck on a line saturated by garbage
authored Apr 14, 2011
123 uint8_t i = 0;
124
125 /* Wait a moment to receive the remaining garbage but avoid getting stuck
126 * because the line is saturated */
127 while (Serial.available() && i++ < 10) {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
128 Serial.flush();
129 delay(3);
130 }
131 }
132
133 static int receive(uint8_t *req, uint8_t _slave)
e3fdcdf @stephane Initial import
authored Apr 10, 2011
134 {
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
135 uint8_t i;
136 uint8_t length_to_read;
137 uint8_t req_index;
138 uint8_t step;
139 uint8_t function;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
140
141 /* We need to analyse the message step by step. At the first step, we want
142 * to reach the function code because all packets contain this
143 * information. */
144 step = _STEP_FUNCTION;
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
145 length_to_read = _MODBUS_RTU_FUNCTION + 1;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
146
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
147 req_index = 0;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
148 while (length_to_read != 0) {
149
0e8c3a5 @stephane Add a timeout on receiving with a granularity of 1 ms and max of 10 ms
authored Apr 12, 2011
150 /* The timeout is defined to ~10 ms between each bytes. Precision is
151 not that important so I rather to avoid millis() to apply the KISS
152 principle (millis overflows after 50 days, etc) */
e3fdcdf @stephane Initial import
authored Apr 10, 2011
153 if (!Serial.available()) {
f9e3352 @stephane Handle illegal function exception
authored Apr 14, 2011
154 i = 0;
0e8c3a5 @stephane Add a timeout on receiving with a granularity of 1 ms and max of 10 ms
authored Apr 12, 2011
155 while (!Serial.available()) {
156 delay(1);
157 if (++i == 10) {
158 /* Too late, bye */
159 return -1;
160 }
161 }
e3fdcdf @stephane Initial import
authored Apr 10, 2011
162 }
163
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
164 req[req_index] = Serial.read();
e3fdcdf @stephane Initial import
authored Apr 10, 2011
165
166 /* Moves the pointer to receive other data */
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
167 req_index++;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
168
169 /* Computes remaining bytes */
170 length_to_read--;
171
172 if (length_to_read == 0) {
173 switch (step) {
174 case _STEP_FUNCTION:
175 /* Function code position */
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
176 function = req[_MODBUS_RTU_FUNCTION];
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
177 if (function == _FC_READ_HOLDING_REGISTERS) {
178 length_to_read = 4;
179 } else if (function == _FC_WRITE_MULTIPLE_REGISTERS) {
180 length_to_read = 5;
181 } else {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
182 /* Wait a moment to receive the remaining garbage */
183 flush();
10a2311 @stephane Broadcast address support
authored Apr 22, 2011
184 if (req[_MODBUS_RTU_SLAVE] == _slave ||
185 req[_MODBUS_RTU_SLAVE] == MODBUS_BROADCAST_ADDRESS) {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
186 /* It's for me so send an exception (reuse req) */
187 uint8_t rsp_length = response_exception(
188 _slave, function,
189 MODBUS_EXCEPTION_ILLEGAL_FUNCTION,
190 req);
191 send_msg(req, rsp_length);
192 return - 1 - MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
193 }
194
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
195 return -1;
196 }
197 step = _STEP_META;
198 break;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
199 case _STEP_META:
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
200 length_to_read = _MODBUS_RTU_CHECKSUM_LENGTH;
201
202 if (function == _FC_WRITE_MULTIPLE_REGISTERS)
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
203 length_to_read += req[_MODBUS_RTU_FUNCTION + 5];
a777021 @stephane Simplify code further by removing dead code code and nested calls
authored Apr 12, 2011
204
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
205 if ((req_index + length_to_read) > _MODBUSINO_RTU_MAX_ADU_LENGTH) {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
206 flush();
10a2311 @stephane Broadcast address support
authored Apr 22, 2011
207 if (req[_MODBUS_RTU_SLAVE] == _slave ||
208 req[_MODBUS_RTU_SLAVE] == MODBUS_BROADCAST_ADDRESS) {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
209 /* It's for me so send an exception (reuse req) */
210 uint8_t rsp_length = response_exception(
211 _slave, function,
212 MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE,
213 req);
214 send_msg(req, rsp_length);
215 return - 1 - MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
216 }
217 return -1;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
218 }
219 step = _STEP_DATA;
220 break;
221 default:
222 break;
223 }
224 }
225 }
226
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
227 return check_integrity(req, req_index);
e3fdcdf @stephane Initial import
authored Apr 10, 2011
228 }
229
230
c37f3cf @GregRadion Change a couple of 8-bit data types to 16-bit
GregRadion authored Sep 11, 2012
231 static void reply(uint16_t *tab_reg, uint16_t nb_reg,
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
232 uint8_t *req, uint8_t req_length, uint8_t _slave)
8f5a4b5 @stephane Slave filtering
authored Apr 14, 2011
233 {
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
234 uint8_t slave = req[_MODBUS_RTU_SLAVE];
235 uint8_t function = req[_MODBUS_RTU_FUNCTION];
c347cec @stephane Replace offset variable by constants
authored Apr 13, 2011
236 uint16_t address = (req[_MODBUS_RTU_FUNCTION + 1] << 8) +
237 req[_MODBUS_RTU_FUNCTION + 2];
6c7c574 @stephane Avoid code duplication in reply() to save 42 bytes in binary sketch
authored Apr 14, 2011
238 uint16_t nb = (req[_MODBUS_RTU_FUNCTION + 3] << 8) +
239 req[_MODBUS_RTU_FUNCTION + 4];
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
240 uint8_t rsp[_MODBUSINO_RTU_MAX_ADU_LENGTH];
250f0ee @stephane Replace all int by uint8_t or uint16_t
authored Apr 14, 2011
241 uint8_t rsp_length = 0;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
242
10a2311 @stephane Broadcast address support
authored Apr 22, 2011
243 if (slave != _slave &&
244 slave != MODBUS_BROADCAST_ADDRESS) {
8aa80d7 @stephane Fix void return
authored Apr 14, 2011
245 return;
8f5a4b5 @stephane Slave filtering
authored Apr 13, 2011
246 }
247
6c7c574 @stephane Avoid code duplication in reply() to save 42 bytes in binary sketch
authored Apr 14, 2011
248 if (address + nb > nb_reg) {
249 rsp_length = response_exception(
250 slave, function,
251 MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp);
c37f3cf @GregRadion Change a couple of 8-bit data types to 16-bit
GregRadion authored Sep 11, 2012
252 } else {
14441bf @stephane Reindent code after new else introduced by previous commit
authored Sep 13, 2012
253 req_length -= _MODBUS_RTU_CHECKSUM_LENGTH;
254
255 if (function == _FC_READ_HOLDING_REGISTERS) {
4b4fd3a @stephane Fix another integer size to store address (16 bits)
authored Sep 13, 2012
256 uint16_t i;
257
14441bf @stephane Reindent code after new else introduced by previous commit
authored Sep 13, 2012
258 rsp_length = build_response_basis(slave, function, rsp);
259 rsp[rsp_length++] = nb << 1;
260 for (i = address; i < address + nb; i++) {
261 rsp[rsp_length++] = tab_reg[i] >> 8;
262 rsp[rsp_length++] = tab_reg[i] & 0xFF;
263 }
264 } else {
4b4fd3a @stephane Fix another integer size to store address (16 bits)
authored Sep 13, 2012
265 uint16_t i, j;
6c7c574 @stephane Avoid code duplication in reply() to save 42 bytes in binary sketch
authored Apr 14, 2011
266
14441bf @stephane Reindent code after new else introduced by previous commit
authored Sep 13, 2012
267 for (i = address, j = 6; i < address + nb; i++, j += 2) {
268 /* 6 and 7 = first value */
269 tab_reg[i] = (req[_MODBUS_RTU_FUNCTION + j] << 8) +
270 req[_MODBUS_RTU_FUNCTION + j + 1];
271 }
6c7c574 @stephane Avoid code duplication in reply() to save 42 bytes in binary sketch
authored Apr 14, 2011
272
14441bf @stephane Reindent code after new else introduced by previous commit
authored Sep 13, 2012
273 rsp_length = build_response_basis(slave, function, rsp);
274 /* 4 to copy the address (2) and the no. of registers */
275 memcpy(rsp + rsp_length, req + rsp_length, 4);
276 rsp_length += 4;
277 }
e3fdcdf @stephane Initial import
authored Apr 10, 2011
278 }
279
8aa80d7 @stephane Fix void return
authored Apr 14, 2011
280 send_msg(rsp, rsp_length);
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
281 }
282
c37f3cf @GregRadion Change a couple of 8-bit data types to 16-bit
GregRadion authored Sep 11, 2012
283 int ModbusinoSlave::loop(uint16_t* tab_reg, uint16_t nb_reg)
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
284 {
63dc407 @stephane Avoid undefined rc in loop (closes #1)
authored Sep 16, 2011
285 int rc = 0;
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
286 uint8_t req[_MODBUSINO_RTU_MAX_ADU_LENGTH];
287
288 if (Serial.available()) {
d2f64cb @stephane Fix don't raise an exception if not for me and better flushing
authored Apr 14, 2011
289 rc = receive(req, _slave);
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
290 if (rc > 0) {
8f5a4b5 @stephane Slave filtering
authored Apr 13, 2011
291 reply(tab_reg, nb_reg, req, rc, _slave);
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
292 }
293 }
294
8f5a4b5 @stephane Slave filtering
authored Apr 13, 2011
295 /* Returns a positive value if successful,
296 0 if a slave filtering has occured,
297 -1 if an undefined error has occured,
298 -2 for MODBUS_EXCEPTION_ILLEGAL_FUNCTION
299 etc */
ff14e8d @stephane Better integration in the Arduino loop and very low footprint
authored Apr 13, 2011
300 return rc;
e3fdcdf @stephane Initial import
authored Apr 10, 2011
301 }
Something went wrong with that request. Please try again.