Estoy un poco confundido acerca de leer y escribir en un puerto serie. Tengo un dispositivo USB en Linux que utiliza 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 hay un estándar para esto.
¿Me estoy perdiendo algo o alguien puede indicarme la dirección correcta?
Escribí esto hace mucho tiempo (de los años 1985 a 1992, con solo algunos ajustes desde entonces), y solo copié y pegué 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, configure 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 colgar 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 la velocidad son B115200
, B230400
, B9600
, B19200
, B38400
, B57600
, B1200
, B2400
, B4800
etc. Los valores para la paridad son 0
(es decir, sin paridad), PARENB|PARODD
(habilitar paridad y usar impar), PARENB
(habilitar paridad y usar incluso), PARENB|PARODD|CMSPAR
(marque la paridad), y PARENB|CMSPAR
(paridad espacial).
“Bloqueo” establece si un read()
en el puerto espera que llegue el número especificado de caracteres. Entorno sin bloqueo significa que un read()
devuelve la cantidad de caracteres disponibles sin esperar más, hasta el límite del búfer.
Apéndice:
CMSPAR
solo se necesita 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 cabecera /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.
...
*/
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 POSIXse ofrece lo siguiente.
Este código debería ejecutarse correctamente utilizando Linux en x86, así como procesadores ARM (o incluso CRIS).
Se deriva esencialmente de la otra respuesta, pero se han corregido los comentarios inexactos y engañosos.
Este programa de demostración abre e inicializa un terminal serial 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 a la otra terminal y se demora mientras se realiza la salida.
Luego, el programa ingresa en un bucle infinito para recibir y mostrar datos del terminal serial.
De forma predeterminada, los datos recibidos se muestran como valores de bytes hexadecimales.
Para hacer que el programa trate los datos recibidos como códigos ASCII, compile el programa con el símbolo DISPLAY_STRING, por ejemplo
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 obtener 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 el manejo byte a byte de la entrada, vea esta respuesta.
¿Has echado un vistazo a la Cómo programar en serie?
– ribram
04/08/2011 a las 19:30
EDITAR: miraría el enlace de ribram. Sin embargo, el punto sigue siendo que, si bien un dispositivo serial 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
yfcntl
.– Sr. Shickadance
04/08/2011 a las 19:31
Enlace actualizado a Guía de programación en serie para sistemas operativos POSIX.
– svec
28 de diciembre de 2013 a las 1:29
Comprender UNIX termios VMIN y VTIME es un gran recurso para comprender VTIME y VMIN que se utilizan para manejar las características de bloqueo de una lectura () en un puerto serie.
– flak37
11/08/2014 a las 20:43
No use 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.
– aserrín
22 de enero de 2019 a las 0:37