Diferencia entre revisiones de «Tutorial:Puerto serie en Linux»
(No se muestran 31 ediciones intermedias del mismo usuario) | |||
Línea 1: | Línea 1: | ||
+ | [[Imagen:Tutorial-linux-serial.png|thumb|300px|[http://www.iearobotics.com/wiki/images/b/b2/Tutorial-linux-serial-portada.zip fuentes]]] | ||
+ | |||
+ | = <font color="#0000FF">Tutorial: Linux and the Serial port</font> = | ||
+ | |||
+ | {| {{tablabonita}} | ||
+ | | [[Imagen:English.gif]] | ||
+ | || This tutorial is written in Spanish but all '''the source code is in English'''. A lot of documentation can be found in the sources. Start by downloading the [http://www.iearobotics.com/wiki/images/5/55/Serial-1.0.tar.gz Serial-1.0.tar.gz] package, uncompress it, change to the serial-1.0 directory and type make to compile the examples. Please, read the [http://svn.iearobotics.com/serial/trunk/Tutorial.txt Tutorial.txt] file. | ||
+ | |} | ||
+ | |||
+ | = <font color="#0000FF">Tutorial: Puerto serie en Linux</font> = | ||
+ | |||
== Introducción == | == Introducción == | ||
Breve tutorial sobre cómo manejar el '''puerto serie desde Linux'''. Yo lo utilizo para comunicarme con microcontroladores externos y para el control de mis robots. Aunque nuestro ordenador no disponga de puerto serie, no hay problema. Se pueden utilizar '''conversores USB-serie'''. | Breve tutorial sobre cómo manejar el '''puerto serie desde Linux'''. Yo lo utilizo para comunicarme con microcontroladores externos y para el control de mis robots. Aunque nuestro ordenador no disponga de puerto serie, no hay problema. Se pueden utilizar '''conversores USB-serie'''. | ||
Línea 7: | Línea 18: | ||
Para compilar los ejemplos, seguir los siguientes pasos: | Para compilar los ejemplos, seguir los siguientes pasos: | ||
− | # Descargar el paquete con los ejemplos: | + | # Descargar el paquete con los ejemplos: [http://www.iearobotics.com/wiki/images/5/55/Serial-1.0.tar.gz Serial-1.0.tar.gz] |
# Descomprimirlo | # Descomprimirlo | ||
# Entrar en el directorio serial-1.0 | # Entrar en el directorio serial-1.0 | ||
Línea 19: | Línea 30: | ||
gcc -Iserial -c -o term_ex/console_io.o term_ex/console_io.c | gcc -Iserial -c -o term_ex/console_io.o term_ex/console_io.c | ||
gcc -o term term_ex/term.o term_ex/console_io.o serial.o | gcc -o term term_ex/term.o term_ex/console_io.o serial.o | ||
+ | |||
+ | == BUG: Error de compilacion [Solucionado] == | ||
+ | [2015/oct/27] Esteban <esteban.e at lcc.cl> ha reportado el siguiente error de compilacion: | ||
+ | |||
+ | esteban serial-1.0 $ make | ||
+ | gcc -o serial.o -c serial/serial.c | ||
+ | serial/serial.c: En la función ‘serial_read’: | ||
+ | serial/serial.c:99:3: error: nombre de tipo ‘fd_set’ desconocido | ||
+ | fd_set fds; | ||
+ | ^ | ||
+ | serial/serial.c:100:18: error: no se conoce el tamaño de almacenamiento de ‘timeout’ | ||
+ | struct timeval timeout; | ||
+ | [...] | ||
+ | |||
+ | Se soluciona añadiendo el siguiente include al comienzo del fichero serial.c: | ||
+ | |||
+ | #include <sys/select.h> | ||
== Pruebas con el puerto serie == | == Pruebas con el puerto serie == | ||
Línea 33: | Línea 61: | ||
* Tarjeta [[Skypic]], conectada al PC por medio de un cable serie o conversor USB-serie | * Tarjeta [[Skypic]], conectada al PC por medio de un cable serie o conversor USB-serie | ||
* El firmware grabado es el [http://www.iearobotics.com/proyectos/stargate/servidores/sg-echo/sg-echo.html Servidor de eco] | * El firmware grabado es el [http://www.iearobotics.com/proyectos/stargate/servidores/sg-echo/sg-echo.html Servidor de eco] | ||
− | * Si se conecta la [ | + | * Si se conecta la [[Freeleds| tarjeta Freeleds]] al puerto B de la Skypic, se podrá ver en binario todo lo que se ha recibido. |
* La Skypic se configura muy fácilmente para este ejemplo ejecutando el programa [[Pydownloader-wx]] y '''pulsando el botón de "ECO"''', que descarga automáticamente el servidor de eco. | * La Skypic se configura muy fácilmente para este ejemplo ejecutando el programa [[Pydownloader-wx]] y '''pulsando el botón de "ECO"''', que descarga automáticamente el servidor de eco. | ||
Línea 60: | Línea 88: | ||
| /dev/ttyUSBnn || n-ésimo conversor USB-serie | | /dev/ttyUSBnn || n-ésimo conversor USB-serie | ||
|} | |} | ||
+ | |||
+ | La forma de acceder al puerto serie es la misma que para trabajar con cualquier otro fichero. Se usan las llamadas '''read()''' y '''write()''' para leer y escribir respectivamente. Pero a diferencia de los archivos normales, '''antes de usar el puerto serie es necesario configurarlo''', estableciendo la velocidad de trabajo y su modo de funcionamiento. | ||
+ | |||
+ | == Módulo serial.c == | ||
+ | === Código fuente === | ||
+ | {| {{tablabonita}} | ||
+ | | [http://svn.iearobotics.com/serial/trunk/serial/serial.c serial.c] | ||
+ | | Fuentes del módulo para comunicaciones serie (en el SVN) | ||
+ | |---- | ||
+ | | [http://svn.iearobotics.com/serial/trunk/serial/serial.h serial.h] | ||
+ | | Ficheros de cabecera | ||
+ | |} | ||
+ | |||
+ | === Descripción === | ||
+ | El módulo serial.c es el que implementa las funciones para '''abir/cerrar''' el puerto serie y '''enviar/recibir''' datos. El resto de ejemplos hacen uso de ellas. | ||
+ | |||
+ | La función '''serial_open()''' abre el puerto serie para lectura/escritura y en '''modo crudo''' (raw). En este modo no se procesan ciertos caracteres especiales de control. Se usa el puerto serie sólo para enviar/recibir bytes sin interpretarlos. | ||
+ | |||
+ | Se configura el '''modo de funcionamiento serie a 8N1''' (8 bits de datos, 1 bit de stop y sin paridad). | ||
+ | La velocidad es la indicada por el usuario en el parámetro baud. Es una constante que puede valer: B9600, B19200, etc. Se puede encontrar más información sobre todos los valores posibles en la '''página de manual de termios''' (man termios) | ||
+ | |||
+ | Esta función '''devuelve el descriptor del puerto serie''' (o -1 si ha ocurrido un error), que será necesario para realizar las lecturas y escrituras. | ||
+ | |||
+ | int '''serial_open'''(char *serial_name, speed_t baud) | ||
+ | { | ||
+ | struct termios newtermios; | ||
+ | int fd; | ||
+ | |||
+ | fd = open(serial_name,O_RDWR | O_NOCTTY); | ||
+ | |||
+ | newtermios.c_cflag= CBAUD | CS8 | CLOCAL | CREAD; | ||
+ | newtermios.c_iflag=IGNPAR; | ||
+ | newtermios.c_oflag=0; | ||
+ | newtermios.c_lflag=0; | ||
+ | newtermios.c_cc[VMIN]=1; | ||
+ | newtermios.c_cc[VTIME]=0; | ||
+ | |||
+ | cfsetospeed(&newtermios,baud); | ||
+ | cfsetispeed(&newtermios,baud); | ||
+ | |||
+ | if (tcflush(fd,TCIFLUSH)==-1) return -1; | ||
+ | if (tcflush(fd,TCOFLUSH)==-1) return -1; | ||
+ | if (tcsetattr(fd,TCSANOW,&newtermios)==-1) return -1; | ||
+ | |||
+ | return fd; | ||
+ | } | ||
+ | |||
+ | La función '''serial_send()''' envía una cadena de bytes por el puerto serie. Lo único que hace es invocar la llamada al sistema '''write()'''. Como parámetros se pasa el descriptor serie del puerto serie (''serial_fd''), devuelto por la función ''serial_open()'', el array con los datos a enviar (''data'') y su tamaño (''size''). | ||
+ | |||
+ | void '''serial_send'''(int serial_fd, char *data, int size) | ||
+ | { | ||
+ | write(serial_fd, data, size); | ||
+ | } | ||
+ | |||
+ | La función '''serial_read()''' se usa para leer datos del puerto serie. Se le pasan como parámetros el descriptor del puerto serie (''serial_fd''), el array donde almacenar los datos recibidos (''data''), el tamaño máximo de bytes a recibir (''size'', para no desbordar el array) y el tiempo máximo para recibir los datos (''timeout_usec''). Si transcurre un tiempo igual a ''timeout_usec'' y no se han recibido datos, la función retornará y devolverá el control a la que la invocó. | ||
+ | |||
+ | Para realizar las lecturas no bloqueantes, se usa la llamada al sistema select(). En este caso devuelve 1 si hay datos esperando a ser leídos y 0 si ha ocurrido un timeout. A continuación se invoca a read() para leer estos datos. El proceso se repite para garantizar que es posible recibir datos de tamaño size. Al utilizar conversores USB-serie, los datos llegan en diferentes grupos siendo necesario realizar más de una llamada a read(). | ||
+ | |||
+ | Se devuelve el número de bytes leídos (o 0 si ha ocurrido un timeout). | ||
+ | |||
+ | int '''serial_read'''(int serial_fd, char *data, int size, int timeout_usec) | ||
+ | { | ||
+ | fd_set fds; | ||
+ | struct timeval timeout; | ||
+ | int count=0; | ||
+ | int ret; | ||
+ | int n; | ||
+ | |||
+ | do { | ||
+ | FD_ZERO(&fds); | ||
+ | FD_SET (serial_fd, &fds); | ||
+ | |||
+ | timeout.tv_sec = 0; | ||
+ | timeout.tv_usec = timeout_usec; | ||
+ | |||
+ | ret=select (FD_SETSIZE,&fds, NULL, NULL,&timeout); | ||
+ | |||
+ | if (ret==1) { | ||
+ | n=read (serial_fd, &data[count], size-count); | ||
+ | count+=n; | ||
+ | data[count]=0; | ||
+ | } | ||
+ | } while (count<size && ret==1); | ||
+ | |||
+ | return count; | ||
+ | } | ||
+ | |||
+ | La última función es '''serial_close()''', que simplemente invoca a close() para cerrar el puerto serie. | ||
+ | |||
+ | void serial_close(int fd) | ||
+ | { | ||
+ | close(fd); | ||
+ | } | ||
== Ejemplo 1: enviar/recibir cadenas == | == Ejemplo 1: enviar/recibir cadenas == | ||
Línea 69: | Línea 190: | ||
|} | |} | ||
+ | === Ejecución === | ||
+ | Este ejemplo envía la cadena "''ASCII Command test''" por el puerto serie a la velocidad de 9600 baudios y espera recibir una cadena de respuesta. | ||
+ | |||
+ | Si se ha conectado un dispositivo que haga eco, el ejecutar este ejemplo se obtendrá: | ||
+ | |||
+ | $ '''./send_receive /dev/ttyUSB0''' | ||
+ | String sent------> ASCII Command test | ||
+ | String received--> ASCII Command test (18 bytes) | ||
+ | |||
+ | Si no hay eco la salida será la siguiente: | ||
+ | |||
+ | $ '''./send_receive /dev/ttyUSB0''' | ||
+ | String sent------> ASCII Command test | ||
+ | String received--> Timeout! | ||
+ | |||
+ | === Explicación === | ||
+ | Sólo se explicarán algunos fragmentos del código. Primero se abre el puerto serie, a la velocidad de 9600 baudios invocando la función ''serial_open()''. Se pasa como argumento el nombre del dispositivo serie obtenido de la línea de comandos. Se indica si ha ocurrido un error al abrirlo (por ejemplo si se especifica un dispositivo serie incorrecto): | ||
+ | |||
+ | serial_fd='''serial_open'''(argv[1],B9600); | ||
+ | |||
+ | if (serial_fd==-1) { | ||
+ | printf ("Error opening the serial device: %s\n",argv[1]); | ||
+ | perror("OPEN"); | ||
+ | exit(0); | ||
+ | } | ||
+ | |||
+ | Luego se envía la cadena definida en la constante CMD y se espera recibir la misma | ||
+ | |||
+ | '''serial_send'''(serial_fd, CMD, CMD_LEN); | ||
+ | printf ("String sent------> %s\n",CMD); | ||
+ | |||
+ | n='''serial_read'''(serial_fd,data,CMD_LEN,TIMEOUT); | ||
+ | |||
+ | La variable n contiene el número de bytes recibidos. Si n es 0 es porque habrá ocurrido un timeout. En caso contrario se imprimen en pantalla la cadena recibida. | ||
+ | |||
+ | Por último se cierra el puerto serie: | ||
+ | |||
+ | serial_close(serial_fd); | ||
+ | |||
+ | == Ejemplo 2: Mini-terminal == | ||
+ | |||
+ | === Código fuente === | ||
+ | {| {{tablabonita}} | ||
+ | | [http://svn.iearobotics.com/serial/trunk/term_ex/term.c term.c] | ||
+ | | Fuentes del terminal, en el SVN | ||
+ | |---------- | ||
+ | | [http://svn.iearobotics.com/serial/trunk/term_ex/console_io.c console_io.c] | ||
+ | | Fuentes de las Rutinas para control del teclado, en el SVN | ||
+ | |} | ||
+ | |||
+ | === Ejecución === | ||
+ | Este ejemplo es un mini terminal de comunicaciones. Todo lo que el usuario teclee será enviado por el puerto serie y todo lo recibido será mostrado en pantalla. | ||
+ | |||
+ | Para utilizarlo: | ||
+ | |||
+ | $ ./term /dev/ttyUSB0 | ||
+ | Press the ESC key to quit | ||
+ | |||
+ | Comienza a funcionar. Pulsando la tecla ESC se termina. Si se tiene conectada la tarjeta skypic con una Freeleds se podrá ver en los leds el valor binario del carácter enviado, además de mostrarse en pantalla el eco. | ||
+ | |||
+ | === Explicación === | ||
+ | |||
+ | Al ejecutarse el programa se abre el puerto serie a 9600 baudios (igual que en el ejemplo anterior) y se invoca a la función '''term()''' que es la que implementa el bucle principal. | ||
+ | |||
+ | Dentro de este bucle primero se comprueba si el usuario a pulsado una tecla. Esto se hace llamando a la función '''console_io_kbhit()''' definida en el fichero '''console_io.c''' y que no es objeto de este tutorial su explicación. | ||
+ | Si se ha apretado una tecla, se lee usando '''console_io_getch()''' y se envía por el puerto serie con '''serial_send()'''. | ||
+ | La llegada de datos se comprueba periódicamente, invocando serial_read(). Los datos recibidos se imprimen en la pantalla. Si ha ocurrido un timeout significa que no se ha recibido nada. | ||
+ | |||
+ | void '''term'''(void) | ||
+ | { | ||
+ | char c[1]; | ||
+ | char rec[1]; | ||
+ | int n; | ||
+ | |||
+ | //-- Main loop | ||
+ | do { | ||
+ | if (console_io_kbhit()) { | ||
+ | c[0]=console_io_getch(); | ||
+ | if (c[0]!=ESC) | ||
+ | serial_send(serial_fd,c,1); | ||
+ | } | ||
+ | n=serial_read(serial_fd,rec,1,TIMEOUT); | ||
+ | if (n==1) { | ||
+ | printf ("%c",rec[0]); | ||
+ | fflush(stdout); | ||
+ | } | ||
+ | } while (c[0]!=ESC); | ||
+ | } | ||
== Descarga de los ejemplos == | == Descarga de los ejemplos == | ||
Línea 75: | Línea 284: | ||
!Fichero!!Descripción | !Fichero!!Descripción | ||
|-------------------- | |-------------------- | ||
− | | [] | + | | [http://www.iearobotics.com/wiki/images/5/55/Serial-1.0.tar.gz Serial-1.0.tar.gz] |
|| Fuentes | || Fuentes | ||
|-------------------- | |-------------------- | ||
− | | [] | + | | [http://www.iearobotics.com/wiki/images/4/4b/Serial-1.0-bin.tar.gz Serial-1.0-bin.tar.gz] |
− | || | + | || Ejecutables de los ejemplos, compilados para Debian Lenny 5.0 (intel 32 bits) |
|} | |} | ||
Línea 93: | Línea 302: | ||
== Enlaces == | == Enlaces == | ||
+ | * [[Tutorial: Puerto serie en Linux en C++]] | ||
+ | * [http://www.iearobotics.com/proyectos/cuadernos/ct1/ct1.html Comunicaciones serie (Hw)] | ||
+ | * [http://www.iearobotics.com/proyectos/cuadernos/ct11/ct11.html Comunicaciones serie con Python] | ||
+ | * [http://www.iearobotics.com/personal/juan/proyectos/consola_io/consola_io.html Módulo consola_io para acceso al teclado] (lenguaje C, Linux) | ||
== Repositorio == | == Repositorio == | ||
Línea 99: | Línea 312: | ||
Para obtener la última versión del SVN: | Para obtener la última versión del SVN: | ||
− | svn co http://svn.iearobotics.com/serial/ | + | svn co http://svn.iearobotics.com/serial/serial-1.0 |
== Noticias == | == Noticias == | ||
+ | * '''27/Oct/2015''': Error de compilación reportado por Esteban. Solucionado en la wiki (no en el repo ni en los tgz) | ||
+ | * '''20/Enero/2009''': Versión inicial. Publicado serial-1.0 | ||
* '''19/Enero/2009''': Comenzada esta página | * '''19/Enero/2009''': Comenzada esta página | ||
[[Categoría:SVN]] | [[Categoría:SVN]] |
Revisión actual del 00:26 27 oct 2015
Contenido
- 1 Tutorial: Linux and the Serial port
- 2 Tutorial: Puerto serie en Linux
- 2.1 Introducción
- 2.2 Compilación
- 2.3 BUG: Error de compilacion [Solucionado]
- 2.4 Pruebas con el puerto serie
- 2.5 Pruebas con la tarjeta Skypic
- 2.6 Puerto serie y Linux
- 2.7 Módulo serial.c
- 2.8 Ejemplo 1: enviar/recibir cadenas
- 2.9 Ejemplo 2: Mini-terminal
- 2.10 Descarga de los ejemplos
- 2.11 Licencia
- 2.12 Autor
- 2.13 Enlaces
- 2.14 Repositorio
- 2.15 Noticias
Tutorial: Linux and the Serial port
This tutorial is written in Spanish but all the source code is in English. A lot of documentation can be found in the sources. Start by downloading the Serial-1.0.tar.gz package, uncompress it, change to the serial-1.0 directory and type make to compile the examples. Please, read the Tutorial.txt file. |
Tutorial: Puerto serie en Linux
Introducción
Breve tutorial sobre cómo manejar el puerto serie desde Linux. Yo lo utilizo para comunicarme con microcontroladores externos y para el control de mis robots. Aunque nuestro ordenador no disponga de puerto serie, no hay problema. Se pueden utilizar conversores USB-serie.
Los ejemplos de este tutorial se han probado en la distribución de Linux Debian/Lenny (5.0)
Compilación
Para compilar los ejemplos, seguir los siguientes pasos:
- Descargar el paquete con los ejemplos: Serial-1.0.tar.gz
- Descomprimirlo
- Entrar en el directorio serial-1.0
- Ejecutar make
$ make gcc -o serial.o -c serial/serial.c gcc -Iserial -c -o send_receive_ex/send_receive.o send_receive_ex/send_receive.c gcc -o send_receive send_receive_ex/send_receive.o serial.o gcc -Iserial -c -o term_ex/term.o term_ex/term.c gcc -Iserial -c -o term_ex/console_io.o term_ex/console_io.c gcc -o term term_ex/term.o term_ex/console_io.o serial.o
BUG: Error de compilacion [Solucionado]
[2015/oct/27] Esteban <esteban.e at lcc.cl> ha reportado el siguiente error de compilacion:
esteban serial-1.0 $ make gcc -o serial.o -c serial/serial.c serial/serial.c: En la función ‘serial_read’: serial/serial.c:99:3: error: nombre de tipo ‘fd_set’ desconocido fd_set fds; ^ serial/serial.c:100:18: error: no se conoce el tamaño de almacenamiento de ‘timeout’ struct timeval timeout; [...]
Se soluciona añadiendo el siguiente include al comienzo del fichero serial.c:
#include <sys/select.h>
Pruebas con el puerto serie
Para probar los ejemplos de este tutorial es necesario tener conectado un dispositivo al puerto serie que nos haga "eco" de todo lo recibido. De esta manera se puede verificar que el software del PC está funcionando correctamente.
Existen varias alternativas:
- Alternativa 1: Utilizar un cable para unir los pines 2 y 3 del puerto serie. Todo lo que se envíe por el pin de transmisión (TX) se recibirá automáticamente por el de recepción (RX).
- Alternativa 2: Utilizar un microcontrolador externo que ejecute un firmware para hacer eco. Esta es la opción que se ha usado para probar los ejemplos de este tutorial. Se ha utilizado la tarjeta Skypic
Pruebas con la tarjeta Skypic
- Tarjeta Skypic, conectada al PC por medio de un cable serie o conversor USB-serie
- El firmware grabado es el Servidor de eco
- Si se conecta la tarjeta Freeleds al puerto B de la Skypic, se podrá ver en binario todo lo que se ha recibido.
- La Skypic se configura muy fácilmente para este ejemplo ejecutando el programa Pydownloader-wx y pulsando el botón de "ECO", que descarga automáticamente el servidor de eco.
Puerto serie y Linux
Los nombres que se dan en Linux a los dispositivos serie son:
Nombre del dispositivo | Descripción |
---|---|
/dev/ttyS0 | Primer puerto serie nativo. (Equivalente a COM1 en Windows) |
/dev/ttyS1 | Segundo puerto serie nativo. (Equivalente a COM2 en Windows) |
/dev/ttySnn | n-ésimo puerto serie nativo |
/dev/ttyUSB0 | Conversor USB-serie 1 |
/dev/ttyUSB1 | Conversor USB-serie 2 |
/dev/ttyUSBnn | n-ésimo conversor USB-serie |
La forma de acceder al puerto serie es la misma que para trabajar con cualquier otro fichero. Se usan las llamadas read() y write() para leer y escribir respectivamente. Pero a diferencia de los archivos normales, antes de usar el puerto serie es necesario configurarlo, estableciendo la velocidad de trabajo y su modo de funcionamiento.
Módulo serial.c
Código fuente
serial.c | Fuentes del módulo para comunicaciones serie (en el SVN) |
serial.h | Ficheros de cabecera |
Descripción
El módulo serial.c es el que implementa las funciones para abir/cerrar el puerto serie y enviar/recibir datos. El resto de ejemplos hacen uso de ellas.
La función serial_open() abre el puerto serie para lectura/escritura y en modo crudo (raw). En este modo no se procesan ciertos caracteres especiales de control. Se usa el puerto serie sólo para enviar/recibir bytes sin interpretarlos.
Se configura el modo de funcionamiento serie a 8N1 (8 bits de datos, 1 bit de stop y sin paridad). La velocidad es la indicada por el usuario en el parámetro baud. Es una constante que puede valer: B9600, B19200, etc. Se puede encontrar más información sobre todos los valores posibles en la página de manual de termios (man termios)
Esta función devuelve el descriptor del puerto serie (o -1 si ha ocurrido un error), que será necesario para realizar las lecturas y escrituras.
int serial_open(char *serial_name, speed_t baud) { struct termios newtermios; int fd; fd = open(serial_name,O_RDWR | O_NOCTTY); newtermios.c_cflag= CBAUD | CS8 | CLOCAL | CREAD; newtermios.c_iflag=IGNPAR; newtermios.c_oflag=0; newtermios.c_lflag=0; newtermios.c_cc[VMIN]=1; newtermios.c_cc[VTIME]=0; cfsetospeed(&newtermios,baud); cfsetispeed(&newtermios,baud); if (tcflush(fd,TCIFLUSH)==-1) return -1; if (tcflush(fd,TCOFLUSH)==-1) return -1; if (tcsetattr(fd,TCSANOW,&newtermios)==-1) return -1; return fd; }
La función serial_send() envía una cadena de bytes por el puerto serie. Lo único que hace es invocar la llamada al sistema write(). Como parámetros se pasa el descriptor serie del puerto serie (serial_fd), devuelto por la función serial_open(), el array con los datos a enviar (data) y su tamaño (size).
void serial_send(int serial_fd, char *data, int size) { write(serial_fd, data, size); }
La función serial_read() se usa para leer datos del puerto serie. Se le pasan como parámetros el descriptor del puerto serie (serial_fd), el array donde almacenar los datos recibidos (data), el tamaño máximo de bytes a recibir (size, para no desbordar el array) y el tiempo máximo para recibir los datos (timeout_usec). Si transcurre un tiempo igual a timeout_usec y no se han recibido datos, la función retornará y devolverá el control a la que la invocó.
Para realizar las lecturas no bloqueantes, se usa la llamada al sistema select(). En este caso devuelve 1 si hay datos esperando a ser leídos y 0 si ha ocurrido un timeout. A continuación se invoca a read() para leer estos datos. El proceso se repite para garantizar que es posible recibir datos de tamaño size. Al utilizar conversores USB-serie, los datos llegan en diferentes grupos siendo necesario realizar más de una llamada a read().
Se devuelve el número de bytes leídos (o 0 si ha ocurrido un timeout).
int serial_read(int serial_fd, char *data, int size, int timeout_usec) { fd_set fds; struct timeval timeout; int count=0; int ret; int n; do { FD_ZERO(&fds); FD_SET (serial_fd, &fds); timeout.tv_sec = 0; timeout.tv_usec = timeout_usec; ret=select (FD_SETSIZE,&fds, NULL, NULL,&timeout); if (ret==1) { n=read (serial_fd, &data[count], size-count); count+=n; data[count]=0; } } while (count<size && ret==1); return count; }
La última función es serial_close(), que simplemente invoca a close() para cerrar el puerto serie.
void serial_close(int fd) { close(fd); }
Ejemplo 1: enviar/recibir cadenas
Código fuente
send_receive.c | Fuentes del ejemplo, en el SVN |
Ejecución
Este ejemplo envía la cadena "ASCII Command test" por el puerto serie a la velocidad de 9600 baudios y espera recibir una cadena de respuesta.
Si se ha conectado un dispositivo que haga eco, el ejecutar este ejemplo se obtendrá:
$ ./send_receive /dev/ttyUSB0 String sent------> ASCII Command test String received--> ASCII Command test (18 bytes)
Si no hay eco la salida será la siguiente:
$ ./send_receive /dev/ttyUSB0 String sent------> ASCII Command test String received--> Timeout!
Explicación
Sólo se explicarán algunos fragmentos del código. Primero se abre el puerto serie, a la velocidad de 9600 baudios invocando la función serial_open(). Se pasa como argumento el nombre del dispositivo serie obtenido de la línea de comandos. Se indica si ha ocurrido un error al abrirlo (por ejemplo si se especifica un dispositivo serie incorrecto):
serial_fd=serial_open(argv[1],B9600); if (serial_fd==-1) { printf ("Error opening the serial device: %s\n",argv[1]); perror("OPEN"); exit(0); }
Luego se envía la cadena definida en la constante CMD y se espera recibir la misma
serial_send(serial_fd, CMD, CMD_LEN); printf ("String sent------> %s\n",CMD); n=serial_read(serial_fd,data,CMD_LEN,TIMEOUT);
La variable n contiene el número de bytes recibidos. Si n es 0 es porque habrá ocurrido un timeout. En caso contrario se imprimen en pantalla la cadena recibida.
Por último se cierra el puerto serie:
serial_close(serial_fd);
Ejemplo 2: Mini-terminal
Código fuente
term.c | Fuentes del terminal, en el SVN |
console_io.c | Fuentes de las Rutinas para control del teclado, en el SVN |
Ejecución
Este ejemplo es un mini terminal de comunicaciones. Todo lo que el usuario teclee será enviado por el puerto serie y todo lo recibido será mostrado en pantalla.
Para utilizarlo:
$ ./term /dev/ttyUSB0 Press the ESC key to quit
Comienza a funcionar. Pulsando la tecla ESC se termina. Si se tiene conectada la tarjeta skypic con una Freeleds se podrá ver en los leds el valor binario del carácter enviado, además de mostrarse en pantalla el eco.
Explicación
Al ejecutarse el programa se abre el puerto serie a 9600 baudios (igual que en el ejemplo anterior) y se invoca a la función term() que es la que implementa el bucle principal.
Dentro de este bucle primero se comprueba si el usuario a pulsado una tecla. Esto se hace llamando a la función console_io_kbhit() definida en el fichero console_io.c y que no es objeto de este tutorial su explicación. Si se ha apretado una tecla, se lee usando console_io_getch() y se envía por el puerto serie con serial_send(). La llegada de datos se comprueba periódicamente, invocando serial_read(). Los datos recibidos se imprimen en la pantalla. Si ha ocurrido un timeout significa que no se ha recibido nada.
void term(void) { char c[1]; char rec[1]; int n; //-- Main loop do { if (console_io_kbhit()) { c[0]=console_io_getch(); if (c[0]!=ESC) serial_send(serial_fd,c,1); } n=serial_read(serial_fd,rec,1,TIMEOUT); if (n==1) { printf ("%c",rec[0]); fflush(stdout); } } while (c[0]!=ESC); }
Descarga de los ejemplos
Versión: 1.0
Fichero | Descripción |
---|---|
Serial-1.0.tar.gz | Fuentes |
Serial-1.0-bin.tar.gz | Ejecutables de los ejemplos, compilados para Debian Lenny 5.0 (intel 32 bits) |
Licencia
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Spain License. |
El código está liberado bajo licencia GPLv3 |
Autor
Enlaces
- Tutorial: Puerto serie en Linux en C++
- Comunicaciones serie (Hw)
- Comunicaciones serie con Python
- Módulo consola_io para acceso al teclado (lenguaje C, Linux)
Repositorio
- SVN del proyecto http://svn.iearobotics.com/serial/
Para obtener la última versión del SVN:
svn co http://svn.iearobotics.com/serial/serial-1.0
Noticias
- 27/Oct/2015: Error de compilación reportado por Esteban. Solucionado en la wiki (no en el repo ni en los tgz)
- 20/Enero/2009: Versión inicial. Publicado serial-1.0
- 19/Enero/2009: Comenzada esta página