Wie lese ich Binärdaten über ein serielles Terminal in einem C-Programm?

Ich habe die folgenden Links und andere Quellen gelesen, aber keine Antwort auf meine Frage gefunden.

Binärdaten über serielles Terminal

Daten werden während der Übertragung über die serielle Schnittstelle beschädigt

Ich kommuniziere mit meinem eingebetteten Gerät über eine serielle Schnittstelle. Standardmäßig verwendet Embedded Linux diesen Port als Terminal. Ich möchte aber auch binäre Daten (Service-Pakete) über den Port übertragen. Meine / etc / inittab-Datei hat einen "getty" -Aufruf:console :: respawn: / sbin / getty 115200 ttyS0

Ich habe auch die Datei / etc / passwd mit der Zeichenfolge, in der der Benutzer "admin" nach dem Anmelden die Anwendung "cli" startet:admin: 8Mt / Jtxcyg8AY: 1000: 0: admin: / tmp: / tmp / cli

Meine Standardeinstellungen für ttyS0 vor dem Ausführen des Programms sind:

~ # 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
~ #

Also mache ich in meinem Cli-Programm Folgendes:

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);
}

Ich habe versucht, die Funktion read () (mit vorzeichenlosem Zeichenpuffer) zum Abrufen von Binärdaten zu verwenden, konnte jedoch keine korrekten Daten empfangen. Ich öffne auch vorläufig / dev / ttyS0 erneut, um file_descriptor abzurufen und benutze read () func.

Mein Programm sendet 3 Bytes: 0xAA, 0x02, 0xFE. Aber im Syslog sehe ich immer, dass das Gerät falsche Symbole empfängt: 0x98, 0xE6, 0x18.

Was ist da los? Wie bekomme ich korrekte Binärdaten?

Ein ganzer Code, den ich gerade teste.

#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. Sept. 2012, 07:19
Danke Sägemehl! Sie haben recht, die zweite Variante ist eine bequemere Methode. Aber ich habe eine alte Software, die mit früheren eingebetteten Geräten kommuniziert und diese nicht unterstützt. Es ist sehr wahrscheinlich, dass wir es ändern müssen, um ein neues zu unterstützen. Trotzdem, warum kann ich kein Sonderzeichen bekommen, wenn ich mit einer read () -Funktion in einen Raw-Modus gehe (siehe oben genannten Code)?
 sawdust16. Sept. 2012, 00:30
Ihre serielle Schnittstelle ist für die kanonische Eingabe eingerichtet, d. H. Für ASCII-Textzeilen. Entweder wechseln Sie zuRaw-Modus oder transformieren Sie die Binärdaten an beiden Enden. Ansehenuuencode unduudecode zur Übertragung von Binärdaten über ein ASCII-Medium. Dies ist die traditionelle Methode zum Senden von Binärdaten in Nur-Text-E-Mails und USENET-Postings. Eine andere Methode zum Senden binärer oder "spezieller" Zeichen besteht darin, ein "Escape" -Zeichen als Präfix für "spezielles" Zeichen zu verwenden und das Byte auf ein gültiges ASCII-Zeichen zu normalisieren. Übrigens-parenb -parodd cs8 erzeugt 11-Bit-Zeichenrahmen. "Keine Parität" ist typisch, wenn 8-Bit-Datenzeichen verwendet werden.

Antworten auf die Frage(1)

Lösung für das Problem
system("stty erase ^H);
system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter into non-canonical (raw) mode

POSIX-Konventionen), um die serielle Schnittstelle in den unformatierten oder nicht-kanonischen Modus zu versetzen.
Die einfachste Methode in C und Linux ist die Verwendung der Funktioncfmakeraw() Dies ist sowohl in der GNU libc- als auch in der uClibc-Bibliothek verfügbar. Verwenden Sie dieManpage um die Details zu erhaltentermios Strukturelemente, die von geändert werdencfmakeraw().
Passen Sie das aufcfmakeraw() richtet den seriellen Port im Raw-Modus für eine Datenlänge von 8 Bit und einNein Parität für einen Gesamtzeichenrahmen von 10 Bits (unter der Annahme eines Stoppbits).

Die bevorzugte Methode ist die Aufbewahrung einer Kopie vontermios strukturieren (zur Wiederherstellung beim Beenden des Programms) und nur die erforderlichen Flag-Bits ändern (anstatt die vollständigen Strukturelemente zu schreiben).

REVISION

Der Code, der auf meinem ARM-SoC funktioniert, lautet:

#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);
}

Das kompilierte Programm wird auf die Zielkarte geladen und ausgeführt.

Von der Hostseite der seriellen Kommunikationsverbindung die Dateiseq.bin mit folgendem Inhalt wird versandt:

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

Dann wird "ABC" auf dem Host eingegeben (auf dem das ausgeführt wird)minicom Terminal-Emulator-Programm), gefolgt von einem Wagenrücklauf. Das Programm wird auf dem Ziel beendet und das Syslog wird dann untersucht:

# 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              
# 

Die Binärdaten wurden intakt empfangen.

Beachten Sie, dass dies der Rohmodus ist und die eingegebenen Zeichen relativ langsam eingegeben wurdenread() gibt "partielle" Daten zurück und das Anwenderprogramm wäre für das Puffern / Zusammensetzen der Daten zu vollständigen "Nachrichten" verantwortlich. Wenn die Nachrichten eine feste Länge haben, dannc_cc[VMIN]Mitgliedkönnte auf die Nachrichtenlänge eingestellt werden. Achten Sie jedoch auf Probleme beim Framing von Nachrichten und auf Komplikationen, wenn die Frame-Synchronisierung verloren geht!

 sawdust24. Sept. 2012, 02:33
Meine Antwort wurde überarbeitet, um ein vollständiges Arbeitsbeispiel für den Empfang von Binärdaten über eine serielle Schnittstelle zu enthalten. Es gibt (mindestens) zwei Punkte, die Sie in Ihrem Code korrigieren sollten. (1) Da Sie Schwierigkeiten haben, sollten Sie die Rückgabewerte für alle libc / system-Aufrufe überprüfen. (2) Der entartete Fall vonlesen() Das Zurückgeben von 0 Bytes (oder eines kurzen Lesevorgangs in diesem Fall) wird nicht erkannt, und das Protokoll gibt Müllwerte aus. Viel Glück.
 Bakir18. Sept. 2012, 06:31
Ich habe diese Funktion benutzt und das gleiche Ergebnis erzielt :)
 Bakir21. Sept. 2012, 12:24
Also versuchte ich es erneut. Ich habe eine bedingte Anweisung eingefügt und sie besagt aus irgendeinem Grund, dass SERIALPORT KEINE KONSOLE ist. Und einfach read () funktioniert in meinem Code nicht, deshalb habe ich fd set mit read () func in loop verwendet, um zu erhalten Ich habe das gleiche erhalten: 0x98, 0xE6, 0x18. Vielen Dank !!!
 Bakir18. Sept. 2012, 07:39
Ich glaube, ich habe die Vorbereitung von tty config nicht vollständig abgeschlossen. Zuvor habe ich das Programm RS232toETH geschrieben und all diese Tools verwendet, und sie haben funktioniert. Aber in diesem Fall habe ich den Start der Konsole beim Start von Linux kommentiert. Es gab nur meine Anwendung, die / dev / ttyS0 verwendete. Vielen Dank Sägemehl! :) Du hast mir eine Idee gegeben. Ich denke, ich werde Workaround-Methode versuchen (Uuencode und Uudecode / Escape-Zeichen). Vielen Dank für Ihre Zeit.
 sawdust21. Sept. 2012, 21:04
Ein realer Code wäre wahrscheinlich verständlicher als Ihre Textzusammenfassung. FYI "SERIALPORT_IS_CONSOLE" ist ein erfundenes bedingtes Kompilierungssymbol. Sie könnten entweder einfach diesfd = STDIN_FILENOoder installieren#define SERIALPORT_IS_CONSOLE am Anfang oder hinzufügen-DSERIALPORT_IS_CONSOLE als gcc compile-line switch. Haben Sie irgendwelche Hygienetests durchgeführt, wie z. B. "ABC" (d. H. 0x41 0x42 0x43) für die 3 Bytes zu senden? Was ist dieses "Syslog", das die "schlechten Daten" meldet?
 sawdust19. Sept. 2012, 01:29
Öffnen Sie die Konsole explizit erneut, um einen Dateideskriptor zu erhalten?sdf, oder hast du es einfach mit probiertsfd = STDIN_FILENO anstelle deropen()? Bitte überprüfen Sie die am obigen Codebeispiel vorgenommenen Änderungen.
 Bakir26. Sept. 2012, 07:02
Es klappt!!! Ich schäme mich. Die Ursache für all das ist meine Unaufmerksamkeit! :( Das Programm, das meine Com-App testet, war so konfiguriert, dass es eine Bitrate von 57600 verwendet, als ich 115200 benötigte. Beide Codes sind korrekt. Das tut mir sehr leid. Vielen Dank, Sägemehl! :)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. Sept. 2012, 07:24
Also habe ich deinen Code überprüft. Leider habe ich das gleiche Bild bekommen.
 Bakir18. Sept. 2012, 06:33
Aber ich werde es noch einmal versuchen.
 Bakir21. Sept. 2012, 10:22
Ich öffne explizit die Konsole, um eine Datei des zu bekommen. Ich werde das versuchen.
 Bakir22. Sept. 2012, 11:30
Ich habe die Definition "SERIALPORT_IS_CONSOLE" eingefügt, sie hat sich jedoch nicht auf den Empfang der Symbole ausgewirkt. Und ich habe Hygienetests durchgeführt, zum Beispiel habe ich reguläre Zeichen gesendet und die richtigen Symbole auf der Empfangsseite erhalten. Ich verwende ein Systemprotokoll über / var / log / messages. Ich habe meiner Frage einen ganzen Code hinzugefügt. Bitte schauen Sie oben. Freundliche Grüße.

Ihre Antwort auf die Frage