537 lines
16 KiB
C
537 lines
16 KiB
C
/*
|
|
* 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 <drivers/uart.h>
|
|
|
|
#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_ */
|