1 /*
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 
35 #ifdef __cplusplus
36 extern "C" {
37 #endif
38 
39 /** Length of MBAP Header */
40 #define MODBUS_MBAP_LENGTH		7
41 /** Length of MBAP Header plus function code */
42 #define MODBUS_MBAP_AND_FC_LENGTH	(MODBUS_MBAP_LENGTH + 1)
43 
44 /**
45  * @brief Frame struct used internally and for raw ADU support.
46  */
47 struct modbus_adu {
48 	/** Transaction Identifier */
49 	uint16_t trans_id;
50 	/** Protocol Identifier */
51 	uint16_t proto_id;
52 	/** Length of the data only (not the length of unit ID + PDU) */
53 	uint16_t length;
54 	/** Unit Identifier */
55 	uint8_t unit_id;
56 	/** Function Code */
57 	uint8_t fc;
58 	/** Transaction Data */
59 	uint8_t data[CONFIG_MODBUS_BUFFER_SIZE - 4];
60 	/** RTU CRC */
61 	uint16_t crc;
62 };
63 
64 /**
65  * @brief Coil read (FC01)
66  *
67  * Sends a Modbus message to read the status of coils from a server.
68  *
69  * @param iface      Modbus interface index
70  * @param unit_id    Modbus unit ID of the server
71  * @param start_addr Coil starting address
72  * @param coil_tbl   Pointer to an array of bytes containing the value
73  *                   of the coils read.
74  *                   The format is:
75  *
76  *                                       MSB                               LSB
77  *                                       B7   B6   B5   B4   B3   B2   B1   B0
78  *                                       -------------------------------------
79  *                       coil_tbl[0]     #8   #7                            #1
80  *                       coil_tbl[1]     #16  #15                           #9
81  *                            :
82  *                            :
83  *
84  *                   Note that the array that will be receiving the coil
85  *                   values must be greater than or equal to:
86  *                   (num_coils - 1) / 8 + 1
87  * @param num_coils  Quantity of coils to read
88  *
89  * @retval           0 If the function was successful
90  */
91 int modbus_read_coils(const int iface,
92 		      const uint8_t unit_id,
93 		      const uint16_t start_addr,
94 		      uint8_t *const coil_tbl,
95 		      const uint16_t num_coils);
96 
97 /**
98  * @brief Read discrete inputs (FC02)
99  *
100  * Sends a Modbus message to read the status of discrete inputs from
101  * a server.
102  *
103  * @param iface      Modbus interface index
104  * @param unit_id    Modbus unit ID of the server
105  * @param start_addr Discrete input starting address
106  * @param di_tbl     Pointer to an array that will receive the state
107  *                   of the discrete inputs.
108  *                   The format of the array is as follows:
109  *
110  *                                     MSB                               LSB
111  *                                     B7   B6   B5   B4   B3   B2   B1   B0
112  *                                     -------------------------------------
113  *                       di_tbl[0]     #8   #7                            #1
114  *                       di_tbl[1]     #16  #15                           #9
115  *                            :
116  *                            :
117  *
118  *                   Note that the array that will be receiving the discrete
119  *                   input values must be greater than or equal to:
120  *                        (num_di - 1) / 8 + 1
121  * @param num_di     Quantity of discrete inputs to read
122  *
123  * @retval           0 If the function was successful
124  */
125 int modbus_read_dinputs(const int iface,
126 			const uint8_t unit_id,
127 			const uint16_t start_addr,
128 			uint8_t *const di_tbl,
129 			const uint16_t num_di);
130 
131 /**
132  * @brief Read holding registers (FC03)
133  *
134  * Sends a Modbus message to read the value of holding registers
135  * from a server.
136  *
137  * @param iface      Modbus interface index
138  * @param unit_id    Modbus unit ID of the server
139  * @param start_addr Register starting address
140  * @param reg_buf    Is a pointer to an array that will receive
141  *                   the current values of the holding registers from
142  *                   the server.  The array pointed to by 'reg_buf' needs
143  *                   to be able to hold at least 'num_regs' entries.
144  * @param num_regs   Quantity of registers to read
145  *
146  * @retval           0 If the function was successful
147  */
148 int modbus_read_holding_regs(const int iface,
149 			     const uint8_t unit_id,
150 			     const uint16_t start_addr,
151 			     uint16_t *const reg_buf,
152 			     const uint16_t num_regs);
153 
154 /**
155  * @brief Read input registers (FC04)
156  *
157  * Sends a Modbus message to read the value of input registers from
158  * a server.
159  *
160  * @param iface      Modbus interface index
161  * @param unit_id    Modbus unit ID of the server
162  * @param start_addr Register starting address
163  * @param reg_buf    Is a pointer to an array that will receive
164  *                   the current value of the holding registers
165  *                   from the server.  The array pointed to by 'reg_buf'
166  *                   needs to be able to hold at least 'num_regs' entries.
167  * @param num_regs   Quantity of registers to read
168  *
169  * @retval           0 If the function was successful
170  */
171 int modbus_read_input_regs(const int iface,
172 			   const uint8_t unit_id,
173 			   const uint16_t start_addr,
174 			   uint16_t *const reg_buf,
175 			   const uint16_t num_regs);
176 
177 /**
178  * @brief Write single coil (FC05)
179  *
180  * Sends a Modbus message to write the value of single coil to a server.
181  *
182  * @param iface      Modbus interface index
183  * @param unit_id    Modbus unit ID of the server
184  * @param coil_addr  Coils starting address
185  * @param coil_state Is the desired state of the coil
186  *
187  * @retval           0 If the function was successful
188  */
189 int modbus_write_coil(const int iface,
190 		      const uint8_t unit_id,
191 		      const uint16_t coil_addr,
192 		      const bool coil_state);
193 
194 /**
195  * @brief Write single holding register (FC06)
196  *
197  * Sends a Modbus message to write the value of single holding register
198  * to a server unit.
199  *
200  * @param iface      Modbus interface index
201  * @param unit_id    Modbus unit ID of the server
202  * @param start_addr Coils starting address
203  * @param reg_val    Desired value of the holding register
204  *
205  * @retval           0 If the function was successful
206  */
207 int modbus_write_holding_reg(const int iface,
208 			     const uint8_t unit_id,
209 			     const uint16_t start_addr,
210 			     const uint16_t reg_val);
211 
212 /**
213  * @brief Read diagnostic (FC08)
214  *
215  * Sends a Modbus message to perform a diagnostic function of a server unit.
216  *
217  * @param iface      Modbus interface index
218  * @param unit_id    Modbus unit ID of the server
219  * @param sfunc      Diagnostic sub-function code
220  * @param data       Sub-function data
221  * @param data_out   Pointer to the data value
222  *
223  * @retval           0 If the function was successful
224  */
225 int modbus_request_diagnostic(const int iface,
226 			      const uint8_t unit_id,
227 			      const uint16_t sfunc,
228 			      const uint16_t data,
229 			      uint16_t *const data_out);
230 
231 /**
232  * @brief Write coils (FC15)
233  *
234  * Sends a Modbus message to write to coils on a server unit.
235  *
236  * @param iface      Modbus interface index
237  * @param unit_id    Modbus unit ID of the server
238  * @param start_addr Coils starting address
239  * @param coil_tbl   Pointer to an array of bytes containing the value
240  *                   of the coils to write.
241  *                   The format is:
242  *
243  *                                       MSB                               LSB
244  *                                       B7   B6   B5   B4   B3   B2   B1   B0
245  *                                       -------------------------------------
246  *                       coil_tbl[0]     #8   #7                            #1
247  *                       coil_tbl[1]     #16  #15                           #9
248  *                            :
249  *                            :
250  *
251  *                   Note that the array that will be receiving the coil
252  *                   values must be greater than or equal to:
253  *                   (num_coils - 1) / 8 + 1
254  * @param num_coils  Quantity of coils to write
255  *
256  * @retval           0 If the function was successful
257  */
258 int modbus_write_coils(const int iface,
259 		       const uint8_t unit_id,
260 		       const uint16_t start_addr,
261 		       uint8_t *const coil_tbl,
262 		       const uint16_t num_coils);
263 
264 /**
265  * @brief Write holding registers (FC16)
266  *
267  * Sends a Modbus message to write to integer holding registers
268  * to a server unit.
269  *
270  * @param iface      Modbus interface index
271  * @param unit_id    Modbus unit ID of the server
272  * @param start_addr Register starting address
273  * @param reg_buf    Is a pointer to an array containing
274  *                   the value of the holding registers to write.
275  *                   Note that the array containing the register values must
276  *                   be greater than or equal to 'num_regs'
277  * @param num_regs   Quantity of registers to write
278  *
279  * @retval           0 If the function was successful
280  */
281 int modbus_write_holding_regs(const int iface,
282 			      const uint8_t unit_id,
283 			      const uint16_t start_addr,
284 			      uint16_t *const reg_buf,
285 			      const uint16_t num_regs);
286 
287 /**
288  * @brief Read floating-point holding registers (FC03)
289  *
290  * Sends a Modbus message to read the value of floating-point
291  * holding registers from a server unit.
292  *
293  * @param iface      Modbus interface index
294  * @param unit_id    Modbus unit ID of the server
295  * @param start_addr Register starting address
296  * @param reg_buf    Is a pointer to an array that will receive
297  *                   the current values of the holding registers from
298  *                   the server.  The array pointed to by 'reg_buf' needs
299  *                   to be able to hold at least 'num_regs' entries.
300  * @param num_regs   Quantity of registers to read
301  *
302  * @retval           0 If the function was successful
303  */
304 int modbus_read_holding_regs_fp(const int iface,
305 				const uint8_t unit_id,
306 				const uint16_t start_addr,
307 				float *const reg_buf,
308 				const uint16_t num_regs);
309 
310 /**
311  * @brief Write floating-point holding registers (FC16)
312  *
313  * Sends a Modbus message to write to floating-point holding registers
314  * to a server unit.
315  *
316  * @param iface      Modbus interface index
317  * @param unit_id    Modbus unit ID of the server
318  * @param start_addr Register starting address
319  * @param reg_buf    Is a pointer to an array containing
320  *                   the value of the holding registers to write.
321  *                   Note that the array containing the register values must
322  *                   be greater than or equal to 'num_regs'
323  * @param num_regs   Quantity of registers to write
324  *
325  * @retval           0 If the function was successful
326  */
327 int modbus_write_holding_regs_fp(const int iface,
328 				 const uint8_t unit_id,
329 				 const uint16_t start_addr,
330 				 float *const reg_buf,
331 				 const uint16_t num_regs);
332 
333 /** Modbus Server User Callback structure */
334 struct modbus_user_callbacks {
335 	/** Coil read callback */
336 	int (*coil_rd)(uint16_t addr, bool *state);
337 
338 	/** Coil write callback */
339 	int (*coil_wr)(uint16_t addr, bool state);
340 
341 	/** Discrete Input read callback */
342 	int (*discrete_input_rd)(uint16_t addr, bool *state);
343 
344 	/** Input Register read callback */
345 	int (*input_reg_rd)(uint16_t addr, uint16_t *reg);
346 
347 	/** Floating Point Input Register read callback */
348 	int (*input_reg_rd_fp)(uint16_t addr, float *reg);
349 
350 	/** Holding Register read callback */
351 	int (*holding_reg_rd)(uint16_t addr, uint16_t *reg);
352 
353 	/** Holding Register write callback */
354 	int (*holding_reg_wr)(uint16_t addr, uint16_t reg);
355 
356 	/** Floating Point Holding Register read callback */
357 	int (*holding_reg_rd_fp)(uint16_t addr, float *reg);
358 
359 	/** Floating Point Holding Register write callback */
360 	int (*holding_reg_wr_fp)(uint16_t addr, float reg);
361 };
362 
363 /**
364  * @brief Get Modbus interface index according to interface name
365  *
366  * If there is more than one interface, it can be used to clearly
367  * identify interfaces in the application.
368  *
369  * @param iface_name Modbus interface name
370  *
371  * @retval           Modbus interface index or negative error value.
372  */
373 int modbus_iface_get_by_name(const char *iface_name);
374 
375 /**
376  * @brief ADU raw callback function signature
377  *
378  * @param iface      Modbus RTU interface index
379  * @param adu        Pointer to the RAW ADU struct to send
380  * @param user_data  Pointer to the user data
381  *
382  * @retval           0 If transfer was successful
383  */
384 typedef int (*modbus_raw_cb_t)(const int iface, const struct modbus_adu *adu,
385 				void *user_data);
386 
387 /**
388  * @brief Modbus interface mode
389  */
390 enum modbus_mode {
391 	/** Modbus over serial line RTU mode */
392 	MODBUS_MODE_RTU,
393 	/** Modbus over serial line ASCII mode */
394 	MODBUS_MODE_ASCII,
395 	/** Modbus raw ADU mode */
396 	MODBUS_MODE_RAW,
397 };
398 
399 /**
400  * @brief Modbus serial line parameter
401  */
402 struct modbus_serial_param {
403 	/** Baudrate of the serial line */
404 	uint32_t baud;
405 	/** parity UART's parity setting:
406 	 *    UART_CFG_PARITY_NONE,
407 	 *    UART_CFG_PARITY_EVEN,
408 	 *    UART_CFG_PARITY_ODD
409 	 */
410 	enum uart_config_parity parity;
411 	/** stop_bits_client UART's stop bits setting if in client mode:
412 	 *    UART_CFG_STOP_BITS_0_5,
413 	 *    UART_CFG_STOP_BITS_1,
414 	 *    UART_CFG_STOP_BITS_1_5,
415 	 *    UART_CFG_STOP_BITS_2,
416 	 */
417 	enum uart_config_stop_bits stop_bits_client;
418 };
419 
420 /**
421  * @brief Modbus server parameter
422  */
423 struct modbus_server_param {
424 	/** Pointer to the User Callback structure */
425 	struct modbus_user_callbacks *user_cb;
426 	/** Modbus unit ID of the server */
427 	uint8_t unit_id;
428 };
429 
430 struct modbus_raw_cb {
431 	modbus_raw_cb_t raw_tx_cb;
432 	void *user_data;
433 };
434 
435 /**
436  * @brief User parameter structure to configure Modbus interface
437  *        as client or server.
438  */
439 struct modbus_iface_param {
440 	/** Mode of the interface */
441 	enum modbus_mode mode;
442 	union {
443 		struct modbus_server_param server;
444 		/** Amount of time client will wait for
445 		 *  a response from the server.
446 		 */
447 		uint32_t rx_timeout;
448 	};
449 	union {
450 		/** Serial support parameter of the interface */
451 		struct modbus_serial_param serial;
452 		/** Pointer to raw ADU callback function */
453 		struct modbus_raw_cb rawcb;
454 	};
455 };
456 
457 /**
458  * @brief Configure Modbus Interface as raw ADU server
459  *
460  * @param iface      Modbus RTU interface index
461  * @param param      Configuration parameter of the server interface
462  *
463  * @retval           0 If the function was successful
464  */
465 int modbus_init_server(const int iface, struct modbus_iface_param param);
466 
467 /**
468  * @brief Configure Modbus Interface as raw ADU client
469  *
470  * @param iface      Modbus RTU interface index
471  * @param param      Configuration parameter of the client interface
472  *
473  * @retval           0 If the function was successful
474  */
475 int modbus_init_client(const int iface, struct modbus_iface_param param);
476 
477 /**
478  * @brief Disable Modbus Interface
479  *
480  * This function is called to disable Modbus interface.
481  *
482  * @param iface      Modbus interface index
483  *
484  * @retval           0 If the function was successful
485  */
486 int modbus_disable(const uint8_t iface);
487 
488 /**
489  * @brief Submit raw ADU
490  *
491  * @param iface      Modbus RTU interface index
492  * @param adu        Pointer to the RAW ADU struct that is received
493  *
494  * @retval           0 If transfer was successful
495  */
496 int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu);
497 
498 /**
499  * @brief Put MBAP header into a buffer
500  *
501  * @param adu        Pointer to the RAW ADU struct
502  * @param header     Pointer to the buffer in which MBAP header
503  *                   will be placed.
504  */
505 void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header);
506 
507 /**
508  * @brief Get MBAP header from a buffer
509  *
510  * @param adu        Pointer to the RAW ADU struct
511  * @param header     Pointer to the buffer containing MBAP header
512  */
513 void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header);
514 
515 /**
516  * @brief Set Server Device Failure exception
517  *
518  * This function modifies ADU passed by the pointer.
519  *
520  * @param adu        Pointer to the RAW ADU struct
521  */
522 void modbus_raw_set_server_failure(struct modbus_adu *adu);
523 
524 /**
525  * @brief Use interface as backend to send and receive ADU
526  *
527  * This function overwrites ADU passed by the pointer and generates
528  * exception responses if backend interface is misconfigured or
529  * target device is unreachable.
530  *
531  * @param iface      Modbus client interface index
532  * @param adu        Pointer to the RAW ADU struct
533  *
534  * @retval           0 If transfer was successful
535  */
536 int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu);
537 
538 #ifdef __cplusplus
539 }
540 #endif
541 
542 /**
543  * @}
544  */
545 
546 #endif /* ZEPHYR_INCLUDE_MODBUS_H_ */
547