mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
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
This commit is contained in:
parent
a58d406a3c
commit
ee46686fb6
13 changed files with 351 additions and 11 deletions
10
TODO
10
TODO
|
|
@ -29,13 +29,3 @@
|
|||
$ tio --socket ws:1234
|
||||
|
||||
Use libwesockets to implement feature.
|
||||
|
||||
* RS-485 support
|
||||
|
||||
Many modern RS-485 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 still 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.
|
||||
|
||||
To enable RS-485 mode on such serial devices the idea is to add a --rs-485
|
||||
option.
|
||||
|
|
|
|||
|
|
@ -44,3 +44,9 @@ color = 11
|
|||
pattern = usb([0-9]*)
|
||||
tty = /dev/ttyUSB%s
|
||||
color = 12
|
||||
|
||||
[rs-485-device]
|
||||
tty = /dev/ttyUSB0
|
||||
rs-485 = enable
|
||||
rs-485-config = RTS_ON_SEND=1,RTS_AFTER_SEND=1,RTS_DELAY_BEFORE_SEND=60,RTS_DELAY_AFTER_SEND=80,RX_DURING_TX
|
||||
color = 13
|
||||
|
|
|
|||
36
man/tio.1.in
36
man/tio.1.in
|
|
@ -229,6 +229,33 @@ response mode to make it easy to parse the response.
|
|||
|
||||
Set timeout [ms] of line response (default: 100).
|
||||
|
||||
.TP
|
||||
.BR " \-\-rs\-485"
|
||||
|
||||
Enable RS-485 mode.
|
||||
|
||||
.TP
|
||||
.BR " \-\-rs\-485\-config " \fI<config>
|
||||
|
||||
Set the RS-485 configuration using the following key or key value pair format in
|
||||
the configuration field:
|
||||
|
||||
.RS
|
||||
.TP 30n
|
||||
.IP \fBRTS_ON_SEND=value
|
||||
Set logical level (0 or 1) for RTS pin when sending
|
||||
.IP \fBRTS_AFTER_SEND=value
|
||||
Set logical level (0 or 1) for RTS pin after sending
|
||||
.IP \fBRTS_DELAY_BEFORE_SEND=value
|
||||
Set RTS delay (ms) before sending
|
||||
.IP \fBRTS_DELAY_AFTER_SEND=value
|
||||
Set RTS delay (ms) after sending
|
||||
.IP \fBRX_DURING_TX
|
||||
Receive data even while sending data
|
||||
.P
|
||||
If defining more than one key or key value pair, they must be comma separated.
|
||||
.RE
|
||||
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
|
||||
|
|
@ -358,6 +385,10 @@ Set prefix ctrl key (a..z, default: t)
|
|||
Enable wait for line response
|
||||
.IP "\fBresponse-timeout"
|
||||
Set line response timeout
|
||||
.IP "\fBrs-485"
|
||||
Enable RS-485 mode
|
||||
.IP "\fBrs-485-config"
|
||||
Set RS-485 configuration
|
||||
|
||||
.SH "CONFIGURATION FILE EXAMPLES"
|
||||
|
||||
|
|
@ -507,6 +538,11 @@ Likewise, to pipe data from file to the serial device:
|
|||
|
||||
$ cat data.bin | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
|
||||
|
||||
.TP
|
||||
Enable RS-485 mode:
|
||||
|
||||
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
||||
|
||||
.SH "WEBSITE"
|
||||
.PP
|
||||
Visit https://tio.github.io
|
||||
|
|
|
|||
|
|
@ -71,5 +71,12 @@ foreach rate : test_baudrates
|
|||
endif
|
||||
endforeach
|
||||
|
||||
# Test for RS-485 support on Linux
|
||||
if host_machine.system() == 'linux'
|
||||
if compiler.check_header('linux/serial.h')
|
||||
enable_rs485 = compiler.has_header_symbol('sys/ioctl.h', 'TIOCSRS485')
|
||||
endif
|
||||
endif
|
||||
|
||||
subdir('src')
|
||||
subdir('man')
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ _tio()
|
|||
-S --socket \
|
||||
-r --response-wait \
|
||||
--response-timeout \
|
||||
--rs-485 \
|
||||
--rs-485-config \
|
||||
-x --hexadecimal \
|
||||
-v --version \
|
||||
-h --help"
|
||||
|
|
@ -122,6 +124,14 @@ _tio()
|
|||
COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
--rs-485)
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
--rs-485-config)
|
||||
COMPREPLY=( $(compgen -W "RTS_ON_SEND RTS_AFTER_SEND RTS_DELAY_BEFORE_SEND RTS_DELAY_AFTER_SEND RX_DURING_TX" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
-x | --hexadecimal)
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
#include "options.h"
|
||||
#include "error.h"
|
||||
#include "print.h"
|
||||
#include "rs485.h"
|
||||
|
||||
static struct config_t *c;
|
||||
|
||||
|
|
@ -264,6 +265,22 @@ static int data_handler(void *user, const char *section, const char *name,
|
|||
{
|
||||
option.response_timeout = atoi(value);
|
||||
}
|
||||
else if (!strcmp(name, "rs-485"))
|
||||
{
|
||||
if (!strcmp(value, "enable"))
|
||||
{
|
||||
option.rs485 = true;
|
||||
}
|
||||
else if (!strcmp(value, "disable"))
|
||||
{
|
||||
option.rs485 = false;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(name, "rs-485-config"))
|
||||
{
|
||||
rs485_parse_config(value);
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ tio_sources = [
|
|||
'configfile.c',
|
||||
'signals.c',
|
||||
'socket.c',
|
||||
'setspeed.c'
|
||||
'setspeed.c',
|
||||
'rs485.c'
|
||||
]
|
||||
|
||||
tio_dep = dependency('inih', required: true,
|
||||
|
|
@ -31,6 +32,10 @@ if enable_iossiospeed
|
|||
tio_c_args += '-DHAVE_IOSSIOSPEED'
|
||||
endif
|
||||
|
||||
if enable_rs485
|
||||
tio_c_args += '-DHAVE_RS485'
|
||||
endif
|
||||
|
||||
executable('tio',
|
||||
tio_sources,
|
||||
c_args: tio_c_args,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "misc.h"
|
||||
#include "print.h"
|
||||
#include "tty.h"
|
||||
#include "rs485.h"
|
||||
|
||||
enum opt_t
|
||||
{
|
||||
|
|
@ -44,6 +45,8 @@ enum opt_t
|
|||
OPT_LOG_STRIP,
|
||||
OPT_LINE_PULSE_DURATION,
|
||||
OPT_RESPONSE_TIMEOUT,
|
||||
OPT_RS485,
|
||||
OPT_RS485_CONFIG,
|
||||
};
|
||||
|
||||
/* Default options */
|
||||
|
|
@ -78,6 +81,10 @@ struct option_t option =
|
|||
.response_wait = false,
|
||||
.response_timeout = 100,
|
||||
.mute = false,
|
||||
.rs485 = false,
|
||||
.rs485_config_flags = 0,
|
||||
.rs485_delay_rts_before_send = -1,
|
||||
.rs485_delay_rts_after_send = -1,
|
||||
};
|
||||
|
||||
void print_help(char *argv[])
|
||||
|
|
@ -109,6 +116,8 @@ void print_help(char *argv[])
|
|||
printf(" -x, --hexadecimal Enable hexadecimal mode\n");
|
||||
printf(" -r, --response-wait Wait for line response then quit\n");
|
||||
printf(" --response-timeout <ms> Response timeout (default: 100)\n");
|
||||
printf(" --rs-485 Enable RS-485 mode\n");
|
||||
printf(" --rs-485-config <config> Set RS-485 configuration\n");
|
||||
printf(" -v, --version Display version\n");
|
||||
printf(" -h, --help Display help\n");
|
||||
printf("\n");
|
||||
|
|
@ -295,6 +304,8 @@ void options_parse(int argc, char *argv[])
|
|||
{"hexadecimal", no_argument, 0, 'x' },
|
||||
{"response-wait", no_argument, 0, 'r' },
|
||||
{"response-timeout", required_argument, 0, OPT_RESPONSE_TIMEOUT },
|
||||
{"rs-485", no_argument, 0, OPT_RS485 },
|
||||
{"rs-485-config", required_argument, 0, OPT_RS485_CONFIG },
|
||||
{"version", no_argument, 0, 'v' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
|
|
@ -437,6 +448,14 @@ void options_parse(int argc, char *argv[])
|
|||
option.response_timeout = string_to_long(optarg);
|
||||
break;
|
||||
|
||||
case OPT_RS485:
|
||||
option.rs485 = true;
|
||||
break;
|
||||
|
||||
case OPT_RS485_CONFIG:
|
||||
rs485_parse_config(optarg);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
printf("tio v%s\n", VERSION);
|
||||
exit(EXIT_SUCCESS);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <termios.h>
|
||||
|
|
@ -70,6 +71,10 @@ struct option_t
|
|||
bool response_wait;
|
||||
int response_timeout;
|
||||
bool mute;
|
||||
bool rs485;
|
||||
uint32_t rs485_config_flags;
|
||||
int32_t rs485_delay_rts_before_send;
|
||||
int32_t rs485_delay_rts_after_send;
|
||||
};
|
||||
|
||||
extern struct option_t option;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "misc.h"
|
||||
#include "error.h"
|
||||
|
|
|
|||
200
src/rs485.c
Normal file
200
src/rs485.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* 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
|
||||
27
src/rs485.h
Normal file
27
src/rs485.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void rs485_parse_config(const char *arg);
|
||||
int rs485_mode_enable(int fd);
|
||||
void rs485_mode_restore(int fd);
|
||||
void rs485_print_config(void);
|
||||
17
src/tty.c
17
src/tty.c
|
|
@ -50,6 +50,7 @@
|
|||
#include "error.h"
|
||||
#include "socket.h"
|
||||
#include "setspeed.h"
|
||||
#include "rs485.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define PATH_SERIAL_DEVICES "/dev/"
|
||||
|
|
@ -456,6 +457,10 @@ void handle_command_sequence(char input_char, char previous_char, char *output_c
|
|||
tio_printf("Configuration:");
|
||||
config_file_print();
|
||||
options_print();
|
||||
if (option.rs485)
|
||||
{
|
||||
rs485_print_config();
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_E:
|
||||
|
|
@ -947,6 +952,12 @@ void tty_restore(void)
|
|||
{
|
||||
tcsetattr(fd, TCSANOW, &tio_old);
|
||||
|
||||
if (option.rs485)
|
||||
{
|
||||
/* Restore original RS-485 mode */
|
||||
rs485_mode_restore(fd);
|
||||
}
|
||||
|
||||
if (connected)
|
||||
{
|
||||
tty_disconnect();
|
||||
|
|
@ -1081,6 +1092,12 @@ int tty_connect(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Manage RS-485 mode */
|
||||
if (option.rs485)
|
||||
{
|
||||
rs485_mode_enable(fd);
|
||||
}
|
||||
|
||||
/* Make sure we restore tty settings on exit */
|
||||
if (first)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue