2
votes

I'm trying to communicate with several Modbus RTU devices through one usb to RS232 to RS485 port at baudrate 38400, 1 start bit, 8databits, no parity and 1 stop bit.

Communication processes with one Modbus RTU device is as follows:

  1. Send 8 bytes to the device;
  2. wait for replies from the device;
  3. Receive 23 bytes of replies.

According to my calculation and the digital oscilloscope, send 8 bytes costs 2.083ms, receive 23 bytes costs 5.99ms, response time of the Modbus RTU device is about 1.3ms. So time of the communication process costs 9.373ms in total. But in my test program I found the average communication time is about 15ms (10000 times average). I wonder where does the additional 5 more milliseconds come from and how could I optimize my program to reduce this time.

Thanks in advance!

The test program is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

void print_hex_buf(unsigned char *buffer, int size)
{
    for (int i=0; i<size; i++)
    {
        printf("%02x ", buffer[i]);
    }
    printf("\n");
}

void diff_time(struct timeval t1, struct timeval t2, struct timeval *diff)
{
    time_t sec;
    suseconds_t usec;

    //time in two different days
    if (t1.tv_sec > t2.tv_sec)
        sec = t2.tv_sec + 24*60*60 - t1.tv_sec;
    else
        sec = t2.tv_sec - t1.tv_sec;

    usec = t2.tv_usec - t1.tv_usec;
    if (usec < 0)
    {
        sec -= 1;
        usec += 1000000;
    }

    diff->tv_sec = sec;
    diff->tv_usec = usec;
}

int serial_write(int uart_fd, char *buffer, int size)
{
    int count = 0;

    count = write(uart_fd, buffer, size);
    return count;
}

int serial_read(int uart_fd, char *buffer, int size)
{
    int count = 0;
    int bytes_read = 0;
    int read_retry = 0;

    fd_set fds_read;
    struct timeval timeout;
    FD_ZERO(&fds_read);
    FD_SET(uart_fd, &fds_read);

    timeout.tv_sec = 0;
    timeout.tv_usec = 500000;   //500ms

    int ret = select(uart_fd + 1, &fds_read, NULL, NULL, &timeout);
    if (ret > 0 && FD_ISSET(uart_fd, &fds_read))
    {
        count = read(uart_fd, buffer, size);
        bytes_read = (count > 0)?count:0;
        while (bytes_read < size && read_retry++ < 500)
        {
            count = read(uart_fd, buffer+bytes_read, size-bytes_read);
            bytes_read += (count > 0)?count:0;
            if (bytes_read >= size)
                break;
        }
    }
    else
    {
        printf("Failed to from uart!\n");
        return -1;
    }

    return bytes_read;
}

int main(int argc, char** argv)
{
    int fd;
    struct termios opt;
    int count;
    unsigned char send_buf[] = { 0x01, 0x04, 0x00, 0x00, 0x00, 0x09, 0x30, 0x0c};
    unsigned char buffer[256];
    int iteration = 0;
    int delay_ms = 0;
    int err_count = 0;
    int cycle = 0;
    suseconds_t average_time = 0;

    setbuf(stdout, NULL);

    if (argc != 3)
    {
        printf("Usage: testuart [uart device] [iteration]\n");
        return 0;
    }

    iteration = atoi(argv[2]);

    fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)
    {
        printf("Failed to open port: %s\n", argv[1]);
        return -1;
    }

    if (tcgetattr(fd, &opt) != 0)
    {
        printf("Failed to get uart attribute!\n");
        return -1;
    }

    opt.c_cflag = B38400|CS8|CREAD|CLOCAL;
    opt.c_iflag = IGNPAR;
    opt.c_cflag &= ~PARENB;
    opt.c_cflag &= ~PARODD;
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    opt.c_oflag &= ~OPOST;
    opt.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);

    tcflush(fd, TCIFLUSH);

    if (tcsetattr(fd, TCSANOW, &opt) != 0)
    {
        printf("Failed to setup serial port!\n");
        close(fd);
        return -1;
    }

    while (cycle++ < iteration)
    {
        printf("Send hex command:\n");
        print_hex_buf(send_buf, 8);
        struct timeval tm_start;
        struct timeval tm_end;
        struct timeval tm_diff;
        gettimeofday(&tm_start, NULL);
        count = serial_write(fd, send_buf, 8);
        if (count != 8)
        {
            printf("Failed to write 8 bytes!\n");
            close(fd);
            return -1;
        }

        count = serial_read(fd, buffer, 23);
        if (count <= 0)
        {
            printf("serial read returns %d\n", count);
            close(fd);
            return -1;
        }

        gettimeofday(&tm_end, NULL);
        diff_time(tm_start, tm_end, &tm_diff);

        print_hex_buf(buffer, count);
        printf("serial communication costs %ld.%06ld seconds.\n",
                                tm_diff.tv_sec, tm_diff.tv_usec);
        average_time = ((average_time*(cycle-1))+tm_diff.tv_usec)/cycle;
    }

    printf("%d times, average time in usec is %ld\n", cycle-1, average_time);
    close(fd);
    return 0;
}
1
My platform OS is OpenWrt, Linux kernel 4.14.37.Walker
Your calculated time is a number that your application program can never achieve. The total time you measure includes system overhead and process suspension. Some of this time can be reduced. See stackoverflow.com/questions/4667141/… Is your kernel configured with preemption? The use of nonblocking I/O probably doesn't help as much as you think. Is your RS485 full duplex?sawdust
Is there a reason you spin on the read() call instead of calling select() again after a partial read? That would give you a more correct implementation; you don't know how long 500 retries will take. It could be very fast.janm
@sawdust Thanks for your reply! The kernel is configured with "No Forced Preemption (Server)". The use of nonblocking I/O is just incase of the Modbus RTU device doesn't replies for some reason. My RS485 is half duplex.Walker
@janm The 500 retries (with 1ms delay for each retry) is used before I add the select function, and I forgot to remove it. The first read function could work when the select function returns, so it won't go to the while loop. The Modbus RTU device only replies once after I send it a command.Walker

1 Answers

0
votes

Thanks to sawdust! The following link helps! The average time has reduced from 15ms to 10ms. High delay in RS232 communication on a PXA270