19d0b474c1
Use enum for MODBUS mode. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
768 lines
17 KiB
C
768 lines
17 KiB
C
/*
|
|
* Copyright (c) 2020 PHYTEC Messtechnik GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/*
|
|
* This file is based on mb.c and mb_util.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.
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(modbus, CONFIG_MODBUS_LOG_LEVEL);
|
|
|
|
#include <kernel.h>
|
|
#include <string.h>
|
|
#include <sys/byteorder.h>
|
|
#include <modbus_internal.h>
|
|
|
|
#define DT_DRV_COMPAT zephyr_modbus_serial
|
|
|
|
#define MB_RTU_DEFINE_GPIO_CFG(n, d) \
|
|
static struct mb_rtu_gpio_config d##_cfg_##n = { \
|
|
.name = DT_INST_GPIO_LABEL(n, d), \
|
|
.pin = DT_INST_GPIO_PIN(n, d), \
|
|
.flags = DT_INST_GPIO_FLAGS(n, d), \
|
|
};
|
|
|
|
#define MB_RTU_DEFINE_GPIO_CFGS(n) \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, de_gpios), \
|
|
(MB_RTU_DEFINE_GPIO_CFG(n, de_gpios)), ()) \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, re_gpios), \
|
|
(MB_RTU_DEFINE_GPIO_CFG(n, re_gpios)), ())
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(MB_RTU_DEFINE_GPIO_CFGS)
|
|
|
|
#define MB_RTU_ASSIGN_GPIO_CFG(n, d) \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, d), \
|
|
(&d##_cfg_##n), (NULL))
|
|
|
|
#define MODBUS_DT_GET_DEV(n) { \
|
|
.iface_name = DT_INST_LABEL(n), \
|
|
.dev_name = DT_INST_BUS_LABEL(n), \
|
|
.de = MB_RTU_ASSIGN_GPIO_CFG(n, de_gpios), \
|
|
.re = MB_RTU_ASSIGN_GPIO_CFG(n, re_gpios), \
|
|
},
|
|
|
|
static struct modbus_context mb_ctx_tbl[] = {
|
|
DT_INST_FOREACH_STATUS_OKAY(MODBUS_DT_GET_DEV)
|
|
};
|
|
|
|
static void mb_tx_enable(struct modbus_context *ctx)
|
|
{
|
|
if (ctx->de != NULL) {
|
|
gpio_pin_set(ctx->de->dev, ctx->de->pin, 1);
|
|
}
|
|
|
|
uart_irq_tx_enable(ctx->dev);
|
|
}
|
|
|
|
static void mb_tx_disable(struct modbus_context *ctx)
|
|
{
|
|
uart_irq_tx_disable(ctx->dev);
|
|
if (ctx->de != NULL) {
|
|
gpio_pin_set(ctx->de->dev, ctx->de->pin, 0);
|
|
}
|
|
}
|
|
|
|
static void mb_rx_enable(struct modbus_context *ctx)
|
|
{
|
|
if (ctx->re != NULL) {
|
|
gpio_pin_set(ctx->re->dev, ctx->re->pin, 1);
|
|
}
|
|
|
|
uart_irq_rx_enable(ctx->dev);
|
|
}
|
|
|
|
static void mb_rx_disable(struct modbus_context *ctx)
|
|
{
|
|
uart_irq_rx_disable(ctx->dev);
|
|
if (ctx->re != NULL) {
|
|
gpio_pin_set(ctx->re->dev, ctx->re->pin, 0);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_MODBUS_ASCII_MODE
|
|
/* The function calculates an 8-bit Longitudinal Redundancy Check. */
|
|
static uint8_t mb_ascii_get_lrc(uint8_t *src, size_t length)
|
|
{
|
|
uint8_t lrc = 0;
|
|
uint8_t tmp;
|
|
uint8_t *pblock = src;
|
|
|
|
while (length-- > 0) {
|
|
/* Add the data byte to LRC, increment data pointer. */
|
|
if (hex2bin(pblock, 2, &tmp, sizeof(tmp)) != sizeof(tmp)) {
|
|
return 0;
|
|
}
|
|
|
|
lrc += tmp;
|
|
pblock += 2;
|
|
}
|
|
|
|
/* Two complement the binary sum */
|
|
lrc = ~lrc + 1;
|
|
|
|
return lrc;
|
|
}
|
|
|
|
/* Parses and converts an ASCII mode frame into a Modbus RTU frame. */
|
|
static int mb_rx_ascii_frame(struct modbus_context *ctx)
|
|
{
|
|
uint8_t *pmsg;
|
|
uint8_t *prx_data;
|
|
uint16_t rx_size;
|
|
uint8_t frame_lrc;
|
|
uint8_t calc_lrc;
|
|
|
|
rx_size = ctx->uart_buf_ctr;
|
|
prx_data = &ctx->rx_frame.data[0];
|
|
|
|
if (!(rx_size & 0x01)) {
|
|
LOG_WRN("Message should have an odd number of bytes");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (rx_size < MODBUS_ASCII_MIN_MSG_SIZE) {
|
|
LOG_WRN("Frame length error");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if ((ctx->uart_buf[0] != MODBUS_ASCII_START_FRAME_CHAR) ||
|
|
(ctx->uart_buf[rx_size - 2] != MODBUS_ASCII_END_FRAME_CHAR1) ||
|
|
(ctx->uart_buf[rx_size - 1] != MODBUS_ASCII_END_FRAME_CHAR2)) {
|
|
LOG_WRN("Frame character error");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
/* Take away for the ':', CR, and LF */
|
|
rx_size -= 3;
|
|
/* Point past the ':' to the address. */
|
|
pmsg = &ctx->uart_buf[1];
|
|
|
|
hex2bin(pmsg, 2, &ctx->rx_frame.addr, 1);
|
|
pmsg += 2;
|
|
rx_size -= 2;
|
|
hex2bin(pmsg, 2, &ctx->rx_frame.fc, 1);
|
|
pmsg += 2;
|
|
rx_size -= 2;
|
|
|
|
/* Get the data from the message */
|
|
ctx->rx_frame.length = 0;
|
|
while (rx_size > 2) {
|
|
hex2bin(pmsg, 2, prx_data, 1);
|
|
prx_data++;
|
|
pmsg += 2;
|
|
rx_size -= 2;
|
|
/* Increment the number of Modbus packets received */
|
|
ctx->rx_frame.length++;
|
|
}
|
|
|
|
/* Extract the message's LRC */
|
|
hex2bin(pmsg, 2, &frame_lrc, 1);
|
|
ctx->rx_frame.crc = frame_lrc;
|
|
|
|
/*
|
|
* The LRC is calculated on the ADDR, FC and Data fields,
|
|
* not the ':', CR/LF and LRC placed in the message
|
|
* by the sender. We thus need to subtract 5 'ASCII' characters
|
|
* from the received message to exclude these.
|
|
*/
|
|
calc_lrc = mb_ascii_get_lrc(&ctx->uart_buf[1],
|
|
(ctx->uart_buf_ctr - 5) / 2);
|
|
|
|
if (calc_lrc != frame_lrc) {
|
|
LOG_ERR("Calculated LRC does not match received LRC");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t *mb_bin2hex(uint8_t value, uint8_t *pbuf)
|
|
{
|
|
uint8_t u_nibble = (value >> 4) & 0x0F;
|
|
uint8_t l_nibble = value & 0x0F;
|
|
|
|
hex2char(u_nibble, pbuf);
|
|
pbuf++;
|
|
hex2char(l_nibble, pbuf);
|
|
pbuf++;
|
|
|
|
return pbuf;
|
|
}
|
|
|
|
static void mb_tx_ascii_frame(struct modbus_context *ctx)
|
|
{
|
|
uint16_t tx_bytes = 0;
|
|
uint8_t lrc;
|
|
uint8_t *pbuf;
|
|
|
|
/* Place the start-of-frame character into output buffer */
|
|
ctx->uart_buf[0] = MODBUS_ASCII_START_FRAME_CHAR;
|
|
tx_bytes = 1;
|
|
|
|
pbuf = &ctx->uart_buf[1];
|
|
pbuf = mb_bin2hex(ctx->tx_frame.addr, pbuf);
|
|
tx_bytes += 2;
|
|
pbuf = mb_bin2hex(ctx->tx_frame.fc, pbuf);
|
|
tx_bytes += 2;
|
|
|
|
for (int i = 0; i < ctx->tx_frame.length; i++) {
|
|
pbuf = mb_bin2hex(ctx->tx_frame.data[i], pbuf);
|
|
tx_bytes += 2;
|
|
}
|
|
|
|
/*
|
|
* Add the LRC checksum in the packet.
|
|
*
|
|
* The LRC is calculated on the ADDR, FC and Data fields,
|
|
* not the ':' which was inserted in the uart_buf[].
|
|
* Thus we subtract 1 ASCII character from the LRC.
|
|
* The LRC and CR/LF bytes are not YET in the .uart_buf[].
|
|
*/
|
|
lrc = mb_ascii_get_lrc(&ctx->uart_buf[1], (tx_bytes - 1) / 2);
|
|
pbuf = mb_bin2hex(lrc, pbuf);
|
|
tx_bytes += 2;
|
|
|
|
*pbuf++ = MODBUS_ASCII_END_FRAME_CHAR1;
|
|
*pbuf++ = MODBUS_ASCII_END_FRAME_CHAR2;
|
|
tx_bytes += 2;
|
|
|
|
/* Update the total number of bytes to send */
|
|
ctx->uart_buf_ctr = tx_bytes;
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
|
|
LOG_DBG("Start frame transmission");
|
|
mb_rx_disable(ctx);
|
|
mb_tx_enable(ctx);
|
|
}
|
|
#else
|
|
static int mb_rx_ascii_frame(struct modbus_context *ctx)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void mb_tx_ascii_frame(struct modbus_context *ctx)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static uint16_t mb_rtu_crc16(uint8_t *src, size_t length)
|
|
{
|
|
uint16_t crc = 0xFFFF;
|
|
uint8_t shiftctr;
|
|
bool flag;
|
|
uint8_t *pblock = src;
|
|
|
|
while (length > 0) {
|
|
length--;
|
|
crc ^= (uint16_t)*pblock++;
|
|
shiftctr = 8;
|
|
do {
|
|
/* Determine if the shift out of rightmost bit is 1 */
|
|
flag = (crc & 0x0001) ? true : false;
|
|
/* Shift CRC to the right one bit. */
|
|
crc >>= 1;
|
|
/*
|
|
* If bit shifted out of rightmost bit was a 1
|
|
* exclusive OR the CRC with the generating polynomial.
|
|
*/
|
|
if (flag == true) {
|
|
crc ^= MODBUS_CRC16_POLY;
|
|
}
|
|
|
|
shiftctr--;
|
|
} while (shiftctr > 0);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/* Copy Modbus RTU frame and check if the CRC is valid. */
|
|
static int mb_rx_rtu_frame(struct modbus_context *ctx)
|
|
{
|
|
uint16_t calc_crc;
|
|
uint16_t crc_idx;
|
|
uint8_t *data_ptr;
|
|
|
|
/* Is the message long enough? */
|
|
if ((ctx->uart_buf_ctr < MODBUS_RTU_MIN_MSG_SIZE) ||
|
|
(ctx->uart_buf_ctr > CONFIG_MODBUS_BUFFER_SIZE)) {
|
|
LOG_WRN("Frame length error");
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
ctx->rx_frame.addr = ctx->uart_buf[0];
|
|
ctx->rx_frame.fc = ctx->uart_buf[1];
|
|
data_ptr = &ctx->uart_buf[2];
|
|
/* Payload length without node address, function code, and CRC */
|
|
ctx->rx_frame.length = ctx->uart_buf_ctr - 4;
|
|
/* CRC index */
|
|
crc_idx = ctx->uart_buf_ctr - sizeof(uint16_t);
|
|
|
|
memcpy(ctx->rx_frame.data, data_ptr, ctx->rx_frame.length);
|
|
|
|
ctx->rx_frame.crc = sys_get_le16(&ctx->uart_buf[crc_idx]);
|
|
/* Calculate CRC over address, function code, and payload */
|
|
calc_crc = mb_rtu_crc16(&ctx->uart_buf[0],
|
|
ctx->uart_buf_ctr - sizeof(ctx->rx_frame.crc));
|
|
|
|
if (ctx->rx_frame.crc != calc_crc) {
|
|
LOG_WRN("Calculated CRC does not match received CRC");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mb_tx_rtu_frame(struct modbus_context *ctx)
|
|
{
|
|
uint16_t tx_bytes = 0;
|
|
uint8_t *data_ptr;
|
|
|
|
ctx->uart_buf[0] = ctx->tx_frame.addr;
|
|
ctx->uart_buf[1] = ctx->tx_frame.fc;
|
|
tx_bytes = 2 + ctx->tx_frame.length;
|
|
data_ptr = &ctx->uart_buf[2];
|
|
|
|
memcpy(data_ptr, ctx->tx_frame.data, ctx->tx_frame.length);
|
|
|
|
ctx->tx_frame.crc = mb_rtu_crc16(&ctx->uart_buf[0],
|
|
ctx->tx_frame.length + 2);
|
|
sys_put_le16(ctx->tx_frame.crc,
|
|
&ctx->uart_buf[ctx->tx_frame.length + 2]);
|
|
tx_bytes += 2;
|
|
|
|
ctx->uart_buf_ctr = tx_bytes;
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
|
|
LOG_HEXDUMP_DBG(ctx->uart_buf, ctx->uart_buf_ctr, "uart_buf");
|
|
LOG_DBG("Start frame transmission");
|
|
mb_rx_disable(ctx);
|
|
mb_tx_enable(ctx);
|
|
}
|
|
|
|
void mb_tx_frame(struct modbus_context *ctx)
|
|
{
|
|
switch (ctx->mode) {
|
|
case MODBUS_MODE_RTU:
|
|
mb_tx_rtu_frame(ctx);
|
|
break;
|
|
case MODBUS_MODE_ASCII:
|
|
if (IS_ENABLED(CONFIG_MODBUS_ASCII_MODE)) {
|
|
mb_tx_ascii_frame(ctx);
|
|
break;
|
|
}
|
|
default:
|
|
LOG_ERR("Unknown MODBUS mode");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A byte has been received from a serial port. We just store it in the buffer
|
|
* for processing when a complete packet has been received.
|
|
*/
|
|
static void mb_cb_handler_rx(struct modbus_context *ctx)
|
|
{
|
|
if ((ctx->mode == MODBUS_MODE_ASCII) &&
|
|
IS_ENABLED(CONFIG_MODBUS_ASCII_MODE)) {
|
|
uint8_t c;
|
|
|
|
if (uart_fifo_read(ctx->dev, &c, 1) != 1) {
|
|
LOG_ERR("Failed to read UART");
|
|
return;
|
|
}
|
|
|
|
if (c == MODBUS_ASCII_START_FRAME_CHAR) {
|
|
/* Restart a new frame */
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
ctx->uart_buf_ctr = 0;
|
|
}
|
|
|
|
if (ctx->uart_buf_ctr < CONFIG_MODBUS_BUFFER_SIZE) {
|
|
*ctx->uart_buf_ptr++ = c;
|
|
ctx->uart_buf_ctr++;
|
|
}
|
|
|
|
if (c == MODBUS_ASCII_END_FRAME_CHAR2) {
|
|
k_work_submit(&ctx->server_work);
|
|
}
|
|
|
|
} else {
|
|
int n;
|
|
|
|
/* Restart timer on a new character */
|
|
k_timer_start(&ctx->rtu_timer,
|
|
K_USEC(ctx->rtu_timeout), K_NO_WAIT);
|
|
|
|
n = uart_fifo_read(ctx->dev, ctx->uart_buf_ptr,
|
|
(CONFIG_MODBUS_BUFFER_SIZE -
|
|
ctx->uart_buf_ctr));
|
|
|
|
ctx->uart_buf_ptr += n;
|
|
ctx->uart_buf_ctr += n;
|
|
}
|
|
}
|
|
|
|
static void mb_cb_handler_tx(struct modbus_context *ctx)
|
|
{
|
|
int n;
|
|
|
|
if (ctx->uart_buf_ctr > 0) {
|
|
n = uart_fifo_fill(ctx->dev, ctx->uart_buf_ptr,
|
|
ctx->uart_buf_ctr);
|
|
ctx->uart_buf_ctr -= n;
|
|
ctx->uart_buf_ptr += n;
|
|
} else {
|
|
/* Disable transmission */
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
mb_tx_disable(ctx);
|
|
mb_rx_enable(ctx);
|
|
}
|
|
}
|
|
|
|
static void mb_uart_cb_handler(const struct device *dev, void *app_data)
|
|
{
|
|
struct modbus_context *ctx = (struct modbus_context *)app_data;
|
|
|
|
if (ctx == NULL) {
|
|
LOG_ERR("Modbus hardware is not properly initialized");
|
|
return;
|
|
}
|
|
|
|
while (uart_irq_update(ctx->dev) && uart_irq_is_pending(ctx->dev)) {
|
|
|
|
if (uart_irq_rx_ready(ctx->dev)) {
|
|
mb_cb_handler_rx(ctx);
|
|
}
|
|
|
|
if (uart_irq_tx_ready(ctx->dev)) {
|
|
mb_cb_handler_tx(ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mb_rx_handler(struct k_work *item)
|
|
{
|
|
struct modbus_context *ctx =
|
|
CONTAINER_OF(item, struct modbus_context, server_work);
|
|
|
|
if (ctx == NULL) {
|
|
LOG_ERR("Where is my pointer?");
|
|
return;
|
|
}
|
|
|
|
mb_rx_disable(ctx);
|
|
|
|
switch (ctx->mode) {
|
|
case MODBUS_MODE_RTU:
|
|
ctx->rx_frame_err = mb_rx_rtu_frame(ctx);
|
|
break;
|
|
case MODBUS_MODE_ASCII:
|
|
if (IS_ENABLED(CONFIG_MODBUS_ASCII_MODE)) {
|
|
ctx->rx_frame_err = mb_rx_ascii_frame(ctx);
|
|
break;
|
|
}
|
|
default:
|
|
LOG_ERR("Unknown MODBUS mode");
|
|
return;
|
|
}
|
|
|
|
ctx->uart_buf_ctr = 0;
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
|
|
if (ctx->client == true) {
|
|
k_sem_give(&ctx->client_wait_sem);
|
|
} else if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
|
|
if (mbs_rx_handler(ctx) == false) {
|
|
/* Server does not send response, re-enable RX */
|
|
mb_rx_enable(ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This function is called when the RTU framing timer expires. */
|
|
static void mb_rtu_tmr_handler(struct k_timer *t_id)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
ctx = (struct modbus_context *)k_timer_user_data_get(t_id);
|
|
|
|
if (ctx == NULL) {
|
|
LOG_ERR("Failed to get Modbus context");
|
|
return;
|
|
}
|
|
|
|
k_work_submit(&ctx->server_work);
|
|
}
|
|
|
|
static int mb_configure_uart(struct modbus_context *ctx,
|
|
uint32_t baudrate,
|
|
enum uart_config_parity parity)
|
|
{
|
|
const uint32_t if_delay_max = 3500000;
|
|
const uint32_t numof_bits = 11;
|
|
struct uart_config uart_cfg;
|
|
|
|
ctx->dev = device_get_binding(ctx->dev_name);
|
|
if (ctx->dev == NULL) {
|
|
LOG_ERR("Failed to get UART device %s",
|
|
log_strdup(ctx->dev_name));
|
|
return -ENODEV;
|
|
}
|
|
|
|
uart_cfg.baudrate = baudrate,
|
|
uart_cfg.flow_ctrl = UART_CFG_FLOW_CTRL_NONE;
|
|
|
|
if (ctx->mode == MODBUS_MODE_ASCII) {
|
|
uart_cfg.data_bits = UART_CFG_DATA_BITS_7;
|
|
} else {
|
|
uart_cfg.data_bits = UART_CFG_DATA_BITS_8;
|
|
}
|
|
|
|
switch (parity) {
|
|
case UART_CFG_PARITY_ODD:
|
|
case UART_CFG_PARITY_EVEN:
|
|
uart_cfg.parity = parity;
|
|
uart_cfg.stop_bits = UART_CFG_STOP_BITS_1;
|
|
break;
|
|
case UART_CFG_PARITY_NONE:
|
|
/* Use of no parity requires 2 stop bits */
|
|
uart_cfg.parity = parity;
|
|
uart_cfg.stop_bits = UART_CFG_STOP_BITS_2;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (uart_configure(ctx->dev, &uart_cfg) != 0) {
|
|
LOG_ERR("Failed to configure UART");
|
|
return -EINVAL;
|
|
}
|
|
|
|
uart_irq_callback_user_data_set(ctx->dev, mb_uart_cb_handler, ctx);
|
|
mb_rx_enable(ctx);
|
|
|
|
if (baudrate <= 38400) {
|
|
ctx->rtu_timeout = (numof_bits * if_delay_max) / baudrate;
|
|
} else {
|
|
ctx->rtu_timeout = (numof_bits * if_delay_max) / 38400;
|
|
}
|
|
|
|
LOG_INF("RTU timeout %u us", ctx->rtu_timeout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct modbus_context *mb_get_context(const uint8_t iface)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
if (iface >= ARRAY_SIZE(mb_ctx_tbl)) {
|
|
LOG_ERR("Interface %u not available", iface);
|
|
return NULL;
|
|
}
|
|
|
|
ctx = &mb_ctx_tbl[iface];
|
|
|
|
if (!atomic_test_bit(&ctx->state, MODBUS_STATE_CONFIGURED)) {
|
|
LOG_ERR("Interface not configured");
|
|
return NULL;
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static int mb_configure_gpio(struct modbus_context *ctx)
|
|
{
|
|
if (ctx->de != NULL) {
|
|
ctx->de->dev = device_get_binding(ctx->de->name);
|
|
if (ctx->de->dev == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (gpio_pin_configure(ctx->de->dev, ctx->de->pin,
|
|
GPIO_OUTPUT_INACTIVE | ctx->de->flags)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
|
|
if (ctx->re != NULL) {
|
|
ctx->re->dev = device_get_binding(ctx->re->name);
|
|
if (ctx->re->dev == NULL) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (gpio_pin_configure(ctx->re->dev, ctx->re->pin,
|
|
GPIO_OUTPUT_INACTIVE | ctx->re->flags)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct modbus_context *mb_cfg_iface(const uint8_t iface,
|
|
const uint8_t node_addr,
|
|
const uint32_t baud,
|
|
const enum uart_config_parity parity,
|
|
const uint32_t rx_timeout,
|
|
const bool client,
|
|
const bool ascii_mode)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
if (iface >= ARRAY_SIZE(mb_ctx_tbl)) {
|
|
LOG_ERR("Interface %u not available", iface);
|
|
return NULL;
|
|
}
|
|
|
|
ctx = &mb_ctx_tbl[iface];
|
|
|
|
if (atomic_test_and_set_bit(&ctx->state, MODBUS_STATE_CONFIGURED)) {
|
|
LOG_ERR("Interface allready used");
|
|
return NULL;
|
|
}
|
|
|
|
if ((client == true) &&
|
|
!IS_ENABLED(CONFIG_MODBUS_CLIENT)) {
|
|
LOG_ERR("Modbus client support is not enabled");
|
|
ctx->client = false;
|
|
return NULL;
|
|
}
|
|
|
|
ctx->rxwait_to = rx_timeout;
|
|
ctx->node_addr = node_addr;
|
|
ctx->client = client;
|
|
ctx->mode = ascii_mode ? MODBUS_MODE_ASCII : MODBUS_MODE_RTU;
|
|
ctx->mbs_user_cb = NULL;
|
|
k_mutex_init(&ctx->iface_lock);
|
|
|
|
ctx->uart_buf_ctr = 0;
|
|
ctx->uart_buf_ptr = &ctx->uart_buf[0];
|
|
|
|
k_sem_init(&ctx->client_wait_sem, 0, 1);
|
|
k_work_init(&ctx->server_work, mb_rx_handler);
|
|
|
|
if (IS_ENABLED(CONFIG_MODBUS_FC08_DIAGNOSTIC)) {
|
|
mbs_reset_statistics(ctx);
|
|
}
|
|
|
|
if (mb_configure_gpio(ctx) != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (mb_configure_uart(ctx, baud, parity) != 0) {
|
|
LOG_ERR("Failed to configure UART");
|
|
return NULL;
|
|
}
|
|
|
|
k_timer_init(&ctx->rtu_timer, mb_rtu_tmr_handler, NULL);
|
|
k_timer_user_data_set(&ctx->rtu_timer, ctx);
|
|
LOG_DBG("Modbus interface %s initialized", ctx->iface_name);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
int modbus_init_server(const uint8_t iface, const uint8_t node_addr,
|
|
const uint32_t baud, const enum uart_config_parity parity,
|
|
struct modbus_user_callbacks *const cb,
|
|
const bool ascii_mode)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
if (!IS_ENABLED(CONFIG_MODBUS_SERVER)) {
|
|
LOG_ERR("Modbus server support is not enabled");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (cb == NULL) {
|
|
LOG_ERR("User callbacks should be available");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = mb_cfg_iface(iface, node_addr, baud,
|
|
parity, 0, false, ascii_mode);
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->mbs_user_cb = cb;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int modbus_iface_get_by_name(const char *iface_name)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(mb_ctx_tbl); i++) {
|
|
if (strcmp(iface_name, mb_ctx_tbl[i].iface_name) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
int modbus_init_client(const uint8_t iface,
|
|
const uint32_t baud, const enum uart_config_parity parity,
|
|
const uint32_t rx_timeout,
|
|
const bool ascii_mode)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
if (!IS_ENABLED(CONFIG_MODBUS_CLIENT)) {
|
|
LOG_ERR("Modbus client support is not enabled");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
ctx = mb_cfg_iface(iface, 0, baud,
|
|
parity, rx_timeout, true, ascii_mode);
|
|
|
|
if (ctx == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int modbus_disable(const uint8_t iface)
|
|
{
|
|
struct modbus_context *ctx;
|
|
|
|
if (iface >= ARRAY_SIZE(mb_ctx_tbl)) {
|
|
LOG_ERR("Interface %u not available", iface);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = &mb_ctx_tbl[iface];
|
|
|
|
mb_tx_disable(ctx);
|
|
mb_rx_disable(ctx);
|
|
k_timer_stop(&ctx->rtu_timer);
|
|
|
|
ctx->rxwait_to = 0;
|
|
ctx->node_addr = 0;
|
|
ctx->mode = MODBUS_MODE_RTU;
|
|
ctx->mbs_user_cb = NULL;
|
|
atomic_clear_bit(&ctx->state, MODBUS_STATE_CONFIGURED);
|
|
|
|
LOG_INF("Disable Modbus interface");
|
|
|
|
return 0;
|
|
}
|