Случайные паузы между последовательными передачами UART
Я пытаюсь разработать мастер шины LIN, используя приведенный здесь пример:
https://github.com/trainman419/linux-lin/tree/master/misc/tty_lin_master
По сути это отправляет сообщения протокола LIN через последовательный порт.
Я немного изменил код, чтобы упростить его для тестирования функциональности низкого уровня. Я хочу посмотреть, будет ли анализатор LIN правильно декодировать очень примитивное сообщение LIN, но я столкнулся со странными проблемами, касающимися последовательного порта. Я посылаю несколько последовательных символов через интерфейс /dev/ttymxc4 (RS-232), но я случайно вижу где-то посередине паузу в передаче пакета. Интересно, что эта пауза начинается с некоторого значения, я захватил 8,6 мс, но затем постепенно сжимается, пока не исчезнет... но затем он снова запускается.
По сути, если вы посмотрите на main, я буквально просто посылаю 10 символов по RS-232...
Вот код, если у кого-нибудь есть идеи:
/*
* 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;
}
2 ответа
По сути, если вы посмотрите на main, я буквально просто посылаю 10 символов по RS-232...
Проблема в вашем методе вывода.
Вместо десяти системных вызовов 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 ().
Наш код пользовательского пространства на https://github.com/lin-bus/linux-lin/tree/master/misc/tty_lin_master предназначен только для экспериментов и использовался при разработке драйверов ядра. Вы не должны использовать его напрямую. Если вы хотите использовать LIN на UART, вам следует собрать ядро с полностью вытесняющими (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, но ведомая сторона требует настройки, изменения исходного кода ядра для установки уровня триггера без FIFO или Rx FIFO на единицу. Это требует изменения кода ядра, потому что общий API для настройки уровня триггера Rx FIFO не был реализован и не отправлен в основную линию. Приветствуется сотрудничество, а также централизация знаний и проблем в одном месте, на данный момент в проекте GitHub.