Тест петли / дуплекс последовательного порта, в Bash или C?(процесс замены) - PullRequest
8 голосов
/ 13 сентября 2010

У меня есть последовательное устройство, настроенное как шлейф (то есть оно будет просто отображать любой полученный символ), и я хотел бы измерить эффективную скорость передачи.Для этого я надеялся, что смогу использовать time, как в

time bash -c '...'

, где '...' будет командой, которую я смогу выполнить.

Теперь, первая проблема в том, что я хочу использовать устройство на скорости 2000000 бит / с, поэтому я не могу использовать ttylog или screen (они оба, кажется, доходят доТолько 115200 бит / с).Тем не менее, работа с /dev/ttyUSB0 в качестве файла (с использованием перенаправления файлов и cat), кажется, работает нормально:

# initialize serial port
stty 2000000 -ixon icanon </dev/ttyUSB0

# check settings
stty -a -F /dev/ttyUSB0

# in one terminal - read from serial port
while (true) do cat -A /dev/ttyUSB0 ; done

# in other terminal - write to serial port
echo "1234567890" > /dev/ttyUSB0

# back to first terminal, I now have:
# $ while (true) do cat -A /dev/ttyUSB0 ; done
# 1234567890$
# ...

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

Я подумал, что мог бы использовать подстановку процесса Bash, чтобы части «записи» и «чтения» перешли, вроде как, в «параллель» - если я попробую это с именованными каналами, это сработает:

# mkfifo my.pipe # same as below:
$ mknod my.pipe p

$ comm <(echo -e "test\ntest\ntest\n" > my.pipe) <(cat my.pipe)
    test
    test
    test
comm: file 2 is not in sorted order

Вверху я не использую comm для каких-либо других целей, чем (вроде) объединить два процесса в одну команду (я думаю, я мог бы так же хорошо использовать echo вместо).

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

$ comm <(echo "1234567890" > /dev/ttyUSB0) <(while (true) do cat -A /dev/ttyUSB0 ; done)
cat: /dev/ttyUSB0: Invalid argument

..., однако, обычно я просто не получаювывод вообще.Это говорит мне о том, что: либо нет контроля над тем, какой процесс запускается первым, и поэтому cat может начать чтение до того, как порт будет готов (однако, в первом примере выше это не кажется проблемой);или в Linux / Bash вы не можете одновременно выполнять чтение и запись на последовательный порт, поэтому «Invalid argument» может произойти в те моменты, когда и чтение, и запись происходят одновременно.

Итак, мои вопросы:

  • Есть ли способ сделать что-то вроде этого (cat файл на последовательный порт, настроенный как loopback; прочитайте его и посмотрите, как долготребуется) только в Bash, не прибегая к написанию специальной C-программы?
  • Если мне нужна выделенная программа на C, какие-нибудь исходные примеры в сети, которые я мог бы использовать?

Большое спасибо за любые ответы,

Приветствия!

РЕДАКТИРОВАТЬ: я знаю, что цикл while, написанный выше, не завершается;эта командная строка была для предварительного тестирования, и я прерываю ее, используя Ctrl-C.( Я мог бы в принципе прервать его чем-то вроде timeout -9 0.1 bash -c 'while (true) do echo AA ; done', но это бы победило цель time, тогда:) )

Причина, по которой while существует,в данный момент считывание с устройства через cat завершается немедленно;время от времени я настраивал устройство , поэтому при выдаче cat оно фактически блокирует и ожидает входящие данные;но я пока не могу понять, что происходит (и частично поэтому я ищу способ тестирования из командной строки).

В случае, если я не использовал while, я полагаю, для синхронизации я использовал бы что-то вроде:

time bash -c 'comm <(echo "1234567890" > /dev/ttyUSB0) <(cat -A /dev/ttyUSB0)'

... однако дляэто работает, вроде как, предполагает, что cat -A /dev/ttyUSB0 запускается первым и блокируется;затем echo пишет в последовательный порт (и выходит);а затем cat -A выводит все, что читает из последовательного порта, и затем завершает работу.( И я не совсем уверен, может ли последовательный порт вообще себя так вести, и нельзя ли заставить cat блокировать и произвольно выходить таким образом ).

Точный метод действительно не имеет значения;если это вообще возможно, я бы просто хотел избежать написания моей собственной C-программы для такого рода тестирования, поэтому мой основной интерес заключается в том, возможно ли как-нибудь выполнить такой «полнодуплексный тест» с использованием базового Bash /Linux (то есть coreutils);(, а если нет, то если есть готовый код, который я могу использовать для чего-то подобного ).

РЕДАКТИРОВАТЬ 2: Возможно также имеет отношение:

Ответы [ 2 ]

5 голосов
/ 14 сентября 2010

Ну, вот что-то вроде частичного ответа - хотя вопрос об использовании bash все еще открыт.Я попытался немного взглянуть на некоторые решения кода на C - и это, кажется, тоже не тривиально!:)

Во-первых, давайте посмотрим, что, возможно, не работает для этого случая - ниже приведен пример из " между записью и чтением: последовательный порт. - C ":

// from: between write and read:serial port. - C - http://www.daniweb.com/forums/thread286634.html
// gcc -o sertest -Wall -g sertest.c

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

int main(int argc, char *argv[])
{
    char line[1024];
    int chkin;
    char input[1024];
    char msg[1024];
    char serport[24];

    // argv[1] - serial port
    // argv[2] - file or echo 

    sprintf(serport, "%s", argv[1]);

    int file= open(serport, O_RDWR | O_NOCTTY | O_NDELAY);

    if (file == 0)
    {
        sprintf(msg, "open_port: Unable to open %s.\n", serport);
        perror(msg);
    }
    else
        fcntl(file, F_SETFL, FNDELAY); //fcntl(file, F_SETFL, 0);

    while (1)
    {

        printf("enter input data:\n");
        scanf("%s",&input[0]);

        chkin=write(file,input,sizeof input);

        if (chkin<0)
        {
            printf("cannot write to port\n");
        }

        //chkin=read(file,line,sizeof line);

        while ((chkin=read(file,line,sizeof line))>=0)
        {
            if (chkin<0)
            {
                printf("cannot read from port\n");
            }
            else
            {
                printf("bytes: %d, line=%s\n",chkin, line);
            }
        }

        /*CODE TO EXIT THE LOOP GOES HERE*/
        if (input[0] == 'q') break;
    }

    close(file);
    return 0;
}

Проблема с приведенным выше кодом заключается в том, что он не инициализирует последовательный порт в явном виде для символьной (" сырой ") операции;поэтому в зависимости от того, как порт был установлен ранее, сеанс может выглядеть следующим образом:

$ ./sertest /dev/ttyUSB0 
enter input data:
t1
enter input data:
t2
enter input data:
t3
enter input data:
^C

... другими словами, нет повторения входных данных.Однако, если последовательный порт настроен правильно, мы можем получить сеанс, подобный следующему:

$ ./sertest /dev/ttyUSB0 
enter input data:
t1
enter input data:
t2
bytes: 127, line=t1
enter input data:
t3
bytes: 127, line=t2
enter input data:
t4
bytes: 127, line=t3
enter input data:
^C

... (, но даже в этом случае этот код sertest не будет работать на входных словах больше 3иероглифы. )

Наконец, с помощью некоторых онлайн-копаний мне удалось найти " (решено) последовательное программирование, запись-чтение, выпуск ", в котором предлагается пример writeread.cpp.Однако для этого побайтного случая «дуплекс» даже этого было недостаточно, а именно: « Последовательное программирование HOWTO » отмечает: « Каноническая обработка ввода ... это нормальный режим обработкидля терминалов ... это означает, что чтение вернет только полную строку ввода. По умолчанию строка заканчивается на NL (ASCII LF) ...";и, таким образом, мы должны явно установить последовательный порт в «неканонический» (или «необработанный») режим в нашем коде через ICANON ( другими словами, просто установив O_NONBLOCK черезopen это не достаточно ) - пример для этого дан в " 3.2 Как я могу читать отдельные символы из терминала? - Часто задаваемые вопросы по программированию Unix - 3. Терминал I/ O ».Как только это будет сделано, вызов writeread также "правильно" установит последовательный порт для примера serport (см. Выше).

Таким образом, я изменил часть этого writeread кода обратно на C, добавил необходимые средства инициализации, а также измерения времени, возможность отправки строк или файлов и дополнительный поток вывода ( для 'трубопровода)'читать последовательные данные в отдельный файл ).Код ниже как writeread.c и serial.h, и с ним я могу сделать что-то вроде следующего сеанса Bash:

$ ./writeread /dev/ttyUSB0 2000000 writeread.c 3>myout.txt
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 2000000 (4107/0x100b);
Got file/string 'writeread.c'; opened as file (4182).

+++DONE+++
Wrote: 4182 bytes; Read: 4182 bytes; Total: 8364 bytes. 
Start: 1284422340 s 443302 us; End: 1284422347 s 786999 us; Delta: 7 s 343697 us. 
2000000 baud for 8N1 is 200000 Bps (bytes/sec).
Measured: write 569.47 Bps, read 569.47 Bps, total 1138.94 Bps.

$ diff writeread.c myout.txt 

$ ./writeread /dev/ttyUSB0 2000000 writeread.c 3>/dev/null 
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 2000000 (4107/0x100b);
Got file/string 'writeread.c'; opened as file (4182).

+++DONE+++
Wrote: 4182 bytes; Read: 4182 bytes; Total: 8364 bytes. 
Start: 1284422380 s -461710 us; End: 1284422388 s 342977 us; Delta: 8 s 804687 us. 
2000000 baud for 8N1 is 200000 Bps (bytes/sec).
Measured: write 474.97 Bps, read 474.97 Bps, total 949.95 Bps.

Ну:

  • Первый сюрприз- это происходит быстрее, если я записываю в файл, чем если я передаю /dev/null!
  • Кроме того, получая около 1000 бит / с - тогда как устройство, очевидно, установлено на 200000 бит / с !!

На данный момент, я думаю, что замедление происходит потому, что после каждого записанного байта в writeread.c мы ожидаем очистки флага прерыванием чтения, прежде чем приступить к чтению последовательного буфера,Возможно, если бы чтение и запись были отдельными потоками, то и чтение, и запись могли бы попытаться использовать большие блоки байтов в одиночных вызовах read или write, и поэтому полоса пропускания использовалась бы лучше ?!( Или, может быть, обработчик прерываний действует в некотором смысле как «поток», работающий параллельно - так что, возможно, чего-то подобного можно добиться, переместив все связанные с чтением функции в обработчик прерываний?! )

Ах, хорошо - на данный момент я очень открыт для предложений / ссылок для существующего кода, такого как writeread.c, но многопоточного :) И, конечно, для любых других возможных инструментов Linux, или, возможно, методов Bash (хотякажется, что Bash не сможет осуществлять такой контроль ...)

Ура!

writeread.c :

/*
    writeread.c - based on writeread.cpp
    [SOLVED] Serial Programming, Write-Read Issue - http://www.linuxquestions.org/questions/programming-9/serial-programming-write-read-issue-822980/

    build with: gcc -o writeread -Wall -g writeread.c
*/

#include <stdio.h>
#include <string.h>
#include <stddef.h>

#include <stdlib.h>
#include <sys/time.h>

#include "serial.h"


int serport_fd;

void usage(char **argv)
{
    fprintf(stdout, "Usage:\n"); 
    fprintf(stdout, "%s port baudrate file/string\n", argv[0]); 
    fprintf(stdout, "Examples:\n"); 
    fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt\n", argv[0]); 
    fprintf(stdout, "%s /dev/ttyUSB0 115200 \"some text test\"\n", argv[0]); 
}


int main( int argc, char **argv ) 
{

    if( argc != 4 ) { 
        usage(argv);
        return 1; 
    }

    char *serport;
    char *serspeed;
    speed_t serspeed_t;
    char *serfstr;
    int serf_fd; // if < 0, then serfstr is a string
    int bytesToSend; 
    int sentBytes; 
    char byteToSend[2];
    int readChars;
    int recdBytes, totlBytes; 

    char sResp[11];

    struct timeval timeStart, timeEnd, timeDelta;
    float deltasec; 

    /* Re: connecting alternative output stream to terminal - 
    * http://coding.derkeiler.com/Archive/C_CPP/comp.lang.c/2009-01/msg01616.html 
    * send read output to file descriptor 3 if open, 
    * else just send to stdout
    */
    FILE *stdalt;
    if(dup2(3, 3) == -1) {
        fprintf(stdout, "stdalt not opened; ");
        stdalt = fopen("/dev/tty", "w");
    } else {
        fprintf(stdout, "stdalt opened; ");
        stdalt = fdopen(3, "w");
    }
    fprintf(stdout, "Alternative file descriptor: %d\n", fileno(stdalt));

    // Get the PORT name
    serport = argv[1];
    fprintf(stdout, "Opening port %s;\n", serport);

    // Get the baudrate
    serspeed = argv[2];
    serspeed_t = string_to_baud(serspeed);
    fprintf(stdout, "Got speed %s (%d/0x%x);\n", serspeed, serspeed_t, serspeed_t);

    //Get file or command;
    serfstr = argv[3];
    serf_fd = open( serfstr, O_RDONLY );
    fprintf(stdout, "Got file/string '%s'; ", serfstr);
    if (serf_fd < 0) {
        bytesToSend = strlen(serfstr);
        fprintf(stdout, "interpreting as string (%d).\n", bytesToSend);
    } else {
        struct stat st;
        stat(serfstr, &st);
        bytesToSend = st.st_size;
        fprintf(stdout, "opened as file (%d).\n", bytesToSend);
    }


    // Open and Initialise port
    serport_fd = open( serport, O_RDWR | O_NOCTTY | O_NONBLOCK );
    if ( serport_fd < 0 ) { perror(serport); return 1; }
    initport( serport_fd, serspeed_t );

    sentBytes = 0; recdBytes = 0;
    byteToSend[0]='x'; byteToSend[1]='\0';
    gettimeofday( &timeStart, NULL );

    // write / read loop - interleaved (i.e. will always write 
    // one byte at a time, before 'emptying' the read buffer ) 
    while ( sentBytes < bytesToSend )
    {
        // read next byte from input...
        if (serf_fd < 0) { //interpreting as string
            byteToSend[0] = serfstr[sentBytes];
        } else { //opened as file 
            read( serf_fd, &byteToSend[0], 1 );
        }

        if ( !writeport( serport_fd, byteToSend ) ) { 
            fprintf(stdout, "write failed\n"); 
        }
        //~ fprintf(stdout, "written:%s\n", byteToSend );

        while ( wait_flag == TRUE );

        if ( (readChars = readport( serport_fd, sResp, 10)) >= 0 ) 
        {
            //~ fprintf(stdout, "InVAL: (%d) %s\n", readChars, sResp);
            recdBytes += readChars;
            fprintf(stdalt, "%s", sResp);
        }

        wait_flag = TRUE; // was ==
        //~ usleep(50000);
        sentBytes++;
    }

    gettimeofday( &timeEnd, NULL );

    // Close the open port
    close( serport_fd );
    if (!(serf_fd < 0)) close( serf_fd );

    fprintf(stdout, "\n+++DONE+++\n");

    totlBytes = sentBytes + recdBytes;
    timeval_subtract(&timeDelta, &timeEnd, &timeStart);
    deltasec = timeDelta.tv_sec+timeDelta.tv_usec*1e-6;

    fprintf(stdout, "Wrote: %d bytes; Read: %d bytes; Total: %d bytes. \n", sentBytes, recdBytes, totlBytes);
    fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us. \n", timeStart.tv_sec, timeStart.tv_usec, timeEnd.tv_sec, timeEnd.tv_usec, timeDelta.tv_sec, timeDelta.tv_usec);
    fprintf(stdout, "%s baud for 8N1 is %d Bps (bytes/sec).\n", serspeed, atoi(serspeed)/10);
    fprintf(stdout, "Measured: write %.02f Bps, read %.02f Bps, total %.02f Bps.\n", sentBytes/deltasec, recdBytes/deltasec, totlBytes/deltasec);

    return 0;
}

serial.h :

/* serial.h
    (C) 2004-5 Captain http://www.captain.at

    Helper functions for "ser"

    Used for testing the PIC-MMC test-board
    http://www.captain.at/electronic-index.php
*/

#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/types.h>

#define TRUE    1
#define FALSE   0

int wait_flag = TRUE;   // TRUE while no signal received

// Definition of Signal Handler
void DAQ_signal_handler_IO ( int status )
{
    //~ fprintf(stdout, "received SIGIO signal %d.\n", status);
    wait_flag = FALSE;
}


int writeport( int fd, char *comm ) 
{
    int len = strlen( comm );
    int n = write( fd, comm, len );

    if ( n < 0 ) 
    {
        fprintf(stdout, "write failed!\n");
        return 0;
    }

    return n;
}


int readport( int fd, char *resp, size_t nbyte ) 
{
    int iIn = read( fd, resp, nbyte );
    if ( iIn < 0 ) 
    {
        if ( errno == EAGAIN ) 
        {
            fprintf(stdout, "SERIAL EAGAIN ERROR\n");
            return 0;
        } 
        else 
        {
            fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
            return 0;
        }
    }

    if ( resp[iIn-1] == '\r' )
        resp[iIn-1] = '\0';
    else
        resp[iIn] = '\0';

    return iIn;
}


int getbaud( int fd ) 
{
    struct termios termAttr;
    int inputSpeed = -1;
    speed_t baudRate;
    tcgetattr( fd, &termAttr );
    // Get the input speed
    baudRate = cfgetispeed( &termAttr );
    switch ( baudRate )
    {
        case B0:      inputSpeed = 0; break;
        case B50:     inputSpeed = 50; break;
        case B110:    inputSpeed = 110; break;
        case B134:    inputSpeed = 134; break;
        case B150:    inputSpeed = 150; break;
        case B200:    inputSpeed = 200; break;
        case B300:    inputSpeed = 300; break;
        case B600:    inputSpeed = 600; break;
        case B1200:   inputSpeed = 1200; break;
        case B1800:   inputSpeed = 1800; break;
        case B2400:   inputSpeed = 2400; break;
        case B4800:   inputSpeed = 4800; break;
        case B9600:   inputSpeed = 9600; break;
        case B19200:  inputSpeed = 19200; break;
        case B38400:  inputSpeed = 38400; break;
        case B115200: inputSpeed = 115200; break;
        case B2000000: inputSpeed = 2000000; break; //added
    }
    return inputSpeed;
}


/* ser.c
    (C) 2004-5 Captain http://www.captain.at

    Sends 3 characters (ABC) via the serial port (/dev/ttyS0) and reads
    them back if they are returned from the PIC.

    Used for testing the PIC-MMC test-board
    http://www.captain.at/electronic-index.php

*/


int initport( int fd, speed_t baudRate ) 
{
    struct termios options;
    struct sigaction saio;  // Definition of Signal action

    // Install the signal handler before making the device asynchronous
    saio.sa_handler = DAQ_signal_handler_IO;
    saio.sa_flags = 0;
    saio.sa_restorer = NULL;
    sigaction( SIGIO, &saio, NULL );

    // Allow the process to receive SIGIO
    fcntl( fd, F_SETOWN, getpid() );
    // Make the file descriptor asynchronous (the manual page says only 
    // O_APPEND and O_NONBLOCK, will work with F_SETFL...)
    fcntl( fd, F_SETFL, FASYNC );
    //~ fcntl( fd, F_SETFL, FNDELAY); //doesn't work; //fcntl(file, F_SETFL, 0);

    // Get the current options for the port...
    tcgetattr( fd, &options );
/*       
    // Set port settings for canonical input processing
    options.c_cflag = BAUDRATE | CRTSCTS | CLOCAL | CREAD;
    options.c_iflag = IGNPAR | ICRNL;
    //options.c_iflag = IGNPAR;
    options.c_oflag = 0;
    options.c_lflag = ICANON;
    //options.c_lflag = 0;
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 0;
*/   
    /* ADDED - else 'read' will not return, unless it sees LF '\n' !!!!
    * From: Unix Programming Frequently Asked Questions - 3. Terminal I/O - 
    * http://www.steve.org.uk/Reference/Unix/faq_4.html 
    */
    /* Disable canonical mode, and set buffer size to 1 byte */
    options.c_lflag &= (~ICANON);
    options.c_cc[VTIME] = 0;
    options.c_cc[VMIN] = 1; 

    // Set the baud rates to...
    cfsetispeed( &options, baudRate );
    cfsetospeed( &options, baudRate );

    // Enable the receiver and set local mode...
    options.c_cflag |= ( CLOCAL | CREAD );
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    // Flush the input & output...
    tcflush( fd, TCIOFLUSH );

    // Set the new options for the port...
    tcsetattr( fd, TCSANOW, &options );

    return 1;
}


/* 
    ripped from 
    http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/stty.c
*/

#define STREQ(a, b)     (strcmp((a), (b)) == 0)

struct speed_map
{
  const char *string;       /* ASCII representation. */
  speed_t speed;        /* Internal form. */
  unsigned long int value;  /* Numeric value. */
};

static struct speed_map const speeds[] =
{
  {"0", B0, 0},
  {"50", B50, 50},
  {"75", B75, 75},
  {"110", B110, 110},
  {"134", B134, 134},
  {"134.5", B134, 134},
  {"150", B150, 150},
  {"200", B200, 200},
  {"300", B300, 300},
  {"600", B600, 600},
  {"1200", B1200, 1200},
  {"1800", B1800, 1800},
  {"2400", B2400, 2400},
  {"4800", B4800, 4800},
  {"9600", B9600, 9600},
  {"19200", B19200, 19200},
  {"38400", B38400, 38400},
  {"exta", B19200, 19200},
  {"extb", B38400, 38400},
#ifdef B57600
  {"57600", B57600, 57600},
#endif
#ifdef B115200
  {"115200", B115200, 115200},
#endif
#ifdef B230400
  {"230400", B230400, 230400},
#endif
#ifdef B460800
  {"460800", B460800, 460800},
#endif
#ifdef B500000
  {"500000", B500000, 500000},
#endif
#ifdef B576000
  {"576000", B576000, 576000},
#endif
#ifdef B921600
  {"921600", B921600, 921600},
#endif
#ifdef B1000000
  {"1000000", B1000000, 1000000},
#endif
#ifdef B1152000
  {"1152000", B1152000, 1152000},
#endif
#ifdef B1500000
  {"1500000", B1500000, 1500000},
#endif
#ifdef B2000000
  {"2000000", B2000000, 2000000},
#endif
#ifdef B2500000
  {"2500000", B2500000, 2500000},
#endif
#ifdef B3000000
  {"3000000", B3000000, 3000000},
#endif
#ifdef B3500000
  {"3500000", B3500000, 3500000},
#endif
#ifdef B4000000
  {"4000000", B4000000, 4000000},
#endif
  {NULL, 0, 0}
};

static speed_t
string_to_baud (const char *arg)
{
  int i;

  for (i = 0; speeds[i].string != NULL; ++i)
    if (STREQ (arg, speeds[i].string))
      return speeds[i].speed;
  return (speed_t) -1;
}



/* http://www.gnu.org/software/libtool/manual/libc/Elapsed-Time.html
Subtract the `struct timeval' values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0.  */
int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
{
    /* Perform the carry for the later subtraction by updating y. */
    if (x->tv_usec < y->tv_usec) {
     int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
     y->tv_usec -= 1000000 * nsec;
     y->tv_sec += nsec;
    }
    if (x->tv_usec - y->tv_usec > 1000000) {
     int nsec = (x->tv_usec - y->tv_usec) / 1000000;
     y->tv_usec += 1000000 * nsec;
     y->tv_sec -= nsec;
    }

    /* Compute the time remaining to wait.
      tv_usec is certainly positive. */
    result->tv_sec = x->tv_sec - y->tv_sec;
    result->tv_usec = x->tv_usec - y->tv_usec;

    /* Return 1 if result is negative. */
    return x->tv_sec < y->tv_sec;
}
4 голосов
/ 14 сентября 2010

Ну, мне удалось поместить writeread.c в многопоточную версию, используя pthread ( код ниже - я не думаю, что serial.h сильно изменился; в любом случае, в многопоточной версии он не так уж и много используется ). Я также снизил скорость до 115200, и теперь я могу подтвердить эти измерения с помощью устройства в следующем примере сеанса командной строки:

$ ./writeread /dev/ttyUSB0 115200 writeread.c 3>myout.txt
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 115200 (4098/0x1002);
Got file/string 'writeread.c'; opened as file (6131).
write_thread_function spawned
   write: 6131
   read: 18
   read: 64
   read: 110
   read: 156
   read: 202
...
   read: 6066
   read: 6089
   read: 6123
   read: 6131

+++DONE+++
Wrote: 6131 bytes; Read: 6131 bytes; Total: 12262 bytes. 
Start: 1284462824 s 141104 us; End: 1284462824 s 682598 us; Delta: 0 s 541494 us. 
115200 baud for 8N1 is 11520 Bps (bytes/sec).
Measured: write 11322.38 Bps (98.28%), read 11322.38 Bps (98.28%), total 22644.76 Bps.

$ diff writeread.c myout.txt 
$ 

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

  • Для этого устройства write выполняется в виде одного фрагмента (, поскольку ПК должен иметь возможность обрабатывать последовательности для пакетов, если необходимо ),
  • в то время как read продолжается небольшими порциями (, вероятно, указывает на то, что устройство не ожидает поступления целых порций - вместо этого оно начинает отправлять меньшие порции, как только получит достаточно )

Ну, я думаю, это то, что мне было нужно изначально; Я также думаю, что, вероятно, невозможно организовать cat и echo через подстановку процесса для выполнения в этом, давайте назовем это «многопоточным», манера :) ( Теперь у меня есть проблема с тем же на 2000000 бод, но это указывает на проблему с программированием устройства ).

Ура!

writeread.c - версия с резьбой

/*
    writeread.c - based on writeread.cpp
    [SOLVED] Serial Programming, Write-Read Issue - http://www.linuxquestions.org/questions/programming-9/serial-programming-write-read-issue-822980/

    build with: gcc -o writeread -lpthread -Wall -g writeread.c
*/

#include <stdio.h>
#include <string.h>
#include <stddef.h>

#include <stdlib.h>
#include <sys/time.h>

#include <pthread.h>

#include "serial.h"


int serport_fd;

//POSIX Threads Programming - https://computing.llnl.gov/tutorials/pthreads/#PassingArguments
struct write_thread_data{
   int  fd;
   char* comm; //string to send
   int bytesToSend;
   int writtenBytes;
};

void usage(char **argv)
{
    fprintf(stdout, "Usage:\n"); 
    fprintf(stdout, "%s port baudrate file/string\n", argv[0]); 
    fprintf(stdout, "Examples:\n"); 
    fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt\n", argv[0]); 
    fprintf(stdout, "%s /dev/ttyUSB0 115200 \"some text test\"\n", argv[0]); 
}

// POSIX threads explained - http://www.ibm.com/developerworks/library/l-posix1.html
// instead of writeport
void *write_thread_function(void *arg) {
    int lastBytesWritten;
    struct write_thread_data *my_data;
    my_data = (struct write_thread_data *) arg;

    fprintf(stdout, "write_thread_function spawned\n");

    my_data->writtenBytes = 0; 
    while(my_data->writtenBytes < my_data->bytesToSend)
    {
        lastBytesWritten = write( my_data->fd, my_data->comm + my_data->writtenBytes, my_data->bytesToSend - my_data->writtenBytes );   
        my_data->writtenBytes += lastBytesWritten;  
        if ( lastBytesWritten < 0 ) 
        {
            fprintf(stdout, "write failed!\n");
            return 0;
        }
        fprintf(stderr, "   write: %d - %d\n", lastBytesWritten, my_data->writtenBytes);
    }
    return NULL; //pthread_exit(NULL)
}

int main( int argc, char **argv ) 
{

    if( argc != 4 ) { 
        usage(argv);
        return 1; 
    }

    char *serport;
    char *serspeed;
    speed_t serspeed_t;
    char *serfstr;
    int serf_fd; // if < 0, then serfstr is a string
    int sentBytes; 
    int readChars;
    int recdBytes, totlBytes; 

    char* sResp;
    char* sRespTotal;

    struct timeval timeStart, timeEnd, timeDelta;
    float deltasec, expectBps, measReadBps, measWriteBps; 

    struct write_thread_data wrdata;
    pthread_t myWriteThread;

    /* Re: connecting alternative output stream to terminal - 
    * http://coding.derkeiler.com/Archive/C_CPP/comp.lang.c/2009-01/msg01616.html 
    * send read output to file descriptor 3 if open, 
    * else just send to stdout
    */
    FILE *stdalt;
    if(dup2(3, 3) == -1) {
        fprintf(stdout, "stdalt not opened; ");
        stdalt = fopen("/dev/tty", "w");
    } else {
        fprintf(stdout, "stdalt opened; ");
        stdalt = fdopen(3, "w");
    }
    fprintf(stdout, "Alternative file descriptor: %d\n", fileno(stdalt));

    // Get the PORT name
    serport = argv[1];
    fprintf(stdout, "Opening port %s;\n", serport);

    // Get the baudrate
    serspeed = argv[2];
    serspeed_t = string_to_baud(serspeed);
    fprintf(stdout, "Got speed %s (%d/0x%x);\n", serspeed, serspeed_t, serspeed_t);

    //Get file or command;
    serfstr = argv[3];
    serf_fd = open( serfstr, O_RDONLY );
    fprintf(stdout, "Got file/string '%s'; ", serfstr);
    if (serf_fd < 0) {
        wrdata.bytesToSend = strlen(serfstr);
        wrdata.comm = serfstr; //pointer already defined 
        fprintf(stdout, "interpreting as string (%d).\n", wrdata.bytesToSend);
    } else {
        struct stat st;
        stat(serfstr, &st);
        wrdata.bytesToSend = st.st_size;
        wrdata.comm = (char *)calloc(wrdata.bytesToSend, sizeof(char));
        read(serf_fd, wrdata.comm, wrdata.bytesToSend);
        fprintf(stdout, "opened as file (%d).\n", wrdata.bytesToSend);
    }

    sResp = (char *)calloc(wrdata.bytesToSend, sizeof(char));
    sRespTotal = (char *)calloc(wrdata.bytesToSend, sizeof(char));

    // Open and Initialise port
    serport_fd = open( serport, O_RDWR | O_NOCTTY | O_NONBLOCK );
    if ( serport_fd < 0 ) { perror(serport); return 1; }
    initport( serport_fd, serspeed_t );

    wrdata.fd = serport_fd;

    sentBytes = 0; recdBytes = 0;

    gettimeofday( &timeStart, NULL );

    // start the thread for writing.. 
    if ( pthread_create( &myWriteThread, NULL, write_thread_function, (void *) &wrdata) ) {
        printf("error creating thread.");
        abort();
    }

    // run read loop 
    while ( recdBytes < wrdata.bytesToSend )
    {

        while ( wait_flag == TRUE );

        if ( (readChars = read( serport_fd, sResp, wrdata.bytesToSend)) >= 0 ) 
        {
            //~ fprintf(stdout, "InVAL: (%d) %s\n", readChars, sResp);
            // binary safe - add sResp chunk to sRespTotal
            memmove(sRespTotal+recdBytes, sResp+0, readChars*sizeof(char));
            /* // text safe, but not binary:
            sResp[readChars] = '\0'; 
            fprintf(stdalt, "%s", sResp);
            */
            recdBytes += readChars;
        } else {
            if ( errno == EAGAIN ) 
            {
                fprintf(stdout, "SERIAL EAGAIN ERROR\n");
                return 0;
            } 
            else 
            {
                fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
                return 0;
            }           
        }
        fprintf(stderr, "   read: %d\n", recdBytes);        

        wait_flag = TRUE; // was ==
        //~ usleep(50000);
    }

    if ( pthread_join ( myWriteThread, NULL ) ) {
        printf("error joining thread.");
        abort();
    }

    gettimeofday( &timeEnd, NULL );

    // binary safe - dump sRespTotal to stdalt
    fwrite(sRespTotal, sizeof(char), recdBytes, stdalt);

    // Close the open port
    close( serport_fd );
    if (!(serf_fd < 0)) { 
        close( serf_fd );
        free(wrdata.comm); 
    } 
    free(sResp);
    free(sRespTotal);

    fprintf(stdout, "\n+++DONE+++\n");

    sentBytes = wrdata.writtenBytes; 
    totlBytes = sentBytes + recdBytes;
    timeval_subtract(&timeDelta, &timeEnd, &timeStart);
    deltasec = timeDelta.tv_sec+timeDelta.tv_usec*1e-6;
    expectBps = atoi(serspeed)/10.0f; 
    measWriteBps = sentBytes/deltasec;
    measReadBps = recdBytes/deltasec;

    fprintf(stdout, "Wrote: %d bytes; Read: %d bytes; Total: %d bytes. \n", sentBytes, recdBytes, totlBytes);
    fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us. \n", timeStart.tv_sec, timeStart.tv_usec, timeEnd.tv_sec, timeEnd.tv_usec, timeDelta.tv_sec, timeDelta.tv_usec);
    fprintf(stdout, "%s baud for 8N1 is %d Bps (bytes/sec).\n", serspeed, (int)expectBps);
    fprintf(stdout, "Measured: write %.02f Bps (%.02f%%), read %.02f Bps (%.02f%%), total %.02f Bps.\n", measWriteBps, (measWriteBps/expectBps)*100, measReadBps, (measReadBps/expectBps)*100, totlBytes/deltasec);

    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...