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 #include <zephyr/sys/slist.h> 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 /** @name Modbus exception codes 45 * @{ 46 */ 47 /** No exception */ 48 #define MODBUS_EXC_NONE 0 49 /** Illegal function code */ 50 #define MODBUS_EXC_ILLEGAL_FC 1 51 /** Illegal data address */ 52 #define MODBUS_EXC_ILLEGAL_DATA_ADDR 2 53 /** Illegal data value */ 54 #define MODBUS_EXC_ILLEGAL_DATA_VAL 3 55 /** Server device failure */ 56 #define MODBUS_EXC_SERVER_DEVICE_FAILURE 4 57 /** Acknowledge */ 58 #define MODBUS_EXC_ACK 5 59 /** Server device busy */ 60 #define MODBUS_EXC_SERVER_DEVICE_BUSY 6 61 /** Memory parity error */ 62 #define MODBUS_EXC_MEM_PARITY_ERROR 8 63 /** Gateway path unavailable */ 64 #define MODBUS_EXC_GW_PATH_UNAVAILABLE 10 65 /** Gateway target device failed to respond */ 66 #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 struct modbus_adu { 73 /** Transaction Identifier */ 74 uint16_t trans_id; 75 /** Protocol Identifier */ 76 uint16_t proto_id; 77 /** Length of the data only (not the length of unit ID + PDU) */ 78 uint16_t length; 79 /** Unit Identifier */ 80 uint8_t unit_id; 81 /** Function Code */ 82 uint8_t fc; 83 /** Transaction Data */ 84 uint8_t data[CONFIG_MODBUS_BUFFER_SIZE - 4]; 85 /** RTU CRC */ 86 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 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 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 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 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 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 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 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 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 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 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 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 struct modbus_user_callbacks { 360 /** Coil read callback */ 361 int (*coil_rd)(uint16_t addr, bool *state); 362 363 /** Coil write callback */ 364 int (*coil_wr)(uint16_t addr, bool state); 365 366 /** Discrete Input read callback */ 367 int (*discrete_input_rd)(uint16_t addr, bool *state); 368 369 /** Input Register read callback */ 370 int (*input_reg_rd)(uint16_t addr, uint16_t *reg); 371 372 /** Floating Point Input Register read callback */ 373 int (*input_reg_rd_fp)(uint16_t addr, float *reg); 374 375 /** Holding Register read callback */ 376 int (*holding_reg_rd)(uint16_t addr, uint16_t *reg); 377 378 /** Holding Register write callback */ 379 int (*holding_reg_wr)(uint16_t addr, uint16_t reg); 380 381 /** Floating Point Holding Register read callback */ 382 int (*holding_reg_rd_fp)(uint16_t addr, float *reg); 383 384 /** Floating Point Holding Register write callback */ 385 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 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 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 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 #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 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 struct modbus_serial_param { 479 /** Baudrate of the serial line */ 480 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 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 enum uart_config_stop_bits stop_bits_client; 494 }; 495 496 /** 497 * @brief Modbus server parameter 498 */ 499 struct modbus_server_param { 500 /** Pointer to the User Callback structure */ 501 struct modbus_user_callbacks *user_cb; 502 /** Modbus unit ID of the server */ 503 uint8_t unit_id; 504 }; 505 506 struct modbus_raw_cb { 507 modbus_raw_cb_t raw_tx_cb; 508 void *user_data; 509 }; 510 511 /** 512 * @brief User parameter structure to configure Modbus interface 513 * as client or server. 514 */ 515 struct modbus_iface_param { 516 /** Mode of the interface */ 517 enum modbus_mode mode; 518 union { 519 struct modbus_server_param server; 520 /** Amount of time client will wait for 521 * a response from the server. 522 */ 523 uint32_t rx_timeout; 524 }; 525 union { 526 /** Serial support parameter of the interface */ 527 struct modbus_serial_param serial; 528 /** Pointer to raw ADU callback function */ 529 struct modbus_raw_cb rawcb; 530 }; 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 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 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 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 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 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 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 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 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 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_ */ 641