Commit 3ddb47cc authored by Johann Fischer's avatar Johann Fischer Committed by Carles Cufi
Browse files

modbus: move MODBUS over serial line code to own file



Move MODBUS over serial line code to own source file.

Signed-off-by: default avatarJohann Fischer <johann.fischer@nordicsemi.no>
parent 19d0b474
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ if(CONFIG_MODBUS)

	zephyr_library_sources(
		modbus_core.c
		modbus_serial.c
	)

	zephyr_library_sources_ifdef(
+32 −537
Original line number Diff line number Diff line
/*
 * Copyright (c) 2020 PHYTEC Messtechnik GmbH
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * 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);

@@ -59,512 +45,61 @@ 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)
static void modbus_rx_handler(struct k_work *item)
{
	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];
	struct modbus_context *ctx;

	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);
	ctx = CONTAINER_OF(item, struct modbus_context, server_work);
	if (ctx == NULL) {
		LOG_ERR("Failed to obtain context pointer?");
		return;
	}

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);
		modbus_serial_rx_disable(ctx);
		ctx->rx_frame_err = modbus_serial_rx_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?");
	if (ctx->client == true) {
		k_sem_give(&ctx->client_wait_sem);
		return;
	}

	mb_rx_disable(ctx);
	if (IS_ENABLED(CONFIG_MODBUS_SERVER)) {
		bool respond = mbs_rx_handler(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;
			if (respond == false) {
				LOG_DBG("Server has dropped frame");
				modbus_serial_rx_enable(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);
		}
	}
			break;
		}

/* 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)
void mb_tx_frame(struct modbus_context *ctx)
{
	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 (ctx->mode) {
	case MODBUS_MODE_RTU:
	case MODBUS_MODE_ASCII:
		if (modbus_serial_tx_frame(ctx)) {
			LOG_ERR("Unsupported MODBUS serial mode");
		}

	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_ERR("Unknown MODBUS mode");
	}

	LOG_INF("RTU timeout %u us", ctx->rtu_timeout);

	return 0;
}

struct modbus_context *mb_get_context(const uint8_t iface)
@@ -586,36 +121,6 @@ struct modbus_context *mb_get_context(const uint8_t iface)
	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,
@@ -648,7 +153,6 @@ static struct modbus_context *mb_cfg_iface(const uint8_t iface,
	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);

@@ -656,23 +160,17 @@ static struct modbus_context *mb_cfg_iface(const uint8_t iface,
	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);
	k_work_init(&ctx->server_work, modbus_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");
	if (modbus_serial_init(ctx, baud, parity, ascii_mode) != 0) {
		LOG_ERR("Failed to init MODBUS over serial line");
		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;
@@ -751,10 +249,7 @@ int modbus_disable(const uint8_t iface)

	ctx = &mb_ctx_tbl[iface];

	mb_tx_disable(ctx);
	mb_rx_disable(ctx);
	k_timer_stop(&ctx->rtu_timer);

	modbus_serial_disable(ctx);
	ctx->rxwait_to = 0;
	ctx->node_addr = 0;
	ctx->mode = MODBUS_MODE_RTU;
+11 −2
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ struct modbus_context {
	/* Number of bytes received or to send */
	uint16_t uart_buf_ctr;
	/* Records error from frame reception, e.g. CRC error */
	uint16_t rx_frame_err;
	int rx_frame_err;

#ifdef CONFIG_MODBUS_FC08_DIAGNOSTIC
	uint16_t mbs_msg_ctr;
@@ -152,10 +152,19 @@ struct modbus_context {
};

struct modbus_context *mb_get_context(const uint8_t iface);
int mb_rx_frame(struct modbus_context *ctx);
void mb_tx_frame(struct modbus_context *ctx);

bool mbs_rx_handler(struct modbus_context *ctx);
void mbs_reset_statistics(struct modbus_context *pch);

void modbus_serial_rx_disable(struct modbus_context *ctx);
void modbus_serial_rx_enable(struct modbus_context *ctx);
int modbus_serial_rx_frame(struct modbus_context *ctx);
int modbus_serial_tx_frame(struct modbus_context *ctx);
int modbus_serial_init(struct modbus_context *ctx,
		       uint32_t baudrate,
		       enum uart_config_parity parity,
		       const bool ascii_mode);
void modbus_serial_disable(struct modbus_context *ctx);

#endif /* ZEPHYR_INCLUDE_MODBUS_INTERNAL_H_ */
+581 −0

File added.

Preview size limit exceeded, changes collapsed.