Primeros pasos con Embedded Linux 3-2-Serial Port

sistema tty

tty es la abreviatura de teletipo. Cuando las computadoras eran caras en el siglo pasado, muchas personas podían conectarse y compartir una computadora a través de este terminal. Hoy en día, tty se ha convertido en un término general para los dispositivos de caracteres. Dichos dispositivos incluyen: Interfaces seriales físicas como consolas , UART y pseudoterminales. Es decir, el puerto serial físico de la placa de desarrollo generalmente corresponde a los archivos del dispositivo tty* en el directorio /dev en Linux Acceder a estos archivos significa acceder al puerto serial del hardware.

consejos: Las siguientes son notas para leer la Guía de programación en serie para sistemas operativos POSIX

Puede ir aquí para leer el texto original directamente: Guía de programación en serie para sistemas operativos POSIX

Conocimientos básicos de hardware de puerto serie.

Un puerto serial es un tipo de interfaz que transmite datos en forma serial (1 bit cada vez).Este tipo de interfaz se usa a menudo en equipos de red, teclados, ratones, módems y equipos terminales (teletipo, cuando las computadoras aún eran grandes). en el siglo pasado, todo el mundo usaba este equipo para compartir remotamente una computadora), etc.

Comúnmente se usa bps (número de bits transmitidos por segundo) o baud rate para indicar la velocidad de comunicación del puerto serial, los comunes son 9600bps, 38400bps, 115200bps, etc.

Para la interfaz RS232 de uso común, además de las tres líneas de señal de comunicación necesarias de TXD, RXD y GND, también hay algunas líneas de señal para control:

nombre dirección usar
DCD (detección de portador de datos) ingresar Si lee un nivel bajo (lógica 1), el dispositivo en el lado opuesto del cable está correctamente conectado.
DTR (Terminal de datos listo) producción La salida de nivel bajo (lógica 1) indica que nuestro lado está listo para la transmisión de datos.Cuando el software general abre el puerto serie, esta señal se bajará automáticamente.
CTS (Borrar para enviar) ingresar Si lee un nivel bajo (lógica 1), el dispositivo al otro lado del cable nos está permitiendo enviar datos en este momento.
RTS (Solicitud de envío) producción Envíe un nivel bajo (lógica 1) para solicitar que el dispositivo en el lado opuesto del cable transmita datos. La mayoría de las veces, esta línea de señal siempre emite un nivel bajo.

**Generalmente, al conectar, nuestro DTR está conectado al DCD del oponente, y nuestro RTS está conectado al CTS del oponente, y viceversa. **Las señales DCD y DTR se usan para la detección de dispositivos, y las señales CTS y RTS se usan para el control del flujo de señales (también hay un método de control de flujo de señales de software, por supuesto, rara vez los uso en mi trabajo).

Programación de puerto serie bajo Linux

Dado que los dispositivos seriales existen en forma de archivos en Linux, por supuesto podemos acceder a ellos con funciones generales de E/S de archivos, pero el acceso a los archivos del dispositivo generalmente requiere privilegios de superadministrador, y generalmente iniciamos sesión en el sistema como root en la placa de desarrollo. Por supuesto que no hay problema. Además, para configurar el puerto serie, el sistema Linux proporciona un archivo denominado termios.h, que contiene la estructura de datos y la interfaz de funciones relacionadas con la configuración del puerto serie. Estas interfaces de funciones en realidad llaman a la función de E/S del archivo en el análisis final La función ioctl en.

En el código de ejemplo a continuación, los siguientes archivos de encabezado se incluyen de forma predeterminada:

#include <stdio.h>
#include <string.h>
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */

abrir puerto serie

El siguiente código demuestra cómo abrir correctamente un archivo de dispositivo serie

/* 
 * open serial port 
 * return file descriptor on success or -1 on error.
 */
int open_port(char *path)
{
    
    
    int fd;
    
    fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)
    {
    
    
        /* open failure, print system error msg. */
        perror("open_port: Unable to open port - ");
    }
    else
    {
    
    
        fcntl(fd, F_SETFL, 0);
        return fd;
    }
}

Al abrir el archivo del dispositivo terminal, usamos las banderas O_NOCTTY y O_NDELAY, la siguiente cita explica por qué:

El indicador O_NOCTTY le dice a UNIX que este programa no quiere ser el "terminal de control" para ese puerto. Si no especifica esto, cualquier entrada (como las señales de cancelación del teclado, etc.) afectará su proceso. Programas como getty(1M/8) usan esta función cuando inician el proceso de inicio de sesión, pero normalmente un programa de usuario no desea este comportamiento.

El indicador O_NDELAY le dice a UNIX que a este programa no le importa en qué estado se encuentra la línea de señal DCD, si el otro extremo del puerto está activo y funcionando. Si no especifica este indicador, su proceso se suspenderá hasta que la línea de señal DCD sea el voltaje espacial.

Después de que la apertura sea exitosa, usamos la función fcntl para borrar el indicador de estado del archivo, pero el método de llamada de la función fcntl en el ejemplo en realidad no es riguroso, fcntl se usa para modificar el descriptor del archivo de acuerdo con el cmd especificado por el segundo parámetro, donde F_SETFL Los comandos se explican de la siguiente manera:

Establezca los indicadores de estado del archivo en el valor especificado por arg. Se ignoran el modo de acceso a archivos (O_RDONLY, O_WRONLY, O_RDWR) y las marcas de creación de archivos (es decir, O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) en arg. En Linux, este comando solo puede cambiar los indicadores O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME y O_NONBLOCK. No es posible cambiar las banderas O_DSYNC y O_SYNC; ver ERRORES, a continuación.

Hay más discusión sobre este problema en StackOverflow, vea este enlace: ¿Por qué fcntl (fd, F_SETFL, 0) se usa en la programación del puerto serie?

Escritura de datos en serie

Solo necesita escribir el archivo del dispositivo correspondiente al puerto serie como escribir datos en el archivo ordinario, y luego puede enviar datos a través del puerto serie, de la siguiente manera:

/*
 * write data to serial port
 * return the length of bytes actually written on success, or -1 on error
 */
ssize_t write_port(int fd, void *buf,size_t len)
{
    
    
    ssize_t actual_write;

    actual_write = write(fd, buf, len);
    if (actual_write == -1)
    {
    
    
        /* write failure */
        perror("write_port: Unable to write port - ");
    }
    return actual_write;
}

Lectura de datos del puerto serie

Del mismo modo, podemos leer los caracteres recibidos actualmente del búfer del puerto serie leyendo el archivo del dispositivo. De acuerdo con esta idea, podemos escribir la primera versión de la función de lectura:

/*
 * read data from serial port
 * return the length of bytes actually read on success, or -1 on error
 */
ssize_t read_port(int fd, void *buf, size_t len)
{
    
    
    ssize_t actual_read;

    actual_read = read(fd, buf, len);
    if (actual_read == -1)
    {
    
    
        /* read failure */
        perror("read_port: Unable to read port - ");
    }
    return actual_read;
}

Esta versión de la función implementa la función de lectura, pero hay dos problemas:

  1. Solo saldrá después de leer una línea, es decir, leer '\r' o '\n', de lo contrario, el proceso se bloqueará hasta que la función de lectura;
  2. Si el puerto serie no recibe datos, la función se bloqueará hasta que se agote el tiempo o se reciban datos, la duración mínima de recepción y el tiempo de espera se pueden configurar a través de la función de configuración que se menciona más adelante;

El problema 1 se puede resolver configurando el terminal del puerto serie para que funcione en modo RAW, y el problema 2 se puede resolver configurando el número mínimo de bytes recibidos y el tiempo de espera del puerto serie. Para obtener más información, consulte el contenido de configuración del puerto serie a continuación. .

Configuración del puerto serie

Linux proporciona un conjunto de estructuras de datos e interfaces de funciones correspondientes para configurar terminales serie, denominado "interfaz termios POSIX". Para usarlos, simplemente incluya este archivo de encabezado:

#include <termios.h> /* POSIX terminal control definitions */

Configurar el puerto serie es en realidad establecer una estructura relacionada con la configuración. La definición de esta estructura es la siguiente (diferentes versiones pueden tener diferencias):

struct termios
  {
    
    
    tcflag_t c_iflag;		/* input mode flags */
    tcflag_t c_oflag;		/* output mode flags */
    tcflag_t c_cflag;		/* control mode flags */
    tcflag_t c_lflag;		/* local mode flags */
    cc_t c_line;			/* line discipline */
    cc_t c_cc[NCCS];		/* control characters */
    speed_t c_ispeed;		/* input speed */
    speed_t c_ospeed;		/* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
  };

A través de las siguientes dos interfaces proporcionadas por el sistema, podemos obtener el estado del puerto serie actual o establecer un nuevo estado:

int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

El proceso correcto de configuración del puerto serie es el siguiente:

  1. Utilice tcgetattr() para obtener la configuración actual del puerto serie;

  2. Lea y modifique la configuración del puerto serie leyendo y escribiendo directamente la estructura termios devuelta o utilizando las siguientes funciones de modificación:

    /* Return the output baud rate stored in *TERMIOS_P.  */
    extern speed_t cfgetospeed (const struct termios *__termios_p) __THROW;
    
    /* Return the input baud rate stored in *TERMIOS_P.  */
    extern speed_t cfgetispeed (const struct termios *__termios_p) __THROW;
    
    /* Set the output baud rate stored in *TERMIOS_P to SPEED.  */
    extern int cfsetospeed (struct termios *__termios_p, speed_t __speed) __THROW;
    
    /* Set the input baud rate stored in *TERMIOS_P to SPEED.  */
    extern int cfsetispeed (struct termios *__termios_p, speed_t __speed) __THROW;
    
    /* Set both the input and output baud rates in *TERMIOS_OP to SPEED.  */
    extern int cfsetspeed (struct termios *__termios_p, speed_t __speed) __THROW;
    
    /* Set *TERMIOS_P to indicate raw mode.  */
    extern void cfmakeraw (struct termios *__termios_p) __THROW;
    
  3. Utilice tcsetattr() para escribir la configuración modificada en el puerto serie;

El siguiente ejemplo es una función de configuración de puerto serie, que configura el puerto serie como velocidad de transmisión entrante, 8 bits de datos, sin dígito de control y formato RAW:

/*
 * config serial port baud 
 * and set data format to 8N1(8bits data, no parity bit, 1 stop bit)
 * return 0 on success, or -1 on error
 */
int config_port(int fd, speed_t baud)
{
    
    
    struct termios term;

    /* read serial port configure */
    if (tcgetattr(fd, &term) != 0)
    {
    
    
        perror("config_port: Unable to read configure - ");
        return -1;
    }

    /* set baudrate */
    cfsetspeed(&term, baud);

    /* 8N1 mode */
    term.c_cflag &= ~PARENB;
    term.c_cflag &= ~CSTOPB;
    term.c_cflag &= ~CSIZE;
    term.c_cflag |= CS8;

    /* enbale raw mode */
    cfmakeraw(&term);

    /* write serial port configure */
    if (tcsetattr(fd, TCSADRAIN, &term) != 0)
    {
    
    
        perror("config_port: Unable to write configure - ");
        return -1;
    }
}

La función cfmakeraw() hace que el puerto serie funcione en modo RAW. En este modo, el subsistema tty enviará directamente todos los datos de bytes recibidos del puerto serie a la capa de aplicación sin procesamiento adicional. En este momento, la función read() estar en Salir inmediatamente después de leer todos los datos en el búfer y devolver el valor realmente leído, en lugar de esperar la llegada de \r o \n, lo que resuelve el problema 1 de la función de lectura anterior .

Mejorar la lectura de datos del puerto serie

Después de habilitar el modo RAW del puerto serie, podemos configurar los miembros en la estructura termios:

.c_cc[VTIME]; /* 指定超时时间 */
.c_cc[VMIN];  /* 指定最少读取的字节 */

Para especificar al menos cuántos bytes deben leerse antes de regresar (esto debe coincidir con el formato RAW del puerto serie), y también puede configurar el tiempo de espera para recibir. Si configura ambos en 0, no importa si lee o no los datos, la función de lectura saldrá inmediatamente, lo que resuelve el problema 2 mencionado anteriormente .

La función mejorada es la siguiente:

/* set timeout and minimum number of bytes to read */
int read_port_setup(int fd, cc_t timeout_sec, cc_t min_bytes)
{
    
    
    struct termios term;

    /* read serial port configure */
    if (tcgetattr(fd, &term) != 0)
    {
    
    
        perror("config_port: Unable to read configure - ");
        return -1;
    }

    /* set timeout in deciseconds for noncanonical read */
    term.c_cc[VTIME] = timeout_sec * 10;

    /* set minimum number of bytes to read */
    term.c_cc[VMIN] = min_bytes;

    /* write serial port configure */
    if (tcsetattr(fd, TCSADRAIN, &term) != 0)
    {
    
    
        perror("config_port: Unable to write configure - ");
        return -1;
    }
}

/*
 * read data from serial port
 * return the length of bytes actually read on success, or -1 on error
 *
 * fd - file descriptor
 * buf - pointer of buffer to receive bytes
 * len - buffer
 * sec - timeout second
 * min - minimum bytes to read
 */
ssize_t read_port(int fd, void *buf, size_t len, cc_t sec, cc_t min)
{
    
    
    ssize_t actual_read;

    /* setup serial port */
    if (read_port_setup(fd, sec, min) == -1)
    {
    
    
        return -1;
    }

    /* read bytes from serial port */
    actual_read = read(fd, buf, len);
    if (actual_read == -1)
    {
    
    
        /* read failure */
        perror("read_port: Unable to read port - ");
    }

    return actual_read;
}

Uso común:

read_port(fd, buf, len, 0, 0); /* polling read, 不管是否收到字节都会立刻退出 */
read_port(fd, buf, len, 5, 0); /* read with timeout, 收到至少一个字节或超时5秒时退出 */
read_port(fd, buf, len, 0, 5); /* blocking read, 读到至少5个字节后退出 */

/* 
 * read with interbyte timeout, 这种情况比较复杂
 * 一个字节间隔计时器会在收到第一个字节后启动,每当收到新的字节,
 * 计时器都会复位,以下情况会导致函数退出: 
 *     1. 收到至少5个字节
 *     2. 字节间隔计时器超时5秒
 */
read_port(fd, buf, len, 5, 5); 

cerrar el puerto serie

Simplemente cierre el puerto serie como cerrar un archivo normal:

/*
 * close serial port 
 */
int close_port(int fd)
{
    
    
    if (close(fd) == -1)
    {
    
    
        perror("close_port: Unable to close port - ");
        return -1;
    }
    return 0;
}

código de ejemplo completo

#include <errno.h> /* Error number definitions */
#include <fcntl.h> /* File control definitions */
#include <stdio.h>
#include <string.h>
#include <termios.h> /* POSIX terminal control definitions */
#include <unistd.h>  /* UNIX standard function definitions */

/*
 * open serial port
 * return file descriptor on success or -1 on error.
 */
int open_port(char *path)
{
    
    
    int fd;

    fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)
    {
    
    
        /* open failure, print system error msg. */
        perror("open_port: Unable to open port - ");
    }
    else
    {
    
    
        /* clear file descriptor flags */
        fcntl(fd, F_SETFL, 0);
        return fd;
    }
}

/*
 * write data to serial port
 * return the length of bytes actually written on success, or -1 on error
 */
ssize_t write_port(int fd, void *buf, size_t len)
{
    
    
    ssize_t actual_write;

    actual_write = write(fd, buf, len);
    if (actual_write == -1)
    {
    
    
        /* write failure */
        perror("write_port: Unable to write port - ");
    }
    return actual_write;
}

/* set timeout and minimum number of bytes to read */
int read_port_setup(int fd, cc_t timeout_sec, cc_t min_bytes)
{
    
    
    struct termios term;

    /* read serial port configure */
    if (tcgetattr(fd, &term) != 0)
    {
    
    
        perror("config_port: Unable to read configure - ");
        return -1;
    }

    /* set timeout in deciseconds for noncanonical read */
    term.c_cc[VTIME] = timeout_sec * 10;

    /* set minimum number of bytes to read */
    term.c_cc[VMIN] = min_bytes;

    /* write serial port configure */
    if (tcsetattr(fd, TCSADRAIN, &term) != 0)
    {
    
    
        perror("config_port: Unable to write configure - ");
        return -1;
    }
}

/*
 * read data from serial port
 * return the length of bytes actually read on success, or -1 on error
 *
 * fd - file descriptor
 * buf - pointer of buffer to receive bytes
 * len - buffer
 * sec - timeout second
 * min - minimum bytes to read
 */
ssize_t read_port(int fd, void *buf, size_t len, cc_t sec, cc_t min)
{
    
    
    ssize_t actual_read;

    /* setup serial port */
    if (read_port_setup(fd, sec, min) == -1)
    {
    
    
        return -1;
    }

    /* read bytes from serial port */
    actual_read = read(fd, buf, len);
    if (actual_read == -1)
    {
    
    
        /* read failure */
        perror("read_port: Unable to read port - ");
    }

    return actual_read;
}

/*
 * config serial port baud and set data format to 8N1
 * (8bits data, no parity bit, 1 stop bit)
 * return 0 on success, or -1 on error
 */
int config_port(int fd, speed_t baud)
{
    
    
    struct termios term;

    /* read serial port configure */
    if (tcgetattr(fd, &term) != 0)
    {
    
    
        perror("config_port: Unable to read configure - ");
        return -1;
    }

    /* set baudrate */
    cfsetspeed(&term, baud);

    /* 8N1 mode */
    term.c_cflag &= ~PARENB;
    term.c_cflag &= ~CSTOPB;
    term.c_cflag &= ~CSIZE;
    term.c_cflag |= CS8;

    /* enbale raw mode */
    cfmakeraw(&term);

    /* write serial port configure */
    if (tcsetattr(fd, TCSADRAIN, &term) != 0)
    {
    
    
        perror("config_port: Unable to write configure - ");
        return -1;
    }
}

/*
 * close serial port 
 */
int close_port(int fd)
{
    
    
    if (close(fd) == -1)
    {
    
    
        perror("close_port: Unable to close port - ");
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[])
{
    
    
    char *path = "/dev/ttyS0";
    int fd;
    char send_string[] = "hello world!\n";
    char read_string[20] = "";
    ssize_t length;

    /* open port */
    fd = open_port(path);
    if (fd > 0)
    {
    
    
        printf("open %s success, fd = %d.\n", path, fd);
    }

    /* config port */
    config_port(fd, B9600);

    /* write data to port */
    length = write_port(fd, send_string, sizeof(send_string));
    if (length >= 0)
    {
    
    
        printf("%ld bytes written.\n", length);
    }

    /* read data from port */
    length = read_port(fd, read_string, 20, 5, 5);
    if (length >= 0)
    {
    
    
        printf("%ld bytes read : ", length);
        for (ssize_t i = 0; i < length; i++)
        {
    
    
            printf("%02X ", (int)(read_string[i]));
        }
        printf("\n");
    }

    /* close port */
    close_port(fd);

    return 0;
}

falta contenido

Parte del documento original en inglés no está registrado porque no puedo usarlo temporalmente. En mi opinión, vale la pena leer los siguientes capítulos:

  1. El capítulo 2, Configuración del puerto serie, también analiza el contenido relacionado con el control de flujo; si necesita usarlo, puede leerlo;
  2. Capítulo 4, Programación en serie avanzada Algunas características avanzadas de la programación del puerto en serie, como el uso de la función de llamada al sistema ioctl realmente llamada al configurar el puerto en serie y el uso de la función de llamada al sistema select;

Supongo que te gusta

Origin blog.csdn.net/lczdk/article/details/123828037
Recomendado
Clasificación