From cb7ef2048529110c05ab834762b10850f1546ef5 Mon Sep 17 00:00:00 2001 From: Ryan Mulder Date: Thu, 29 May 2025 09:54:12 -0400 Subject: [PATCH] Fixed Linux MS/TP 76800 bitrate for Linux 2.6.20+ circa 2007 and added get/set API for config. (#1007) --- CMakeLists.txt | 1 + apps/router/mstpmodule.c | 2 - ports/linux/dlmstp_port.c | 25 +- ports/linux/dlmstp_port.h | 7 +- ports/linux/rs485.c | 531 +++++++++++--------------------------- ports/linux/rs485.h | 6 + ports/linux/termios2.h | 25 ++ 7 files changed, 202 insertions(+), 395 deletions(-) create mode 100644 ports/linux/termios2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a30c31a6..9335def1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -724,6 +724,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") $<$:ports/linux/arcnet.c> $<$:ports/linux/rs485.c> $<$:ports/linux/rs485.h> + $<$:ports/linux/termios2.h> $<$:ports/linux/dlmstp.c> $<$:ports/linux/ethernet.c> $<$:ports/linux/bsc-event.c> diff --git a/apps/router/mstpmodule.c b/apps/router/mstpmodule.c index a11a62d5..75625a2a 100644 --- a/apps/router/mstpmodule.c +++ b/apps/router/mstpmodule.c @@ -15,7 +15,6 @@ #include "mstpmodule.h" #include "bacnet/bacint.h" #include "dlmstp_port.h" -#include void *dl_mstp_thread(void *pArgs) { @@ -27,7 +26,6 @@ void *dl_mstp_thread(void *pArgs) shared_port_data.MSTP_Packets = 0; shared_port_data.RS485_Handle = -1; - shared_port_data.RS485_Baud = B38400; shared_port_data.RS485MOD = 0; switch (port->params.mstp_params.databits) { diff --git a/ports/linux/dlmstp_port.c b/ports/linux/dlmstp_port.c index c25a98fb..8654bb95 100644 --- a/ports/linux/dlmstp_port.c +++ b/ports/linux/dlmstp_port.c @@ -12,11 +12,9 @@ #include #include #include -#include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ -#include "bacnet/bacdef.h" #include "bacnet/bacaddr.h" #include "bacnet/npdu.h" #include "bacnet/datalink/mstp.h" @@ -99,7 +97,8 @@ void dlmstp_cleanup(void *poPort) } /* restore the old port settings */ - tcsetattr(poSharedData->RS485_Handle, TCSANOW, &poSharedData->RS485_oldtio); + termios2_tcsetattr( + poSharedData->RS485_Handle, TCSANOW, &poSharedData->RS485_oldtio2); close(poSharedData->RS485_Handle); pthread_cond_destroy(&poSharedData->Received_Frame_Flag); @@ -770,7 +769,7 @@ bool dlmstp_init(void *poPort, char *ifname) unsigned long hThread = 0; int rv = 0; SHARED_MSTP_DATA *poSharedData; - struct termios newtio; + struct termios2 newtio; struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; if (!mstp_port) { return false; @@ -819,18 +818,22 @@ bool dlmstp_init(void *poPort, char *ifname) fcntl(poSharedData->RS485_Handle, F_SETFL, 0); #endif /* save current serial port settings */ - tcgetattr(poSharedData->RS485_Handle, &poSharedData->RS485_oldtio); + termios2_tcgetattr( + poSharedData->RS485_Handle, &poSharedData->RS485_oldtio2); /* clear struct for new port settings */ - bzero(&newtio, sizeof(newtio)); + memset(&newtio, 0, sizeof(newtio)); /* - BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. + BOTHER: Set bps rate. + https://man7.org/linux/man-pages/man2/TCSETS.2const.html CRTSCTS : output hardware flow control (only used if the cable has all necessary lines. See sect. 7 of Serial-HOWTO) - CLOCAL : local connection, no modem contol + CLOCAL : local connection, no modem control CREAD : enable receiving characters */ newtio.c_cflag = - poSharedData->RS485_Baud | poSharedData->RS485MOD | CLOCAL | CREAD; + poSharedData->RS485MOD | CLOCAL | CREAD | BOTHER | (BOTHER << IBSHIFT); + newtio.c_ispeed = poSharedData->RS485_Baud; + newtio.c_ospeed = poSharedData->RS485_Baud; /* Raw input */ newtio.c_iflag = 0; /* Raw output */ @@ -838,10 +841,10 @@ bool dlmstp_init(void *poPort, char *ifname) /* no processing */ newtio.c_lflag = 0; /* activate the settings for the port after flushing I/O */ - tcsetattr(poSharedData->RS485_Handle, TCSAFLUSH, &newtio); + termios2_tcsetattr(poSharedData->RS485_Handle, TCSAFLUSH, &newtio); /* flush any data waiting */ usleep(200000); - tcflush(poSharedData->RS485_Handle, TCIOFLUSH); + termios2_tcflush(poSharedData->RS485_Handle, TCIOFLUSH); /* ringbuffer */ FIFO_Init( &poSharedData->Rx_FIFO, poSharedData->Rx_Buffer, diff --git a/ports/linux/dlmstp_port.h b/ports/linux/dlmstp_port.h index d4d4e8c5..b3675a44 100644 --- a/ports/linux/dlmstp_port.h +++ b/ports/linux/dlmstp_port.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include "termios2.h" /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ @@ -77,14 +77,13 @@ typedef struct shared_mstp_data { /* handle returned from open() */ int RS485_Handle; - /* baudrate settings are defined in , which is - included by */ + /* baudrate */ unsigned int RS485_Baud; /* serial port name, /dev/ttyS0, /dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */ char *RS485_Port_Name; /* serial I/O settings */ - struct termios RS485_oldtio; + struct termios2 RS485_oldtio2; /* some terminal I/O have RS-485 specific functionality */ tcflag_t RS485MOD; /* Ring buffer for incoming bytes, in order to speed up the receiving. */ diff --git a/ports/linux/rs485.c b/ports/linux/rs485.c index ce16d9f4..8813aa9f 100644 --- a/ports/linux/rs485.c +++ b/ports/linux/rs485.c @@ -23,11 +23,8 @@ #include #include #include -#include #include #include -#include /* for struct serial_struct */ -#include /* for calculation of custom divisor */ #include /* for scandir */ #include @@ -53,9 +50,8 @@ http://www.easysw.com/~mike/serial/serial.html */ /* handle returned from open() */ static int RS485_Handle = -1; -/* baudrate settings are defined in , which is - included by */ -static unsigned int RS485_Baud = B38400; +/* baudrate */ +static unsigned int RS485_Baud = 38400; /* serial port name, /dev/ttyS0, /dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */ static char *RS485_Port_Name = "/dev/ttyUSB0"; @@ -64,19 +60,13 @@ static char *RS485_Port_Name = "/dev/ttyUSB0"; #define RS485MOD 0 #endif /* serial I/O settings */ -static struct termios RS485_oldtio; -/* for setting custom divisor */ -static struct serial_struct RS485_oldserial; -/* indicator of special baud rate */ -static bool RS485_SpecBaud = false; +static struct termios2 RS485_oldtio2; /* Ring buffer for incoming bytes, in order to speed up the receiving. */ static FIFO_BUFFER Rx_FIFO; /* buffer size needs to be a power of 2 */ static uint8_t Rx_Buffer[4096]; -#define _POSIX_SOURCE 1 /* POSIX compliant source */ - /********************************************************************* * DESCRIPTION: Configures the interface name * RETURN: none @@ -110,77 +100,7 @@ const char *RS485_Interface(void) *****************************************************************************/ uint32_t RS485_Get_Baud_Rate(void) { - uint32_t baud = 0; - - switch (RS485_Baud) { - case B0: - baud = 0; - break; - case B50: - baud = 50; - break; - case B75: - baud = 75; - break; - case B110: - baud = 110; - break; - case B134: - baud = 134; - break; - case B150: - baud = 150; - break; - case B200: - baud = 200; - break; - case B300: - baud = 300; - break; - case B600: - baud = 600; - break; - case B1200: - baud = 1200; - break; - case B1800: - baud = 1800; - break; - case B2400: - baud = 2400; - break; - case B4800: - baud = 4800; - break; - case B9600: - baud = 9600; - break; - case B19200: - baud = 19200; - break; - case B38400: - if (!RS485_SpecBaud) { - /* Linux asks for custom divisor - only when baud is set on 38400 */ - baud = 38400; - } else { - baud = 76800; - } - break; - case B57600: - baud = 57600; - break; - case B115200: - baud = 115200; - break; - case B230400: - baud = 230400; - break; - default: - baud = 9600; - } - - return baud; + return RS485_Baud; } /**************************************************************************** @@ -191,75 +111,11 @@ uint32_t RS485_Get_Baud_Rate(void) *****************************************************************************/ uint32_t RS485_Get_Port_Baud_Rate(struct mstp_port_struct_t *mstp_port) { - uint32_t baud = 0; SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; if (!poSharedData) { return 0; } - switch (poSharedData->RS485_Baud) { - case B0: - baud = 0; - break; - case B50: - baud = 50; - break; - case B75: - baud = 75; - break; - case B110: - baud = 110; - break; - case B134: - baud = 134; - break; - case B150: - baud = 150; - break; - case B200: - baud = 200; - break; - case B300: - baud = 300; - break; - case B600: - baud = 600; - break; - case B1200: - baud = 1200; - break; - case B1800: - baud = 1800; - break; - case B2400: - baud = 2400; - break; - case B4800: - baud = 4800; - break; - case B9600: - baud = 9600; - break; - case B19200: - baud = 19200; - break; - case B38400: - baud = 38400; - break; - case B57600: - baud = 57600; - break; - case B115200: - baud = 115200; - break; - case B230400: - baud = 230400; - break; - default: - baud = 9600; - break; - } - - return baud; + return poSharedData->RS485_Baud; } /**************************************************************************** @@ -270,81 +126,30 @@ uint32_t RS485_Get_Port_Baud_Rate(struct mstp_port_struct_t *mstp_port) *****************************************************************************/ bool RS485_Set_Baud_Rate(uint32_t baud) { - bool valid = true; + RS485_Baud = baud; + return true; +} - RS485_SpecBaud = false; - switch (baud) { - case 0: - RS485_Baud = B0; - break; - case 50: - RS485_Baud = B50; - break; - case 75: - RS485_Baud = B75; - break; - case 110: - RS485_Baud = B110; - break; - case 134: - RS485_Baud = B134; - break; - case 150: - RS485_Baud = B150; - break; - case 200: - RS485_Baud = B200; - break; - case 300: - RS485_Baud = B300; - break; - case 600: - RS485_Baud = B600; - break; - case 1200: - RS485_Baud = B1200; - break; - case 1800: - RS485_Baud = B1800; - break; - case 2400: - RS485_Baud = B2400; - break; - case 4800: - RS485_Baud = B4800; - break; - case 9600: - RS485_Baud = B9600; - break; - case 19200: - RS485_Baud = B19200; - break; - case 38400: - RS485_Baud = B38400; - break; - case 57600: - RS485_Baud = B57600; - break; - case 76800: - RS485_Baud = B38400; - RS485_SpecBaud = true; - break; - case 115200: - RS485_Baud = B115200; - break; - case 230400: - RS485_Baud = B230400; - break; - default: - valid = false; - break; - } +/**************************************************************************** + * DESCRIPTION: Gets RS485 config (e.g. automatic RTS for half-duplex direction) + * RETURN: true on success + * ALGORITHM: none + * NOTES: https://www.kernel.org/doc/Documentation/serial/serial-rs485.txt + *****************************************************************************/ +bool RS485_Get_Config(struct serial_rs485 *config) +{ + return ioctl(RS485_Handle, TIOCGRS485, config) == 0; +} - if (valid) { - /* FIXME: store the baud rate */ - } - - return valid; +/**************************************************************************** + * DESCRIPTION: Sets RS485 config (e.g. automatic RTS for half-duplex direction) + * RETURN: true on success + * ALGORITHM: none + * NOTES: https://www.kernel.org/doc/Documentation/serial/serial-rs485.txt + *****************************************************************************/ +bool RS485_Set_Config(const struct serial_rs485 *const config) +{ + return ioctl(RS485_Handle, TIOCSRS485, config) == 0; } /**************************************************************************** @@ -356,72 +161,45 @@ bool RS485_Set_Baud_Rate(uint32_t baud) void RS485_Send_Frame( struct mstp_port_struct_t *mstp_port, /* port specific data */ const uint8_t *buffer, /* frame to send (up to 501 bytes of data) */ - uint16_t nbytes) -{ /* number of bytes of data (up to 501) */ + uint16_t nbytes /* number of bytes of data (up to 501) */) +{ uint32_t turnaround_time_usec = Tturnaround * 1000000UL; - uint32_t baud; + uint32_t baud = RS485_Baud; + int handle = RS485_Handle; ssize_t written = 0; int greska; const SHARED_MSTP_DATA *poSharedData = NULL; - if (mstp_port) { + if (mstp_port && mstp_port->UserData) { poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; - } - if (!poSharedData) { - baud = RS485_Get_Baud_Rate(); - /* sleeping for turnaround time is necessary to give other devices - time to change from sending to receiving state. */ - usleep(turnaround_time_usec / baud); - /* - On success, the number of bytes written are returned (zero - indicates nothing was written). On error, -1 is returned, and - errno is set appropriately. If count is zero and the file - descriptor refers to a regular file, 0 will be returned without - causing any other effect. For a special file, the results are not - portable. - */ - written = write(RS485_Handle, buffer, nbytes); - greska = errno; - if (written <= 0) { - printf("write error: %s\n", strerror(greska)); - } else { - /* wait until all output has been transmitted. */ - tcdrain(RS485_Handle); - } - /* tcdrain(RS485_Handle); */ - /* per MSTP spec, sort of */ - if (mstp_port) { - mstp_port->SilenceTimerReset((void *)mstp_port); - } - } else { - baud = RS485_Get_Port_Baud_Rate(mstp_port); - /* sleeping for turnaround time is necessary to give other devices - time to change from sending to receiving state. */ - usleep(turnaround_time_usec / baud); - /* - On success, the number of bytes written are returned (zero - indicates nothing was written). On error, -1 is returned, and - errno is set appropriately. If count is zero and the file - descriptor refers to a regular file, 0 will be returned without - causing any other effect. For a special file, the results are not - portable. - */ - written = write(poSharedData->RS485_Handle, buffer, nbytes); - greska = errno; - if (written <= 0) { - printf("write error: %s\n", strerror(greska)); - } else { - /* wait until all output has been transmitted. */ - tcdrain(poSharedData->RS485_Handle); - } - /* tcdrain(RS485_Handle); */ - /* per MSTP spec, sort of */ - if (mstp_port) { - mstp_port->SilenceTimerReset((void *)mstp_port); - } + baud = poSharedData->RS485_Baud; + handle = poSharedData->RS485_Handle; } - return; + /* sleeping for turnaround time is necessary to give other devices + time to change from sending to receiving state. */ + usleep(turnaround_time_usec / baud); + /* + On success, the number of bytes written are returned (zero + indicates nothing was written). On error, -1 is returned, and + errno is set appropriately. If count is zero and the file + descriptor refers to a regular file, 0 will be returned without + causing any other effect. For a special file, the results are not + portable. + */ + written = write(handle, buffer, nbytes); + greska = errno; + if (written <= 0) { + printf("write error: %s\n", strerror(greska)); + } else { + /* wait until all output has been transmitted. */ + termios2_tcdrain(handle); + } + + /* per MSTP spec, sort of */ + if (mstp_port) { + mstp_port->SilenceTimerReset((void *)mstp_port); + } } /**************************************************************************** @@ -435,89 +213,59 @@ void RS485_Check_UART_Data(struct mstp_port_struct_t *mstp_port) fd_set input; struct timeval waiter; uint8_t buf[2048]; - int n; + ssize_t n; + int handle = RS485_Handle; + FIFO_BUFFER *fifo = &Rx_FIFO; SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; - if (!poSharedData) { - if (mstp_port->ReceiveError == true) { - /* do nothing but wait for state machine to clear the error */ - /* burning time, so wait a longer time */ + if (poSharedData) { + handle = poSharedData->RS485_Handle; + fifo = &poSharedData->Rx_FIFO; + } + + if (mstp_port->ReceiveError == true) { + /* do nothing but wait for state machine to clear the error */ + /* burning time, so wait a longer time */ + waiter.tv_sec = 0; + waiter.tv_usec = 5000; + } else if (mstp_port->DataAvailable == false) { + /* wait for state machine to read from the DataRegister */ + if (FIFO_Count(fifo) > 0) { + /* data is available */ + mstp_port->DataRegister = FIFO_Get(fifo); + mstp_port->DataAvailable = true; + /* FIFO is giving data - just poll */ + waiter.tv_sec = 0; + waiter.tv_usec = 0; + } else { + /* FIFO is empty - wait a longer time */ waiter.tv_sec = 0; waiter.tv_usec = 5000; - } else if (mstp_port->DataAvailable == false) { - /* wait for state machine to read from the DataRegister */ - if (FIFO_Count(&Rx_FIFO) > 0) { - /* data is available */ - mstp_port->DataRegister = FIFO_Get(&Rx_FIFO); - mstp_port->DataAvailable = true; - /* FIFO is giving data - just poll */ - waiter.tv_sec = 0; - waiter.tv_usec = 0; - } else { - /* FIFO is empty - wait a longer time */ - waiter.tv_sec = 0; - waiter.tv_usec = 5000; - } - } - /* grab bytes and stuff them into the FIFO every time */ - FD_ZERO(&input); - FD_SET(RS485_Handle, &input); - n = select(RS485_Handle + 1, &input, NULL, NULL, &waiter); - if (n < 0) { - return; - } - if (FD_ISSET(RS485_Handle, &input)) { - n = read(RS485_Handle, buf, sizeof(buf)); - FIFO_Add(&Rx_FIFO, &buf[0], n); - } - } else { - if (mstp_port->ReceiveError == true) { - /* do nothing but wait for state machine to clear the error */ - /* burning time, so wait a longer time */ - waiter.tv_sec = 0; - waiter.tv_usec = 5000; - } else if (mstp_port->DataAvailable == false) { - /* wait for state machine to read from the DataRegister */ - if (FIFO_Count(&poSharedData->Rx_FIFO) > 0) { - /* data is available */ - mstp_port->DataRegister = FIFO_Get(&poSharedData->Rx_FIFO); - mstp_port->DataAvailable = true; - /* FIFO is giving data - just poll */ - waiter.tv_sec = 0; - waiter.tv_usec = 0; - } else { - /* FIFO is empty - wait a longer time */ - waiter.tv_sec = 0; - waiter.tv_usec = 5000; - } - } - /* grab bytes and stuff them into the FIFO every time */ - FD_ZERO(&input); - FD_SET(poSharedData->RS485_Handle, &input); - n = select(poSharedData->RS485_Handle + 1, &input, NULL, NULL, &waiter); - if (n < 0) { - return; - } - if (FD_ISSET(poSharedData->RS485_Handle, &input)) { - n = read(poSharedData->RS485_Handle, buf, sizeof(buf)); - FIFO_Add(&poSharedData->Rx_FIFO, &buf[0], n); } } + /* grab bytes and stuff them into the FIFO every time */ + FD_ZERO(&input); + FD_SET(handle, &input); + n = select(handle + 1, &input, NULL, NULL, &waiter); + if (n < 0) { + return; + } + if (FD_ISSET(handle, &input)) { + n = read(handle, buf, sizeof(buf)); + FIFO_Add(fifo, &buf[0], n); + } } void RS485_Cleanup(void) { /* restore the old port settings */ - tcsetattr(RS485_Handle, TCSANOW, &RS485_oldtio); - ioctl(RS485_Handle, TIOCSSERIAL, &RS485_oldserial); + termios2_tcsetattr(RS485_Handle, TCSANOW, &RS485_oldtio2); close(RS485_Handle); } void RS485_Initialize(void) { - struct termios newtio; - struct serial_struct newserial; - float baud_error = 0.0; + struct termios2 newtio; #if PRINT_ENABLED fprintf(stdout, "RS485 Interface: %s\n", RS485_Port_Name); @@ -539,22 +287,22 @@ void RS485_Initialize(void) fcntl(RS485_Handle, F_SETFL, 0); #endif /* save current serial port settings */ - tcgetattr(RS485_Handle, &RS485_oldtio); - /* we read the old serial setup */ - ioctl(RS485_Handle, TIOCGSERIAL, &RS485_oldserial); - /* we need a copy of existing settings */ - memcpy(&newserial, &RS485_oldserial, sizeof(struct serial_struct)); + termios2_tcgetattr(RS485_Handle, &RS485_oldtio2); /* clear struct for new port settings */ - bzero(&newtio, sizeof(newtio)); + memset(&newtio, 0, sizeof(newtio)); /* - BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. + BOTHER: Set bps rate. + https://man7.org/linux/man-pages/man2/TCSETS.2const.html CRTSCTS : output hardware flow control (only used if the cable has all necessary lines. See sect. 7 of Serial-HOWTO) CS8 : 8n1 (8bit,no parity,1 stopbit) - CLOCAL : local connection, no modem contol + CLOCAL : local connection, no modem control CREAD : enable receiving characters */ - newtio.c_cflag = RS485_Baud | CS8 | CLOCAL | CREAD | RS485MOD; + newtio.c_cflag = + CS8 | CLOCAL | CREAD | RS485MOD | BOTHER | (BOTHER << IBSHIFT); + newtio.c_ispeed = RS485_Baud; + newtio.c_ospeed = RS485_Baud; /* Raw input */ newtio.c_iflag = 0; /* Raw output */ @@ -562,27 +310,7 @@ void RS485_Initialize(void) /* no processing */ newtio.c_lflag = 0; /* activate the settings for the port after flushing I/O */ - tcsetattr(RS485_Handle, TCSAFLUSH, &newtio); - if (RS485_SpecBaud) { - /* 76800, custom divisor must be set */ - newserial.flags |= ASYNC_SPD_CUST; - newserial.custom_divisor = round(((float)newserial.baud_base) / 76800); - /* we must check that we calculated some sane value; - small baud bases yield bad custom divisor values */ - baud_error = fabs( - 1 - - ((float)newserial.baud_base) / ((float)newserial.custom_divisor) / - 76800); - if ((newserial.custom_divisor == 0) || (baud_error > 0.02)) { - /* bad divisor */ - fprintf( - stderr, "RS485 bad custom divisor %d, base baud %d\n", - newserial.custom_divisor, newserial.baud_base); - exit(EXIT_FAILURE); - } - /* if all goes well, set new divisor */ - ioctl(RS485_Handle, TIOCSSERIAL, &newserial); - } + termios2_tcsetattr(RS485_Handle, TCSAFLUSH, &newtio); #if PRINT_ENABLED fprintf(stdout, "RS485 Baud Rate %u\n", RS485_Get_Baud_Rate()); fflush(stdout); @@ -591,7 +319,7 @@ void RS485_Initialize(void) atexit(RS485_Cleanup); /* flush any data waiting */ usleep(200000); - tcflush(RS485_Handle, TCIOFLUSH); + termios2_tcflush(RS485_Handle, TCIOFLUSH); /* ringbuffer */ FIFO_Init(&Rx_FIFO, Rx_Buffer, sizeof(Rx_Buffer)); } @@ -668,6 +396,53 @@ void RS485_Print_Ports(void) } } +int termios2_tcsetattr( + const int fildes, + int optional_actions, + const struct termios2 *const termios2_p) +{ + /* https://man7.org/linux/man-pages/man2/TCSETS.2const.html */ + + switch (optional_actions) { + case TCSANOW: + optional_actions = TCSETS2; + break; + case TCSADRAIN: + optional_actions = TCSETSW2; + break; + case TCSAFLUSH: + optional_actions = TCSETSF2; + break; + default: + errno = EINVAL; + return -1; + }; + return ioctl(fildes, optional_actions, termios2_p); +} + +int termios2_tcgetattr(const int fildes, struct termios2 *termios2_p) +{ + /* https://man7.org/linux/man-pages/man2/TCSETS.2const.html */ + return ioctl(fildes, TCGETS2, termios2_p); +} + +int termios2_tcflush(const int fildes, const int queue_selector) +{ + /* https://manpages.opensuse.org/Tumbleweed/man-pages/TCFLSH.2const.en.html + */ + return ioctl(fildes, TCFLSH, queue_selector); +} + +int termios2_tcdrain(const int fildes) +{ + /* + https://man7.org/linux/man-pages/man2/TCSBRK.2const.html + TCSBRK Equivalent to tcsendbreak(fd, arg). + Linux treats tcsendbreak(fd,arg) with nonzero arg like tcdrain(fd) + */ + return ioctl(fildes, TCSBRK, 1); +} + #ifdef TEST_RS485 #include int main(int argc, char *argv[]) diff --git a/ports/linux/rs485.h b/ports/linux/rs485.h index d57453b2..e611c69e 100644 --- a/ports/linux/rs485.h +++ b/ports/linux/rs485.h @@ -11,6 +11,8 @@ #include #include "bacnet/datalink/mstp.h" +#include /* for serial_rs485 */ + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -38,6 +40,10 @@ BACNET_STACK_EXPORT uint32_t RS485_Get_Baud_Rate(void); BACNET_STACK_EXPORT bool RS485_Set_Baud_Rate(uint32_t baud); +BACNET_STACK_EXPORT +bool RS485_Get_Config(struct serial_rs485 *config); +BACNET_STACK_EXPORT +bool RS485_Set_Config(const struct serial_rs485 *config); BACNET_STACK_EXPORT void RS485_Cleanup(void); diff --git a/ports/linux/termios2.h b/ports/linux/termios2.h new file mode 100644 index 00000000..7926b46b --- /dev/null +++ b/ports/linux/termios2.h @@ -0,0 +1,25 @@ +#ifndef TERMIOS2_H +#define TERMIOS2_H + +#define termios asmtermios /*avoid conflicts with others including termios.h*/ +#include +#undef termios + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int termios2_tcsetattr( + int fildes, int optional_actions, const struct termios2 *termios2_p); + +int termios2_tcgetattr(int fildes, struct termios2 *termios2_p); + +int termios2_tcflush(int fildes, int queue_selector); + +int termios2_tcdrain(int fildes); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif