Line data Source code
1 0 : /*
2 : * Copyright (c) 2020 PHYTEC Messtechnik GmbH
3 : * Copyright (c) 2021 Nordic Semiconductor ASA
4 : *
5 : * SPDX-License-Identifier: Apache-2.0
6 : */
7 :
8 : /*
9 : * Client API in this file is based on mbm_core.c from uC/Modbus Stack.
10 : *
11 : * uC/Modbus
12 : * The Embedded Modbus Stack
13 : *
14 : * Copyright 2003-2020 Silicon Laboratories Inc. www.silabs.com
15 : *
16 : * SPDX-License-Identifier: APACHE-2.0
17 : *
18 : * This software is subject to an open source license and is distributed by
19 : * Silicon Laboratories Inc. pursuant to the terms of the Apache License,
20 : * Version 2.0 available at www.apache.org/licenses/LICENSE-2.0.
21 : */
22 :
23 : /**
24 : * @brief MODBUS transport protocol API
25 : * @defgroup modbus MODBUS
26 : * @ingroup io_interfaces
27 : * @{
28 : */
29 :
30 : #ifndef ZEPHYR_INCLUDE_MODBUS_H_
31 : #define ZEPHYR_INCLUDE_MODBUS_H_
32 :
33 : #include <zephyr/drivers/uart.h>
34 : #include <zephyr/sys/slist.h>
35 : #ifdef __cplusplus
36 : extern "C" {
37 : #endif
38 :
39 : /** Length of MBAP Header */
40 1 : #define MODBUS_MBAP_LENGTH 7
41 : /** Length of MBAP Header plus function code */
42 1 : #define MODBUS_MBAP_AND_FC_LENGTH (MODBUS_MBAP_LENGTH + 1)
43 :
44 : /** @name Modbus exception codes
45 : * @{
46 : */
47 : /** No exception */
48 1 : #define MODBUS_EXC_NONE 0
49 : /** Illegal function code */
50 1 : #define MODBUS_EXC_ILLEGAL_FC 1
51 : /** Illegal data address */
52 1 : #define MODBUS_EXC_ILLEGAL_DATA_ADDR 2
53 : /** Illegal data value */
54 1 : #define MODBUS_EXC_ILLEGAL_DATA_VAL 3
55 : /** Server device failure */
56 1 : #define MODBUS_EXC_SERVER_DEVICE_FAILURE 4
57 : /** Acknowledge */
58 1 : #define MODBUS_EXC_ACK 5
59 : /** Server device busy */
60 1 : #define MODBUS_EXC_SERVER_DEVICE_BUSY 6
61 : /** Memory parity error */
62 1 : #define MODBUS_EXC_MEM_PARITY_ERROR 8
63 : /** Gateway path unavailable */
64 1 : #define MODBUS_EXC_GW_PATH_UNAVAILABLE 10
65 : /** Gateway target device failed to respond */
66 1 : #define MODBUS_EXC_GW_TARGET_FAILED_TO_RESP 11
67 : /** @} */
68 :
69 : /**
70 : * @brief Frame struct used internally and for raw ADU support.
71 : */
72 1 : struct modbus_adu {
73 : /** Transaction Identifier */
74 1 : uint16_t trans_id;
75 : /** Protocol Identifier */
76 1 : uint16_t proto_id;
77 : /** Length of the data only (not the length of unit ID + PDU) */
78 1 : uint16_t length;
79 : /** Unit Identifier */
80 1 : uint8_t unit_id;
81 : /** Function Code */
82 1 : uint8_t fc;
83 : /** Transaction Data */
84 1 : uint8_t data[CONFIG_MODBUS_BUFFER_SIZE - 4];
85 : /** RTU CRC */
86 1 : uint16_t crc;
87 : };
88 :
89 : /**
90 : * @brief Coil read (FC01)
91 : *
92 : * Sends a Modbus message to read the status of coils from a server.
93 : *
94 : * @param iface Modbus interface index
95 : * @param unit_id Modbus unit ID of the server
96 : * @param start_addr Coil starting address
97 : * @param coil_tbl Pointer to an array of bytes containing the value
98 : * of the coils read.
99 : * The format is:
100 : *
101 : * MSB LSB
102 : * B7 B6 B5 B4 B3 B2 B1 B0
103 : * -------------------------------------
104 : * coil_tbl[0] #8 #7 #1
105 : * coil_tbl[1] #16 #15 #9
106 : * :
107 : * :
108 : *
109 : * Note that the array that will be receiving the coil
110 : * values must be greater than or equal to:
111 : * (num_coils - 1) / 8 + 1
112 : * @param num_coils Quantity of coils to read
113 : *
114 : * @retval 0 If the function was successful
115 : */
116 1 : int modbus_read_coils(const int iface,
117 : const uint8_t unit_id,
118 : const uint16_t start_addr,
119 : uint8_t *const coil_tbl,
120 : const uint16_t num_coils);
121 :
122 : /**
123 : * @brief Read discrete inputs (FC02)
124 : *
125 : * Sends a Modbus message to read the status of discrete inputs from
126 : * a server.
127 : *
128 : * @param iface Modbus interface index
129 : * @param unit_id Modbus unit ID of the server
130 : * @param start_addr Discrete input starting address
131 : * @param di_tbl Pointer to an array that will receive the state
132 : * of the discrete inputs.
133 : * The format of the array is as follows:
134 : *
135 : * MSB LSB
136 : * B7 B6 B5 B4 B3 B2 B1 B0
137 : * -------------------------------------
138 : * di_tbl[0] #8 #7 #1
139 : * di_tbl[1] #16 #15 #9
140 : * :
141 : * :
142 : *
143 : * Note that the array that will be receiving the discrete
144 : * input values must be greater than or equal to:
145 : * (num_di - 1) / 8 + 1
146 : * @param num_di Quantity of discrete inputs to read
147 : *
148 : * @retval 0 If the function was successful
149 : */
150 1 : int modbus_read_dinputs(const int iface,
151 : const uint8_t unit_id,
152 : const uint16_t start_addr,
153 : uint8_t *const di_tbl,
154 : const uint16_t num_di);
155 :
156 : /**
157 : * @brief Read holding registers (FC03)
158 : *
159 : * Sends a Modbus message to read the value of holding registers
160 : * from a server.
161 : *
162 : * @param iface Modbus interface index
163 : * @param unit_id Modbus unit ID of the server
164 : * @param start_addr Register starting address
165 : * @param reg_buf Is a pointer to an array that will receive
166 : * the current values of the holding registers from
167 : * the server. The array pointed to by 'reg_buf' needs
168 : * to be able to hold at least 'num_regs' entries.
169 : * @param num_regs Quantity of registers to read
170 : *
171 : * @retval 0 If the function was successful
172 : */
173 1 : int modbus_read_holding_regs(const int iface,
174 : const uint8_t unit_id,
175 : const uint16_t start_addr,
176 : uint16_t *const reg_buf,
177 : const uint16_t num_regs);
178 :
179 : /**
180 : * @brief Read input registers (FC04)
181 : *
182 : * Sends a Modbus message to read the value of input registers from
183 : * a server.
184 : *
185 : * @param iface Modbus interface index
186 : * @param unit_id Modbus unit ID of the server
187 : * @param start_addr Register starting address
188 : * @param reg_buf Is a pointer to an array that will receive
189 : * the current value of the holding registers
190 : * from the server. The array pointed to by 'reg_buf'
191 : * needs to be able to hold at least 'num_regs' entries.
192 : * @param num_regs Quantity of registers to read
193 : *
194 : * @retval 0 If the function was successful
195 : */
196 1 : int modbus_read_input_regs(const int iface,
197 : const uint8_t unit_id,
198 : const uint16_t start_addr,
199 : uint16_t *const reg_buf,
200 : const uint16_t num_regs);
201 :
202 : /**
203 : * @brief Write single coil (FC05)
204 : *
205 : * Sends a Modbus message to write the value of single coil to a server.
206 : *
207 : * @param iface Modbus interface index
208 : * @param unit_id Modbus unit ID of the server
209 : * @param coil_addr Coils starting address
210 : * @param coil_state Is the desired state of the coil
211 : *
212 : * @retval 0 If the function was successful
213 : */
214 1 : int modbus_write_coil(const int iface,
215 : const uint8_t unit_id,
216 : const uint16_t coil_addr,
217 : const bool coil_state);
218 :
219 : /**
220 : * @brief Write single holding register (FC06)
221 : *
222 : * Sends a Modbus message to write the value of single holding register
223 : * to a server unit.
224 : *
225 : * @param iface Modbus interface index
226 : * @param unit_id Modbus unit ID of the server
227 : * @param start_addr Coils starting address
228 : * @param reg_val Desired value of the holding register
229 : *
230 : * @retval 0 If the function was successful
231 : */
232 1 : int modbus_write_holding_reg(const int iface,
233 : const uint8_t unit_id,
234 : const uint16_t start_addr,
235 : const uint16_t reg_val);
236 :
237 : /**
238 : * @brief Read diagnostic (FC08)
239 : *
240 : * Sends a Modbus message to perform a diagnostic function of a server unit.
241 : *
242 : * @param iface Modbus interface index
243 : * @param unit_id Modbus unit ID of the server
244 : * @param sfunc Diagnostic sub-function code
245 : * @param data Sub-function data
246 : * @param data_out Pointer to the data value
247 : *
248 : * @retval 0 If the function was successful
249 : */
250 1 : int modbus_request_diagnostic(const int iface,
251 : const uint8_t unit_id,
252 : const uint16_t sfunc,
253 : const uint16_t data,
254 : uint16_t *const data_out);
255 :
256 : /**
257 : * @brief Write coils (FC15)
258 : *
259 : * Sends a Modbus message to write to coils on a server unit.
260 : *
261 : * @param iface Modbus interface index
262 : * @param unit_id Modbus unit ID of the server
263 : * @param start_addr Coils starting address
264 : * @param coil_tbl Pointer to an array of bytes containing the value
265 : * of the coils to write.
266 : * The format is:
267 : *
268 : * MSB LSB
269 : * B7 B6 B5 B4 B3 B2 B1 B0
270 : * -------------------------------------
271 : * coil_tbl[0] #8 #7 #1
272 : * coil_tbl[1] #16 #15 #9
273 : * :
274 : * :
275 : *
276 : * Note that the array that will be receiving the coil
277 : * values must be greater than or equal to:
278 : * (num_coils - 1) / 8 + 1
279 : * @param num_coils Quantity of coils to write
280 : *
281 : * @retval 0 If the function was successful
282 : */
283 1 : int modbus_write_coils(const int iface,
284 : const uint8_t unit_id,
285 : const uint16_t start_addr,
286 : uint8_t *const coil_tbl,
287 : const uint16_t num_coils);
288 :
289 : /**
290 : * @brief Write holding registers (FC16)
291 : *
292 : * Sends a Modbus message to write to integer holding registers
293 : * to a server unit.
294 : *
295 : * @param iface Modbus interface index
296 : * @param unit_id Modbus unit ID of the server
297 : * @param start_addr Register starting address
298 : * @param reg_buf Is a pointer to an array containing
299 : * the value of the holding registers to write.
300 : * Note that the array containing the register values must
301 : * be greater than or equal to 'num_regs'
302 : * @param num_regs Quantity of registers to write
303 : *
304 : * @retval 0 If the function was successful
305 : */
306 1 : int modbus_write_holding_regs(const int iface,
307 : const uint8_t unit_id,
308 : const uint16_t start_addr,
309 : uint16_t *const reg_buf,
310 : const uint16_t num_regs);
311 :
312 : /**
313 : * @brief Read floating-point holding registers (FC03)
314 : *
315 : * Sends a Modbus message to read the value of floating-point
316 : * holding registers from a server unit.
317 : *
318 : * @param iface Modbus interface index
319 : * @param unit_id Modbus unit ID of the server
320 : * @param start_addr Register starting address
321 : * @param reg_buf Is a pointer to an array that will receive
322 : * the current values of the holding registers from
323 : * the server. The array pointed to by 'reg_buf' needs
324 : * to be able to hold at least 'num_regs' entries.
325 : * @param num_regs Quantity of registers to read
326 : *
327 : * @retval 0 If the function was successful
328 : */
329 1 : int modbus_read_holding_regs_fp(const int iface,
330 : const uint8_t unit_id,
331 : const uint16_t start_addr,
332 : float *const reg_buf,
333 : const uint16_t num_regs);
334 :
335 : /**
336 : * @brief Write floating-point holding registers (FC16)
337 : *
338 : * Sends a Modbus message to write to floating-point holding registers
339 : * to a server unit.
340 : *
341 : * @param iface Modbus interface index
342 : * @param unit_id Modbus unit ID of the server
343 : * @param start_addr Register starting address
344 : * @param reg_buf Is a pointer to an array containing
345 : * the value of the holding registers to write.
346 : * Note that the array containing the register values must
347 : * be greater than or equal to 'num_regs'
348 : * @param num_regs Quantity of registers to write
349 : *
350 : * @retval 0 If the function was successful
351 : */
352 1 : int modbus_write_holding_regs_fp(const int iface,
353 : const uint8_t unit_id,
354 : const uint16_t start_addr,
355 : float *const reg_buf,
356 : const uint16_t num_regs);
357 :
358 : /** Modbus Server User Callback structure */
359 1 : struct modbus_user_callbacks {
360 : /** Coil read callback */
361 1 : int (*coil_rd)(uint16_t addr, bool *state);
362 :
363 : /** Coil write callback */
364 1 : int (*coil_wr)(uint16_t addr, bool state);
365 :
366 : /** Discrete Input read callback */
367 1 : int (*discrete_input_rd)(uint16_t addr, bool *state);
368 :
369 : /** Input Register read callback */
370 1 : int (*input_reg_rd)(uint16_t addr, uint16_t *reg);
371 :
372 : /** Floating Point Input Register read callback */
373 1 : int (*input_reg_rd_fp)(uint16_t addr, float *reg);
374 :
375 : /** Holding Register read callback */
376 1 : int (*holding_reg_rd)(uint16_t addr, uint16_t *reg);
377 :
378 : /** Holding Register write callback */
379 1 : int (*holding_reg_wr)(uint16_t addr, uint16_t reg);
380 :
381 : /** Floating Point Holding Register read callback */
382 1 : int (*holding_reg_rd_fp)(uint16_t addr, float *reg);
383 :
384 : /** Floating Point Holding Register write callback */
385 1 : int (*holding_reg_wr_fp)(uint16_t addr, float reg);
386 : };
387 :
388 : /**
389 : * @brief Get Modbus interface index according to interface name
390 : *
391 : * If there is more than one interface, it can be used to clearly
392 : * identify interfaces in the application.
393 : *
394 : * @param iface_name Modbus interface name
395 : *
396 : * @retval Modbus interface index or negative error value.
397 : */
398 1 : int modbus_iface_get_by_name(const char *iface_name);
399 :
400 : /**
401 : * @brief ADU raw callback function signature
402 : *
403 : * @param iface Modbus RTU interface index
404 : * @param adu Pointer to the RAW ADU struct to send
405 : * @param user_data Pointer to the user data
406 : *
407 : * @retval 0 If transfer was successful
408 : */
409 1 : typedef int (*modbus_raw_cb_t)(const int iface, const struct modbus_adu *adu,
410 : void *user_data);
411 :
412 : /**
413 : * @brief Custom function code handler function signature.
414 : *
415 : * Modbus allows user defined function codes which can be used to extend
416 : * the base protocol. These callbacks can also be used to implement
417 : * function codes currently not supported by Zephyr's Modbus subsystem.
418 : *
419 : * If an error occurs during the handling of the request, the handler should
420 : * signal this by setting excep_code to a modbus exception code.
421 : *
422 : * User data pointer can be used to pass state between subsequent calls to
423 : * the handler.
424 : *
425 : * @param iface Modbus interface index
426 : * @param rx_adu Pointer to the received ADU struct
427 : * @param tx_adu Pointer to the outgoing ADU struct
428 : * @param excep_code Pointer to possible exception code
429 : * @param user_data Pointer to user data
430 : *
431 : * @retval true If response should be sent, false otherwise
432 : */
433 1 : typedef bool (*modbus_custom_cb_t)(const int iface,
434 : const struct modbus_adu *const rx_adu,
435 : struct modbus_adu *const tx_adu,
436 : uint8_t *const excep_code,
437 : void *const user_data);
438 :
439 : /** @cond INTERNAL_HIDDEN */
440 : /**
441 : * @brief Custom function code definition.
442 : */
443 : struct modbus_custom_fc {
444 : sys_snode_t node;
445 : modbus_custom_cb_t cb;
446 : void *user_data;
447 : uint8_t fc;
448 : uint8_t excep_code;
449 : };
450 : /** @endcond INTERNAL_HIDDEN */
451 :
452 : /**
453 : * @brief Helper macro for initializing custom function code structs
454 : */
455 1 : #define MODBUS_CUSTOM_FC_DEFINE(name, user_cb, user_fc, userdata) \
456 : static struct modbus_custom_fc modbus_cfg_##name = { \
457 : .cb = user_cb, \
458 : .user_data = userdata, \
459 : .fc = user_fc, \
460 : .excep_code = MODBUS_EXC_NONE, \
461 : }
462 :
463 : /**
464 : * @brief Modbus interface mode
465 : */
466 1 : enum modbus_mode {
467 : /** Modbus over serial line RTU mode */
468 : MODBUS_MODE_RTU,
469 : /** Modbus over serial line ASCII mode */
470 : MODBUS_MODE_ASCII,
471 : /** Modbus raw ADU mode */
472 : MODBUS_MODE_RAW,
473 : };
474 :
475 : /**
476 : * @brief Modbus serial line parameter
477 : */
478 1 : struct modbus_serial_param {
479 : /** Baudrate of the serial line */
480 1 : uint32_t baud;
481 : /** parity UART's parity setting:
482 : * UART_CFG_PARITY_NONE,
483 : * UART_CFG_PARITY_EVEN,
484 : * UART_CFG_PARITY_ODD
485 : */
486 1 : enum uart_config_parity parity;
487 : /** stop_bits_client UART's stop bits setting if in client mode:
488 : * UART_CFG_STOP_BITS_0_5,
489 : * UART_CFG_STOP_BITS_1,
490 : * UART_CFG_STOP_BITS_1_5,
491 : * UART_CFG_STOP_BITS_2,
492 : */
493 1 : enum uart_config_stop_bits stop_bits_client;
494 : };
495 :
496 : /**
497 : * @brief Modbus server parameter
498 : */
499 1 : struct modbus_server_param {
500 : /** Pointer to the User Callback structure */
501 1 : struct modbus_user_callbacks *user_cb;
502 : /** Modbus unit ID of the server */
503 1 : uint8_t unit_id;
504 : };
505 :
506 0 : struct modbus_raw_cb {
507 0 : modbus_raw_cb_t raw_tx_cb;
508 0 : void *user_data;
509 : };
510 :
511 : /**
512 : * @brief User parameter structure to configure Modbus interface
513 : * as client or server.
514 : */
515 1 : struct modbus_iface_param {
516 : /** Mode of the interface */
517 1 : enum modbus_mode mode;
518 : union {
519 0 : struct modbus_server_param server;
520 : /** Amount of time client will wait for
521 : * a response from the server.
522 : */
523 1 : uint32_t rx_timeout;
524 0 : };
525 : union {
526 : /** Serial support parameter of the interface */
527 1 : struct modbus_serial_param serial;
528 : /** Pointer to raw ADU callback function */
529 1 : struct modbus_raw_cb rawcb;
530 0 : };
531 : };
532 :
533 : /**
534 : * @brief Configure Modbus Interface as raw ADU server
535 : *
536 : * @param iface Modbus RTU interface index
537 : * @param param Configuration parameter of the server interface
538 : *
539 : * @retval 0 If the function was successful
540 : */
541 1 : int modbus_init_server(const int iface, struct modbus_iface_param param);
542 :
543 : /**
544 : * @brief Configure Modbus Interface as raw ADU client
545 : *
546 : * @param iface Modbus RTU interface index
547 : * @param param Configuration parameter of the client interface
548 : *
549 : * @retval 0 If the function was successful
550 : */
551 1 : int modbus_init_client(const int iface, struct modbus_iface_param param);
552 :
553 : /**
554 : * @brief Disable Modbus Interface
555 : *
556 : * This function is called to disable Modbus interface.
557 : *
558 : * @param iface Modbus interface index
559 : *
560 : * @retval 0 If the function was successful
561 : */
562 1 : int modbus_disable(const uint8_t iface);
563 :
564 : /**
565 : * @brief Submit raw ADU
566 : *
567 : * @param iface Modbus RTU interface index
568 : * @param adu Pointer to the RAW ADU struct that is received
569 : *
570 : * @retval 0 If transfer was successful
571 : */
572 1 : int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu);
573 :
574 : /**
575 : * @brief Put MBAP header into a buffer
576 : *
577 : * @param adu Pointer to the RAW ADU struct
578 : * @param header Pointer to the buffer in which MBAP header
579 : * will be placed.
580 : */
581 1 : void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header);
582 :
583 : /**
584 : * @brief Get MBAP header from a buffer
585 : *
586 : * @param adu Pointer to the RAW ADU struct
587 : * @param header Pointer to the buffer containing MBAP header
588 : */
589 1 : void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header);
590 :
591 : /**
592 : * @brief Set Server Device Failure exception
593 : *
594 : * This function modifies ADU passed by the pointer.
595 : *
596 : * @param adu Pointer to the RAW ADU struct
597 : */
598 1 : void modbus_raw_set_server_failure(struct modbus_adu *adu);
599 :
600 : /**
601 : * @brief Use interface as backend to send and receive ADU
602 : *
603 : * This function overwrites ADU passed by the pointer and generates
604 : * exception responses if backend interface is misconfigured or
605 : * target device is unreachable.
606 : *
607 : * @param iface Modbus client interface index
608 : * @param adu Pointer to the RAW ADU struct
609 : *
610 : * @retval 0 If transfer was successful
611 : */
612 1 : int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu);
613 :
614 : /**
615 : * @brief Register a user-defined function code handler.
616 : *
617 : * The Modbus specification allows users to define standard function codes
618 : * missing from Zephyr's Modbus implementation as well as add non-standard
619 : * function codes in the ranges 65 to 72 and 100 to 110 (decimal), as per
620 : * specification.
621 : *
622 : * This function registers a new handler at runtime for the given
623 : * function code.
624 : *
625 : * @param iface Modbus client interface index
626 : * @param custom_fc User defined function code and callback pair
627 : *
628 : * @retval 0 on success
629 : */
630 1 : int modbus_register_user_fc(const int iface, struct modbus_custom_fc *custom_fc);
631 :
632 : #ifdef __cplusplus
633 : }
634 : #endif
635 :
636 : /**
637 : * @}
638 : */
639 :
640 : #endif /* ZEPHYR_INCLUDE_MODBUS_H_ */
|