LINUX.ORG.RU

задержка при записи в последовательный порт


0

0

По долгу службы работаю над приложением, которое запускается под uClinux на встраиваемой системе. Приложение использует последовательный порт (rs-485) для запроса и сбора информации с различных датчиков. При помощи осциллографа удалось выяснить, что удалённые устройства-датчики работают нормально и отвечают спустя 15-20 мс после получения ими соответствующей команды от приложения. Ошибка - со стороны приложения. Длина отрезка времени после вызова write и до фактического появления данных на шине может достигать 200 мс и меняется каждый раз - никакой закономерности не прослеживается.

Очевидно, что данная проблема скрыта где-то на уровне операционной системы/настроек последовательного порта. Друзья, может кому-то приходилось сталкиваться с подобными вопросами? Подскажите, пожалуйста, в какую сторону копать, и какая информация с моей стороны может помочь вам?

Ответ на: комментарий от val-amart

Настройки последовательного порта:

int set_up_comms( char *device, int baud_i, 
                  char *parity, int stopbits, int readtimeout )
{
	int ttyfd;
	struct termios settings;
  	struct serial_struct ser;
	speed_t baud_rate;
	
	/* get integral part (in seconds) of MODBUS reading timeout */
	read_timeout_s  = readtimeout / 1000;

	/* get fractional part (in microseconds) of MODBUS reading timeout */
	read_timeout_us = (readtimeout - read_timeout_s * 1000) * 1000;
	
#ifdef DEBUG
	fprintf( stderr, "opening %s\n", device );
#endif

	switch( baud_i )
	{
		case 110:
			baud_rate = B110;
			char_interval_timeout = TO_B110;
			break;
		case 300:
			baud_rate = B300;
			char_interval_timeout = TO_B300;
			break;
		case 600:
			baud_rate = B600;
			char_interval_timeout = TO_B600;
			break;
		case 1200:
			baud_rate = B1200;
			char_interval_timeout = TO_B1200;
			break;
		case 2400:
			baud_rate = B2400;
			char_interval_timeout = TO_B2400;
			break;
		case 4800:
			baud_rate = B4800;
			char_interval_timeout = TO_B4800;
			break;
		case 9600: case 0:
			baud_rate = B9600;
			char_interval_timeout = TO_B9600;
			break;
		case 19200:
			baud_rate = B19200;
			char_interval_timeout = TO_B19200;
			break;
		case 38400:
			baud_rate = B38400;
			char_interval_timeout = TO_B38400;
			break;
		case 57600:
			baud_rate = B57600;
			char_interval_timeout = TO_B57600;
			break;
		case 115200:
			baud_rate = B115200;
			char_interval_timeout = TO_B115200;
			break;
		case 230400:
			baud_rate = B230400;
			char_interval_timeout = TO_B230400;
			break;			
		case 460800:
			baud_rate = B460800;
			char_interval_timeout = TO_B460800;
			break;	
		case 921600:
			baud_rate = B921600;
			char_interval_timeout = TO_B921600;
			break;	
		default:
			baud_rate = B9600;
			char_interval_timeout = TO_B9600;
			fprintf(stderr, "Unknown baud rate %d for %s.", baud_i, device);
	}



	if(( ttyfd = open( device, O_RDWR  | O_NOCTTY |  O_NDELAY | O_SYNC ) ) < 0 )
	{
		fprintf( stderr, "Error opening device %s.   ", device );
			
		fprintf( stderr, "Error no. %d \n",errno );
		
		return -1;
	}
	
  	ioctl( ttyfd, TIOCGSERIAL, &ser );
  	ser.flags |= ASYNC_LOW_LATENCY;
  	ioctl( ttyfd, TIOCSSERIAL, &ser );

#ifdef DEBUG
	fprintf( stderr, "%s open\n", device );
#endif

	bzero( &settings, sizeof(settings) );

	cfsetispeed( &settings, baud_rate );/* Set the baud rate */
	cfsetospeed( &settings, baud_rate );

	settings.c_line = 0; 

	settings.c_iflag |=  IGNBRK;  /* ignore BREAK condition on input */
	settings.c_iflag |=  IGNPAR;  /* ignore framing errors and parity errors */
	settings.c_iflag &= ~PARMRK;
	settings.c_iflag &= ~INPCK;   /* disable input parity checking */
	settings.c_iflag &= ~ISTRIP;  /* disable strip off eighth bit */
	settings.c_iflag &= ~INLCR;	  /* don't map NL to CR-NL on input */
	settings.c_iflag &= ~IGNCR;   /* don't ignore carriage return(CR) on input */
	settings.c_iflag &= ~ICRNL;	  /* don't map CR to NL on input */
	settings.c_iflag &= ~IUCLC;   /* don't map uppercase->lowcase on input */
	settings.c_iflag &= ~IXON;    /* disable XON/XOFF flow control on input */ 
	settings.c_iflag |=  IXANY;
	settings.c_iflag &= ~IXOFF;   /* disable XON/XOFF flow control on output */ 
	settings.c_iflag &= ~IMAXBEL; /* not implemented in Linux */

	settings.c_oflag &= ~OPOST;   /* disable implementation-defined output processing */
	settings.c_oflag &= ~OLCUC;	  /* don't map uppercase->lowcase on output */
	settings.c_oflag &= ~ONLCR;   /* don't map NL to CR-NL on output */
	settings.c_oflag &= ~OCRNL;   /* don't map CR to NL on output */
	settings.c_oflag |=  ONOCR;   /* don't output CR at column 0 */
	settings.c_oflag &= ~ONLRET;
	settings.c_oflag &= ~OFILL;	  /* use a timed delay rather than send fill chars */
	settings.c_oflag &= ~OFDEL;	  /* not implemented in Linux */

	settings.c_cflag &= ~CSIZE;	  /* mask the character size bits */
	settings.c_cflag |=  CS8;	  /* 8 data bits */	
	settings.c_cflag |=  CLOCAL;  /* ignore modem control lines */
	settings.c_cflag |=  CREAD;   /* enable receiver */
	settings.c_cflag &= ~HUPCL;
	settings.c_cflag &= ~CRTSCTS; /* disable RTS/CTS (hardware) flow control */
	settings.c_lflag &= ~ISIG;    /* disable generation of EOF and other signals */
	settings.c_lflag &= ~ICANON;  /* disable canonical mode */
	settings.c_lflag &= ~ECHO;    /* disable echo input characters */
	settings.c_lflag &= ~IEXTEN;  /* disable implementation-defined input processing */
		
	/********** applying stopbits count settings ***************/
	
	if( stopbits == 2 )
	{
		settings.c_cflag |=  CSTOPB;	/* explicitly set up 2 stopbits */		
	}
	else	
	{
		settings.c_cflag &= ~CSTOPB;	/* one stopbit in any other cases */
	}

	/*********** applying parity settings **********************/

	if( strncmp( parity, "none", 4 ) == 0 )
	{
		settings.c_cflag &=~ PARENB;
		settings.c_cflag &=~ PARODD;
	}
	else
	if( strncmp( parity, "even", 4 ) == 0 )
	{
		settings.c_cflag |= PARENB;
		settings.c_cflag &=~ PARODD;
	}
	else
	{
		settings.c_cflag |= PARENB;
		settings.c_cflag |= PARODD;
	}

	settings.c_cc[VMIN]  = 0; //4
	settings.c_cc[VTIME] = 0; //1

	tcflush(ttyfd, TCOFLUSH | TCIFLUSH );

	if( tcsetattr( ttyfd, TCSANOW, &settings ) < 0 )
	{
		fprintf( stderr, "tcsetattr failed\n");
		return -1;
	}


	return( ttyfd );
}
Bazarov ()
Ответ на: комментарий от Bazarov

>>tcdrain( ttyfd );

ну вот здесь ты ждешь, пока у тебя байты не отправятся по шине сами, попробуй их флушить на шину сразу.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от kuzulis

Проблем с получением данных от датчиков, к счастью, нет. Поначалу на них грешил, поэтому с select'ом тоже игрался. Его вызов/использование ведь никак не влияет на «передающие способности» ОС ?

Bazarov ()
Ответ на: комментарий от MuZHiK-2

Так точно. Датчики опрашиваются циклически, верхний потолок продолжительности цикла - фиксирован. Если не использовать tcdrain, то иногда вся посылка просто не успевать появиться на шине до окончания цикла, и запросы к датчикам теряются... tcdrain() в данном случае просто костыль, который насильно увеличивает продолжительность цикла..

попробуй их флушить на шину сразу.

Уточните, пожалуйста, что имеется в виду?

Bazarov ()

Люди, о чем вы? Какие шины? Какие вообще проблемы? Протокол Modbus - это запрос/ответ... Какие проблемы вообще? 1. Шлем запрос к девайсу 2. Запускаем селект и ждем ответа 3. При получении данных - читаем, если данные не пришли - то ошибка - «таймаут ожидания ответа» --

kuzulis ★★ ()
Ответ на: комментарий от kuzulis

>>Люди, о чем вы? Какие шины? Какие вообще проблемы? Протокол Modbus - это запрос/ответ... Какие проблемы вообще? 1. Шлем запрос к девайсу 2. Запускаем селект и ждем ответа 3. При получении данных - читаем, если данные не пришли - то ошибка - «таймаут ожидания ответа» --

Разве ТС что-то говорил о модбазе?

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от kuzulis

Необходимо сделать запрос по MODBUS (передать данные на шину) и получить ответ от устройства за определённый промежуток времени. В данный момент на передачу запроса _к девайсу_ тратится порядка 150-200 мс (промежуток между вызовом write и появлением данных на шине). Это не устраивает.. Если пользоваться таймаутами при необходимых (согласно ТЗ) значениях продолжительности цикла опроса датчиков, то кол-во ошибок >70 %. Вывод - таймауты, конечно, полезны, но тут не катят. Надо как-то операционнку раскачать..

Bazarov ()
Ответ на: комментарий от MuZHiK-2

> tcflush (fd, TCOFLUSH), или как там точно, я уже не помню.

http://www.opennet.ru/cgi-bin/opennet/man.cgi?topic=tcflush&category=3

tcflush(): отказывается от данных, записанных, но не переданных на объект, на который ссылается fd, или принятых, но не считанных данных (в зависимости от значения queue_selector):

По факту данные даже не появятся на шине.. или я не прав?

Bazarov ()
Ответ на: комментарий от MuZHiK-2

Речь действительно идёт про modbus. Упоминание об этом можно увидеть в одном из комментариев к исходникам.

Bazarov ()
Ответ на: комментарий от kuzulis

Люди, о чем вы? Какие шины? Какие вообще проблемы? Протокол Modbus - это запрос/ответ... Какие проблемы вообще?

Наверное ТС хочет делать опрос ЧУДНЫМ способом:

  • Отправляем запрос.
  • Ждем N милисекунд.
  • Смотрим на то, что пришло.
  • ...
  • PROFIT

По нормальному надо делать новый запрос по факту приема ответа от старого. Но modbus - $%#&@#. По этой причине красиво эту схему сделать все равно не удастся. При приеме надо ждать когда интервал между байтами станет больше времени передачи 3 символов, так как это индикатор разрыва между пакетами. Выждать такой короткий интервал не всегда легко. Иногда приходится ждать дольше. Это может свести на нет все преимущества этого подхода.

Если у тебя не резервированная схема, то можно класть на все таймауты, а конец пакета определять по структуре пришедших данных.

pathfinder ★★★★ ()

Причины проблемы могут быть разными.

Как измерялось время между write и фактическим появлением данных на линии?

write_stat = write( ttyfd, query, string_length ); - не вижу в коже обработку write_stat. Драйвер может не принять весь объем данных, который ты ему захотел передать.

pathfinder ★★★★ ()
Ответ на: комментарий от Bazarov

>>По факту данные даже не появятся на шине.. или я не прав?

Проверь :) Я больше не помню, как еще можно сфлушить буферы данных.

MuZHiK-2 ★★★★ ()
Ответ на: комментарий от pathfinder

> Как измерялось время между write и фактическим появлением данных на линии?

перед вызовом write включал светодиод и двухканальным осциллом измерял. Время постоянно плавает, но оно просто _огромное_ ...

не вижу в коже обработку write_stat.

Я толком обработки возвращаемого значения не делал, подразумевая его положительным. Это уголовно наказуемо?

Драйвер может не принять весь объем данных, который ты ему захотел передать.

Поясните, пожалуйста, подробнее

Bazarov ()
Ответ на: комментарий от pathfinder

> tcdrain()/tcflush() - для чего нужны эти функции в рамках данной задачи?

Опрос датчиков происходит циклически. Если не использовать tcdrain(), то в большинстве случаев данные не успевают появиться на линии в полном объёме до конца цикла ==> посылка битая. tcdrain() - всего лишь средство, чтобы искусственно растянуть цикл на некоторое время.

Каким образом можно сделать (и можно ли?) небуферизированный вывод данных на пин проца?

Bazarov ()
Ответ на: комментарий от Bazarov

>> Драйвер может не принять весь объем данных, который ты ему захотел передать.

Поясните, пожалуйста, подробнее

write() возвращает количество байт которые были переданы драйверу порта. Это число может быть меньше количества байт которые ты собирался передать.

Например write(fd,buffer,100) может вернуть 99. Тогда оставшийся байт надо передавать потом ещё одним вызовом write().

Не факт, что в этом проблема. Но лучше не делать никаких предположений по поводу того, сколько данных драйвер порта может принять в текущий момент.

pathfinder ★★★★ ()
Ответ на: комментарий от Bazarov

ТС, чтобы правильно интерпретировались таймауты между символами при приеме - необходимо настроить settings.c_cc[VTIME] . т.е. сюда нужно подставить char_interval_timeout . А у тебя сейчас (при = 0) ф-я чтения будет читать только то что пришло в приемный буфер по факту и сразу возвращать управление не дожидаясь следующего символа. Поэтому не факт что ты за раз прочтешь весь ADU!!! Для отправки запроса к датчику делаешь write, а потом drain, а потом select на время ожидания ответа от датчика (например 500 мс), а потом после отработке селекта ты либо читаешь данные либо говориш что таймаут и переходишь к чтению следующего датчика!!!

kuzulis ★★ ()
Ответ на: комментарий от Bazarov

>Опрос датчиков происходит циклически. Если не использовать tcdrain(), то в большинстве случаев данные не успевают появиться на линии в полном объёме до конца цикла ==> посылка битая. tcdrain() - всего лишь средство, чтобы искусственно растянуть цикл на некоторое время.

А нельзя просто послать данные а потом ждать ответа? Если пришел ответ, то сразу формируем новый запрос, если нет, то ждем скажем 1 секунду и начинаем новый.

pathfinder ★★★★ ()
Ответ на: комментарий от kuzulis

>Поэтому не факт что ты за раз прочтешь весь ADU!!!

Я не понимаю. Почему принять APU за несколько вызовов read() это так страшно?

pathfinder ★★★★ ()
Ответ на: комментарий от pathfinder

>А нельзя просто послать данные а потом ждать ответа? Если пришел ответ, то сразу формируем новый запрос, если нет, то ждем скажем 1 секунду и начинаем новый.

Именно так лучше всего делать! ИМХО! Ну или типо того! :)

kuzulis ★★ ()
Ответ на: комментарий от pathfinder

>Я не понимаю. Почему принять APU за несколько вызовов read() это так страшно?

А нахрена? Откуда ты знаешь какой длины должно быть ADU? :) Кароч лучше всего читать за раз и потом разбирать.

kuzulis ★★ ()
Ответ на: комментарий от kuzulis

В общем, настраиваем таймауты символов и делаем read(buf, 256) и все буит харашо.

Хотя, вариантов «как читать» туева хуча. В общем, думаю, ТС сам разберется.

kuzulis ★★ ()

Вопрос не в тему. Мы в своё время для таких вещей юзали realtime-ядро. Тогда и различные таймауты в ядре гарантированные.

Пользуешь какое-нить realtime-расширение?

Lego_12239 ★★ ()
Ответ на: комментарий от kuzulis

Друзья, прочтите, пожалуйста, внимательно, что я пишу. У меня нет _никаких_ проблем с чтением от датчика. Задача - уменьшить, причём существенно, latency при передаче *в* последовательный порт. Сейчас эта задержка (при _передаче_) доходит до 200 мс - и это, очевидно, не нормально.

write() возвращает количество байт которые были переданы драйверу порта. Это число может быть меньше количества байт которые ты собирался передать.

Согласен, но это второй вопрос. Зачем я буду наворачивать фарш на write, если не могу добиться её адекватной работы?

Не факт, что в этом проблема. Но лучше не делать никаких предположений по поводу того, сколько данных драйвер порта может принять в текущий момент.

Согласен

ТС, чтобы правильно интерпретировались таймауты между символами при приеме - необходимо настроить settings.c_cc[VTIME] . т.е. сюда нужно подставить char_interval_timeout .

К чему это? Речь ведь о передаче, а не приёме сейчас идёт7

А у тебя сейчас (при = 0) ф-я чтения будет читать только то что пришло в приемный буфер по факту и сразу возвращать управление не дожидаясь следующего символа.

Именно это мне и нужно - так называемое неблокирующее чтение.

Для отправки запроса к датчику делаешь write, а потом drain, а потом select на время ожидания ответа от датчика (например 500 мс), а потом после отработке селекта ты либо читаешь данные либо говориш что таймаут и переходишь к чтению следующего датчика!!!

Чучка - писатель? Я выше упоминал, что даже 200 мс мне много,а о каких 500 мс может вообще идти речь?

Bazarov ()
Ответ на: комментарий от pathfinder

> А нельзя просто послать данные а потом ждать ответа? Если пришел ответ, то сразу формируем новый запрос, если нет, то ждем скажем 1 секунду и начинаем новый.

Ответ обязательно придёт - через 10-15 мс, как я писал. И ждём мы не ответа, а начала передачи - что очень не здорово. 1 секунда - очень много.

Bazarov ()
Ответ на: комментарий от Lego_12239

> Пользуешь какое-нить realtime-расширение?

Неа. Самый обыкновенный uclinux.

Но неужели не rt-ядро на передаче пакета размером < 16 байт зависает на 200 мс ? Думаю, что нет... Но не знаю, куда копать

Bazarov ()
Ответ на: комментарий от Bazarov

>1 секунда - очень много.

А в TCP есть таймауты по несколько минут. Как ни странно оно работает.

Ты видимо не понял сказанного.

pathfinder ★★★★ ()
Ответ на: комментарий от Bazarov

Видать неправильно открываешь свой порт. Возьми исходники от MOXA UC7400 и посмотри как там реализовано.А то ты нагородил хероты какой-то в settings. :)

kuzulis ★★ ()

Какая скорость? Какое оборудование? Порт на контроллере или какой-нибудь UsbToSerial или EthernetToSerial?

pathfinder ★★★★ ()

Попробуй настроить параметры порта через команды консоли, а в программе открывать порт как есть, без настройки. Убери tcflush() и tcdrain(). Юзай только open(),write(),read(). После этого посмотри на задержку между write() и фактической передачей. Если большая задержка останется, то жопа - скорее всего дело в железе или в драйверах.

pathfinder ★★★★ ()
Ответ на: комментарий от Bazarov
-  if(( ttyfd = open( device, O_RDWR  | O_NOCTTY |  O_NDELAY | O_SYNC ) ) < 0 )
+  if(( ttyfd = open( device, O_RDWR  | O_NOCTTY ) ) < 0 )
LamerOk ★★★★★ ()
Ответ на: комментарий от pathfinder

> Такой вопрос. 200 миллисекунд стабильно? Или оч-ч-чень редко, но бывает.

Стабильно... до 80% передаваемых пакетов оказываются на шине не раньше, чем через 170-200 мс..

Bazarov ()
Ответ на: комментарий от pathfinder

>Какая скорость?

38400 8N1

Какое оборудование?

сигналы с UART'а преобразуются в rs485 и подключаются к модулю аналогового ввода от ОВЕНа

Порт на контроллере или какой-нибудь UsbToSerial или EthernetToSerial?

Нет

Bazarov ()
Ответ на: комментарий от pathfinder

> Попробуй настроить параметры порта через команды консоли

setserial ?

Убери tcflush() и tcdrain()

обязательно уберу - они для отладочных целей

Если большая задержка останется, то жопа - скорее всего дело в железе или в драйверах.

драйвера - стандартные линуксовые (8250). Дело, на мой взгляд, в операционнке... она почему-то долго держит данные где-то внутри себя, прежде чем выкинуть их на шину.. Железо проверено с использованием других тест-кейсов.. до 1 Мбит/с раскачивали

Bazarov ()

Сейчас то у вас задержка на каком железе --- на встраиваемом или на обычном PC?

mky ★★★★★ ()

Вполне возможно, что для обеспечения гарантированных задержке придутся работать с портом «поближе к ядру» - либо найти готовый драйвер для Modbus, либо написать самому - например, по аналогии с линуксовыми драйверами для I2C.

RVictor ()
Ответ на: комментарий от Bazarov

200 мс это совсем много. Наверное, можно вобще написать простую тестовую программу write()/read() и замкнув Tx и Rx считать время выполнения. Так, ИМХО, будет проще смотреть задержку от числа передаваемых байт, настроек порта, RT-ядра.

mky ★★★★★ ()
Ответ на: комментарий от barsuchina

Как я понял, критичны не задержки в управляющей программе, а задержки между выбором select'а для rs-485 и,собственно, чтением/записью на последовательных линиях.

Kernel-Module спасёт при необходимости хоть как-то гарантировать временные интервалы в протоколе.

В результате программа будет работать с символьным устройством типа /dev/modbus0, а реализация протокола будут вынесена в модуль, где можно хоть на прерывания реагировать.

Поскольку все работает на строго регламентированном железе, то под это дело бодро переписывается код, реализующий последовательный порт для данного типа rs232 ресурсов контроллера.

RVictor ()
Ответ на: комментарий от RVictor

не знаю как у автора темы, а мне видится что если пакет длиной 10 милисекунд накрывается сверху стробом в 200 милисекунд это не есть гут

barsuchina ()
Ответ на: комментарий от barsuchina

Судя по этмоу: http://mhonarc.axis.se/dev-etrax/msg07942.html , проблема действительно сущетсвует. Это патч вам не подойдёт, но может найдёте другой или разберётесь в этом и поправите своё ядро.

mky ★★★★★ ()
Ответ на: комментарий от barsuchina

проблему не решил, но обуздал фронт посылки. сначала, чтобы как то работать с внешними устройствами увеличил строб рзрешения передачи по 485му до 220мс ибо это то время в котором посылка гуляла, но такое долгое течение строба не давало принимать ответы от девайсов, потом посмотрел в сторону регистра U3LSR в итоге имеем - ставим строб отправки акете, отправляем пакет данных и ждём пока этот регистр сообщит нам , что данные сначала поступили на отправку, а затем , что они ушли из буфера , затем ставим строб на приём ответа и слушаем ...    int write_stat,fd;

   unsigned char *U3LSR,data,res;    fd = open(«/dev/mem», O_RDWR);    U3LSR = mmap(0, 0x100, PROT_READ, MAP_SHARED, fd, 0xE007C000);    if (U3LSR == MAP_FAILED) {       printf(«Error mmapping the file»);       return 0;    } #ifdef DEBUG    int i; #endif

   modbus_query( query, string_length );    string_length += 2;     #ifdef DEBUG    fprintf( stderr, «\n» );    for( i = 0; i < string_length; i++ )    {       fprintf( stderr, «[%0.2X]», query[ i ] );    }    fprintf( stderr, «\n» ); #endif    tcflush( ttyfd, TCIOFLUSH );       setP01(1,13, 1);    write_stat = write( ttyfd, query, string_length );    data = 1; //ждём захода данных в буфер отправки    while(data)    {       data = *(U3LSR+0x14)&(1<<6);       res = *(U3LSR+0x14);       printf(«\ndata =%x»,res);    }    printf(«\n»); //ждём когда они покинут буфер    while(!data)    {       data = *(U3LSR+0x14)&(1<<6);       res = *(U3LSR+0x14);       printf(«\nres =%x»,res);    }    printf(«\n»);    //delay(220);    //setP01(1,13, 0);    if (munmap(U3LSR, 0x100) == -1) {    perror(«Error un-mmapping the file»);

} close(fd);    return( write_stat );

завтра наверное надо будет добавить таймаутов, а в целом код рабочий, правда по большому счёту не устраивает и надо будет искать другое решение

barsuchina ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.