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 <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  *
381  * @retval           0 If transfer was successful
382  */
383 typedef int (*modbus_raw_cb_t)(const int iface, const struct modbus_adu *adu);
384 
385 /**
386  * @brief Modbus interface mode
387  */
388 enum modbus_mode {
389 	/** Modbus over serial line RTU mode */
390 	MODBUS_MODE_RTU,
391 	/** Modbus over serial line ASCII mode */
392 	MODBUS_MODE_ASCII,
393 	/** Modbus raw ADU mode */
394 	MODBUS_MODE_RAW,
395 };
396 
397 /**
398  * @brief Modbus serial line parameter
399  */
400 struct modbus_serial_param {
401 	/** Baudrate of the serial line */
402 	uint32_t baud;
403 	/** parity UART's parity setting:
404 	 *    UART_CFG_PARITY_NONE,
405 	 *    UART_CFG_PARITY_EVEN,
406 	 *    UART_CFG_PARITY_ODD
407 	 */
408 	enum uart_config_parity parity;
409 };
410 
411 /**
412  * @brief Modbus server parameter
413  */
414 struct modbus_server_param {
415 	/** Pointer to the User Callback structure */
416 	struct modbus_user_callbacks *user_cb;
417 	/** Modbus unit ID of the server */
418 	uint8_t unit_id;
419 };
420 
421 /**
422  * @brief User parameter structure to configure Modbus interfase
423  *        as client or server.
424  */
425 struct modbus_iface_param {
426 	/** Mode of the interface */
427 	enum modbus_mode mode;
428 	union {
429 		struct modbus_server_param server;
430 		/** Amount of time client will wait for
431 		 *  a response from the server.
432 		 */
433 		uint32_t rx_timeout;
434 	};
435 	union {
436 		/** Serial support parameter of the interface */
437 		struct modbus_serial_param serial;
438 		/** Pointer to raw ADU callback function */
439 		modbus_raw_cb_t raw_tx_cb;
440 	};
441 };
442 
443 /**
444  * @brief Configure Modbus Interface as raw ADU server
445  *
446  * @param iface      Modbus RTU interface index
447  * @param param      Configuration parameter of the server interface
448  *
449  * @retval           0 If the function was successful
450  */
451 int modbus_init_server(const int iface, struct modbus_iface_param param);
452 
453 /**
454  * @brief Configure Modbus Interface as raw ADU client
455  *
456  * @param iface      Modbus RTU interface index
457  * @param param      Configuration parameter of the client interface
458  *
459  * @retval           0 If the function was successful
460  */
461 int modbus_init_client(const int iface, struct modbus_iface_param param);
462 
463 /**
464  * @brief Disable Modbus Interface
465  *
466  * This function is called to disable Modbus interface.
467  *
468  * @param iface      Modbus interface index
469  *
470  * @retval           0 If the function was successful
471  */
472 int modbus_disable(const uint8_t iface);
473 
474 /**
475  * @brief Submit raw ADU
476  *
477  * @param iface      Modbus RTU interface index
478  * @param adu        Pointer to the RAW ADU struct that is received
479  *
480  * @retval           0 If transfer was successful
481  */
482 int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu);
483 
484 /**
485  * @brief Put MBAP header into a buffer
486  *
487  * @param adu        Pointer to the RAW ADU struct
488  * @param header     Pointer to the buffer in which MBAP header
489  *                   will be placed.
490  *
491  * @retval           0 If transfer was successful
492  */
493 void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header);
494 
495 /**
496  * @brief Get MBAP header from a buffer
497  *
498  * @param adu        Pointer to the RAW ADU struct
499  * @param header     Pointer to the buffer containing MBAP header
500  *
501  * @retval           0 If transfer was successful
502  */
503 void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header);
504 
505 /**
506  * @brief Set Server Device Failure exception
507  *
508  * This function modifies ADU passed by the pointer.
509  *
510  * @param adu        Pointer to the RAW ADU struct
511  */
512 void modbus_raw_set_server_failure(struct modbus_adu *adu);
513 
514 /**
515  * @brief Use interface as backend to send and receive ADU
516  *
517  * This function overwrites ADU passed by the pointer and generates
518  * exception responses if backend interface is misconfigured or
519  * target device is unreachable.
520  *
521  * @param iface      Modbus client interface index
522  * @param adu        Pointer to the RAW ADU struct
523  *
524  * @retval           0 If transfer was successful
525  */
526 int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu);
527 
528 #ifdef __cplusplus
529 }
530 #endif
531 
532 /**
533  * @}
534  */
535 
536 #endif /* ZEPHYR_INCLUDE_MODBUS_H_ */
537