walid yahia
Posts: 3
Joined: Thu Aug 19, 2021 8:48 am

Sending data using RS485

Tue Sep 14, 2021 4:47 pm

I have a solution which depend on RS485 to communicate with other device, I need some help for these questions:

How can I enable Rts and Cts pins on Raspberry pi4 ?
Also, How can implement RS485 communication using C#?
Last edited by walid yahia on Wed Sep 15, 2021 8:02 am, edited 2 times in total.

Heater
Posts: 18758
Joined: Tue Jul 17, 2012 3:02 pm

Re: Sending data using RS485

Tue Sep 14, 2021 8:42 pm

In linux one can control RTS/CTS and other serial port features using ioctl() system calls on the serial device.
https://man7.org/linux/man-pages/man2/ioctl_tty.2.html

I have only ever done this in C. You will find examples of doing that all over the net. For example: https://www.xanthium.in/Controlling-RTS ... t-in-Linux

Apparently it can be done in C# easily as well:
https://www.xanthium.in/Serial-Programm ... on-Windows
https://create.arduino.cc/projecthub/sx ... nux-0f2ed4
Memory in C++ is a leaky abstraction .

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Wed Sep 15, 2021 12:57 pm

Heater wrote:
Tue Sep 14, 2021 8:42 pm
I have only ever done this in C. You will find examples of doing that all over the net. For example: https://www.xanthium.in/Controlling-RTS ... t-in-Linux
But that doesn't work for the integral Pi serial ports - it's not implemented in the PL011 UART serial driver; nor does the driver support the type of RTS control needed to enable an RS485 transceiver.
I'm implementing a similar requirement (in C), and as far as I can tell there's little option but to configure the RTS pin as a simple output, and control it directly in code.

Heater
Posts: 18758
Joined: Tue Jul 17, 2012 3:02 pm

Re: Sending data using RS485

Wed Sep 15, 2021 3:01 pm

Good point. Everything depends on the serial port features the hardware supports.

Using a GPIO pin to get the job done will do it.
Memory in C++ is a leaky abstraction .

MiscBits
Posts: 327
Joined: Wed Jan 27, 2021 12:48 pm

Re: Sending data using RS485

Sun Sep 19, 2021 10:26 pm

Strange, the only RS/485 I've done is two wire simplex or four wire duplex and RTS/CTS has not been used.

Even 422 was two wire though the interface could not go tri-state.

A quick check on the pi hut website shows only the AB pins on the output (plus ground though not needed).

The main issue I remember is that some chip sets had a habit of inverting the signal voltage (grrr)...
Sig currently hiding till 2022 - bah humbug.

Heater
Posts: 18758
Joined: Tue Jul 17, 2012 3:02 pm

Re: Sending data using RS485

Mon Sep 20, 2021 11:56 am

MiscBits wrote:
Sun Sep 19, 2021 10:26 pm
Strange, the only RS/485 I've done is two wire simplex or four wire duplex and RTS/CTS has not been used.
Not strange at all.

RS485 can transmit and receive over the same two wires. That means the transmitter has to be disabled when it is required to receive. That is to say the transmitter is a trigger-state output and requires an enable pin to control trigger-state.

This is not Request To Send (RTS) or Clear To Send (CTS) signalling as sometimes used for flow control in RS232 links. But presumably RTS can be used as the RS485 transmit buffer enable signal.

See: https://www.eeweb.com/rs-485-transceiver-tutorial/
Memory in C++ is a leaky abstraction .

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Mon Sep 20, 2021 12:29 pm

Heater wrote:
Mon Sep 20, 2021 11:56 am
MiscBits wrote:
Sun Sep 19, 2021 10:26 pm
Strange, the only RS/485 I've done is two wire simplex or four wire duplex and RTS/CTS has not been used.
Not strange at all.

RS485 can transmit and receive over the same two wires. That means the transmitter has to be disabled when it is required to receive. That is to say the transmitter is a trigger-state output and requires an enable pin to control trigger-state.

This is not Request To Send (RTS) or Clear To Send (CTS) signalling as sometimes used for flow control in RS232 links. But presumably RTS can be used as the RS485 transmit buffer enable signal.

See: https://www.eeweb.com/rs-485-transceiver-tutorial/
'RS-485' has come to mean 'multidrop bus with differential signalling' in some quarters. Certainly most of the RS-484 busses I've come across have been 4-wire, which is often easier to faultfind, and means that the master's transmitter can be left permanently enabled.

Some of the RS-485 interfaces on the market use a dreadful kludge (using a retriggerable timer) which enables the transmitter immediately a start bit is detected - so there's no setup time to help receiver noise immunity, but it does mean that you just need Tx and Rx from the UART.

Some modern embedded systems (e.g. STM32) can be configured to manage the RTS timings (and polarity) in hardware; otherwise it has to be done using software. Linux includes RS-485 mode capabilities (including control of RS-485 polarity) which need low-level (software) driver support; this isn't implemented for the Pi's embedded UARTs, but may be supported for some that you can add via SPI or I2C.

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Mon Sep 20, 2021 5:24 pm

; this isn't implemented for the Pi's embedded UARTs, but may be supported for some that you can add via SPI or I2C.
It is implemented on the PI! it supports RS-485.

viewtopic.php?f=98&t=224533&hilit=rs+485#p1383709

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Mon Sep 20, 2021 7:35 pm

danjperron wrote:
Mon Sep 20, 2021 5:24 pm
; this isn't implemented for the Pi's embedded UARTs, but may be supported for some that you can add via SPI or I2C.
It is implemented on the PI! it supports RS-485.

viewtopic.php?f=98&t=224533&hilit=rs+485#p1383709
That's in Python; presumably they implement the necessary functionality in the interpreter.
I want to use RS-485 comms from C/C++; I would be delighted to be proved wrong (would save me some work!) but my investigations and test code have left me believing that RS-485 is NOT supported on the standard PL011 UARTs in Buster.
(It was a few weeks ago, but IIRC the following call failed with an "unsupported IOCTL" error:

Code: Select all

	osResult = ioctl (fd, TIOCSRS485, &rs485conf);
)

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Tue Sep 21, 2021 1:20 am

Stevend! why it is working for me then! Yah is not working!

1 - The mini uart on serial0 doesn't have rts and cts. You need to flip it back to /dev/ttyAMA0. This is done via /boot/config.
2 - Some dtoverlay exist to set rts and cts.

To test my point I use an available application , rpirtscts ,to set the GPIO to RTS and CTS. I had to modify it for the PI4 using /dev/gpiomem instead of /dev/mem. I should have installed the dtoverlay but I did go for the fast and dirty method.

Then a made a simple serial application to write on /dev/ttyAMA0 which is now /dev/serial0 and I got the RTS pin flip but doesn't return.
I will need to investigate more. I will check the dtoverlay. At least I know that the CTS works!

Yellow is the TX and cyan is the RTS line (GPIO 17)
pic_249_1.gif
cts rts-485
pic_249_1.gif (24.21 KiB) Viewed 1760 times
file serial.c

Code: Select all

#include <sys/ioctl.h>
#include <linux/serial.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>


int
set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                perror("error from tcgetattr");
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // disable break processing
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                perror("error from tcsetattr");
                return -1;
        }
        return 0;
}



void
set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                perror("error from tggetattr");
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                perror("error setting term attributes");
}




int main(int argc, char ** argv) {
  int fd;
  // Open the Port. We want read/write, no "controlling tty" status, and open it no matter what state DCD is in
  fd = open("/dev/serial0", O_RDWR | O_NOCTTY | O_NDELAY | O_SYNC);
  if (fd < 0) {
    perror("open_port: Unable to open /dev/serial0 - ");
    return(-1);
  }

 set_interface_attribs(fd,B9600,0);
 set_blocking(fd,0);


struct serial_rs485 rs485conf;

/* Enable RS485 mode: */
rs485conf.flags |= SER_RS485_ENABLED;

/* Set logical level for RTS pin equal to 1 when sending: */
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
/* or, set logical level for RTS pin equal to 0 when sending: */
//rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);

/* Set logical level for RTS pin equal to 1 after sending: */
//rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
/* or, set logical level for RTS pin equal to 0 after sending: */
rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

/* Set rts delay before send, if needed: */
//rs485conf.delay_rts_before_send = 0;

/* Set rts delay after send, if needed: */
//rs485conf.delay_rts_after_send = 0;

/* Set this flag if you want to receive data even while sending data */
//rs485conf.flags |= SER_RS485_RX_DURING_TX;

if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
        /* Error handling. See errno. */
}



  char *string1 = "test1\n";
  // Write to the port
  int n = write(fd,string1, strlen(string1));
  if (n < 0) {
    perror("Write failed - ");
    return -1;
  }

/*
  // Read up to 255 characters from the port if they are there
  char buf[256];
  n = read(fd, (void*)buf, 255);
  if (n < 0) {
    perror("Read failed - ");
    return -1;
  } else if (n == 0) printf("No data on port\n");
  else {
    buf[n] = '\0';
    printf("%i bytes read : %s", n, buf);
  }
*/
  // Don't forget to clean up
  close(fd);
  return 0;
}
the modified rpirtscts.c

Code: Select all

/*
    A command-line utility for enabling hardware flow control on the
    Raspberry Pi serial port.

    Copyright (C) 2013 Matthew Hollingworth.

    40 pin header support for newer Raspberry Pis 
    Copyright (C) 2016 Brendan Traw.

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#define GPIO_OFFSET (0x200000)
#define BLOCK_SIZE (4*1024)
#define GFPSEL3 (3)
#define GPIO3031mask 0x0000003f /* GPIO 30 for CTS0 and 31 for RTS0 */
#define GFPSEL1 (1)
#define GPIO1617mask 0x00fc0000 /* GPIO 16 for CTS0 and 17 for RTS0 */

#define GPIO_header_26 0x00
#define GPIO_header_40 0x01

#define VERSION "1.5"

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

unsigned gpio_base()
{ /* adapted from bcm_host.c */
	unsigned address = ~0;
	FILE *fp = fopen("/proc/device-tree/soc/ranges", "rb");
	if (fp) {
		unsigned char buf[4];
		fseek(fp, 4, SEEK_SET);
		if (fread(buf, 1, sizeof buf, fp) == sizeof buf)
			address = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0;
		fclose(fp);
	}
        printf("address = %x\n",address);
	return (address == ~0 ? 0x20000000 : address) + GPIO_OFFSET;
}

int rpi_version() {
	int result = -1;
	char string[256];
	FILE *fp = fopen("/proc/cpuinfo", "r");
	if (fp) {
		while (fscanf(fp, "%255s", string) == 1)
			if (strcmp(string, "Revision") == 0)
				break;
		while (fscanf(fp, "%255s", string) == 1)
			if (sscanf(string, "%x", &result) == 1)
				break;
		fclose(fp);
	}
	if (result < 0) {
		fprintf(stderr, "can't parse /proc/cpuinfo\n");
		exit(EXIT_FAILURE);
	} else
		result &= ~(1 << 24 | 1 << 25); // clear warranty bits
	return result;
}

int rpi_gpio_header_type() {
	switch (rpi_version()) { /* Adapted from http://elinux.org/RPi_HardwareHistory */
	case 0x000002: // Model B Rev 1.0
	case 0x000003: // Model B Rev 1.0+
	case 0x000004: // Model B Rev 2.0
	case 0x000005: // Model B Rev 2.0
	case 0x000006: // Model B Rev 2.0
	case 0x000007: // Model A
	case 0x000008: // Model A
	case 0x000009: // Model A
	case 0x00000d: // Model B Rev 2.0
	case 0x00000e: // Model B Rev 2.0
	case 0x00000f: // Model B Rev 2.0
		printf("26-pin GPIO header detected\n");
		return GPIO_header_26;
	case 0x000011: // Compute Module 1
	case 0x000014: // Compute Module 1
	case 0xa020a0: // Compute Module 3
		fprintf(stderr, "compute module not supported\n");
		exit(EXIT_FAILURE);
	case 0x000010: // Model B+ Rev 1.0
	case 0x000012: // Model A+ Rev 1.1
	case 0x000013: // Model B+ Rev 1.2
	case 0x000015: // Model A+ Rev 1.1
	case 0x900021: // Model A+ Rev 1.1
	case 0x900032: // Model B+ Rev 1.2
	case 0x900092: // Pi Zero Rev 1.2
	case 0x900093: // Pi Zero Rev 1.3
	case 0x9000c1: // Pi Zero W
	case 0x920093: // Pi Zero Rev 1.3
	case 0xa01040: // Pi 2 Model B Rev 1.0
	case 0xa01041: // Pi 2 Model B Rev 1.1
	case 0xa02082: // Pi 3 Model B Rev 1.2
	case 0xa21041: // Pi 2 Model B Rev 1.1
	case 0xa22042: // Pi 2 Model B Rev 1.2
	case 0xa22082: // Pi 3 Model B Rev 1.2
	case 0xa32082: // Pi 3 Model B Rev 1.2
		printf("40-pin GPIO header detected\n");
		return GPIO_header_40;
	default:
		printf("assuming 40-pin GPIO header\n");
		return GPIO_header_40;
	}
}


void set_rts_cts(int enable) {
	int gfpsel, gpiomask;
	int fd = open("/dev/gpiomem", O_RDWR|O_SYNC);
	if (fd < 0) {
		fprintf(stderr, "can't open /dev/gpiomem (%s)\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	void *gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	close(fd);
	if (gpio_map == MAP_FAILED) {
		fprintf(stderr, "mmap error (%s)\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	
	volatile unsigned *gpio = (volatile unsigned *)gpio_map;

	if (rpi_gpio_header_type() == GPIO_header_40) { /* newer 40 pin GPIO header */
		gfpsel = GFPSEL1;
		gpiomask = GPIO1617mask;
		enable ? printf("Enabling ") : printf("Disabling ");
		printf("CTS0 and RTS0 on GPIOs 16 and 17\n");
	}
	else { /* 26 pin GPIO header */
		gfpsel = GFPSEL3;
		gpiomask = GPIO3031mask;
		enable ? printf("Enabling ") : printf("Disabling ");
		printf("CTS0 and RTS0 on GPIOs 30 and 31\n");
	}
	
	enable ? (gpio[gfpsel] |= gpiomask) : (gpio[gfpsel] &= ~gpiomask);
}

void print_usage() {
	printf( \
	"Version: " VERSION "\n" \
	"Usage: rpirtscts on|off\n" \
	"Enable or disable hardware flow control pins on ttyAMA0.\n" \
	"\nFor 26 pin GPIO header boards:\n"    \
	"P5 header pins remap as follows:\n"	\
	"    P5-05 (GPIO30) -> CTS (input)\n" \
	"    P5-06 (GPIO31) -> RTS (output)\n" \
	"\nFor 40 pin GPIO header boards:\n"    \
	"    P1-36 (GPIO16) -> CTS (input)\n" \
	"    P1-11 (GPIO17) -> RTS (output)\n" \
	"\nYou may also need to enable flow control in the driver:\n" \
	"    stty -F /dev/ttyAMA0 crtscts\n" \
	);
}

int main(int argc, char *argv[]) {
	if (argc != 2) {
		print_usage();
	} else {
		int  enable = strcmp(argv[1],  "on") == 0;
		int disable = strcmp(argv[1], "off") == 0;
		enable || disable ? set_rts_cts(enable) : print_usage();
	}
	return EXIT_SUCCESS;
}
It needs more investigation. .......

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Tue Sep 21, 2021 12:05 pm

@danjperron - I think you'll find that CTS and RTS work together to manage flow control if you enable them; not checked, but seems likely that they just enable the mode available in the UART. Certainly don't appear able to function as a 'transmitter enable'

A side problem for me is that I don't need or want to dedicate the CTS pin to serial comms - I'm already short of pins.

I did generate a device tree overlay which enabled and set up just TxD, RxD and RTS, and that works in terms of configuring the port pins. But trying to set RS-485 mode fell at the first hurdle.
The most relevant of my research is detailed in the first post of viewtopic.php?f=44&t=318466

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Tue Sep 21, 2021 6:29 pm

@stevend, After trying some settings I came to the same conclusion that you found.

Maybe the way to set it up is to toggle the RTS line manually .

A good example will be libmodbus source code.

Check https://github.com/stephane/libmodbus/ ... dbus-rtu.c line 270 function _modbus_rtu_send.

I will try to implement a simple write function using this technic.

For my self a long time ago I made my own timing derivative hardware using a small PIC.
https://github.com/danjperron/RS485switch

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Tue Sep 21, 2021 8:05 pm

danjperron wrote:
Tue Sep 21, 2021 6:29 pm
@stevend, After trying some settings I came to the same conclusion that you found.

Maybe the way to set it up is to toggle the RTS line manually .

A good example will be libmodbus source code.

Check https://github.com/stephane/libmodbus/ ... dbus-rtu.c line 270 function _modbus_rtu_send.

I will try to implement a simple write function using this technic.

For my self a long time ago I made my own timing derivative hardware using a small PIC.
https://github.com/danjperron/RS485switch
@danjperron Interestingly, we seem to be going down the same route! I also found that code to be very useful guidance, along with some of the pigpio stuff.

I'll be doing something a bit different, since I've already got a well-established serial driver running on an RTOS, and want to add the Pi+Linux as a hardware option. My driver already supports direct control of RTS; the main challenge is proving to be mapping the existing RTOS functions to Linux, since I've not had to dig that deep before.

P.S. It really needs someone to add the functionality to the Linux driver, but I'm certainly not ready for that yet!

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Wed Sep 22, 2021 2:56 pm

danjperron wrote:
Tue Sep 21, 2021 6:29 pm
Check https://github.com/stephane/libmodbus/ ... dbus-rtu.c line 270 function _modbus_rtu_send.
Better is: https://github.com/dhruvvyas90/libmodbu ... dbus-rtu.c - it has some Pi-specific additions in it, which were particularly helpful.

Incidentally, in your serial code example, you should have found that the following line generates an error:

Code: Select all

if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
        /* Error handling. See errno. */
}

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Wed Sep 22, 2021 4:00 pm

Better is: https://github.com/dhruvvyas90/libmodbu ... dbus-rtu.c - it has some Pi-specific additions in it, which were particularly helpful.
Really interresting!


And yes after adding debug to ioctl (fd, TIOCSRS485, &rs485conf) I do have an error. TIOCGRS485 didn't give me an error but I should have add the check for TIOCSRS485 ioctl command.

thanks,

Daniel

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Wed Sep 22, 2021 11:23 pm

@stevend

I found a kernel patch! I don't know How good it is but it is working!
https://patchwork.kernel.org/project/li ... utions.sk/

I re-did the patch (I hope it works!)
oops My patch is temporary removed! I need to add some files in it!
RS485.jpg
RS485.jpg (36.34 KiB) Viewed 1614 times
To compile the kernel https://www.stephenwagner.com/2020/03/1 ... -raspbian/
Be sure to enable rs485 on pl011 in menuconfig! (Device Drivers > Character devices > Serial drivers )

This is my test code
serial2.c

Code: Select all

#include <sys/ioctl.h>
#include <linux/serial.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>




void showRs485(struct serial_rs485 *rs485)
{
printf( "flags = %08X\n",rs485->flags);
printf( "RS485 is %s\n",rs485->flags & 1 == 1 ? " enable" : "disable");
printf( "RTS ON Send is %s\n", (rs485->flags & 2) == 2 ? "1" : "0");
printf( "RTS after Send is %s\n", (rs485->flags & 4) == 4 ? "1" : "0");
printf( "RX during TX is %s\n", (rs485->flags & 8) == 8 ? "true1" : "false");
printf( "delay_rts_before_send is %d ms\n", rs485->delay_rts_before_send);
printf( "delay_rts_after_send is %d ms\n", rs485->delay_rts_after_send);
}




int main(int argc, char ** argv) {

 struct termios tios,old_tios;
 speed_t speed;
 int flags;


  int fd;
  // Open the Port. We want read/write, no "controlling tty" status, and open it no matter what state DCD is in
  fd = open("/dev/serial0", O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
  if (fd < 0) {
    perror("open_port: Unable to open /dev/serial0 - ");
    return(-1);
  }


  // get attribute
  tcgetattr(fd, &old_tios);
  // copy over
  memcpy(&tios,&old_tios, sizeof(struct termios));

  // set speed
  /* Set the baud rate */
    if ((cfsetispeed(&tios, B9600) < 0) ||
        (cfsetospeed(&tios, B9600) < 0)) {
        printf("unable to set baudrate\n");
        close(fd);
        return -1;
    }

  // set  bit and parity
  tios.c_cflag &= ~CSIZE;
  tios.c_cflag |= CS8;     // 8bits
  tios.c_cflag &= ~CSTOPB; // 1 stop bit
  tios.c_cflag &= ~PARENB; // no parity
  tios.c_cflag |= CRTSCTS;

  // raw input
  tios.c_lflag &= ~(ICANON | ECHO | ECHOE |ISIG);
  tios.c_iflag &= ~INPCK;

  // soft control disable
  tios.c_iflag &= ~(IXON | IXOFF | IXANY);



  // Raw OUTPUT
  tios.c_oflag &= ~OPOST;

  tios.c_cc[VMIN] = 0;
  tios.c_cc[VTIME] = 0;

  if (tcsetattr(fd, TCSANOW, &tios) < 0) {
      close(fd);
      printf("unable to set tios attribute\n");
      return -1;
  }



struct serial_rs485 rs485conf;

if (ioctl(fd, TIOCGRS485, &rs485conf) < 0){
 printf("unable to get rs485 config\n");
}

showRs485(&rs485conf);


/* Enable RS485 mode: */
rs485conf.flags |= SER_RS485_ENABLED;

/* Set logical level for RTS pin equal to 1 when sending: */
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
/* or, set logical level for RTS pin equal to 0 when sending: */
//rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);

/* Set logical level for RTS pin equal to 1 after sending: */
//rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
/* or, set logical level for RTS pin equal to 0 after sending: */
rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

/* Set rts delay before send, if needed: */
rs485conf.delay_rts_before_send = 1;

/* Set rts delay after send, if needed: */
rs485conf.delay_rts_after_send = 1;

/* Set this flag if you want to receive data even while sending data */
rs485conf.flags &= ~(SER_RS485_RX_DURING_TX);



if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
   printf("Error writing TIOCSRS485\n");
  // put old attribute back
//  tcsetattr(fd, TCSANOW, &old_tios);
//  close(fd);
// return -1;
}

showRs485(&rs485conf);

char *string1 = "test1\n";
  // Write to the port
for(int loop=0;loop<5;loop++)
{
  int n = write(fd,string1, strlen(string1));
  if (n < 0) {
    perror("Write failed - ");
    return -1;
  }
 usleep(100000);
}


  // put old attribute back
  tcsetattr(fd, TCSANOW, &old_tios);
  close(fd);
  return 0;
}

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Thu Sep 23, 2021 12:22 pm

danjperron wrote:
Wed Sep 22, 2021 11:23 pm
@stevend

I found a kernel patch! I don't know How good it is but it is working!
https://patchwork.kernel.org/project/li ... utions.sk/

I re-did the patch (I hope it works!)
oops My patch is temporary removed! I need to add some files in it!
This looks good news - the kernel should definitely handle things better than anything running in userspace.

I wonder whether RPT could be persuaded to add this patch to their kernel; it's pretty relevant to one type of use for the Pi (and I for one would much rather use a standard kernel than have to learn how to roll my own!)

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Thu Sep 23, 2021 2:16 pm

Ok I fixed my patch.

rs485.patch

Code: Select all

--- linux.stock/arch/arm/configs/bcm2835_defconfig      2021-09-21 22:23:26.892972000 -0400
+++ linux/arch/arm/configs/bcm2835_defconfig    2021-09-22 15:11:14.176220910 -0400
@@ -87,6 +87,7 @@
 CONFIG_SERIAL_8250_BCM2835AUX=y
 CONFIG_SERIAL_AMBA_PL011=y
 CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_AMBA_PL011_SOFT_RS485=y
 CONFIG_SERIAL_DEV_BUS=y
 CONFIG_TTY_PRINTK=y
 CONFIG_I2C_CHARDEV=y
--- linux.stock/drivers/tty/serial/Kconfig      2021-09-21 22:23:58.982599665 -0400
+++ linux/drivers/tty/serial/Kconfig    2021-09-22 15:11:14.176220910 -0400
@@ -73,6 +73,18 @@
          your boot loader (lilo or loadlin) about how to pass options to the
          kernel at boot time.)

+config SERIAL_AMBA_PL011_SOFT_RS485
+       bool "RS485 software direction switching for ARM AMBA PL011 serial"
+       depends on SERIAL_AMBA_PL011=y
+       select SERIAL_CORE
+       help
+         Enable RS485 software direction switching of driver enable (RTS pin)
+         for ARM AMBA PL011 serial. AMBA PL011 does not have HW support for
+         RS485. This driver use 2 hrtimers. One is used for rs485 delays.
+         Secon one is used for polling of TX FIFO. There is not TX FIFO
+         empty interrupt in PL011. Secondary timer is started by empty
+         transmit buffer.
+
 config SERIAL_EARLYCON_ARM_SEMIHOST
        bool "Early console using ARM semihosting"
        depends on ARM64 || ARM
--- linux.stock/drivers/tty/serial/amba-pl011.c 2021-09-21 22:23:58.982599665 -0400
+++ linux/drivers/tty/serial/amba-pl011.c       2021-09-22 15:15:38.484178265 -0400
@@ -14,6 +14,9 @@
  * not have an RI input, nor do they have DTR or RTS outputs.  If
  * required, these have to be supplied via some other means (eg, GPIO)
  * and hooked into this driver.
+ *
+ * Added software RS485 support, 05/jan/2020, Ivan Sistik
+ *     sistik@3ksolutions.sk
  */

 #include <linux/module.h>
@@ -41,6 +44,7 @@
 #include <linux/sizes.h>
 #include <linux/io.h>
 #include <linux/acpi.h>
+#include <linux/math64.h>

 #include "amba-pl011.h"

@@ -55,6 +59,18 @@
 #define UART_DR_ERROR          (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE)
 #define UART_DUMMY_DR_RX       (1 << 16)

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+/*
+ * Enum with current status
+ */
+enum rs485_status {
+       rs485_receiving,
+       rs485_delay_before_send,
+       rs485_sending,
+       rs485_delay_after_send
+};
+#endif
+
 static u16 pl011_std_offsets[REG_ARRAY_SIZE] = {
        [REG_DR] = UART01x_DR,
        [REG_FR] = UART01x_FR,
@@ -266,6 +282,16 @@
        unsigned int            fixed_baud;     /* vendor-set fixed baud rate */
        char                    type[12];
        bool                    irq_locked;     /* in irq, unreleased lock */
+
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+        enum rs485_status       rs485_current_status; /* status used for RTS */
+        enum rs485_status       rs485_next_status; /* this status after tick */
+        struct hrtimer          rs485_delay_timer;
+        struct hrtimer          rs485_tx_empty_poll_timer;
+        unsigned long           send_char_time; /* send char (nanoseconds) */
+        bool                    rs485_last_char_sending;
+#endif
+
 #ifdef CONFIG_DMA_ENGINE
        /* DMA stuff */
        bool                    using_tx_dma;
@@ -276,6 +302,26 @@
 #endif
 };

+
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+
+static void pl011_rs485_start_rts_delay(struct uart_amba_port *uap);
+
+#define RS485_SET_RTS_SIGNAL(pUAP, value)              \
+       do {                                            \
+               unsigned int rts_temp_cr;               \
+               rts_temp_cr = pl011_read(pUAP, REG_CR); \
+               if (!(value))                           \
+                       rts_temp_cr |= UART011_CR_RTS;  \
+               else                                    \
+                       rts_temp_cr &= ~UART011_CR_RTS; \
+               pl011_write(rts_temp_cr, pUAP, REG_CR); \
+       } while (0)
+
+#define RS485_TX_FIFO_EMPTY(pUAP)                      \
+       (pl011_read(pUAP, REG_FR) & UART011_FR_TXFE)
+#endif
+
 static unsigned int pl011_reg_to_offset(const struct uart_amba_port *uap,
        unsigned int reg)
 {
@@ -1295,6 +1341,11 @@
        uap->im &= ~UART011_TXIM;
        pl011_write(uap->im, uap, REG_IMSC);
        pl011_dma_tx_stop(uap);
+
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       if (uap->port.rs485.flags & SER_RS485_ENABLED)
+               pl011_rs485_start_rts_delay(uap);
+#endif
 }

 static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq);
@@ -1313,8 +1364,122 @@
        struct uart_amba_port *uap =
            container_of(port, struct uart_amba_port, port);

-       if (!pl011_dma_tx_start(uap))
-               pl011_start_tx_pio(uap);
+#define START_PL011_TX()                               \
+       do {                                            \
+               if (!pl011_dma_tx_start(uap))           \
+                       pl011_start_tx_pio(uap);        \
+       } while (0)
+
+#ifndef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       START_PL011_TX();
+#else
+
+#define CANCEL_RS485_TIMERS()                                           \
+       do {                                                             \
+               hrtimer_try_to_cancel(&(uap->rs485_delay_timer));        \
+               hrtimer_try_to_cancel(&(uap->rs485_tx_empty_poll_timer));\
+       } while (0)
+
+       if (uap->port.rs485.flags & SER_RS485_ENABLED) {
+               ktime_t ktime;
+
+               switch (uap->rs485_current_status) {
+               case rs485_delay_after_send:
+                       /* stop old delay timer */
+                       CANCEL_RS485_TIMERS();
+
+                       /* check if timer expired */
+                       if (uap->rs485_current_status
+                                       != rs485_delay_after_send) {
+                               /* Timer expired and RTS is in wrong state.*/
+                               uap->rs485_current_status
+                                       = rs485_delay_before_send;
+                               uap->rs485_next_status = rs485_sending;
+
+                               /* Set RTS */
+                               RS485_SET_RTS_SIGNAL(uap,
+                                       uap->port.rs485.flags
+                                               & SER_RS485_RTS_ON_SEND);
+
+                               /* Start timer */
+                               ktime = ktime_set(0,
+                                         uap->port.rs485
+                                               .delay_rts_before_send
+                                         * 1000000L);
+
+                               hrtimer_start(
+                                       &(uap->rs485_delay_timer),
+                                       ktime,
+                                       HRTIMER_MODE_REL);
+                               return;
+                       }
+
+                       /* timer was stopped and driver can continue sending */
+                       uap->rs485_current_status = rs485_sending;
+                       uap->rs485_next_status = rs485_sending;
+
+                       /* driver is already in sending state */
+                       START_PL011_TX();
+                       break;
+
+               case rs485_sending:
+                       /* stop old timer. There can be running timer   */
+                       /* which is checking TX FIFO empty flag         */
+                       CANCEL_RS485_TIMERS();
+
+                       /* driver is already in sending state */
+                       START_PL011_TX();
+                       break;
+
+               case rs485_receiving:
+               default:
+                       /* stop old timer. There can be running timer   */
+                       /* which is checking TX FIFO empty flag         */
+                       CANCEL_RS485_TIMERS();
+
+                       /* Set RTS */
+                       RS485_SET_RTS_SIGNAL(uap,
+                                    uap->port.rs485.flags
+                                            & SER_RS485_RTS_ON_SEND);
+
+                       if (uap->port.rs485.delay_rts_before_send == 0) {
+                               /* Change state */
+                               uap->rs485_current_status
+                                       = rs485_sending;
+                               uap->rs485_next_status
+                                       = rs485_sending;
+
+                               /* driver is in sending state */
+                               START_PL011_TX();
+                               break;
+                       }
+
+                       /* Change state */
+                       uap->rs485_current_status
+                               = rs485_delay_before_send;
+                       uap->rs485_next_status = rs485_sending;
+
+                       /* Start timer */
+                       ktime = ktime_set(0,
+                                 uap->port.rs485.delay_rts_before_send
+                                 * 1000000L);
+                       hrtimer_start(&(uap->rs485_delay_timer),
+                               ktime,
+                               HRTIMER_MODE_REL);
+                       break;
+
+               case rs485_delay_before_send:
+                       /* do nothing because delay timer should be running */
+                       break;
+               }
+       } else {
+               START_PL011_TX();
+       }
+#undef CANCEL_RS485_TIMERS
+
+#endif
+
+#undef START_PL011_TX
 }

 static void pl011_throttle(struct uart_port *port)
@@ -1500,6 +1665,167 @@
        pl011_read(uap, REG_ICR);
 }

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+
+/*
+ * Change state according to pending delay
+ * Locking: port is locked in this function
+ */
+static enum hrtimer_restart
+pl011_rs485_tx_poll_timer(struct hrtimer *timer)
+{
+       unsigned long flags;
+       ktime_t ktime;
+
+       struct uart_amba_port *uap =
+               container_of(timer, struct uart_amba_port,
+                            rs485_tx_empty_poll_timer);
+
+       spin_lock_irqsave(&uap->port.lock, flags);
+
+       if (!(uart_circ_empty(&uap->port.state->xmit))) {
+               spin_unlock_irqrestore(&uap->port.lock, flags);
+               return HRTIMER_NORESTART;
+       }
+
+       if (!RS485_TX_FIFO_EMPTY(uap) || !uap->rs485_last_char_sending) {
+               /*
+                * FIFO is empty but there can be last char in transmit shift
+                * register so we need one more tick
+                */
+               uap->rs485_last_char_sending = RS485_TX_FIFO_EMPTY(uap);
+
+               hrtimer_forward_now(timer, ktime_set(0, uap->send_char_time));
+
+               spin_unlock_irqrestore(&uap->port.lock, flags);
+               return HRTIMER_RESTART;
+       }
+
+       /* Check if delay after send is set*/
+       if (uap->port.rs485.delay_rts_after_send == 0) {
+               /* Change state */
+               uap->rs485_current_status = rs485_receiving;
+               uap->rs485_next_status = rs485_receiving;
+
+               /* if there is no delay after send change RTS value*/
+               RS485_SET_RTS_SIGNAL(uap,
+                            uap->port.rs485.flags
+                                    & SER_RS485_RTS_AFTER_SEND);
+
+               spin_unlock_irqrestore(&uap->port.lock, flags);
+               return HRTIMER_NORESTART;
+       }
+
+       /* Change state */
+       uap->rs485_current_status = rs485_delay_after_send;
+       uap->rs485_next_status = rs485_receiving;
+
+       /* RTS will be set in timer handler */
+
+       /* Start delay timer */
+       ktime = ktime_set(0, (uap->port.rs485.delay_rts_after_send
+                       * 1000000L));
+       hrtimer_start(&(uap->rs485_delay_timer), ktime, HRTIMER_MODE_REL);
+
+       spin_unlock_irqrestore(&uap->port.lock, flags);
+       return HRTIMER_NORESTART;
+}
+
+/*
+ * Change state according to pending delay
+ * Locking: port is locked in this function
+ */
+static enum hrtimer_restart
+pl011_rs485_timer(struct hrtimer *timer)
+{
+       unsigned long flags;
+
+       struct uart_amba_port *uap =
+               container_of(timer, struct uart_amba_port, rs485_delay_timer);
+
+       spin_lock_irqsave(&uap->port.lock, flags);
+
+       if (uap->rs485_current_status == uap->rs485_next_status) {
+               /* timer was canceled or handled */
+               spin_unlock_irqrestore(&uap->port.lock, flags);
+               return HRTIMER_NORESTART;
+       }
+
+       switch (uap->rs485_current_status) {
+       case rs485_delay_before_send:
+               uap->rs485_current_status = rs485_sending;
+               uap->rs485_next_status = rs485_sending;
+               if (!pl011_dma_tx_start(uap))
+                       pl011_start_tx_pio(uap);
+               break;
+
+       case rs485_delay_after_send:
+               uap->rs485_current_status = rs485_receiving;
+               uap->rs485_next_status = rs485_receiving;
+               RS485_SET_RTS_SIGNAL(uap,
+                            uap->port.rs485.flags
+                                    & SER_RS485_RTS_AFTER_SEND);
+               break;
+
+       default:
+               break;
+       }
+
+       spin_unlock_irqrestore(&uap->port.lock, flags);
+       return HRTIMER_NORESTART;
+}
+
+/*
+ * Evaluate transmit buffer status and start delay to off
+ * Locking: called with port lock held and IRQs disabled
+ */
+static void pl011_rs485_start_rts_delay(struct uart_amba_port *uap)
+{
+       ktime_t ktime;
+
+       if (uap->rs485_current_status == rs485_receiving)
+               return;
+
+       /* if there is timeout in progress cancel it and start new */
+       hrtimer_try_to_cancel(&(uap->rs485_delay_timer));
+       hrtimer_try_to_cancel(&(uap->rs485_tx_empty_poll_timer));
+
+
+       if (!RS485_TX_FIFO_EMPTY(uap)
+                       || uap->port.rs485.delay_rts_after_send == 0) {
+               /*
+                * Schedule validation timer if there is data in TX FIFO
+                * because there is not TX FIFO empty interrupt
+                */
+
+               uap->rs485_current_status = rs485_sending;
+               uap->rs485_next_status = rs485_sending;
+
+               uap->rs485_last_char_sending = false;
+
+               ktime = ktime_set(0, uap->send_char_time);
+               hrtimer_start(&(uap->rs485_tx_empty_poll_timer),
+                       ktime,
+                       HRTIMER_MODE_REL);
+               return;
+       }
+
+       /* Change state */
+       uap->rs485_current_status = rs485_delay_after_send;
+       uap->rs485_next_status = rs485_receiving;
+
+       /* RTS will be set in timer handler */
+
+       /* Start timer */
+       ktime = ktime_set(0, (uap->port.rs485.delay_rts_after_send
+                       * 1000000L));
+
+       hrtimer_start(&(uap->rs485_delay_timer),
+               ktime,
+               HRTIMER_MODE_REL);
+}
+#endif
+
 static irqreturn_t pl011_int(int irq, void *dev_id)
 {
        struct uart_amba_port *uap = dev_id;
@@ -1643,6 +1969,11 @@
         */
        pl011_write(pl011_read(uap, REG_IMSC) & ~UART011_TXIM, uap,
                    REG_IMSC);
+
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       if (uap->port.rs485.flags & SER_RS485_ENABLED)
+               pl011_rs485_start_rts_delay(uap);
+#endif
 }

 static int pl011_get_poll_char(struct uart_port *port)
@@ -1732,6 +2063,27 @@
                if (plat->init)
                        plat->init();
        }
+
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       /*
+        * Initialize timers used for RS485
+        */
+       hrtimer_init(&(uap->rs485_delay_timer),
+               CLOCK_MONOTONIC,
+               HRTIMER_MODE_REL);
+
+       uap->rs485_delay_timer.function = &pl011_rs485_timer;
+
+       hrtimer_init(&(uap->rs485_tx_empty_poll_timer),
+               CLOCK_MONOTONIC,
+               HRTIMER_MODE_REL);
+
+       uap->rs485_tx_empty_poll_timer.function = &pl011_rs485_tx_poll_timer;
+
+       uap->rs485_current_status = rs485_receiving;
+       RS485_SET_RTS_SIGNAL(uap, false);
+#endif
+
        return 0;
 }

@@ -1915,6 +2267,16 @@
        struct uart_amba_port *uap =
                container_of(port, struct uart_amba_port, port);

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       if (uap->port.rs485.flags & SER_RS485_ENABLED) {
+               hrtimer_try_to_cancel(&(uap->rs485_delay_timer));
+               hrtimer_try_to_cancel(&(uap->rs485_tx_empty_poll_timer));
+
+               uap->rs485_current_status = rs485_receiving;
+               RS485_SET_RTS_SIGNAL(uap, true);
+       }
+#endif
+
        pl011_disable_interrupts(uap);

        pl011_dma_shutdown(uap);
@@ -1997,6 +2359,24 @@
        unsigned long flags;
        unsigned int baud, quot, clkdiv;

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       unsigned int transfer_bit_count;
+       unsigned long char_transfer_time;
+
+       /*
+        * Calculate bit count which will be send
+        * by UART. It is used for calculation of
+        * time required to start timer until TX FIFO (HW) is empty
+        * There is not interrupt for FIFO empty in PL011.
+        * There is only FIFO empty flag in REG_FR.
+        */
+       transfer_bit_count = 0;
+
+#define        ADD_DATA_BITS(bits)     (transfer_bit_count += bits)
+#else
+#define        ADD_DATA_BITS(bits)
+#endif
+
        if (uap->vendor->oversampling)
                clkdiv = 8;
        else
@@ -2023,29 +2403,53 @@
        switch (termios->c_cflag & CSIZE) {
        case CS5:
                lcr_h = UART01x_LCRH_WLEN_5;
+               ADD_DATA_BITS(7);
                break;
        case CS6:
                lcr_h = UART01x_LCRH_WLEN_6;
+               ADD_DATA_BITS(8);
                break;
        case CS7:
                lcr_h = UART01x_LCRH_WLEN_7;
+               ADD_DATA_BITS(9);
                break;
        default: // CS8
                lcr_h = UART01x_LCRH_WLEN_8;
+               ADD_DATA_BITS(10);
                break;
        }
-       if (termios->c_cflag & CSTOPB)
+
+       if (termios->c_cflag & CSTOPB) {
                lcr_h |= UART01x_LCRH_STP2;
+               ADD_DATA_BITS(1);
+       }
+
        if (termios->c_cflag & PARENB) {
                lcr_h |= UART01x_LCRH_PEN;
+               ADD_DATA_BITS(1);
+
                if (!(termios->c_cflag & PARODD))
                        lcr_h |= UART01x_LCRH_EPS;
+
                if (termios->c_cflag & CMSPAR)
                        lcr_h |= UART011_LCRH_SPS;
        }
+
+#undef ADD_DATA_BITS
+
        if (uap->fifosize > 1)
                lcr_h |= UART01x_LCRH_FEN;

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       /* Calculate time required to send one char (nanoseconds) */
+       char_transfer_time =
+               (unsigned long) div_u64(
+                               mul_u32_u32(
+                                       (u32)transfer_bit_count,
+                                       (u32)NSEC_PER_SEC),
+                               (u32)baud);
+#endif
+
        spin_lock_irqsave(&port->lock, flags);

        /*
@@ -2053,6 +2457,7 @@
         */
        uart_update_timeout(port, termios->c_cflag, baud);

+
        pl011_setup_status_masks(port, termios);

        if (UART_ENABLE_MS(port, termios->c_cflag))
@@ -2062,6 +2467,11 @@
        old_cr = pl011_read(uap, REG_CR);
        pl011_write(0, uap, REG_CR);

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       /* Update send_char_time in locked context */
+       uap->send_char_time = char_transfer_time;
+#endif
+
        if (termios->c_cflag & CRTSCTS) {
                if (old_cr & UART011_CR_RTS)
                        old_cr |= UART011_CR_RTSEN;
@@ -2133,6 +2543,7 @@
 {
        struct uart_amba_port *uap =
            container_of(port, struct uart_amba_port, port);
+
        return uap->port.type == PORT_AMBA ? uap->type : NULL;
 }

@@ -2165,6 +2576,44 @@
 }

 /*
+ * Configure RS485
+ * Locking: called with port lock held and IRQs disabled
+ */
+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+static int pl011_config_rs485(struct uart_port *port,
+                             struct serial_rs485 *rs485)
+{
+       struct uart_amba_port *uap =
+                       container_of(port, struct uart_amba_port, port);
+
+       port->rs485.flags = rs485->flags;
+       port->rs485.delay_rts_after_send = rs485->delay_rts_after_send;
+       port->rs485.delay_rts_before_send = rs485->delay_rts_before_send;
+
+       if (port->rs485.flags & SER_RS485_ENABLED) {
+               unsigned int cr;
+
+               hrtimer_try_to_cancel(&(uap->rs485_delay_timer));
+               hrtimer_try_to_cancel(&(uap->rs485_tx_empty_poll_timer));
+
+               /* If RS485 is enabled, disable auto RTS */
+               cr = pl011_read(uap, REG_CR);
+               cr &= ~UART011_CR_RTSEN;
+               pl011_write(cr, uap, REG_CR);
+
+               uap->rs485_current_status = rs485_receiving;
+               RS485_SET_RTS_SIGNAL(uap,
+                            port->rs485.flags
+                                    & SER_RS485_RTS_AFTER_SEND);
+       } else {
+               RS485_SET_RTS_SIGNAL(uap, true);
+       }
+
+       return 0;
+}
+#endif
+
+/*
  * verify the new serial_struct (for TIOCSSERIAL).
  */
 static int pl011_verify_port(struct uart_port *port, struct serial_struct *ser)
@@ -2735,6 +3184,12 @@
        uap->port.irq = dev->irq[0];
        uap->port.ops = &amba_pl011_pops;

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+       uap->port.rs485_config = &pl011_config_rs485;
+       uap->port.rs485.flags = 0;      /* RS485 is not enabled by default */
+       dev_info(&dev->dev, "Software switching for RS485 enabled\n");
+#endif
+
        snprintf(uap->type, sizeof(uap->type), "PL011 rev%u", amba_rev(dev));

        ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr);
@@ -2908,10 +3363,15 @@

 static int __init pl011_init(void)
 {
+#ifndef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
        printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
+#else
+       printk(KERN_INFO "Serial: AMBA PL011 UART driver with soft RS485 support\n");
+#endif

        if (platform_driver_register(&arm_sbsa_uart_platform_driver))
                pr_warn("could not register SBSA UART platform driver\n");
+
        return amba_driver_register(&pl011_driver);
 }

@@ -2921,6 +3381,11 @@
        amba_driver_unregister(&pl011_driver);
 }

+#ifdef CONFIG_SERIAL_AMBA_PL011_SOFT_RS485
+#undef RS485_SET_RTS_SIGNAL
+#undef RS485_TX_FIFO_EMPTY
+#endif
+
 /*
  * While this can be a module, if builtin it's most likely the console
  * So let's leave module_exit but move module_init to an earlier place
Once you clone the linux kernel, step 3 of the posted webpage, patch the kernel using

Code: Select all

cd linux
patch -p1 <rs485.patch
Continue the steps until you run "make menuconfig". At this step you need to activate RS485.

Code: Select all

[*]    RS485 software direction switching for ARM AMBA PL011 serial
it is found in (Device Drivers > Character devices > Serial drivers ).

Continue with the rest and don't forget sudo for step 7,8 and 9. (if your not root, I'm using pi).

good luck.


N.B. on my test code serial2.c, I should have disabled RS-485 when it close.

Daniel

danjperron
Posts: 3891
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Sending data using RS485

Thu Oct 07, 2021 7:19 pm

@Honey,
And what it is suppose to do ????

stevend
Posts: 441
Joined: Fri Oct 11, 2013 12:28 pm

Re: Sending data using RS485

Fri Oct 08, 2021 12:49 pm

Perhaps slightly off-topic, but I've just rediscovered this extremely comprehensive guide to RS-422/485 comms

Return to “Other programming languages”