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