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