Closed fjuliofontes closed 1 month ago
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
That is true, however the DE/nDE control is still done by software. What essentially does the configuration to RS485? Because from what I understand the configuration is usually used to enable the RTS control and since the RTS pin is not the same as the one actually connected to the DE/nRE pin I'm not sure what will this improve.
At first, it was because the IO was insufficient, which led to the RTS being occupied. Now we are considering whether to correct it by modifying the hardware.
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
That is true, however the DE/nDE control is still done by software. What essentially does the configuration to RS485? Because from what I understand the configuration is usually used to enable the RTS control and since the RTS pin is not the same as the one actually connected to the DE/nRE pin I'm not sure what will this improve.
After configuring the serial port to RS485 mode, the serial port will send data in blocking mode instead of DMA, so tcdrain() is no longer needed.
ing the hardware
that would be perfect...
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
That is true, however the DE/nDE control is still done by software. What essentially does the configuration to RS485? Because from what I understand the configuration is usually used to enable the RTS control and since the RTS pin is not the same as the one actually connected to the DE/nRE pin I'm not sure what will this improve.
After configuring the serial port to RS485 mode, the serial port will send data in blocking mode instead of DMA, so tcdrain() is no longer needed.
Not sure if that works. From the tests that I did it never worked. Could it be because of OS versions? I'm running on Balena OS and the only way that I got it to work (with 10% error rate) was by doing nanosleep() after the write() of the number of bytes transmitted multiplied by the byte time. I'm running with 19200 baud.
This is how I'm configuring the UART port. I'm running everything within C code to try to reduce to the minimum the latency.
struct termios tty;
// setup serial port now
int serial_port = open(tty_name, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_port < 0)
{
printf("Error while configuring serial port: %s\n", tty_name);
fflush(stdout);
return 0;
}
// get serial port configuration
if (tcgetattr(serial_port, &tty) != 0)
{
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
fflush(stdout);
return 0;
}
// update serial port settings
tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity
tty.c_cflag |= PARENB; // Enable parity
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication
tty.c_cflag &= ~CSIZE; // Clear all the size bits
tty.c_cflag |= CS8; // 8 bits per byte
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
// In canonical mode, input is processed when a new line character is received.
tty.c_lflag &= ~ICANON; // Canonical mode is disabled
tty.c_lflag &= ~ECHO; // Disable echo
tty.c_lflag &= ~ECHOE; // Disable erasure
tty.c_lflag &= ~ECHONL; // Disable new-line echo
tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)
tty.c_cc[VTIME] = 0; // wait up to 100ms
tty.c_cc[VMIN] = 0;
// set rs485 mode
struct serial_rs485 rs485conf = {0};
rs485conf.flags |= SER_RS485_ENABLED;
if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) {
perror("ioctl");
return 1;
}
// Set in/out baud rate to be LINE_BAUDRATE bps
cfsetispeed(&tty, LINE_BAUDRATE);
cfsetospeed(&tty, LINE_BAUDRATE);
// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0)
{
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
fflush(stdout);
return 0;
}
With the code block:
// set rs485 mode
struct serial_rs485 rs485conf = {0};
rs485conf.flags |= SER_RS485_ENABLED;
if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) {
perror("ioctl");
return 1;
}
Or without it the results are the same...
We have also conducted tests on RS485 with Balena OS, and we currently have two ways to solve this issue. 1. Add a kernel patch. You can refer to this repository. 2. Continue using the rs485_de tool. You can refer to this reference.
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
That is true, however the DE/nDE control is still done by software. What essentially does the configuration to RS485? Because from what I understand the configuration is usually used to enable the RTS control and since the RTS pin is not the same as the one actually connected to the DE/nRE pin I'm not sure what will this improve.
After configuring the serial port to RS485 mode, the serial port will send data in blocking mode instead of DMA, so tcdrain() is no longer needed.
Not sure if that works. From the tests that I did it never worked. Could it be because of OS versions? I'm running on Balena OS and the only way that I got it to work (with 10% error rate) was by doing nanosleep() after the write() of the number of bytes transmitted multiplied by the byte time. I'm running with 19200 baud.
This is how I'm configuring the UART port. I'm running everything within C code to try to reduce to the minimum the latency.
struct termios tty; // setup serial port now int serial_port = open(tty_name, O_RDWR | O_NOCTTY | O_NDELAY); if (serial_port < 0) { printf("Error while configuring serial port: %s\n", tty_name); fflush(stdout); return 0; } // get serial port configuration if (tcgetattr(serial_port, &tty) != 0) { printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); fflush(stdout); return 0; } // update serial port settings tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity tty.c_cflag |= PARENB; // Enable parity tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication tty.c_cflag &= ~CSIZE; // Clear all the size bits tty.c_cflag |= CS8; // 8 bits per byte tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) // In canonical mode, input is processed when a new line character is received. tty.c_lflag &= ~ICANON; // Canonical mode is disabled tty.c_lflag &= ~ECHO; // Disable echo tty.c_lflag &= ~ECHOE; // Disable erasure tty.c_lflag &= ~ECHONL; // Disable new-line echo tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX) // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX) tty.c_cc[VTIME] = 0; // wait up to 100ms tty.c_cc[VMIN] = 0; // set rs485 mode struct serial_rs485 rs485conf = {0}; rs485conf.flags |= SER_RS485_ENABLED; if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) { perror("ioctl"); return 1; } // Set in/out baud rate to be LINE_BAUDRATE bps cfsetispeed(&tty, LINE_BAUDRATE); cfsetospeed(&tty, LINE_BAUDRATE); // Save tty settings, also checking for error if (tcsetattr(serial_port, TCSANOW, &tty) != 0) { printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); fflush(stdout); return 0; }
With the code block:
// set rs485 mode struct serial_rs485 rs485conf = {0}; rs485conf.flags |= SER_RS485_ENABLED; if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) { perror("ioctl"); return 1; }
Or without it the results are the same...
I think it seems like the RS485 mode hasn't been successfully set. You can try updating the device using the new DTO because in the new DTO, RS485 mode is enabled by default.You can also configure the serial port tool (minicom) to RS485 mode first and conduct a transmission and reception test.
In this commit, RS485_DE configures the serial port to RS485 mode and removes tcdrain().
That is true, however the DE/nDE control is still done by software. What essentially does the configuration to RS485? Because from what I understand the configuration is usually used to enable the RTS control and since the RTS pin is not the same as the one actually connected to the DE/nRE pin I'm not sure what will this improve.
After configuring the serial port to RS485 mode, the serial port will send data in blocking mode instead of DMA, so tcdrain() is no longer needed.
Not sure if that works. From the tests that I did it never worked. Could it be because of OS versions? I'm running on Balena OS and the only way that I got it to work (with 10% error rate) was by doing nanosleep() after the write() of the number of bytes transmitted multiplied by the byte time. I'm running with 19200 baud. This is how I'm configuring the UART port. I'm running everything within C code to try to reduce to the minimum the latency.
struct termios tty; // setup serial port now int serial_port = open(tty_name, O_RDWR | O_NOCTTY | O_NDELAY); if (serial_port < 0) { printf("Error while configuring serial port: %s\n", tty_name); fflush(stdout); return 0; } // get serial port configuration if (tcgetattr(serial_port, &tty) != 0) { printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); fflush(stdout); return 0; } // update serial port settings tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity tty.c_cflag |= PARENB; // Enable parity tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication tty.c_cflag &= ~CSIZE; // Clear all the size bits tty.c_cflag |= CS8; // 8 bits per byte tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) // In canonical mode, input is processed when a new line character is received. tty.c_lflag &= ~ICANON; // Canonical mode is disabled tty.c_lflag &= ~ECHO; // Disable echo tty.c_lflag &= ~ECHOE; // Disable erasure tty.c_lflag &= ~ECHONL; // Disable new-line echo tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX) // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX) tty.c_cc[VTIME] = 0; // wait up to 100ms tty.c_cc[VMIN] = 0; // set rs485 mode struct serial_rs485 rs485conf = {0}; rs485conf.flags |= SER_RS485_ENABLED; if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) { perror("ioctl"); return 1; } // Set in/out baud rate to be LINE_BAUDRATE bps cfsetispeed(&tty, LINE_BAUDRATE); cfsetospeed(&tty, LINE_BAUDRATE); // Save tty settings, also checking for error if (tcsetattr(serial_port, TCSANOW, &tty) != 0) { printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); fflush(stdout); return 0; }
With the code block:
// set rs485 mode struct serial_rs485 rs485conf = {0}; rs485conf.flags |= SER_RS485_ENABLED; if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) { perror("ioctl"); return 1; }
Or without it the results are the same...
I think it seems like the RS485 mode hasn't been successfully set. You can try updating the device using the new DTO because in the new DTO, RS485 mode is enabled by default.You can also configure the serial port tool (minicom) to RS485 mode first and conduct a transmission and reception test.
I can try the new DTO. Testing modbus RUT with minicom is not easy. You have to dump the frame in hex mode. But I will test the new DTO. Where can I get the compiled version "dtbo" for this new DTO? Since I'm running on Balena OS I need to add it compiled I think.
Here is the newly compiled gtbo file. You can try it.
I tested the new dtbo and without the nanosleep function after transmitting the communication won't work. I can connect the enable pin to an oscilloscope if needed but I guess that the enable pin is being toggled before the bytes are copied from the buffers to the UART.
Are you sure that the UART is not in DMA after applying the dtbo?
Here is the newly compiled gtbo file. You can try it.
I tested the new dtbo and without the nanosleep function after transmitting the communication won't work. I can connect the enable pin to an oscilloscope if needed but I guess that the enable pin is being toggled before the bytes are copied from the buffers to the UART.
Are you sure that the UART is not in DMA after applying the dtbo?
In this dtbo, the serial port is configured as RS485 mode by default, and in this mode, the serial port sends data in a blocking manner unless your serial port tool modifies it. For example, if you're using minicom, it will turn off the RS485 mode by default when opening the serial port, so please ensure that your program or serial port tool has not reconfigured the serial port. You can directly use the echo and cat commands to test the serial port at different baud rates. Additionally, with the latest dtbo and rs485_de tools, we can utilize Modbus at 115200 baud rate or higher within node_red.
This is a demo I modified based on your code. It sends the data received by RX out through TX.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <gpiod.h>
#include <pty.h>
#include <linux/serial.h>// setup serial port now
#define LINE_BAUDRATE B115200
#define BUFFER_SIZE 512
#define RS485_CONSUMER "RS485_program"
struct gpiod_chip *chip;
struct gpiod_line *line;
void setup_gpio(char *rs485_de_chip, int rs485_de_line)
{
int ret;
// open GPIO controller
chip = gpiod_chip_open(rs485_de_chip);
if (!chip) {
fprintf(stderr, "gpiod_chip_open");
exit(EXIT_FAILURE);
}
// request GPIO line
line = gpiod_chip_get_line(chip, rs485_de_line);
if (!line) {
fprintf(stderr, "Failed to get GPIO line %d\n", rs485_de_line);
gpiod_chip_close(chip);
exit(EXIT_FAILURE);
}
// set GPIO as output
ret = gpiod_line_request_output(line, RS485_CONSUMER, 0);
if (ret < 0) {
fprintf(stderr, "Failed to request GPIO line as output: %s\n", strerror(-ret));
gpiod_chip_close(chip);
exit(EXIT_FAILURE);
}
}
void toggle_gpio_high() {
gpiod_line_set_value(line, 1);
}
void toggle_gpio_low() {
gpiod_line_set_value(line, 0);
}
int main(int argc, char* argv[])
{
int serial_port, ready, rs485_de_line;
struct termios tty;
fd_set readfds, writefds;
char *rs485_de_chip, *tty_name;
ssize_t n;
char buffer[BUFFER_SIZE];
struct timeval timeout;
if(argc < 4) {
printf("Usage: %s /dev/ttyAMA* DE_CHIP(/dev/gpiochip*) DE_LINE \n", argv[0]);
return 1;
}
tty_name = argv[1];
rs485_de_chip = argv[2];
rs485_de_line = atoi(argv[3]);
setup_gpio(rs485_de_chip, rs485_de_line);
serial_port = open(tty_name, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_port < 0)
{
printf("Error while configuring serial port: %s\n", tty_name);
fflush(stdout);
return 0;
}
// get serial port configuration
if (tcgetattr(serial_port, &tty) != 0)
{
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
fflush(stdout);
return 0;
}
tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity
tty.c_cflag |= PARENB; // Enable parity
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication
tty.c_cflag &= ~CSIZE; // Clear all the size bits
tty.c_cflag |= CS8; // 8 bits per byte
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
// In canonical mode, input is processed when a new line character is received.
tty.c_lflag &= ~ICANON; // Canonical mode is disabled
tty.c_lflag &= ~ECHO; // Disable echo
tty.c_lflag &= ~ECHOE; // Disable erasure
tty.c_lflag &= ~ECHONL; // Disable new-line echo
tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)
tty.c_cc[VTIME] = 0; // wait up to 100ms
tty.c_cc[VMIN] = 0;
// set rs485 mode
struct serial_rs485 rs485conf = {0};
rs485conf.flags |= SER_RS485_ENABLED;
if (ioctl (serial_port, TIOCSRS485, &rs485conf) < 0) {
perror("ioctl");
return 1;
}
// Set in/out baud rate to be LINE_BAUDRATE bps
// tcgetattr(serial_port, &tty);
cfmakeraw(&tty);
cfsetispeed(&tty, LINE_BAUDRATE);
cfsetospeed(&tty, LINE_BAUDRATE);
// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0)
{
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
fflush(stdout);
return 0;
}
int num = 0;
while (1) {
FD_ZERO(&readfds);
FD_SET(serial_port, &readfds);
timeout.tv_sec = 0; // wait 0 second
timeout.tv_usec = 500; // wait 200 micro second
ready = select(serial_port + 1, &readfds, NULL, NULL, &timeout);
if (ready == -1) {
perror("select");
break;
}
// 判断是否还有数据接收
else if (ready == 0) {
if (num > 0) {
// send data to serial port
toggle_gpio_high();
write(serial_port, buffer, num);
toggle_gpio_low();
num = 0;
}
continue;
}
if (FD_ISSET(serial_port, &readfds)) {
// data from serial port is ready to read
n = read(serial_port, buffer + num, BUFFER_SIZE - num);
num += n;
buffer[num] = '\0';
}
}
// close serial port
close(serial_port);
return 0;
}
Here are my test results
The speed of the DE/nRE pin is sufficient. The problem is that you should not send data when receiving data. The program should adopt a half-duplex working mode.
Thank you, I will test and comment. For now since I've a pilot this week I've switched for two external USB-RS485 dongles. I also heard that a new redesign is expected to switch the DE/nRE pin for the RTS pin of the given UART, so I'm not sure if I should waste much more time on this. The program that I'm doing requires a error rate less than 5% since it will read counters and if I fail to read them I will lose data points. With my previous version I was having a error rate of 10%.
I've been using the reComputer R1000 for a while and I do see some use cases for it, but unfortunately my application requires modbus RUT over RS485 and I can't make it work with software DE/nRE pin driving. The tcdrain() function takes a lot of time and when the driver switches to receiving mode the response was gone already.
I think it is a bit shame having the designs not using the proper RTS pins for the UARTS of the CM4 module. The chips are there but they are simple not usable.
I'm writing this to make the developers aware of the issue and with the hope for a version 2 re-design that would make possible to enable rtscts directly and having the DE/nRE pin being directly driven on the hardware layer.