¿Cómo abrir, leer y escribir desde el puerto serie en C?

Estoy un poco confundido acerca de leer y escribir en un puerto serie. Tengo un dispositivo USB en Linux que usa el controlador convertidor de dispositivo serie USB FTDI. Cuando lo conecto, crea: / dev / ttyUSB1.

Pensé que sería sencillo abrirlo y leerlo / escribirlo en C. Conozco la velocidad en baudios y la información de paridad, pero parece que no existe un estándar para esto.

¿Me estoy perdiendo de algo o alguien me puede indicar la dirección correcta?

preguntado el 04 de agosto de 11 a las 16:08

¿Ha echado un vistazo a la CÓMO de programación en serie? -

EDITAR: Miraría el enlace de ribram. Sin embargo, el punto sigue siendo que, si bien un dispositivo en serie se representa como un archivo, los dispositivos a menudo tienen interfaces más específicas implementadas a través de llamadas al sistema como ioctl y fcntl. -

Comprensión de los termios de UNIX VMIN y VTIME es un gran recurso para comprender VTIME y VMIN que se utilizan para manejar las características de bloqueo de un read () en un puerto serie. -

No utilice el código del "CÓMO de programación en serie" de Frerking como se menciona en el primer comentario. No están escritos para ser compatibles con POSIX, por lo que los ejemplos de código no son portátiles y es posible que no funcionen de manera confiable para usted. -

2 Respuestas

Escribí esto hace mucho tiempo (de los años 1985-1992, con solo algunos ajustes desde entonces), y simplemente copie y pegue los bits necesarios en cada proyecto.

Debes llamar cfmakeraw en un tty obtenido de tcgetattr. No puede poner a cero un struct termios, configúrelo y, a continuación, establezca el tty con tcsetattr. Si usa el método de puesta a cero, experimentará fallas intermitentes inexplicables, especialmente en los BSD y OS X. Las "fallas intermitentes inexplicables" incluyen el colgar en read(3).

#include <errno.h>
#include <fcntl.h> 
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tcgetattr", errno);
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // disable break processing
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                error_message ("error %d from tcsetattr", errno);
                return -1;
        }
        return 0;
}

void
set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tggetattr", errno);
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                error_message ("error %d setting term attributes", errno);
}


...
char *portname = "/dev/ttyUSB1"
 ...
int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
        error_message ("error %d opening %s: %s", errno, portname, strerror (errno));
        return;
}

set_interface_attribs (fd, B115200, 0);  // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0);                // set no blocking

write (fd, "hello!\n", 7);           // send 7 character greeting

usleep ((7 + 25) * 100);             // sleep enough to transmit the 7 plus
                                     // receive 25:  approx 100 uS per char transmit
char buf [100];
int n = read (fd, buf, sizeof buf);  // read up to 100 characters if ready to read

Los valores de velocidad son B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, etc. Los valores de paridad son 0 (es decir, sin paridad), PARENB|PARODD (habilitar la paridad y usar impar), PARENB (habilitar la paridad y usar incluso), PARENB|PARODD|CMSPAR (marque la paridad), y PARENB|CMSPAR (paridad espacial).

"Bloqueo" establece si un read() en el puerto espera a que llegue el número especificado de caracteres. Configuración sin bloqueo significa que un read() devuelve, sin embargo, hay muchos caracteres disponibles sin esperar más, hasta el límite del búfer.


Apéndice:

CMSPAR solo es necesario para elegir la paridad de marca y espacio, lo cual es poco común. Para la mayoría de las aplicaciones, se puede omitir. Mi archivo de encabezado /usr/include/bits/termios.h permite la definición de CMSPAR solo si el símbolo del preprocesador __USE_MISC se define. Esa definición ocurre (en features.h) con

#if defined _BSD_SOURCE || defined _SVID_SOURCE
 #define __USE_MISC     1
#endif

Los comentarios introductorios de <features.h> dice:

/* These are defined by the user (or the compiler)
   to specify the desired environment:

...
   _BSD_SOURCE          ISO C, POSIX, and 4.3BSD things.
   _SVID_SOURCE         ISO C, POSIX, and SVID things.
...
 */

respondido 25 nov., 19:04

@wallyk: En mi computadora no hay archivos llamados ttyUSB, los únicos archivos llamados USB son "usbmon". Pero la PC tiene muchos puertos USB. Entonces, ¿cómo los configuro? - Bas

@Bas: si es Linux, use el comando lsusb para ver todos los dispositivos USB. Podrían tener un nombre diferente si su sistema tiene udev normas; ver /etc/udev/rules.d/ Tal vez desde allí puedas elegir el puerto que estás buscando. Ciertamente, al enumerar y luego desconectar / enchufar el puerto, puede identificar la diferencia. - Wallyk

@ wallyk No puedo obtener ninguna salida (no puedo escribir) usando la paridad de espacio (PARENB | CMSPRAR). Pero puedo comunicarme con Mark Parity. ¿Alguna idea de cómo resolverlo? - Bas

Para una crítica de este código, consulte stackoverflow.com/questions/25996171/… - serrín

Como en, envié datos a un dispositivo ttyUSB0 y salió de mi dispositivo tty que en realidad estaba usando. Literalmente estaba enviando spam a mi propio terminal usando este código. La siguiente respuesta del aserrín es una implementación más segura. - Búho

Para el código de demostración que cumple con el estándar POSIX como se describe en Configuración adecuada de los modos de terminal y Guía de programación en serie para sistemas operativos POSIX, se ofrece lo siguiente.
Este código debería ejecutarse correctamente usando Linux en x86 así como procesadores ARM (o incluso CRIS).
Básicamente se deriva de la otra respuesta, pero se han corregido los comentarios inexactos y engañosos.

Este programa de demostración abre e inicializa un terminal en serie a 115200 baudios para el modo no canónico que es lo más portátil posible.
El programa transmite una cadena de texto codificada al otro terminal y se retrasa mientras se realiza la salida.
Luego, el programa ingresa en un bucle infinito para recibir y mostrar datos desde el terminal en serie.
De forma predeterminada, los datos recibidos se muestran como valores de bytes hexadecimales.

Para que el programa trate los datos recibidos como códigos ASCII, compile el programa con el símbolo DISPLAY_STRING, p. Ej.

 cc -DDISPLAY_STRING demo.c

Si los datos recibidos son texto ASCII (en lugar de datos binarios) y desea leerlos como líneas terminadas por el carácter de nueva línea, consulte esta respuesta para un programa de muestra.


#define TERMINAL    "/dev/ttyUSB0"

#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

void set_mincount(int fd, int mcount)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error tcgetattr: %s\n", strerror(errno));
        return;
    }

    tty.c_cc[VMIN] = mcount ? 1 : 0;
    tty.c_cc[VTIME] = 5;        /* half second timer */

    if (tcsetattr(fd, TCSANOW, &tty) < 0)
        printf("Error tcsetattr: %s\n", strerror(errno));
}


int main()
{
    char *portname = TERMINAL;
    int fd;
    int wlen;
    char *xstr = "Hello!\n";
    int xlen = strlen(xstr);

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);
    //set_mincount(fd, 0);                /* set to pure timed read */

    /* simple output */
    wlen = write(fd, xstr, xlen);
    if (wlen != xlen) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd);    /* delay for output */


    /* simple noncanonical input */
    do {
        unsigned char buf[80];
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
#ifdef DISPLAY_STRING
            buf[rdlen] = 0;
            printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
#endif
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Timeout from read\n");
        }               
        /* repeat read to get full message */
    } while (1);
}

Para ver un ejemplo de un programa eficiente que proporciona almacenamiento en búfer de los datos recibidos pero que permite la gestión byte a byte de la entrada, consulte esta respuesta.


Respondido el 14 de enero de 21 a las 22:01

Mucho de eso podría reemplazarse con solo cfmakeraw ¿derecho? - CMCDragonkai

Otros ejemplos que he visto también abren el puerto con O_NDELAY or O_NONBLOCK. Las cmrr.umn.edu/~strupp/serial.html menciona que si abre el descriptor de archivo con esos indicadores, entonces el VTIME se ignora. Entonces, ¿cuál es la diferencia entre correr con O_NONBLOCK descriptor de archivo versus hacerlo con VTIME? - CMCDragonkai

@CMCDragonkai - Es mucho más complicado de lo que escribiste. Ver stackoverflow.com/questions/25996171/… que hace referencia a la respuesta aceptada a esta pregunta. Por cierto, incluso si abre el terminal en modo sin bloqueo, aún puede volver al modo de bloqueo con un fcntl () - serrín

Perdón por la pregunta del novato, pero ¿dónde sale del bucle do while en el bucle principal o se repite para siempre? - bakalolo

@bakalolo: es solo un código de demostración simple para recibir y mostrar para siempre. La intención es un código portátil que se compilará (sin errores) y funcionará de manera confiable (a diferencia de la otra respuesta). Se podría agregar una prueba para determinar el final del mensaje; con datos brutos, la definición de un paquete de mensajes depende del protocolo. O este código podría modificarse para almacenar los datos recibidos en un búfer circular para que otro hilo los procese, como se describe en esta respuesta. - serrín

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.