【问题标题】:Random pauses between consecutive UART transmissions连续 UART 传输之间的随机暂停
【发布时间】:2017-01-22 18:33:21
【问题描述】:

我正在尝试使用此处提供的示例开发 LIN 总线主控器:

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

本质上,这会通过串行端口发送 LIN 协议消息。

我稍微更改了代码以简化低级功能测试。我想看看 LIN 分析器是否能正确解码非常原始的 LIN 消息,但我遇到了与串行端口有关的奇怪问题。我正在通过 /dev/ttymxc4 (RS-232) 接口发送几个连续的字符,但我在数据包传输的中间某处看到随机暂停。有趣的是,这个暂停从某个值开始,我捕获了 8.6 毫秒,然后逐渐缩小,直到它消失......但随后又开始了。

基本上,如果您查看 main,我实际上只是通过 RS-232 发送 10 个字符...

如果有人有任何想法,这是代码:

/*
 * 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;
}

【问题讨论】:

标签: linux serial-port embedded-linux uart


【解决方案1】:

基本上,如果您查看 main,我实际上只是通过 RS-232 发送 10 个字符...

问题在于你的输出方法。
代替十个 write() 系统调用,每个调用只有一个字节(效率非常低),只使用一个 write() 作为十字节的缓冲区。

假设这是在 Linux 下执行的,每个系统调用都将允许调度程序暂停您的进程(因此存在间隙)。
如果您只使用一个系统调用,那么设备驱动程序将尽可能快地传输数据(只有 DMA 或中断延迟可能会导致 xmit 间隙)。

全部替换

    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);

只有这个

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

另外,将写入和读取之间的 sleep() 替换为 tcdrain()

【讨论】:

  • 在我的最终实现中,我需要中断 write() sys 调用,因为 LIN 协议需要在帧的开头有 13 个连续的 0 字符,基本上我这样做的方式是通过更改将波特率更改为 12800 以发送这些字符,然后将其更改回 19200 以获取标头的其余部分。我不能简单地以降低的波特率发送两个 0x00 字节作为 13 个连续 0 的替换,因为这两个字节之间会有一个停止字符。我应该使用不同的系统调用吗? (是的,这是在 Linux 下执行的)
  • 我还应该补充一点,当我将波特率更改为 12800 以发送看似连续 13 个 0 的内容时,我只需以 12800 的速度发送单个字节 0x00。对于从设备,这会出现在 19200 波特时为 0 的 13 位。
  • 我只能回答发布的问题。我无法读懂您的想法或预测您的(难以理解的)“最终实现”。你贴出的具体问题我已经说明了解决方法,你连一丝感谢都表达不出来。
  • 感谢您的回复,非常有帮助!我并不是说我的评论不是在表达“感激之情”,只是想提供更多信息,因为我知道我的原始帖子不是很清楚。无论如何,感谢您的帮助。如果您根据我的附加评论有任何进一步的建议,我将不胜感激!
  • 好指针,有时你只是错过了一个方法分配
【解决方案2】:

https://github.com/lin-bus/linux-lin/tree/master/misc/tty_lin_master 中的用户空间代码仅用于实验,并已在内核驱动程序开发期间使用。你不应该直接使用它。如果您想在 UART 上使用 LIN,那么您应该构建具有完全抢占 (RT) 补丁和支持的内核。常规内核构建并不能保证或统计上的行为正确。如果有网络、磁盘或其他负载,情况会更糟。您的应用程序需要以某些 RT(FIFO,RR)优先级运行,它可以在常规内核上有所帮助。在 RT 内核上,在许多平台上都经过测试,所有延迟都在一定限度内符合多年https://www.osadl.org/QA-Farm-Realtime.linux-real-time.0.html

对于我们的内核驱动,寻找最新的项目版本

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

它通常在大多数 UART 的主机端运行良好,但从机端需要配置、更改内核源以将 no-FIFO 或 Rx FIFO 触发级别设置为 1。这需要修改内核代码,因为用于调整 Rx FIFO 触发级别的通用 API 尚未实现并提交到主线。目前在 GitHub 项目中欢迎合作以及将知识和问题集中在一个地方。

【讨论】:

    猜你喜欢
    • 2013-10-06
    • 2013-09-18
    • 2011-07-03
    • 2019-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多