Jak odczytać dane binarne przez terminal szeregowy w programie C?

Czytałem następujące linki i inne źródła, ale nie znalazłem odpowiedzi na moje pytanie.

Dane binarne przez terminal szeregowy

Dane ulegają uszkodzeniu podczas transmisji przez port szeregowy

Komunikuję się z moim wbudowanym urządzeniem przez port szeregowy. Domyślnie osadzony Linux używa tego portu jako terminala. Ale chcę przenieść także dane binarne (pakiety usług) przez port. Mój plik / etc / inittab ma połączenie „getty”:console :: respawn: / sbin / getty 115200 ttyS0

Mam także plik / etc / passwd z łańcuchem, w którym użytkownik „admin” uruchamia aplikację „cli” po zalogowaniu:admin: 8Mt / Jtxcyg8AY: 1000: 0: admin: / tmp: / tmp / cli

Moje domyślne ustawienia ttyS0 przed uruchomieniem programu to:

~ # stty -a
speed 115200 baud;stty: standard input
 line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ^J;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd 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
~ #

W moim programie cli wykonuję następujące czynności:

main ()
{
    ...
    system("stty erase ^H);
    system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical (raw) mode

    // What function do I need to use here to retrieve binary data (also symbols that > 0x7F) from /dev/ttyS0?

    system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // go back to canonical mode

    ...

    exit(0);
}

Próbowałem funkcji read () (z niepodpisanym buforem char), aby uzyskać dane binarne, ale nie udało mi się uzyskać poprawnych danych. Również wstępnie otwieram / dev / ttyS0 ponownie, aby uzyskać plik_descriptor i użyć funkcji read ().

Mój program wysyła 3 bajty: 0xAA, 0x02, 0xFE. Ale w syslog zawsze widzę, że urządzenie odbiera niepoprawne symbole: 0x98, 0xE6, 0x18.

W czym problem? Jak uzyskać poprawne dane binarne?

Cały kod, który obecnie testuję.

#include "cli.h"
#include "glb_vars.h"

/******************************************
 *** Definitions
 ******************************************/
#define APPLICATION_NAME    "cli"
#define SERIALPORT_IS_CONSOLE

/******************************************
 *** Constants
 ******************************************/
const char dev_name[] = DEV_NAME;
const char lineminstr[] = "\t--------------------------------------------------------\n";

/******************************************
 *** Internal Function Declarations
 ******************************************/
CLI_RETVAL cliInit(void);
CLI_RETVAL cliClose(void);
void cliWorkLoop(Term_callback_t **term);

/******************************************
 *** External Function Declarations
 ******************************************/
extern void Vectors_init(Term_callback_t **vec);
extern char** Menu_completion(const char * text, int start, int end);


/****************************************************************************/
int file_descr, max_fd;
struct termios tty, orig_tty;
fd_set work_set;

/****************************************************************************/
/*!
 *  \brief  Init cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliInit(void)
{
    long spd;

    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGABRT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGILL, SIG_IGN);

//  system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical mode
//  system("stty -a");
//  sleep(1);

#ifdef SERIALPORT_IS_CONSOLE
    file_descr = STDIN_FILENO;
    SYS_LOG_DEBUG("SERIALPORT IS CONSOLE");
#else
    SYS_LOG_DEBUG("SERIALPORT IS NOT CONSOLE");
    file_descr = open("/dev/ttyS0", O_RDWR | O_ASYNC | O_NDELAY);
    if (file_descr == -1) {
        // Could not open the port
        perror("unable to open /dev/ttyS0");
        exit(1);
    }
#endif

    if(tcgetattr(file_descr, &tty) < 0)
    {
        perror("unable to get tty attributes");
        exit(1);
    }
    // backup tty, make it raw and apply changes
    orig_tty = tty;

    spd = B115200;
    cfsetospeed(&tty, (speed_t)spd);
    cfsetispeed(&tty, (speed_t)spd);

    cfmakeraw(&tty);

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
    tty.c_cflag |= CLOCAL | CREAD;
    tcsetattr(file_descr, TCSANOW, &tty);

//  // update local mode flags
//  tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
////    // renew control mode flags
////    tty.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD);
////    tty.c_cflag |= (BAUD | DATABITS | STOPBITS | PARITYON | PARITY);
//  // select 'raw' output mode
//  tty.c_oflag &= ~OPOST;
//  // disable mapping for input mode
//  tty.c_iflag &= ~(INLCR | ICRNL);
//
//
//  if(tcsetattr(file_descr, TCSAFLUSH, &tty) < 0)
//  {
//      perror("unable to set tty attributes");
//      exit(1);
//  }
//
    // Setup fd_set
    FD_ZERO(&work_set);
    FD_SET(file_descr, &work_set);
    max_fd = file_descr + 1;

    /* Readline lib init */
    // Define application name for readline library
    rl_readline_name = APPLICATION_NAME;
    // Update Pointer to alternative function to create matches.
    rl_attempted_completion_function = Menu_completion;
    // Start readline with reading /etc/inputrc file
    using_history();
    stifle_history(CLI_MAX_HISTORY_SIZE);


    // Some other initialization code
    // ...
    // ...

    return CLI_SUCCESS;
}

/****************************************************************************/
/*!
 *  \brief  Close cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliClose(void)
{

//  system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // enter in canonical mode

    tcsetattr(file_descr, TCSANOW, &orig_tty);
//  if(tcsetattr(file_descr, TCSAFLUSH, &orig_tty) < 0)
//  {
//      perror("unable to set orig_tty attributes");
//      exit(1);
//  }
    close(file_descr);

    return CLI_SUCCESS;
}


/****************************************************************************/
/*!
 *  \brief  Main cli processing loop
 *
 *  \no return
 *
 *  \ingroup CLI
 */
/****************************************************************************/
void cliWorkLoop(Term_callback_t **term)
{
    Term_callback_t *cur_term;
    int8 *commandString;
    uint8 ret = CLI_REFRESH, no_prompt;

    char prompt_str[20];

    while (1) {

        cur_term = *term;
        global_cmd_compl_pointer = cur_term->cmd_list;

        commandString = NULL;
        sprintf(prompt_str, "%s:~> ", dev_name);

        if(ret == CLI_REFRESH) {
            CLEAR_SCR();
            if(cur_term->out != NULL) {
                cur_term->out(term, commandString, &ret);
                no_prompt = ret;
            }
            CURSOR_DOWN();
        }

        int n;
        struct timeval timeout;
        uint8 tmpBuf[32];

        while (1)
        {
            // Setup Timeout
            timeout.tv_sec = 60;
            timeout.tv_usec = 0;
            // Wait for new connections
            n = select(max_fd, &work_set, NULL, NULL, &timeout);
            if (n < 0)
            {
                perror("select #2 failed");
                break;
            }
            if (n > 0)
            {
                /* У нас есть ввод */
                if (FD_ISSET(file_descr, &work_set))
                {
                    if (read(file_descr, tmpBuf, 10) < 0) {
                        perror("cannot read");
                        exit(1);
                    }
                    else
                    {
                        SYS_LOG_DEBUG("READ first 4 chars: 0x%X,0x%X,0x%X,0x%X", tmpBuf[0], tmpBuf[1], tmpBuf[2], tmpBuf[3]);
                    }
                }
                break;
            }
        }
//
//
//      n = read(file_descr, tmpBuf, 5);
//      if (n > 0) {
//          unsigned char   *p = tmpBuf;
//
//          while (n-- > 0)
//              printf(" 0x%x", *p++);
//          printf("\r\n");
//      } else {
//          printf("failed to read: %d\r\n", n);
//      }
//
//
        exit(0);
    }

    CLEAR_SCR();
    return;
}


/****************************************************************************/
/*!
 *  \brief Main cli function
 *
 *  \param[in]      argc - argument number.
 *  \param[in,out]  argv - argument values entered by user.
 *
 *  \return  success or failure
 *  \retval  EXIT_SUCCESS, EXIT_FAILURE
 *
 *
 *  \ingroup CLI
 */
/****************************************************************************/
int main(int argc, char *argv[])
{
    Term_callback_t *term;
    char logname[16];
    FILE *fp;


    /* Set mask for file operation */
    umask(0);

    system("stty erase ^H");
    openlog("cli", LOG_CONS, LOG_USER);

    /* Write cli start log */
    syslog(LOG_NOTICE, "Console startup. Software version: %s", VERSION);
    /* Find login name */
    strcpy(logname, "noname");
    if ((fp = popen( "whoami", "r" )) == NULL)
    {
        SYS_LOG_ERR("Can't open process for \"whoami\" command.");
    } else
    {
        fgets(logname, 16, fp);
        pclose(fp);
    }
    SYS_LOG_INFO("Console is entered by \"%s\".", logname); //getenv("USER")

    /* Console initialization */
    if (cliInit() != CLI_SUCCESS) {
        SYS_LOG_CRIT("CLI init failed");
        return EXIT_FAILURE;
    }

    Vectors_init(&term);

    /* Console work loop */
    cliWorkLoop(&term);
    cliClose();

    /* Exiting from cli */
    SYS_LOG_INFO("\"%s\" exited from console.", logname);

    return EXIT_SUCCESS;
}
 Bakir17 wrz 2012, 07:19
Dziękuję trociny! Masz rację, drugi wariant to wygodniejsza metoda. Ale mam stare oprogramowanie, które komunikuje się z poprzednimi urządzeniami wbudowanymi i nie obsługuje go. Istnieje wszelkie prawdopodobieństwo, że będziemy musieli go zmienić, aby obsługiwać nowe. Mimo to, dlaczego nie mogę uzyskać specjalnego znaku podczas przechodzenia do trybu surowego (zobacz wymieniony kod) za pomocą funkcji read ()?
 sawdust16 wrz 2012, 00:30
Twój port szeregowy jest skonfigurowany dla wejścia kanonicznego, tj. Linii tekstu ASCII. Albo przełącz natryb surowy lub przekształć dane binarne na obu końcach. Patrzeć nauuencode iuudecode do transmisji danych binarnych przez medium ASCII. Jest to tradycyjny sposób wysyłania danych binarnych w wiadomościach e-mail zawierających tylko tekst i USENET. Inną metodą wysyłania znaków binarnych lub „specjalnych” jest użycie znaku „escape” w celu przedrostka znaku „special” i normalizowanie bajtu na poprawny znak ASCII. BTW-parenb -parodd cs8 wyprodukuje 11-bitowe ramki znaków. „Brak parzystości” jest typowy w przypadku używania 8-bitowych znaków danych.

questionAnswers(1)

QuestionSolution
system("stty erase ^H);
system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter into non-canonical (raw) mode

Byłby to niewystarczający kod (i słaba praktyka kodowania według konwencji POSIX), aby umieścić port szeregowy w trybie surowym lub niekanonicznym.
Najłatwiejszą metodą w C i Linux jest użycie tej funkcjicfmakeraw() który jest dostępny zarówno w bibliotekach GNU libc, jak i uClibc. Użyjstrona man aby uzyskać szczegóły na temattermios członkowie struktury zmodyfikowani przezcfmakeraw().
Strzeż się tegocfmakeraw() skonfiguruje port szeregowy w trybie surowym dla danych o długości 8 bitów iNie parzystość, dla całkowitej ramki znaków 10 bitów (zakładając jeden bit stopu).

Preferowaną metodą jest zachowanie kopii plikutermios struktura (do przywracania po wyjściu z programu) i modyfikowanie tylko wymaganych bitów flag (zamiast zapisywania pełnych elementów struktury).

REWIZJA

Kod działający na moim ARM SoC to:

#include <stdio.h>
#include <stdlib.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <termios.h>

#define SERIALPORT_IS_CONSOLE

main()
{
    struct termios  tty;
    struct termios  savetty;
    speed_t     spd;
    unsigned int    sfd;
    unsigned char   buf[80];
    int     reqlen = 79;
    int     rc;
    int     rdlen;
    int     pau = 0;

#ifdef SERIALPORT_IS_CONSOLE
    sfd = STDIN_FILENO;
#else
    sfd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
#endif
    if (sfd < 0) {
        syslog(LOG_DEBUG, "failed to open: %d, %s", sfd, strerror(errno));
        exit (-1);
    }
    syslog(LOG_DEBUG, "opened sfd=%d for reading", sfd);

    rc = tcgetattr(sfd, &tty);
    if (rc < 0) {
        syslog(LOG_DEBUG, "failed to get attr: %d, %s", rc, strerror(errno));
        exit (-2);
    }
    savetty = tty;    /* preserve original settings for restoration */

    spd = B115200;
    cfsetospeed(&tty, (speed_t)spd);
    cfsetispeed(&tty, (speed_t)spd);

    cfmakeraw(&tty);

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
    tty.c_cflag |= CLOCAL | CREAD;
    rc = tcsetattr(sfd, TCSANOW, &tty);
    if (rc < 0) {
        syslog(LOG_DEBUG, "failed to set attr: %d, %s", rc, strerror(errno));
        exit (-3);
    }

    do {
        unsigned char   *p = buf;

        rdlen = read(sfd, buf, reqlen);
        if (rdlen > 0) {
            if (*p == '\r')
                pau = 1;
            syslog(LOG_DEBUG, "read: %d, 0x%x 0x%x 0x%x", \
                     rdlen, *p, *(p + 1), *(p + 2));
        } else {
            syslog(LOG_DEBUG, "failed to read: %d, %s", rdlen, strerror(errno));
        }
    } while (!pau);

    tcsetattr(sfd, TCSANOW, &savetty);
    close(sfd);
    exit (0);
}

Skompilowany program jest ładowany i wykonywany na płycie docelowej.

Od strony hosta łącza komunikacji szeregowej plikseq.bin z następującą treścią:

$ od -t x1 seq.bin
0000000 aa 02 fe
0000003

Następnie „ABC” jest wpisywane na hoście (który uruchamiaminicom program emulatora terminala), po którym następuje powrót karetki. Program kończy się na obiekcie docelowym i sprawdzany jest dziennik syslog:

# tail /var/log/messages                                                        
Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3 FS on nvsram, internal journal 
Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3-fs: mounted filesystem with or.
Sep xx xx:xx:42 atmel_soc user.info kernel: kjournald starting.  Commit intervas
Sep xx xx:xx:18 atmel_soc auth.info login[431]: root login on 'ttyS0'           
Sep xx xx:xx:04 atmel_soc user.debug syslog: opened sfd=0 for reading           
Sep xx xx:xx:14 atmel_soc user.debug syslog: read: 3, 0xaa 0x2 0xfe             
Sep xx xx:xx:50 atmel_soc user.debug syslog: read: 1, 0x41 0x2 0xfe              
Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x42 0x2 0xfe              
Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x43 0x2 0xfe              
Sep xx xx:xx:52 atmel_soc user.debug syslog: read: 1, 0xd 0x2 0xfe              
# 

Dane binarne zostały odebrane w stanie nienaruszonym.

Zauważ, że ponieważ jest to tryb surowy i znaki wpisane zostały wprowadzone stosunkowo powoli, toread() zwraca dane „częściowe”, a program użytkownika byłby odpowiedzialny za buforowanie / składanie danych w kompletne „komunikaty”. Jeśli wiadomości mają stałą długość, toc_cc[VMIN]członekmógłby ustawić na długość wiadomości. Ale uważaj na problemy z ramkowaniem wiadomości i komplikacje, gdy synchronizacja klatek zostanie utracona!

 sawdust24 wrz 2012, 02:33
Moja odpowiedź została zmieniona, aby zawierała kompletny przykład pracy do odbierania danych binarnych przez port szeregowy. Są (co najmniej) dwa elementy, które powinieneś naprawić w swoim kodzie. (1) Ponieważ masz problemy, powinieneś sprawdzać wartości zwracane dla wszystkich wywołań libc / system. (2) Zdegenerowany przypadekczytać() zwrot 0 bajtów (lub jakikolwiek krótki odczyt) nie zostanie wykryty, a dziennik zgłosi wartości śmieci. Powodzenia.
 sawdust21 wrz 2012, 21:04
Jakiś prawdziwy kod byłby prawdopodobnie bardziej zrozumiały niż twoje streszczenie tekstowe. FYI „SERIALPORT_IS_CONSOLE” to wymyślony symbol kompilacji warunkowej; możesz albo po prostu zakodować kodsfd = STDIN_FILENOlub zainstaluj#define SERIALPORT_IS_CONSOLE na początku lub dodaj-DSERIALPORT_IS_CONSOLE jako przełącznik linii kompilacji gcc. Czy wykonałeś testy poprawności, takie jak wysyłanie czegoś innego jak „ABC” (tj. 0x41 0x42 0x43) dla 3 bajtów? Co to takiego „syslog” zgłasza „złe dane”?
 Bakir18 wrz 2012, 07:24
Sprawdziłem twój kod. Niestety mam to samo zdjęcie.
 Bakir18 wrz 2012, 06:31
Użyłem tej funkcji i uzyskałem taki sam wynik :)
 Bakir18 wrz 2012, 06:33
Ale spróbuję jeszcze raz.
 Bakir21 wrz 2012, 10:22
Otwieram jawnie konsolę, aby uzyskać plik des. Spróbuję tego.
 Bakir26 wrz 2012, 07:02
To działa!!! Wstydzę się siebie. Przyczyną tego wszystkiego jest moja nieuwaga! :( Program, który testuje moją aplikację, został skonfigurowany do korzystania z szybkości transmisji 57600, gdy potrzebowałem 115200. Oba kody są poprawne. Bardzo mi przykro z tego powodu. Wielkie dzięki trocinom! :)Jan 1 00:03:42 FM2 user.debug cli: read: 3, 0xaa 0x2 0xfe Jan 1 00:05:44 FM2 user.debug cli: read: 1, 0x41 0x2 0xfe Jan 1 00:05:45 FM2 user.debug cli: read: 1, 0x42 0x2 0xfe Jan 1 00:05:46 FM2 user.debug cli: read: 1, 0x43 0x2 0xfe Jan 1 00:05:50 FM2 user.debug cli: read: 1, 0xd 0x2 0xfe
 Bakir18 wrz 2012, 07:39
Myślę, że nie do końca skończyłem przygotowywać tty config. Wcześniej pisałem program RS232toETH i korzystałem ze wszystkich tych narzędzi i działali. Ale w tym przypadku skomentowałem uruchomienie konsoli w uruchomieniu linuxa. Była tylko moja aplikacja, która używała / dev / ttyS0. Wielkie dzięki trociny! :) Dałeś mi pomysł. Myślę, że spróbuję obejść metodę (uuencode i uudecode / znaki ucieczki). Dziękuję za Twój czas.
 Bakir22 wrz 2012, 11:30
Wprowadziłem definicję „SERIALPORT_IS_CONSOLE”, ale nie wpłynęło to na otrzymanie symboli. I na przykład przeprowadziłem testy psychiczne, wysyłałem regularne znaki i dostałem poprawne symbole po stronie odbioru. Używam dziennika systemowego poprzez / var / log / messages. Dodałem cały kod do mojego pytania. Proszę spojrzeć powyżej. Z poważaniem.
 sawdust19 wrz 2012, 01:29
Czy jawnie otwierasz ponownie konsolę, aby uzyskać deskryptor pliku,sdflub próbowałeś po prostu użyćsfd = STDIN_FILENO w miejsceopen()? Sprawdź zmiany wprowadzone w powyższym przykładzie kodu.
 Bakir21 wrz 2012, 12:24
Więc spróbowałem ponownie. Ale nie udało się uzyskać sukcesu (. Wstawiłem instrukcję warunkową i mówi z jakiegoś powodu, że SERIALPORT NIE JEST KONSOLI. A prosty odczyt () nie działa w moim kodzie, dlatego użyłem fd set z funkcją read () w pętli, aby uzyskać Symbole: Otrzymałem to samo: 0x98, 0xE6, 0x18. Dziękuję bardzo !!!

yourAnswerToTheQuestion