tio/src/rs485.c
Martin Lund ee46686fb6 Add experimental RS-485 support
Many modern RS-485 serial devices such as the ones from FTDI already
operate in RS-485 mode by default and will work with tio out of the box.
However, there are some RS-232/485 devices which need to be switched
from e.g. RS-232 to RS-485 mode to operate accordingly on the physical
level.

This commit implements the switching mechanism and interface required to
enable RS-485 mode. It only works on Linux and with serial devices which
use device drivers that support the Linux RS-485 control interface.

The RS-485 feature is detailed via the following options:

 --rs-485                           Enable RS-485 mode
 --rs-485-config <config>           Set RS-485 configuration

Set the RS-485 configuration using the following key or key value pair
format in the configuration field:

 RTS_ON_SEND=value             Set logical level (0 or 1) for RTS pin when sending
 RTS_AFTER_SEND=value          Set logical level (0 or 1) for RTS pin after sending
 RTS_DELAY_BEFORE_SEND=value   Set RTS delay (ms) before sending
 RTS_DELAY_AFTER_SEND=value    Set RTS delay (ms) after sending
 RX_DURING_TX                  Receive data even while sending data

If defining more than one key or key value pair, they must be comma
separated.

Example use:

 $ tio /dev/ttyUSB0 --rs-485 --rs-r485-config=RTS_DELAY_AFTER_SEND=50,RX_DURING_TX
2022-09-10 17:52:54 +02:00

200 lines
5.5 KiB
C

/*
* tio - a simple serial device I/O tool
*
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include "options.h"
#include "print.h"
#include "error.h"
#ifdef HAVE_RS485
#include <linux/serial.h>
static struct serial_rs485 rs485_config_saved;
static struct serial_rs485 rs485_config;
static bool rs485_config_written = false;
void rs485_parse_config(const char *arg)
{
bool token_found = true;
char *token = NULL;
char *buffer = strdup(arg);
while (token_found == true)
{
if (token == NULL)
{
token = strtok(buffer,",");
}
else
{
token = strtok(NULL, ",");
}
if (token != NULL)
{
char keyname[31];
unsigned int value;
sscanf(token, "%30[^=]=%d", keyname, &value);
if (!strcmp(keyname, "RTS_ON_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 when sending */
option.rs485_config_flags |= SER_RS485_RTS_ON_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 when sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_ON_SEND);
}
}
else if (!strcmp(keyname, "RTS_AFTER_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 after sending */
option.rs485_config_flags |= SER_RS485_RTS_AFTER_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 after sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_AFTER_SEND);
}
}
else if (!strcmp(keyname, "RTS_DELAY_BEFORE_SEND"))
{
/* Set RTS delay before send */
option.rs485_delay_rts_before_send = value;
}
else if (!strcmp(keyname, "RTS_DELAY_AFTER_SEND"))
{
/* Set RTS delay after send */
option.rs485_delay_rts_after_send = value;
}
else if (!strcmp(keyname, "RX_DURING_TX"))
{
/* Receive data even while sending data */
option.rs485_config_flags |= SER_RS485_RX_DURING_TX;
}
}
else
{
token_found = false;
}
}
free(buffer);
}
void rs485_print_config(void)
{
tio_printf(" RS-485 Configuration:");
tio_printf(" RTS_ON_SEND: %s", (rs485_config.flags & SER_RS485_RTS_ON_SEND) ? "high" : "low");
tio_printf(" RTS_AFTER_SEND: %s", (rs485_config.flags & SER_RS485_RTS_AFTER_SEND) ? "high" : "low");
tio_printf(" RTS_DELAY_BEFORE_SEND = %d", rs485_config.delay_rts_before_send);
tio_printf(" RTS_DELAY_AFTER_SEND = %d", rs485_config.delay_rts_after_send);
tio_printf(" RX_DURING_TX: %s", (rs485_config.flags & SER_RS485_RX_DURING_TX) ? "enabled" : "disabled");
}
int rs485_mode_enable(int fd)
{
/* Save existing RS-485 configuration */
ioctl (fd, TIOCGRS485, &rs485_config_saved);
/* Prepare new RS-485 configuration */
rs485_config.flags = SER_RS485_ENABLED;
rs485_config.flags |= option.rs485_config_flags;
if (option.rs485_delay_rts_before_send > 0)
{
rs485_config.delay_rts_before_send = option.rs485_delay_rts_before_send;
}
else
{
rs485_config.delay_rts_before_send = rs485_config_saved.delay_rts_before_send;
}
if (option.rs485_delay_rts_after_send > 0)
{
rs485_config.delay_rts_after_send = option.rs485_delay_rts_after_send;
}
else
{
rs485_config.delay_rts_after_send = rs485_config_saved.delay_rts_after_send;
}
/* Write new RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config) < 0)
{
tio_warning_printf("RS-485 mode is not supported by your device (%s)", strerror(errno));
return -1;
}
rs485_config_written = true;
return 0;
}
void rs485_mode_restore(int fd)
{
if (rs485_config_written)
{
/* Write saved RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config_saved) < 0)
{
tio_warning_printf("TIOCGRS485 ioctl failed (%s)", strerror(errno));
}
}
}
#else
void rs485_parse_config(const char *arg)
{
UNUSED(arg);
return;
}
void rs485_print_config(void)
{
return;
}
int rs485_mode_enable(int fd)
{
UNUSED(fd);
tio_error_printf("RS485 mode is not supported on your system");
exit(EXIT_FAILURE);
}
void rs485_mode_restore(int fd)
{
UNUSED(fd);
return;
}
#endif