/* * Copyright (c) 2020 PHYTEC Messtechnik GmbH * Copyright (c) 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ /* * Client API in this file is based on mbm_core.c from uC/Modbus Stack. * * uC/Modbus * The Embedded Modbus Stack * * Copyright 2003-2020 Silicon Laboratories Inc. www.silabs.com * * SPDX-License-Identifier: APACHE-2.0 * * This software is subject to an open source license and is distributed by * Silicon Laboratories Inc. pursuant to the terms of the Apache License, * Version 2.0 available at www.apache.org/licenses/LICENSE-2.0. */ /** * @brief MODBUS transport protocol API * @defgroup modbus MODBUS * @ingroup io_interfaces * @{ */ #ifndef ZEPHYR_INCLUDE_MODBUS_H_ #define ZEPHYR_INCLUDE_MODBUS_H_ #include #ifdef __cplusplus extern "C" { #endif /** Length of MBAP Header */ #define MODBUS_MBAP_LENGTH 7 /** Length of MBAP Header plus function code */ #define MODBUS_MBAP_AND_FC_LENGTH (MODBUS_MBAP_LENGTH + 1) /** * @brief Frame struct used internally and for raw ADU support. */ struct modbus_adu { /** Transaction Identifier */ uint16_t trans_id; /** Protocol Identifier */ uint16_t proto_id; /** Length of the data only (not the length of unit ID + PDU) */ uint16_t length; /** Unit Identifier */ uint8_t unit_id; /** Function Code */ uint8_t fc; /** Transaction Data */ uint8_t data[CONFIG_MODBUS_BUFFER_SIZE - 4]; /** RTU CRC */ uint16_t crc; }; /** * @brief Coil read (FC01) * * Sends a Modbus message to read the status of coils from a server. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Coil starting address * @param coil_tbl Pointer to an array of bytes containing the value * of the coils read. * The format is: * * MSB LSB * B7 B6 B5 B4 B3 B2 B1 B0 * ------------------------------------- * coil_tbl[0] #8 #7 #1 * coil_tbl[1] #16 #15 #9 * : * : * * Note that the array that will be receiving the coil * values must be greater than or equal to: * (num_coils - 1) / 8 + 1 * @param num_coils Quantity of coils to read * * @retval 0 If the function was successful */ int modbus_read_coils(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint8_t *const coil_tbl, const uint16_t num_coils); /** * @brief Read discrete inputs (FC02) * * Sends a Modbus message to read the status of discrete inputs from * a server. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Discrete input starting address * @param di_tbl Pointer to an array that will receive the state * of the discrete inputs. * The format of the array is as follows: * * MSB LSB * B7 B6 B5 B4 B3 B2 B1 B0 * ------------------------------------- * di_tbl[0] #8 #7 #1 * di_tbl[1] #16 #15 #9 * : * : * * Note that the array that will be receiving the discrete * input values must be greater than or equal to: * (num_di - 1) / 8 + 1 * @param num_di Quantity of discrete inputs to read * * @retval 0 If the function was successful */ int modbus_read_dinputs(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint8_t *const di_tbl, const uint16_t num_di); /** * @brief Read holding registers (FC03) * * Sends a Modbus message to read the value of holding registers * from a server. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Register starting address * @param reg_buf Is a pointer to an array that will receive * the current values of the holding registers from * the server. The array pointed to by 'reg_buf' needs * to be able to hold at least 'num_regs' entries. * @param num_regs Quantity of registers to read * * @retval 0 If the function was successful */ int modbus_read_holding_regs(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint16_t *const reg_buf, const uint16_t num_regs); /** * @brief Read input registers (FC04) * * Sends a Modbus message to read the value of input registers from * a server. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Register starting address * @param reg_buf Is a pointer to an array that will receive * the current value of the holding registers * from the server. The array pointed to by 'reg_buf' * needs to be able to hold at least 'num_regs' entries. * @param num_regs Quantity of registers to read * * @retval 0 If the function was successful */ int modbus_read_input_regs(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint16_t *const reg_buf, const uint16_t num_regs); /** * @brief Write single coil (FC05) * * Sends a Modbus message to write the value of single coil to a server. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param coil_addr Coils starting address * @param coil_state Is the desired state of the coil * * @retval 0 If the function was successful */ int modbus_write_coil(const int iface, const uint8_t unit_id, const uint16_t coil_addr, const bool coil_state); /** * @brief Write single holding register (FC06) * * Sends a Modbus message to write the value of single holding register * to a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Coils starting address * @param reg_val Desired value of the holding register * * @retval 0 If the function was successful */ int modbus_write_holding_reg(const int iface, const uint8_t unit_id, const uint16_t start_addr, const uint16_t reg_val); /** * @brief Read diagnostic (FC08) * * Sends a Modbus message to perform a diagnostic function of a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param sfunc Diagnostic sub-function code * @param data Sub-function data * @param data_out Pointer to the data value * * @retval 0 If the function was successful */ int modbus_request_diagnostic(const int iface, const uint8_t unit_id, const uint16_t sfunc, const uint16_t data, uint16_t *const data_out); /** * @brief Write coils (FC15) * * Sends a Modbus message to write to coils on a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Coils starting address * @param coil_tbl Pointer to an array of bytes containing the value * of the coils to write. * The format is: * * MSB LSB * B7 B6 B5 B4 B3 B2 B1 B0 * ------------------------------------- * coil_tbl[0] #8 #7 #1 * coil_tbl[1] #16 #15 #9 * : * : * * Note that the array that will be receiving the coil * values must be greater than or equal to: * (num_coils - 1) / 8 + 1 * @param num_coils Quantity of coils to write * * @retval 0 If the function was successful */ int modbus_write_coils(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint8_t *const coil_tbl, const uint16_t num_coils); /** * @brief Write holding registers (FC16) * * Sends a Modbus message to write to integer holding registers * to a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Register starting address * @param reg_buf Is a pointer to an array containing * the value of the holding registers to write. * Note that the array containing the register values must * be greater than or equal to 'num_regs' * @param num_regs Quantity of registers to write * * @retval 0 If the function was successful */ int modbus_write_holding_regs(const int iface, const uint8_t unit_id, const uint16_t start_addr, uint16_t *const reg_buf, const uint16_t num_regs); /** * @brief Read floating-point holding registers (FC03) * * Sends a Modbus message to read the value of floating-point * holding registers from a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Register starting address * @param reg_buf Is a pointer to an array that will receive * the current values of the holding registers from * the server. The array pointed to by 'reg_buf' needs * to be able to hold at least 'num_regs' entries. * @param num_regs Quantity of registers to read * * @retval 0 If the function was successful */ int modbus_read_holding_regs_fp(const int iface, const uint8_t unit_id, const uint16_t start_addr, float *const reg_buf, const uint16_t num_regs); /** * @brief Write floating-point holding registers (FC16) * * Sends a Modbus message to write to floating-point holding registers * to a server unit. * * @param iface Modbus interface index * @param unit_id Modbus unit ID of the server * @param start_addr Register starting address * @param reg_buf Is a pointer to an array containing * the value of the holding registers to write. * Note that the array containing the register values must * be greater than or equal to 'num_regs' * @param num_regs Quantity of registers to write * * @retval 0 If the function was successful */ int modbus_write_holding_regs_fp(const int iface, const uint8_t unit_id, const uint16_t start_addr, float *const reg_buf, const uint16_t num_regs); /** Modbus Server User Callback structure */ struct modbus_user_callbacks { /** Coil read callback */ int (*coil_rd)(uint16_t addr, bool *state); /** Coil write callback */ int (*coil_wr)(uint16_t addr, bool state); /** Discrete Input read callback */ int (*discrete_input_rd)(uint16_t addr, bool *state); /** Input Register read callback */ int (*input_reg_rd)(uint16_t addr, uint16_t *reg); /** Floating Point Input Register read callback */ int (*input_reg_rd_fp)(uint16_t addr, float *reg); /** Holding Register read callback */ int (*holding_reg_rd)(uint16_t addr, uint16_t *reg); /** Holding Register write callback */ int (*holding_reg_wr)(uint16_t addr, uint16_t reg); /** Floating Point Holding Register read callback */ int (*holding_reg_rd_fp)(uint16_t addr, float *reg); /** Floating Point Holding Register write callback */ int (*holding_reg_wr_fp)(uint16_t addr, float reg); }; /** * @brief Get Modbus interface index according to interface name * * If there is more than one interface, it can be used to clearly * identify interfaces in the application. * * @param iface_name Modbus interface name * * @retval Modbus interface index or negative error value. */ int modbus_iface_get_by_name(const char *iface_name); /** * @brief ADU raw callback function signature * * @param iface Modbus RTU interface index * @param adu Pointer to the RAW ADU struct to send * * @retval 0 If transfer was successful */ typedef int (*modbus_raw_cb_t)(const int iface, const struct modbus_adu *adu); /** * @brief Modbus interface mode */ enum modbus_mode { /** Modbus over serial line RTU mode */ MODBUS_MODE_RTU, /** Modbus over serial line ASCII mode */ MODBUS_MODE_ASCII, /** Modbus raw ADU mode */ MODBUS_MODE_RAW, }; /** * @brief Modbus serial line parameter */ struct modbus_serial_param { /** Baudrate of the serial line */ uint32_t baud; /** parity UART's parity setting: * UART_CFG_PARITY_NONE, * UART_CFG_PARITY_EVEN, * UART_CFG_PARITY_ODD */ enum uart_config_parity parity; }; /** * @brief Modbus server parameter */ struct modbus_server_param { /** Pointer to the User Callback structure */ struct modbus_user_callbacks *user_cb; /** Modbus unit ID of the server */ uint8_t unit_id; }; /** * @brief User parameter structure to configure Modbus interfase * as client or server. */ struct modbus_iface_param { /** Mode of the interface */ enum modbus_mode mode; union { struct modbus_server_param server; /** Amount of time client will wait for * a response from the server. */ uint32_t rx_timeout; }; union { /** Serial support parameter of the interface */ struct modbus_serial_param serial; /** Pointer to raw ADU callback function */ modbus_raw_cb_t raw_tx_cb; }; }; /** * @brief Configure Modbus Interface as raw ADU server * * @param iface Modbus RTU interface index * @param param Configuration parameter of the server interface * * @retval 0 If the function was successful */ int modbus_init_server(const int iface, struct modbus_iface_param param); /** * @brief Configure Modbus Interface as raw ADU client * * @param iface Modbus RTU interface index * @param param Configuration parameter of the client interface * * @retval 0 If the function was successful */ int modbus_init_client(const int iface, struct modbus_iface_param param); /** * @brief Disable Modbus Interface * * This function is called to disable Modbus interface. * * @param iface Modbus interface index * * @retval 0 If the function was successful */ int modbus_disable(const uint8_t iface); /** * @brief Submit raw ADU * * @param iface Modbus RTU interface index * @param adu Pointer to the RAW ADU struct that is received * * @retval 0 If transfer was successful */ int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu); /** * @brief Put MBAP header into a buffer * * @param adu Pointer to the RAW ADU struct * @param header Pointer to the buffer in which MBAP header * will be placed. * * @retval 0 If transfer was successful */ void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header); /** * @brief Get MBAP header from a buffer * * @param adu Pointer to the RAW ADU struct * @param header Pointer to the buffer containing MBAP header * * @retval 0 If transfer was successful */ void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header); /** * @brief Set Server Device Failure exception * * This function modifies ADU passed by the pointer. * * @param adu Pointer to the RAW ADU struct */ void modbus_raw_set_server_failure(struct modbus_adu *adu); /** * @brief Use interface as backend to send and receive ADU * * This function overwrites ADU passed by the pointer and generates * exception responses if backend interface is misconfigured or * target device is unreachable. * * @param iface Modbus client interface index * @param adu Pointer to the RAW ADU struct * * @retval 0 If transfer was successful */ int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu); #ifdef __cplusplus } #endif /** * @} */ #endif /* ZEPHYR_INCLUDE_MODBUS_H_ */