LINUX.ORG.RU

Тормозит запиcь в ком-порт

 ,


1

2

Всем привет

Имеется прибор на процессоре imx6UL Под него сборка дебиан от производителя.

В общем надо с этого прибора опрашивать другие по rs-485 на скорости 9600.

Порт открывается настраивается в неблокирующем режиме и т.д. и т.п. Чтение / запись работают через select с заданным таймаутом.

В итоге получается как-то так, что реальная скорость отправки данных существенно ниже установленной 9600.

Команда int bytes_write = write(fd, frame_write, frame_write_size) выполняется моментально. Но дальше получаем жуткий таймаут на получение данных. Проверили осциллографом, что данные на опрашиваемый прибор приходят и он отвечает сразу же без задержек.

Стал разбираться. Вставил после write команду tcdrain( fd ) И вот уже на ее выполнение (ожидание пока данные уйдут) отжирается в среднем 30 миллисекунд. В посылке 7 байт.

Ясно, что функцией write сначала скидывается в буфер, а потом запихивается в физическое устройство.

Но не 30 же миллисекунд на отсылку 56 бит?? По заявленной скорости на это должно уходить 5мс в идеале ).

Вопрос: можно ли как-то оптимизировать в линуксе всё это дело.

Под виндами написал такой же код, там всё просто летает. Отправка данных на 9600 на той же линии занимает в пределах 5-7 миллисекуд и примерно через столько же приходит ответ.

Что в линуксе не так?



Последнее исправление: demon051 (всего исправлений: 1)

Разумеется, с трудом можно что-то сказать конкретное, так как процессор специфический. Лечение по фотографии. Посмотри, не включен ли в настройках порта hardware flow control. Вообще, как настроен порт?

Zubok ★★★★★
()

Попробовать открывать устройство с флагом O_SYNC или использовать после write() fsync().

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

программные настройки такие

options.c_cflag = CS8 | CLOCAL | CREAD;

    options.c_iflag = IGNPAR;
    options.c_iflag |= IGNBRK;
    options.c_iflag &= ~BRKINT;
    options.c_iflag &= ~ICRNL;

    options.c_oflag = 0;
    options.c_lflag = 0;


    options.c_line = 0;

    options.c_cc[VTIME]    = 0;   
    options.c_cc[VMIN]     = 0;
demon051
() автор топика

Не знаю что это за драйвер, но может он ждет какой-то EOF символ? \0 там?

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

заведомо отключил его.

Хм, я вот бегло прочитал (учтем, что я не работал с этим SoC), что для UART может использоваться DMA и его можно включить/отключить через файл DTS. Не может быть проблемы тут?

Each UART can be configured to use DMA for the data transfer by enabling the DMA channel in the DTS file. The driver requests two DMA channels for the UARTs that need DMA transfer. On a receive transaction, the driver copies the data from the DMA receive buffer to the TTY Flip Buffer.

While using DMA to transmit, the driver copies the data from the UART transmit buffer to the DMA transmit buffer and sends this buffer to the DMA system. For more information, see the Linux documentation on the serial driver in the kernel source tree.

Zubok ★★★★★
()

Под виндами написал такой же код, там всё просто летает. Отправка данных на 9600 на той же линии занимает в пределах 5-7 миллисекуд и примерно через столько же приходит ответ.

А вот сейчас я не совсем понял, для чего ты программу пишешь? Ты пишешь программу для imx6UL, которая использует его /dev/ttymxc или ты пишешь драйвер для компьютера, на котором работаешь с прибором на imx6UL? Иначе я что-то не понял, при чем тут винды. Винды для этого SoC тоже есть?

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 3)

Копай, нет никакой разницы с виндой.

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

Под Винду написал просто для проверки, что код работает . Там и выяснилось что ещё и на нормальной скорости и с теми же программными настройками

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

Под Винду написал просто для проверки, что код работает . Там и выяснилось что ещё и на нормальной скорости и с теми же программными настройками

А винда-то на чем запущена? А Linux? Винда на PC, а Linux на NXP?

Zubok ★★★★★
()

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

AntonyRF ★★★★
()

Порт открывается настраивается в неблокирующем режиме и т.д. и т.п. Чтение / запись работают через select с заданным таймаутом.

Можешь показать весь код, работающий с портом?

И ответь вопросы @Zubok, а то действительно не понятно: что где запускается, кто с кем общается через rs485, и при чём тут вообще винда.

im-0
()
Ответ на: комментарий от im-0

привожу кусок кода на котором отлаживаюсь. он не совсем «прямой», так что извиняйте )

speed_t portSpeed(int port_speed) { if(port_speed == 19200) return B19200;

return B9600;

}

int main(int argc, char *argv[]) { string dev_pref = «/dev/»; string dev = «ttymxc1»; int baudrate = 9600;

string device = dev_pref + dev;

fd_set read_fs;
fd_set write_fs;
fd_set error_fs;

int fd = -1;

static const int buf_size = 39;
unsigned char buf[buf_size] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

struct termios options;

try
{
    fd = open(device.data(), O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK );
    if (fd == -1)
    {
        printf("ComPort -> Could not open device %s !!!", device.data());
        return 0;
    }
    else
    {
       printf("ComPort %s is open !!!\n", device.data());

       fcntl(fd, F_SETFL, 0);
    }

    tcgetattr(fd, &options);

    options.c_cflag = CS8 | CLOCAL | CREAD;

    options.c_iflag = IGNPAR;
    options.c_iflag |= IGNBRK;
    options.c_iflag &= ~BRKINT;
    options.c_iflag &= ~ICRNL;

    options.c_oflag = 0;
    options.c_lflag = 0;


    options.c_line = 0;

    options.c_cc[VTIME]    = 0;   
    options.c_cc[VMIN]     = 0;

    if(cfsetispeed(&options, portSpeed(baudrate))<0 ||
       cfsetospeed(&options, portSpeed(baudrate)<0))
    {
         printf("Unable to set com-port baudrate!!!\n");
         close(fd);
         return 0;
    }


    // Установка новых опций для порта...
    tcflush(fd, TCIOFLUSH);
    tcsetattr(fd, TCSANOW, &options);
    //tcsetattr(fd, TCSAFLUSH, &options);
}
catch(...)
{
    printf("Device open Exception!\n");
    printf("Exit from thread!\n");
    return 0;
}


struct timeval timeout_wait = { 0, 1000 };


struct timespec sp = { 1, 0 }; //1 секунда


int ret = 0;

static int frame_write_size = 7;
unsigned char frame_write[frame_write_size] = {0x53, 0x03, 0x0F, 0x77, 0x00, 0x2C, 0x0D}; //тестовая посылка

while(1)
{
    try
    {
        if(kbhit()!=0) break;

        FD_ZERO (&read_fs);
        FD_ZERO (&write_fs);
        FD_ZERO (&error_fs);

        FD_SET(fd, &read_fs);
        FD_SET(fd, &write_fs);
        FD_SET(fd, &error_fs);

        if ((ret = select(fd+1, &read_fs, &write_fs, &error_fs, &timeout_wait)) < 0)
        {
            continue;
        }

        if (FD_ISSET(fd, &error_fs))
        {
            printf("ComPort: ERROR DESCRIPTOR SELECTED!!! \n");

            nanosleep(&sp, NULL);
            continue;
        }

        if (FD_ISSET(fd, &read_fs) )
        {
            //memset(buf,0x00, sizeof(unsigned char)*buf_size);
            //buf[buf_size] = {0};
            buf[0] = 0;

//читаем

            int bytes_read = read(fd, buf, buf_size);
            

            if(bytes_read>0)
            {

                printf("Bytes accepted: ");
                printf_asHex(buf, bytes_read);
               
                usleep(3000);//пауза на линии
            }
            else
            {
                printf("Socket read ERROR!!!\n");
                nanosleep(&sp, NULL);
            }
            continue;
        }

        if (FD_ISSET(fd, &write_fs))
        {


                int bytes_write = write(fd, frame_write, frame_write_size);                   

                tcdrain( fd );                   

                if(bytes_write != frame_write_size)
                {
                        printf("Data write ERROR: bytes_write(%d) != frame_write_size(%d)\n",bytes_write,frame_write_size);
                   nanosleep(&sp, NULL);
                }
                else
                {                                                

                    usleep(15000);//15 миллисекунд - искусственно подобранная задержка, чтобы всё пролезало

                }
            }
            continue;
        }

    }
    catch(...)
    {
        printf("ComPort read/write Exception!\n");
        break;
    }
}

if(fd!=-1)
{
    try
    {
        close(fd);
    }
    catch(...)
    {
    }
}
printf("EXIT MAIN\n!!!");
return 0;

}

demon051
() автор топика
Ответ на: комментарий от anonymous

под виндой на PC этот же код работает без задержек, общаясь с тойже самой периферией подключенной по rs 485 к PC на приборе под линуксом -с задержками на отправку и прием данных.c той же самой периферией , подключенной теперь уже к прибору по rs 485

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

винда на пс линукс на приборе

Ну вот. Тогда это не проверка, конечно же, потому что железо разное, все другое. Этим ничего нельзя сказать. Надо было тогда на PC для Linux код для serial написать хотя бы. На приборе и железо другое, и драйвер UART свой, понятное дело.

Я вот думаю, что если в программе у тебя все ок (а в настройке я не заметил явной проблемы), то надо смотреть конфигурацию железа. Вот DMA, как я читал в интернете, для UART включен по умолчанию. Можно отключить. Если у тебя посылка с запаздыванием отправляется, то это может быть потому, что ожидается заполнение какого-то буфера. А если он не заполнен, то отправляется по таймауту. Или баг вполне может быть. Пробуй - потом расскажешь. Я видел, что это можно сделать в секции uart в конфигурационном файле *.dts.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от AntonyRF

stty -a -F /dev/ttymxc1 speed 9600 baud; rows 0; columns 0; line = 0; intr = ^C; quit = ^; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 0; time = 0; -parenb -parodd -cmspar cs8 -hupcl -cstopb cread clocal -crtscts ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke -flusho -extproc

demon051
() автор топика
Ответ на: комментарий от Zubok

в общем вот https://www.toradex.com/community/questions/2998/linux-imx6-uart-driver-issues.html

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

хотя хрен ты его возьмешь (((

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

если тут не врут, то это известная проблема. решается вроде бы изменениями настроек dma

Или отключением DMA вообще в твоем случае.

и последующей пересборкой ядра… что не в наших руках… надо брать за жабры производителя…

А что, исходники ядра производитель не дает? Обязан.

Ты вообще глянь, вдруг можно что-то сделать через /sys/class/tty/ttymxcN/. В теории может быть, что туда драйвер экспортирует какие-нибудь ручки, которые можно крутить. Что там есть?

Еще глянь, а нет ли у драйвера «imx-uart» (или как он называется в твоем девайсе?) нужных параметров. modinfo imx-uart.

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

Тогда понятно, что он туда ручек для кручения с DMA не размещает. FIFO тебе и не надо трогать.

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

Тогда dts правь. А что у тебя в /boot на плате? Там вроде бы должны лежать бинарные файлы dtb (device tree binary).

ls -l /boot

Для работы с файлами device tree нужен пакет device-tree-compiler.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от demon051

boot пустой

Понятно. Если там сборка Debian, то файлы dtb, скорее всего тут: /usr/lib/linux-image-версия-ядра/. Нет там? Или может быть в /lib/firmware/версия-ядра/device-tree/

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

Мне сложно перебирать места, потому что может оказаться где угодно. Тогда ищи. А где образ ядра находится? Вот файл device tree может быть где-то там же.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от Zubok

в общем откопал dtb конвертнул его и вот что получается по искомому порту

serial@021e8000 { compatible = «fsl,imx6ul-uart», «fsl,imx6q-uart», «fsl,imx21-uart»; reg = <0x21e8000 0x4000>; interrupts = <0x0 0x1b 0x4>; clocks = <0x1 0xbf 0x1 0xc0>; clock-names = «ipg», «per»; dmas = <0x7 0x1b 0x4 0x0 0x7 0x1c 0x4 0x0>; dma-names = «rx», «tx»; status = «okay»; pinctrl-names = «default»; pinctrl-0 = <0x2d>; };

по идее как в той рекомендации надо отредактировать dma-names… потом снова собрать dtb и попытаться не превратить девайс в кирпич )))

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

А у вас там не modbus cлучайно протоколом обмена? А то может взять https://libmodbus.org/ вместо изобретения велосипеда?
Хотя либа не спасет от задержек из-за DMA буфера.

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

Или просто удалить (закомментировать) эти строчки (dmas и dma-names). Где-то я видел краем глаза ответ поддержки с примером dts-файла с отключенным DMA. Сейчас попробую найти это.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 3)
Ответ на: комментарий от demon051

Вот человек сделал и у него все ок.

https://community.nxp.com/t5/i-MX-Processors/Linux-hangs-if-UART-is-stressed/...

потом снова собрать dtb и попытаться не превратить девайс в кирпич )))

Должно быть все ок.

https://community.arm.com/developer/tools-software/oss-platforms/w/docs/525/d...

Decompiling a DTB using DTC is lossless, i.e. one can decompile a DTB into a DTS and then compile that DTS back into a DTB without losing any information.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от demon051

Возможно проблема действительно в ядре/драйвере/DMA, но код тоже странноватый:

     tcgetattr(fd, &options);

^ Нет проверки возвращаемого значения.

    options.c_iflag = IGNPAR;
    options.c_iflag |= IGNBRK;
    options.c_iflag &= ~BRKINT;
    options.c_iflag &= ~ICRNL;

^ Это эквивалентно options.c_iflag = IGNPAR | IGNBRK. Зачем остальное?

    tcflush(fd, TCIOFLUSH);
    tcsetattr(fd, TCSANOW, &options);

^ Тоже нет проверки возвращаемого значения. Ещё: Note that tcsetattr() returns success if any of the requested changes could be successfully carried out. Therefore, when making multiple changes it may be necessary to follow this call with a further call to tcgetattr() to check that all changes have been performed successfully.

catch(...)

^ Что ловим =)? Кстати, если уж C++, то почему не взять что-нибудь более высокоуровневое? Boost.Asio или что там нынче в моде…

         if ((ret = select(fd+1, &read_fs, &write_fs, &error_fs, &timeout_wait)) < 0)

^ select() лучше не использовать, в него большие дескрипторы не лезут, так как он использует битовые маски длиной FD_SETSIZE. В реальном приложении лучше взять хотя бы poll() или вообще высокоуровневую обёртку, в которой уже всё сделано как надо.

            nanosleep(&sp, NULL);
            /* и все другие слипы */
                tcdrain( fd );  /* это тоже слип, по сути */

^ В «асинхронном» коде слипов и блокирующих вызовов быть не должно. Они всегда приводят к проблемам, иногда плохо предсказуемым. На каждой итерации, select() может выдать пустые маски, одновременно «возможно чтение» и «возможна запись», или только что-то одно из. Из-за этого, чтение и запись со всеми сопутствующими слипами будут происходить в рандомном порядке.

            continue;

В данном случае, если данные на входе есть, то запись произойдёт только на следующей итерации, после того, как произошло чтение, и отработал usleep(3000). Это точно не баг, а задуманное поведение?

Ещё read() и write() могут возвращать меньшее значение, чем было запрошено на чтение или запись.

    try
    {
        close(fd);
    }
    catch(...)
    {
    }

Если тут обычный close(), а не специальная плюсовая обёртка, то catch тут работать не будет.

im-0
()
Ответ на: комментарий от im-0

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

tcdrain( fd ); - ожидание пока данные уйдут в порт. иначе select тут же вернет по дескриптору возможность новой записи.

написано же что на этом коде отлаживаюсь, он не тот что идёт в работу, поэтому не проверяется и не делается много чего.

но результат выполнения команды stty -a -F /dev/ttymxc1 во время исполнения приведенного кода показывает, что все заданные настройки применяются

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

сделал я всё это шаманство не помогло. никак. (

Надо убедиться, что ты все сделал правильно.

1. А ты точно тот порт исправлял? Их там, сколько? Восемь штук, по-моему?

2. Ты то же имя использовал для файла dtb? Не ошибся?

3. Проверил контрольной декомпиляцией dtd, настройки применились?

4. У тебя в коде какие-то задержки выставлены, я не разбирался. Может, ненужную забыл убрать?

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 1)
Ответ на: комментарий от demon051

порт тот согласно алиасу в системе и адресу.

Убери у всех, чтобы наверняка. Но только оригинальный dtb скопируй куда-нибудь.

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

в том что написано (в коде) действительно есть логика ))

Этот код выглядит так, будто его писал человек, который плохо понимает что именно он делает.

im-0
()
Ответ на: комментарий от Zubok

А почему бы не попробовать Device Tree Overlay вместо того, чтобы компилировать-декомпилировать исходный .dtb файл? Написать свой .dtso где хранить все изменения по сравнению с оригинальным .dtb например. Как это работает для GNU/Linux не скажу, но для FreeBSD - запросто :)

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

Это если они поддерживаются, а они могут не поддерживаться. Если получится через оригинальные dtb, то потом можно оверлеи попробовать. Иначе можно ложный результат получить, думая, что что-то не работает. Начать с оригинального DTB надежнее.

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

А вот оно что, понял, спасибо.

Но я думаю, что если они поддерживаются и производитель их для платы написал, то наличие оверлеев на устройстве надо бы проверить. Иначе они могут переопределять настройки основного файла DTB, который исправляется. Если какие-то оверлеи там есть (что только автор может поискать), то надо посмотреть, что там. Если их нет, то и не волноваться.

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

Ещё одна мысль состоит в том, как сказать загрузчику какие именно оверлеи загружать. Например, во FreeBSD это можно сделать через модификацию /boot/loader.conf и прописать что-то вроде:

fdt_overlays="am335x-boneblack-tscadc.dtbo,am335x-boneblack-pruss.dtbo"

Думаю имеет смысл глянуть если оверлеи и есть, то о каких из них знает загрузчик.

dsl
()
Последнее исправление: dsl (всего исправлений: 1)
Ответ на: комментарий от Zubok

ну что же… можно отчитаться о проделанной работе.

отключение DMA для порта дало результат

как пишут знающие люди, в случае использования dma ожидается полное заполнение буфера dma данными и потом их отправка. если данных меньше, чем размер буфера, то отправка осуществляется по таймауту. я так понимаю он в моем случае был равен длительности вызова tcdrain( fd ); т.е. примерно 30 миллисекунд.

после отключения дма, если вызвать поле write ещё и tcdrain, то он исправно будет ждать чего-то эти самые 30 миллисекунд.

хотя хз… тут не до конца понятно, если при вrлюченном dma не дергать tcdrain, то на отправку данных уходило более 30 миллисекунд. сплошные загадки.

в общем, теперь, когда dma отключен, tcdrain не вызывается, на отправку-прием 7 байт уходит 15-17 миллисекунд вместо получавшихся ранее 40-45.

как-то так.

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

1/9600*7*11 и это в одну сторону :)

у тебя вполне нормальные цифры

Morin ★★★★★
()
Последнее исправление: Morin (всего исправлений: 1)
Ответ на: комментарий от demon051

после отключения дма, если вызвать поле write ещё и tcdrain, то он исправно будет ждать чего-то эти самые 30 миллисекунд.

Если DMA гарантировано отключен, а передача даже 7 байт и последующий вызов tcdrain() занимает 30 мс, то у меня версия, что он ждет очистки FIFO, который 32 байта у тебя. Примерно так и получается, если учесть, что байт на скорости 9600 передается 1 мс.

Каждый драйвер serial должен обеспечивать фунцкию tx_empty. В драйвере imx.c эту функцию обеспечивает функция imx_uart_tx_empty. В ней можно видеть, что проверяется бит TXDC в регистре USR2. Reference manual, к сожалению, на NXP по регистрации можно только получить, а мне сейчас неохота добывать этот reference где-то еще. Я не могу посмотреть, когда этот бит выставляется, однако техподдержка на форуме утверждает, что бит выставляется, когда TxFIFO *и* выходной сдвиговый регистр пуст. Просто пустой TxFIFO - это бит TXFE.

Вот функция из драйвера. Проверяется бит TXDC. Если TXDC выставился, то возвращаем TIOCSER_TEMT, то есть передачик все передал. Если для передачи используется DMA, то не сразу выходим, а проверяем и DMA. Если цикл DMA для передатчик еще не завершился, то считаем, что еще не передали. Вот поэтому с DMA ожидание больше.

/*
 * Return TIOCSER_TEMT when transmitter is not busy.
 */
static unsigned int imx_uart_tx_empty(struct uart_port *port)
{
	struct imx_port *sport = (struct imx_port *)port;
	unsigned int ret;

	ret = (imx_uart_readl(sport, USR2) & USR2_TXDC) ?  TIOCSER_TEMT : 0;

	/* If the TX DMA is working, return 0. */
	if (sport->dma_is_txing)
		ret = 0;

	return ret;
}

Меня смущает в этом предположении, что процессор будет ждать передачи 32 байт, даже если передаются 7 байт. Надо прочесть, как точно он работает в Reference manual. Попробуй увеличить скорость в два раза, запиши свои 7 байт и проверь осциллографом, как изменилось время просиживания в tcdrain(). Пропорционально ли оно скорости?

UPD. Или это таймаут, с которым работает опрос окончания передачи.

Zubok ★★★★★
()
Последнее исправление: Zubok (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.