¿Cómo leer un dato binario sobre un terminal serial en el programa C?

Leí los enlaces seguidos y otras fuentes, pero no encontré respuesta para mi pregunta.

Datos binarios sobre terminal serial

Los datos se corrompen durante la transmisión a través del puerto serie

Me comunico con mi dispositivo integrado a través de un puerto serie. Por defecto, Linux incorporado usa este puerto como terminal. Pero quiero transferir también datos binarios (paquetes de servicio) a través del puerto. Mi archivo / etc / inittab tiene una llamada "getty":consola :: respawn: / sbin / getty 115200 ttyS0

También tengo el archivo / etc / passwd con una cadena donde el usuario "admin" ejecuta mi aplicación "cli" después de iniciar sesión:admin: 8Mt / Jtxcyg8AY: 1000: 0: admin: / tmp: / tmp / cli

Mi configuración ttyS0 predeterminada antes de ejecutar el programa es:

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

Entonces, en mi programa cli hago lo siguiente:

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

He intentado la función de lectura () (con búfer de caracteres sin firmar) para obtener datos binarios, pero no he podido recibir los datos correctos. También preliminarmente abro / dev / ttyS0 nuevamente para obtener file_descriptor y usar la función read ().

Mi programa envía 3 bytes: 0xAA, 0x02, 0xFE. Pero en syslog siempre veo que el dispositivo recibe símbolos incorrectos: 0x98, 0xE6, 0x18.

¿Cuál es el problema? ¿Cómo obtener datos binarios correctos?

Todo un código que estoy probando en este momento.

#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
¡Gracias aserrín! Tiene razón, la segunda variante es un método más conveniente de usar. Pero tengo un software antiguo que se comunica con dispositivos integrados anteriores y no lo admite. Es muy probable que tengamos que cambiarlo para que sea compatible con uno nuevo. Aun así, ¿por qué no puedo obtener un carácter especial cuando voy en modo sin procesar (consulte el código mencionado) con una función read ()?
 sawdust16 sept. 2012 00:30
Su puerto serie está configurado para entrada canónica, es decir, líneas de texto ASCII. O cambia amodo crudo o transformar los datos binarios en ambos extremos. Miraruuencode yuudecode Para la transmisión de datos binarios a través de un medio ASCII. Es la forma tradicional de enviar datos binarios en correos electrónicos de solo texto y publicaciones de USENET. Otro método para enviar caracteres binarios o "especiales" es utilizar un carácter de "escape" para prefijar el carácter "especial" y normalizar el byte a un carácter ASCII válido. Por cierto-parenb -parodd cs8 producirá cuadros de caracteres de 11 bits. "Sin paridad" es típico cuando se usan caracteres de datos de 8 bits.

Respuestas a la pregunta(1)

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

según las convenciones POSIX) para poner el puerto serie en modo sin formato o no canónico.
El método más fácil en C y Linux es usar la funcióncfmakeraw() que está disponible en las bibliotecas libc y uClibc de GNU. Utilizar elpágina de manual para obtener los detalles en eltermios Miembros de la estructura que son modificados porcfmakeraw().
Ten cuidado con esocfmakeraw() configurará el puerto serie en modo sin procesar para una longitud de datos de 8 bits yno paridad, para una trama de caracteres total de 10 bits (suponiendo un bit de parada).

El método preferido es preservar una copia deltermios estructura (para restauración en la salida del programa) y solo modificando los bits de bandera requeridos (en lugar de escribir los miembros de la estructura completa).

REVISIÓN

El código que funciona en mi ARM SoC es:

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

El programa compilado se carga y ejecuta en el tablero de destino.

Desde el lado del host del enlace de comunicación serial, el archivoseq.bin Con los siguientes contenidos se envía:

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

Luego se escribe "ABC" en el host (que ejecuta el comandominicom programa emulador de terminal), seguido de un retorno de carro. El programa termina en el destino y luego se examina el 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              
# 

Los datos binarios se han recibido intactos.

Tenga en cuenta que dado que este es el modo en bruto y los caracteres escritos se ingresaron de manera relativamente lenta, elread() devuelve datos "parciales" y el programa de usuario sería responsable de almacenar en búfer / ensamblar los datos en "mensajes" completos. Si los mensajes son de longitud fija, entoncesc_cc[VMIN]miembropodría se ajustará a la longitud del mensaje. ¡Pero tenga cuidado con los problemas de encuadre del mensaje y las complicaciones cuando se pierde la sincronización de cuadros

 Bakir18 sept. 2012 06:33
Pero lo intentaré una vez más.
 Bakir18 sept. 2012 06:31
He usado esta función y obtuve el mismo resultado :)
 sawdust24 sept. 2012 02:33
Mi respuesta ha sido revisada para incluir un ejemplo completo de trabajo para recibir datos binarios a través de un puerto serie. Hay (al menos) dos elementos que debe corregir en su código. (1) Como tiene dificultades, debe verificar los valores de retorno para todas las llamadas libc / system. (2) El caso degenerado deleer() no se detecta la devolución de 0 bytes (o cualquier lectura corta), y el registro informará los valores de basura. Buena suerte.
 sawdust21 sept. 2012 21:04
Algún código real probablemente sería más comprensible que su sinopsis textual. FYI "SERIALPORT_IS_CONSOLE" es un símbolo de compilación condicional inventado; usted podría simplemente codificar elsfd = STDIN_FILENO, o instalar#define SERIALPORT_IS_CONSOLE al principio, o añadir-DSERIALPORT_IS_CONSOLE como un conmutador de línea de compilación gcc. ¿Ha realizado pruebas de cordura, como enviar algo diferente como "ABC" (es decir, 0x41 0x42 0x43) para los 3 bytes? ¿Qué es este "syslog" que informa los "datos incorrectos"?
 Bakir21 sept. 2012 10:22
Abro explícitamente la consola para obtener un archivo des. Intentaré esto.
 sawdust19 sept. 2012 01:29
¿Está volviendo a abrir explícitamente la consola para obtener un descriptor de archivo,sdf, o has intentado simplemente usandosfd = STDIN_FILENO en lugar de laopen()? Por favor revise los cambios realizados en el ejemplo de código de arriba.
 Bakir22 sept. 2012 11:30
Inserté la definición "SERIALPORT_IS_CONSOLE", pero no afectó a la recepción de los símbolos. E hice pruebas de cordura, por ejemplo, envié caracteres regulares y obtuve los símbolos correctos en el lado de recepción. Utilizo un registro del sistema a través de / var / log / messages. Agregué un código entero en mi pregunta. Por favor mira arriba. Atentamente.
 Bakir18 sept. 2012 07:24
Por lo tanto, he comprobado su código. Lamentablemente tengo la misma foto.
 Bakir26 sept. 2012 07:02
¡¡¡Funciona!!! Estoy avergonzado de mí mismo. ¡La causa de todo eso es mi falta de atención! :( El programa que prueba mi com-app. Se configuró para usar una velocidad de 57600 bits cuando necesitaba 115200. Ambos códigos son correctos. Lo siento mucho. Muchas gracias, aserrín! :)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
 Bakir21 sept. 2012 12:24
Así que, lo intenté de nuevo. Pero no tuve éxito (. He insertado la declaración condicional y dice por alguna razón que SERIALPORT NO ES LA CONSOLA. Y, simplemente, la función de lectura () no funciona en mi código, por eso usé fd set with read () func in loop para obtener Símbolos. Obtuve el mismo: 0x98, 0xE6, 0x18. ¡Muchas gracias!
 Bakir18 sept. 2012 07:39
Creo que no completé completamente la preparación de tty config. Anteriormente escribí el programa RS232toETH, usé todas estas herramientas y funcionaron. Pero en ese caso comenté el lanzamiento de la consola en el arranque de linux. Solo mi aplicación usaba / dev / ttyS0. Muchas gracias aserrín! :) Me diste una idea. Creo que intentaré el método de solución (uuencode y uudecode / escape chars). Gracias por tu tiempo.

Su respuesta a la pregunta