modbus: add raw ADU support

MODBUS raw ADU support allows to implement
MODBUS messaging service over TCP or UDP.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2021-03-11 00:42:21 +01:00 committed by Carles Cufí
parent 4ff616b647
commit 6394298960
6 changed files with 359 additions and 9 deletions

View file

@ -36,6 +36,31 @@
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)
*
@ -347,6 +372,16 @@ struct modbus_user_callbacks {
*/
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
*/
@ -355,6 +390,8 @@ enum modbus_mode {
MODBUS_MODE_RTU,
/** Modbus over serial line ASCII mode */
MODBUS_MODE_ASCII,
/** Modbus raw ADU mode */
MODBUS_MODE_RAW,
};
/**
@ -398,6 +435,8 @@ struct modbus_iface_param {
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;
};
};
@ -432,6 +471,60 @@ int modbus_init_client(const int iface, struct modbus_iface_param param);
*/
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

View file

@ -15,6 +15,11 @@ if(CONFIG_MODBUS)
modbus_serial.c
)
zephyr_library_sources_ifdef(
CONFIG_MODBUS_RAW_ADU
modbus_raw.c
)
zephyr_library_sources_ifdef(
CONFIG_MODBUS_SERVER
modbus_server.c

View file

@ -53,6 +53,18 @@ config MODBUS_ASCII_MODE
help
Enable ASCII transmission mode.
config MODBUS_RAW_ADU
bool "Modbus raw ADU support"
help
Enable Modbus raw ADU support.
config MODBUS_NUMOF_RAW_ADU
int "Number of raw ADU instances"
depends on MODBUS_RAW_ADU
range 1 4
help
Number of raw ADU instances.
config MODBUS_FP_EXTENSIONS
bool "Floating-Point extensions"
default y

View file

@ -51,8 +51,18 @@ static struct modbus_serial_config modbus_serial_cfg[] = {
.cfg = &modbus_serial_cfg[n], \
},
#define DEFINE_MODBUS_RAW_ADU(x, _) { \
.iface_name = "RAW_"#x, \
.raw_tx_cb = NULL, \
.mode = MODBUS_MODE_RAW, \
},
static struct modbus_context mb_ctx_tbl[] = {
DT_INST_FOREACH_STATUS_OKAY(MODBUS_DT_GET_DEV)
#ifdef CONFIG_MODBUS_RAW_ADU
UTIL_LISTIFY(CONFIG_MODBUS_NUMOF_RAW_ADU, DEFINE_MODBUS_RAW_ADU, _)
#endif
};
static void modbus_rx_handler(struct k_work *item)
@ -73,6 +83,11 @@ static void modbus_rx_handler(struct k_work *item)
ctx->rx_adu_err = modbus_serial_rx_adu(ctx);
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU)) {
ctx->rx_adu_err = modbus_raw_rx_adu(ctx);
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
return;
@ -113,6 +128,12 @@ void modbus_tx_adu(struct modbus_context *ctx)
LOG_ERR("Unsupported MODBUS serial mode");
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_tx_adu(ctx)) {
LOG_ERR("Unsupported MODBUS raw mode");
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
}
@ -149,6 +170,17 @@ struct modbus_context *modbus_get_context(const uint8_t iface)
return ctx;
}
int modbus_iface_get_by_ctx(const struct modbus_context *ctx)
{
for (int i = 0; i < ARRAY_SIZE(mb_ctx_tbl); i++) {
if (&mb_ctx_tbl[i] == ctx) {
return i;
}
}
return -ENODEV;
}
int modbus_iface_get_by_name(const char *iface_name)
{
for (int i = 0; i < ARRAY_SIZE(mb_ctx_tbl); i++) {
@ -216,6 +248,14 @@ int modbus_init_server(const int iface, struct modbus_iface_param param)
goto init_server_error;
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS raw ADU support");
rc = -EINVAL;
goto init_server_error;
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
rc = -ENOTSUP;
@ -268,6 +308,14 @@ int modbus_init_client(const int iface, struct modbus_iface_param param)
goto init_client_error;
}
break;
case MODBUS_MODE_RAW:
if (IS_ENABLED(CONFIG_MODBUS_RAW_ADU) &&
modbus_raw_init(ctx, param) != 0) {
LOG_ERR("Failed to init MODBUS raw ADU support");
rc = -EINVAL;
goto init_client_error;
}
break;
default:
LOG_ERR("Unknown MODBUS mode");
rc = -ENOTSUP;
@ -306,6 +354,8 @@ int modbus_disable(const uint8_t iface)
modbus_serial_disable(ctx);
}
break;
case MODBUS_MODE_RAW:
break;
default:
LOG_ERR("Unknown MODBUS mode");
}

View file

@ -61,6 +61,11 @@
#define MODBUS_EXC_ILLEGAL_DATA_ADDR 2
#define MODBUS_EXC_ILLEGAL_DATA_VAL 3
#define MODBUS_EXC_SERVER_DEVICE_FAILURE 4
#define MODBUS_EXC_ACK 5
#define MODBUS_EXC_SERVER_DEVICE_BUSY 6
#define MODBUS_EXC_MEM_PARITY_ERROR 8
#define MODBUS_EXC_GW_PATH_UNAVAILABLE 10
#define MODBUS_EXC_GW_TARGET_FAILED_TO_RESP 11
/* Modbus RTU (ASCII) constants */
#define MODBUS_COIL_OFF_CODE 0x0000
@ -72,13 +77,8 @@
#define MODBUS_ASCII_END_FRAME_CHAR1 '\r'
#define MODBUS_ASCII_END_FRAME_CHAR2 '\n'
struct modbus_adu {
uint16_t length;
uint8_t unit_id;
uint8_t fc;
uint8_t data[CONFIG_MODBUS_BUFFER_SIZE - 4];
uint16_t crc;
};
/* Modbus ADU constants */
#define MODBUS_ADU_PROTO_ID 0x0000
struct mb_rtu_gpio_config {
const char *name;
@ -113,8 +113,12 @@ struct modbus_serial_config {
struct modbus_context {
/* Interface name */
const char *iface_name;
/* Serial line configuration */
struct modbus_serial_config *cfg;
union {
/* Serial line configuration */
struct modbus_serial_config *cfg;
/* RAW TX callback */
modbus_raw_cb_t raw_tx_cb;
};
/* MODBUS mode */
enum modbus_mode mode;
/* True if interface is configured as client */
@ -162,6 +166,15 @@ struct modbus_context {
*/
struct modbus_context *modbus_get_context(const uint8_t iface);
/**
* @brief Get Modbus interface index.
*
* @param ctx Pointer to Modbus interface context
*
* @retval Interface index or negative error value.
*/
int modbus_iface_get_by_ctx(const struct modbus_context *ctx);
/**
* @brief Send ADU.
*
@ -253,4 +266,9 @@ int modbus_serial_init(struct modbus_context *ctx,
*/
void modbus_serial_disable(struct modbus_context *ctx);
int modbus_raw_rx_adu(struct modbus_context *ctx);
int modbus_raw_tx_adu(struct modbus_context *ctx);
int modbus_raw_init(struct modbus_context *ctx,
struct modbus_iface_param param);
#endif /* ZEPHYR_INCLUDE_MODBUS_INTERNAL_H_ */

172
subsys/modbus/modbus_raw.c Normal file
View file

@ -0,0 +1,172 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(modbus_raw, CONFIG_MODBUS_LOG_LEVEL);
#include <kernel.h>
#include <sys/byteorder.h>
#include <modbus_internal.h>
#define MODBUS_ADU_LENGTH_DEVIATION 2
#define MODBUS_RAW_MIN_MSG_SIZE (MODBUS_RTU_MIN_MSG_SIZE - 2)
#define MODBUS_RAW_BUFFER_SIZE (CONFIG_MODBUS_BUFFER_SIZE - 2)
int modbus_raw_rx_adu(struct modbus_context *ctx)
{
if (ctx->rx_adu.length < MODBUS_RAW_MIN_MSG_SIZE ||
ctx->rx_adu.length > MODBUS_RAW_BUFFER_SIZE) {
LOG_WRN("Frame length error");
return -EMSGSIZE;
}
if (ctx->rx_adu.proto_id != MODBUS_ADU_PROTO_ID) {
LOG_ERR("MODBUS protocol not supported");
return -ENOTSUP;
}
return 0;
}
int modbus_raw_tx_adu(struct modbus_context *ctx)
{
int iface = modbus_iface_get_by_ctx(ctx);
if (ctx->mode != MODBUS_MODE_RAW) {
return -ENOTSUP;
}
if (iface < 0) {
return -ENODEV;
}
ctx->raw_tx_cb(iface, &ctx->tx_adu);
return 0;
}
int modbus_raw_submit_rx(const int iface, const struct modbus_adu *adu)
{
struct modbus_context *ctx;
ctx = modbus_get_context(iface);
if (ctx == NULL) {
LOG_ERR("Interface not available");
return -ENODEV;
}
if (ctx->mode != MODBUS_MODE_RAW) {
LOG_ERR("Interface not in RAW mode");
return -ENOTSUP;
}
ctx->rx_adu.trans_id = adu->trans_id;
ctx->rx_adu.proto_id = adu->proto_id;
ctx->rx_adu.length = adu->length;
ctx->rx_adu.unit_id = adu->unit_id;
ctx->rx_adu.fc = adu->fc;
memcpy(ctx->rx_adu.data, adu->data,
MIN(adu->length, CONFIG_MODBUS_BUFFER_SIZE));
k_work_submit(&ctx->server_work);
return 0;
}
void modbus_raw_put_header(const struct modbus_adu *adu, uint8_t *header)
{
uint16_t length = MIN(adu->length, CONFIG_MODBUS_BUFFER_SIZE);
sys_put_be16(adu->trans_id, &header[0]);
sys_put_be16(adu->proto_id, &header[2]);
sys_put_be16(length + MODBUS_ADU_LENGTH_DEVIATION, &header[4]);
header[6] = adu->unit_id;
header[7] = adu->fc;
}
void modbus_raw_get_header(struct modbus_adu *adu, const uint8_t *header)
{
adu->trans_id = sys_get_be16(&header[0]);
adu->proto_id = sys_get_be16(&header[2]);
adu->length = MIN(sys_get_be16(&header[4]), CONFIG_MODBUS_BUFFER_SIZE);
adu->unit_id = header[6];
adu->fc = header[7];
if (adu->length >= MODBUS_ADU_LENGTH_DEVIATION) {
adu->length -= MODBUS_ADU_LENGTH_DEVIATION;
}
}
static void modbus_set_exception(struct modbus_adu *adu,
const uint8_t excep_code)
{
const uint8_t excep_bit = BIT(7);
adu->fc |= excep_bit;
adu->data[0] = excep_code;
adu->length = 1;
}
void modbus_raw_set_server_failure(struct modbus_adu *adu)
{
const uint8_t excep_bit = BIT(7);
adu->fc |= excep_bit;
adu->data[0] = MODBUS_EXC_SERVER_DEVICE_FAILURE;
adu->length = 1;
}
int modbus_raw_backend_txn(const int iface, struct modbus_adu *adu)
{
struct modbus_context *ctx;
int err;
ctx = modbus_get_context(iface);
if (ctx == NULL) {
LOG_ERR("Interface %d not available", iface);
modbus_set_exception(adu, MODBUS_EXC_GW_PATH_UNAVAILABLE);
return -ENODEV;
}
/*
* This is currently only possible over serial line
* since no other medium is directly supported.
*/
if (ctx->client == false ||
(ctx->mode != MODBUS_MODE_RTU && ctx->mode != MODBUS_MODE_ASCII)) {
LOG_ERR("Interface %d has wrong configuration", iface);
modbus_set_exception(adu, MODBUS_EXC_GW_PATH_UNAVAILABLE);
return -ENOTSUP;
}
LOG_DBG("Use backend interface %d", iface);
memcpy(&ctx->tx_adu, adu, sizeof(struct modbus_adu));
err = modbus_tx_wait_rx_adu(ctx);
if (err == 0) {
memcpy(adu, &ctx->rx_adu, sizeof(struct modbus_adu));
} else {
modbus_set_exception(adu, MODBUS_EXC_GW_TARGET_FAILED_TO_RESP);
}
return err;
}
int modbus_raw_init(struct modbus_context *ctx,
struct modbus_iface_param param)
{
if (ctx->mode != MODBUS_MODE_RAW) {
return -ENOTSUP;
}
ctx->raw_tx_cb = param.raw_tx_cb;
return 0;
}
void modbus_raw_disable(struct modbus_context *ctx)
{
}