NoS89
NoS89

Reputation: 81

Random pauses between consecutive UART transmissions

I'm trying to develop a LIN bus master using the example provided here:

https://github.com/trainman419/linux-lin/tree/master/misc/tty_lin_master

Essentially this sends out LIN protocol messages over a serial port.

I changed the code a little bit to make it simpler for low level functionality testing. I want to see if a LIN analyzer will properly decode a very primitive LIN message, but I ran into weird issues that pertain to the serial port. I'm sending several consecutive characters over /dev/ttymxc4 (RS-232) interface, but I'm seeing a pause randomly somewhere in the middle on the packet transmission. Interestingly enough, this pause starts at some value, I captured 8.6ms but then gradually shrinks until it's gone.. but then it starts up again.

Essentially if you look at main, I'm literally just sending 10 characters over RS-232...

Here's the code if anyone has any ideas:

/*
 * UART-LIN master implementation
 */

 #define USE_TERMIOS2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h> /* clock_nanosleep */
#include <getopt.h>

#ifndef USE_TERMIOS2
  #include <linux/serial.h> /* struct struct_serial */
  #include <termios.h>
#else /*USE_TERMIOS2*/
  #include <asm/ioctls.h>
  #include <asm/termbits.h>
#endif /*USE_TERMIOS2*/

#include "lin_common.h"

#define LIN_HDR_SIZE        2

struct sllin_tty {
    int tty_fd;

#ifndef USE_TERMIOS2
    struct termios tattr_orig;
    struct termios tattr;
    struct serial_struct sattr;
#else /*USE_TERMIOS2*/
    struct termios2 tattr_orig;
    struct termios2 tattr;
#endif /*USE_TERMIOS2*/
};

struct sllin_tty sllin_tty_data;

struct sllin sllin_data = {
    .tty = &sllin_tty_data,
};

/* ------------------------------------------------------------------------ */

#ifndef USE_TERMIOS2

static int tty_set_baudrate(struct sllin_tty *tty, int baudrate)
{
    /* Set "non-standard" baudrate in serial_struct struct */
    tty->sattr.flags &= (~ASYNC_SPD_MASK);
    tty->sattr.flags |= (ASYNC_SPD_CUST);
    tty->sattr.custom_divisor = (tty->sattr.baud_base + baudrate / 2) / baudrate;
    if (ioctl(tty->tty_fd, TIOCSSERIAL, &tty->sattr) < 0)
    {
        perror("ioctl TIOCSSERIAL");
        return -1;
    }

    return 0;
}

static int tty_flush(struct sllin_tty *tty, int queue_selector)
{
    return tcflush(tty->tty_fd, queue_selector);
}

#else /*USE_TERMIOS2*/

static int tty_set_baudrate(struct sllin_tty *tty, int baudrate)
{
    tty->tattr.c_ospeed = baudrate;
    tty->tattr.c_ispeed = baudrate;
    tty->tattr.c_cflag &= ~CBAUD;
    tty->tattr.c_cflag |= BOTHER;

    if(ioctl(tty->tty_fd, TCSETS2, &tty->tattr)) {
        perror("ioctl TIOCSSERIAL");
        return -1;
    }

    return 0;
}

static int tty_flush(struct sllin_tty *tty, int queue_selector)
{
    return ioctl(tty->tty_fd, TCFLSH, queue_selector);
}

#endif /*USE_TERMIOS2*/


static int tty_set_mode(struct sllin_tty *tty, int baudrate)
{
    if(!isatty(tty->tty_fd)) {
        fprintf(stderr, "Not a terminal.\n");
        return -1;
    }

    /* Flush input and output queues. */
    if (tty_flush(tty, TCIOFLUSH) != 0) {
        perror("tcflush");
        return -1;;
    }

#ifndef USE_TERMIOS2

    /* Save settings for later restoring */
    if (tcgetattr(tty->tty_fd, &tty->tattr_orig) < 0) {
        perror("tcgetattr");
        return -1;
    }

    /* Save settings into global variables for later use */
    if (tcgetattr(tty->tty_fd, &tty->tattr) < 0) {
        perror("tcgetattr");
        return -1;
    }

    /* Save settings into global variables for later use */
    if (ioctl(tty->tty_fd, TIOCGSERIAL, &tty->sattr) < 0) {
        perror("ioctl TIOCGSERIAL");
    }

#else /*USE_TERMIOS2*/

    /* Save settings for later restoring */
    if (ioctl(tty->tty_fd, TCGETS2, &tty->tattr_orig) < 0) {
        perror("ioctl TCGETS2");
        return -1;
    }

    /* Save settings into global variables for later use */
    if (ioctl(tty->tty_fd, TCGETS2, &tty->tattr) < 0) {
        perror("ioctl TCGETS2");
        return -1;
    }

#endif /*USE_TERMIOS2*/

    /* 8 data bits                  */
    /* Enable receiver              */
    /* Ignore CD (local connection) */
    tty->tattr.c_cflag = CS8 | CREAD | CLOCAL;
    tty->tattr.c_iflag = 0;
    tty->tattr.c_oflag = NL0 | CR0 | TAB0 | BS0 | VT0 | FF0;
    tty->tattr.c_lflag = 0;

    tty->tattr.c_cc[VINTR]    = '\0';
    tty->tattr.c_cc[VQUIT]    = '\0';
    tty->tattr.c_cc[VERASE]   = '\0';
    tty->tattr.c_cc[VKILL]    = '\0';
    tty->tattr.c_cc[VEOF]     = '\0';
    tty->tattr.c_cc[VTIME]    = '\0';
    tty->tattr.c_cc[VMIN]     = 1;
    tty->tattr.c_cc[VSWTC]    = '\0';
    tty->tattr.c_cc[VSTART]   = '\0';
    tty->tattr.c_cc[VSTOP]    = '\0';
    tty->tattr.c_cc[VSUSP]    = '\0';
    tty->tattr.c_cc[VEOL]     = '\0';
    tty->tattr.c_cc[VREPRINT] = '\0';
    tty->tattr.c_cc[VDISCARD] = '\0';
    tty->tattr.c_cc[VWERASE]  = '\0';
    tty->tattr.c_cc[VLNEXT]   = '\0';
    tty->tattr.c_cc[VEOL2]    = '\0';

#ifndef USE_TERMIOS2
    /* Set TX, RX speed to 38400 -- this value allows
       to use custom speed in struct struct_serial */
    cfsetispeed(&tty->tattr, B38400);
    cfsetospeed(&tty->tattr, B38400);

    if (tcsetattr(tty->tty_fd, TCSANOW, &tty->tattr) == -1) {
        perror("tcsetattr()");
        return -1;
    }

#else /*USE_TERMIOS2*/

    /* Set new parameters with previous speed and left */
    /* tty_set_baudrate() to do the rest  */
    if(ioctl(tty->tty_fd, TCSETS2, &tty->tattr)) {
        perror("ioctl TIOCSSERIAL");
        return -1;
    }

#endif /*USE_TERMIOS2*/

    /* Set real speed */
    tty_set_baudrate(tty, baudrate);

    return 0;
}

int sllin_open(struct sllin *sl, const char *dev_fname, int baudrate)
{
    int fd;

    sl->lin_baud = baudrate;

    /* Calculate baudrate for sending LIN break */
    sl->lin_break_baud = (sl->lin_baud * 2) / 3;

    fd = open(dev_fname, O_RDWR);
    if (fd < 0) {
        perror("open()");
        return -1;
    }
    sl->tty->tty_fd = fd;

    return tty_set_mode(sl->tty, sl->lin_baud);
}

int main()
{
    struct sllin *sl = &sllin_data;

    char *dev_fname = "/dev/ttymxc4";
    int lin_baudrate = 19200;
    int lin_id = 1;

    if (sllin_open(sl, dev_fname, lin_baudrate) < 0) {
        fprintf (stderr, "sllin_open open failed\n");
        exit(EXIT_FAILURE);
    }

    fcntl(fileno(stdin), F_SETFL, O_NONBLOCK);
    printf("Press enter to terminate.\n\n");


    while(1) {
        char c;

        tty_flush(sl->tty, TCIOFLUSH);

        unsigned int buff[10] = {1,2,3,4,5,6,7,8,9,10};
        // debug
        write(sl->tty->tty_fd, &buff[0], 1);
        write(sl->tty->tty_fd, &buff[1], 1);
        write(sl->tty->tty_fd, &buff[2], 1);
        write(sl->tty->tty_fd, &buff[3], 1);
        write(sl->tty->tty_fd, &buff[4], 1);
        write(sl->tty->tty_fd, &buff[5], 1);
        write(sl->tty->tty_fd, &buff[6], 1);
        write(sl->tty->tty_fd, &buff[7], 1);
        write(sl->tty->tty_fd, &buff[8], 1);
        write(sl->tty->tty_fd, &buff[9], 1);
        // debug

        sleep(1);

        if (read(fileno(stdin), &c, 1) > 0)
            break;
    }

    return EXIT_SUCCESS;
}

Upvotes: 0

Views: 411

Answers (2)

Pavel Pisa
Pavel Pisa

Reputation: 21

Our user-space code in https://github.com/lin-bus/linux-lin/tree/master/misc/tty_lin_master is intended only for experiments and has been used during kernel driver development. You should not use it directly. If you want to use LIN on UART, then you should build kernel with fully-preemptive (RT) patches and support. Regular kernel build does not guarantee nor statistically behave right. It would be even worse if there is network, disk or other load. Your application needs to be run at some RT (FIFO, RR) priorities, it can help a little on regular kernel. On RT kernels it is on many platforms tested that all latencies fit under some limit for years https://www.osadl.org/QA-Farm-Realtime.linux-real-time.0.html .

For our kernel driver, look for the latest project version

https://github.com/lin-bus/linux-lin

It usually works well on master side on most UARTs, but slave side requires configure, change kernel source for no-FIFO or Rx FIFO trigger level set to one. That requires to modify kernel code because generic API to tune Rx FIFO trigger level has not been implemented and submitted to mainline. Cooperation welcomed as well as centralization of knowledge and issues on one place, at the GitHub project for now.

Upvotes: 0

sawdust
sawdust

Reputation: 17047

Essentially if you look at main, I'm literally just sending 10 characters over RS-232...

The problem is your method of output.
Instead of ten write() syscalls of just one byte each (which is very inefficient), use just one write() for the buffer of ten bytes.

Assuming this is executing under Linux, each syscall would permit the scheduler to suspend your process (hence the gaps).
If you use just one syscall, then the device driver will transmit the data as fast as possible (only DMA or interrupt latency could cause xmit gaps).

Replace all this

    write(sl->tty->tty_fd, &buff[0], 1);
    write(sl->tty->tty_fd, &buff[1], 1);
    write(sl->tty->tty_fd, &buff[2], 1);
    write(sl->tty->tty_fd, &buff[3], 1);
    write(sl->tty->tty_fd, &buff[4], 1);
    write(sl->tty->tty_fd, &buff[5], 1);
    write(sl->tty->tty_fd, &buff[6], 1);
    write(sl->tty->tty_fd, &buff[7], 1);
    write(sl->tty->tty_fd, &buff[8], 1);
    write(sl->tty->tty_fd, &buff[9], 1);

with just this

    write(sl->tty->tty_fd, buff, 10);

Also, replace the sleep() between the write and read with tcdrain().

Upvotes: 1

Related Questions