Adquisicion de Datos Linux

10
Adquisición de datos en Linux Por: Jesús Castrejón Figueroa Facultad de Ciencias, UNAM Introducción Muchas veces, nos vemos necesitados de conectar algún dispositivo a nuestra computadora por medio del puerto serie, con distintos fines, como para la adquisición de datos por ejemplo. Cuando trabajamos en windows la tarea no resulta tan compleja, debido a que la mayoría de los aparatos cuentan con software incluido, pero ¿qué pasa cuando lo queremos usar bajo GNU-Linux?. Este es el motivo de este texto. 1 Adquisición de datos Un dispositivo de adquisición de datos es un medio para convertir variables físicas señales eléctricas, y mediante un convertidor análogo-digital se convierte dicha señal en pulsos eléctricos bien definidos en intensidad y longitud que son reconocibles para la computadora, por ejemplo un pulso de 5 volts representa un uno, mientras que un pulso de 1 volt representa un cero, definiendo así el lenguaje binario usado por la computadora. Por medio de algún protocolo de comunicación previamente definido y conocido universalmente (El protocolo ModBus por ejemplo) se logra establecer una comunicación entre el dispositivo de adquisición y nuestra computadora, dando un significado a las cadenas enviadas y recibidas. Para una información completa de la adquisición de datos y la conversión análoga-digital, ver aqui . 2 Puertos Un puerto de E/S (Entrada/salida) es un modo de conseguir que los datos entren y salgan de la computadora. Existen muchos tipos de puertos de E/S, como los puertos serie, puertos paralelos, controladores de disqueteras, tarjetas Ethernet, etc. Trataremos con puertos serie ya que las terminales (Nuestro dispositivo de adquisición por ejemplo) son dispositivos serie. Cada puerto serie debe tener una dirección de E/S. Tabla1.- Direcciones en Linux de los dispositivos serie Dirección del dispositivo Descripción /dev/ttyS0 Primer puerto serie nativo. (Equivalente a COM1 en Windows) /dev/ttySn n-ésimo puerto serie nativo /dev/ttyUSB0 convertidor USB-serie 1 /dev/ttyUSBn n-ésimo convertidor USB-serie En el caso de que nuestra computadora no cuente con una entrada para puerto serie, podemos usar un convertidor USB-Serie, en tal caso aparecerá no como un dispositivo serie nativo /dev/ttySn sino como /dev/ttyUSBn.

description

Programar modbus en linux.

Transcript of Adquisicion de Datos Linux

Page 1: Adquisicion de Datos Linux

Adquisición de datos en LinuxPor: Jesús Castrejón FigueroaFacultad de Ciencias, UNAM

Introducción

Muchas veces, nos vemos necesitados de conectar algún dispositivo a nuestra computadora por medio del puerto serie, con distintos fines, como para la adquisición de datos por ejemplo. Cuando trabajamos en windows la tarea no resulta tan compleja, debido a que la mayoría de los aparatos cuentan con software incluido, pero ¿qué pasa cuando lo queremos usar bajoGNU-Linux?. Este es el motivo de este texto.

1 Adquisición de datos

Un dispositivo de adquisición de datos es un medio para convertir variables físicas señales eléctricas, y mediante un convertidor análogo-digital se convierte dicha señal en pulsos eléctricos bien definidos en intensidad y longitud que son reconocibles para la computadora, por ejemplo un pulso de 5 volts representa un uno, mientras que un pulso de 1 volt representa un cero, definiendo así el lenguaje binario usado por la computadora.Por medio de algún protocolo de comunicación previamente definido y conocido universalmente (El protocolo ModBus por ejemplo) se logra establecer una comunicación entre el dispositivo de adquisición y nuestra computadora, dando un significado a las cadenas enviadas y recibidas.Para una información completa de la adquisición de datos y la conversión análoga-digital, ver aqui.

2 Puertos

Un puerto de E/S (Entrada/salida) es un modo de conseguir que los datos entren y salgan de la computadora. Existen muchos tipos de puertos de E/S, como los puertos serie, puertos paralelos, controladores de disqueteras, tarjetas Ethernet, etc. Trataremos con puertos serie ya que las terminales (Nuestro dispositivo de adquisición por ejemplo) son dispositivos serie. Cada puerto serie debe tener una dirección de E/S.

Tabla1.- Direcciones en Linux de los dispositivos serie

Dirección del dispositivo Descripción

/dev/ttyS0 Primer puerto serie nativo. (Equivalente a COM1 en Windows)

/dev/ttySn n-ésimo puerto serie nativo

/dev/ttyUSB0 convertidor USB-serie 1

/dev/ttyUSBn n-ésimo convertidor USB-serie

En el caso de que nuestra computadora no cuente con una entrada para puerto serie, podemos usar un convertidor USB-Serie, en tal caso aparecerá no como un dispositivo serie nativo /dev/ttySn sino como /dev/ttyUSBn.

Page 2: Adquisicion de Datos Linux

Fig 1.- Convertidor USB-Serie(RS-232)

Hay veces en las cuales Linux no asigna una dirección a nuestro dispositivo serie, si ese es el caso, ver este Tutorial.

2.1 Leer el puerto directamente

Hay situaciones en las que nuestro dispositivo trabaja sin protocolo de comunicación, ModBus es el protocolo de comunicación mas usual en dispositivos comerciales, y es el el del cual hacemos uso mas adelante en el texto, si este es el caso, puede que el dispositivo este mandando señales continuamente a la computadora, por lo cual podríamos ser capaces de leer directamente la dirección E/S, “cat /dev/ttySn”

O con pequeño script de bash, en este caso para ttyS0:

#!/bin/bash

while truedo read LINE < /dev/ttyS0 echo $LINEdone

Para mas información ver el manual de read.Pero como esto cumple apenas un caso muy particular, lo mejor es tener una herramienta mas general que funcione para cualquier tipo de dispositivo serie.

3 Programando el puerto Serie

A continuación se presentan las funciones básicas para poder acceder, dar lectura o escritura al puerto serie, mediante las funciones open(), read() y write(), hechas en C, y probadas bajo Ubuntu 9.10 con el compilador gcc 4.4.1.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.

La cabecera de un código fuente en C, para estos propósitos, debe incluir al menos, las siguientes bibliotecas:

Page 3: Adquisicion de Datos Linux

#include <termios.h>#include <stdio.h>#include <stdlib.h>#include <string.h>

Para mas información ver el manual de cada biblioteca.

A continuación se muestran algunos ejemplos de como han de ser las funciones para el manejo del puerto

3.1 Serial_open()

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_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.

Page 4: Adquisicion de Datos Linux

3.2 Serial_send()

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

3.3 Serial_read

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 donde almacenar los datos recibidos (data), el tamaño máximo de bytes a recibir (size, para no desbordar el ) 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 convertidores 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;}

Se adjunta un ejemplo de un programa, enviar_recibir.c que envía una cadena de prueba al dispositivo, y recibe la respuesta de éste, de lo contrario espera por respuesta determinado tiempo (timeout) si no la recibe, imprime “timeout”.

Para una información completa acerca de la programación del puerto serie con C en Linux, ver el

Page 5: Adquisicion de Datos Linux

Manual para programacion del puerto serie para sistemas operativos POSIX, de Michael R. Sweet; y el Serial Howto de Greg Hankins.

Para distintos tipos de programación del puerto, ver el Como programar el puerto serie, de Peter Baumann.

4 Protocolo de comunicación ModBus

ModBus es un protocolo de comunicación basado en la arquitectura maestro/esclavo, o cliente/servidor, diseñado en 1979 por Modicon para su gama de controladores lógicos programables (PLCs). Convertido en un protocolo de comunicaciones estándar de facto en la industria es el que goza de mayor disponibilidad para la conexión de dispositivos electrónicos industriales. Una de las razones por las cuales el uso de ModBus es superior a otros protocolos de comunicaciones es porque es publico y su implementación requiere poco desarrollo.

Existen dos tipos de variaciones, RTU con una representación compacta binaria de los datos y ASCII con una representación legible del protocolo pero menos eficiente. Además existe La versión ModBus/TCP es muy semejante al formato RTU, pero estableciendo la transmisión mediante paquetes TCP/IP.

Con el protocolo ModBus, el maestro mediante la dirección del dispositivo esclavo, pide una petición mediante una función (lectura, escritura, etc), el esclavo procesa la validez de la petición, si es valida realiza la acción solicitada y regresa la respuesta al maestro, en caso contrario, devuelve un aviso de error.

Tabla 2.- Funciones del protocolo ModBus.*

*Nota: La lista se encuentra en inglés ya que las bibliotecas que hemos de usar se encuentran programadas en inglés, por lo que no hay mucho caso con traducir cada una de las funciones, para que después vuelvan al inglés.

Page 6: Adquisicion de Datos Linux

Para una descripción detallada de cada una de las funciones ver el manual de ModBus oficial, descargarle de la pagina de ModBus. Ahora nos centraremos en la utilización de la función 03 (read_holding_registers).

4.1 Libmodbus

Libmodbus, es una biblioteca escrita en C, para Linux (OSX) para el manejo del protocolo ModBus, soporta RTU y TPC. La biblioteca esta bajo la licencia GPL. Nosotros hacemos uso de versión libmodbus-2.03, pero se pueden descargar otras versiones descargarles de la pagina oficial.

Para instalar la versión 2.03:

wget http://launchpad.net/libmodbus/trunk/2.0.3/+download/libmodbus-2.0.3.tar.gztar zxvf libmodbus-2.0.3.tar.gzcd libmodbus-2.0.3./configuremakesudo make installsudo ln -s /usr/local/lib/libmodbus.so.2.0.0 /usr/lib/libmodbus.sosudo ln -s /usr/local/lib/libmodbus.so.2.0.0 /usr/lib/libmodbus.so.2exit

Para poder hacer uso de ellas, basta agregar en la cabeza del codigo fuente “include <modbus/modbus.h>” y al momento de compilar, es necesario agregarle “-lmodbus” alf inal de la instruccion:

gcc ejemplo.c -o ejecutable -lmodbus

ver manual de gcc.

4.2 Configuración de un dispositivo

Cuando vamos a hacer uso de un dispositivo, y obtener datos de éste por medio de nuestra computadora, lo primero que debemos de hacer, es configurarlo y después ajustar los parámetros de comunicación que acabamos de configurar a nuestro programa. Los parámetros a configurar, o que debemos de conocer para este fin son los siguientes:

1.-Modo de comunicación RTU

2.-Dirección hardware del dispositivo

Asignada por Linux, al conectar el puerto serie presentadas las posibles en la tabla 1 (/dev/ttyUSB0)

3.-Dirección esclavo del dispositivo

Es la dirección lógica (en hexadecimal si esta configurado el dispositivo en RTU) a la cual el maestro enviara la petición de la acción a realizar, puede tomar valores de 1~125

3.- Tasa de transmisión, o velocidad de transmisión (Baud rate)

ModBus soporta 2,400, 4,800, 9,600, 19,200, 38,400 bps ; 9600 bps por default.

Page 7: Adquisicion de Datos Linux

4.- Longitud de los datos a enviar (en bits)

5.-Paridad

Normalmente se usa “ninguna” (none) ya que esta no forma parte del protocolo, por por cuestiones técnicas a veces es necesario usarla (odd, even), leer el manual de modbus para mas información.

6.-Bits de paro

El numero de bits para “hacer” de separación entre los paquetes de datos a enviar por el esclavo

4.3 Uso de funciones y direcciones de registros

Las direcciones de registro, es la asignación lógica de un numero a un bloque de información del esclavo, es decir, la contraseña que la función dada por el maestro debe dar para poder obtener una acción del esclavo, por ejemplo, un termómetro, guarda el valor de la temperatura actual mostrada en pantalla en la dirección 0x0001, y guarda la información de las unidades usadas en 0x0002, entonces al enviar la petición de función 03 (read holding registers) para leer el valor de la temperatura actual, se tendrá que añadir el parámetro 0x0001, y si queremos cambiar la unidades usadas, enviaremos la petición de la función 06 (write single register) supongamos que 0,1 es para °C y °F respectivamente, entonces si escribimos 0 con la función 06 tendremos la temperatura en °C.

A continuación se muestra en una tabla las características que han de seguir para una petición a la función “read holding registers” (03)

Aquí un ejemplo de solicitud a leer los registros de 108 – 110:

SolicitudCódigo de función 1 Byte 0x03Dirección de inicio 2 Byte 0x0000 a 0xFFFF

Cantidad de Registros 2 Byte 1 ~ 125

RespuestaCódigo de función 1 Byte 0x03Contador de Bytes 1 Byte 2xN*Registro del valor 2xN Bytes

*N= Cantidad de registros

ErrorCódigo de error 1 Byte 0x83

Código de excepción 1 Byte 01, 02, 03, 04

Solicitud RepuestaFunción 3 Función 3

Dirección de inicio alta 0 Contador de Bytes 6dirección de inicio baja 6B Valor del registro alto (108) 2Numero de registros alta 0 Valor del registro bajo (108) 2BNumero de registros baja 3 Valor del registro alto (109) 0

Valor del registro bajo (109) 0Valor del registro alto (110) 0Valor del registro bajo (110) 64

Page 8: Adquisicion de Datos Linux

El contenido del registro 108 se muestran como los dos valores de bytes de 02 2B hexadecimal o decimal 555. El contenido de los registros son 109-110 00 00 y 00 64 hexadecimal o decimal 0 y 100,respectivamente.

4.4 Implementación al Código

A continuación se presenta el código para leer el PV (process value, temperatura en este caso) de un convertidor de temperaturas DELTA DTB (Serie B) que trabaja con un protocolo de comunicación Modbus RTU, utilizando un convertidor Serie USB/RS-485. Ver el manual del convertidor, para tener la información de las direcciones que necesitamos para hacer este programa.

/**************** delta.c *****************/

#include <stdio.h> #include <stdlib.h> #include <modbus/modbus.h>

#define DELTA_DTB 0x01 //Direccion esclavo del convertidor

int main(void) {

modbus_param_t mb_param; int ret;

float a; uint16_t data[2];

/* Parametros de configuracion, direccion hardware velocidad de transmicion (Baud rate), paridad, longitud de los datos, y bit de paro, respectivamente*/

modbus_init_rtu(&mb_param, "/dev/ttyUSB0", 9600, "none", 8, 1);

/* Abrimos el puerto serie-modbus */ if (modbus_connect(&mb_param) == -1) {

printf("ERROR Connection failed\n"); exit(1);

}

/*Usamos la funcion 03 (read holding registers), configurada con los parametros: 1.- &mb_param = /dev/ttyUSB0 2.-Direccion del primer registro a leer 3.-Numero de registros 4.-Longitud de los datos (definido con "uint16_t data[2]" donde el 2 es porque "No. de registros multiplicado por 2" */

ret = read_holding_registers(&mb_param, DELTA_DTB, 0x1000, 1, data);

a=data[0]/10; //data[0] primer registro, con direccion 0x1000 "PV" printf("%.1f",a);

/* Cierra el puerto modbus */ modbus_close(&mb_param);

return(0); }

Page 9: Adquisicion de Datos Linux

Para compilarlo:

gcc delta.c -o delta -lmodbus

Con esto, obtenemos como salida, por ejemplo 12.5. Que es el PV, según el manual de convertidor de temperaturas marca Delta.

4.5 Manipulación de los datos recibidos

Ahora tenemos un programa que hace uso del protocolo de comunicación ModBus, podemos hacer una herramienta mas para que, no solo obtener los datos del conversor, sino obtener un flujo constante de datos a través del tiempo, y aun mas, gratificarlos respecto al tiempo.El siguiente script de bash, realiza la ejecución del programa anterior un numero indefinido de veces, con un intervalo de tiempo constante entre ejecuciones, y muestra en pantalla, el tiempo seguido de la temperatura del conversor, tomando como tiempo inicial 0.

#!/bin/bash

tiempo=$1 nsum=0

while true do sum=$(echo $nsum*$tiempo|bc -l) echo "$sum `./delta`" nsum=$((nsum+1)) a sleep $tiempo done

Se toma como argumento el intervalo de tiempo entre ejecuciones; Asi la ejecucion del script, se realiza de la siguiente forma (El nombre del script es delta_dtb):

./delta_dtb 5

Asi el programa que obtiene el PV del conversor se ejecuta una ves cada 5 seg. A continuación se muestra un ejemplo de la salida del script:

$./delta_dtb0 50.05 50.110 50.215 51.3...

Ademas, con la siguiente opcion:

./delta_dtb 5 >> datos.txt

Copiamos la salida del programa al archivo de texto “datos.txt” . Ahora este archivo lo podemos usar para poder exportar los datos a una hoja de calculo, o gratificarlos directamente con algún programa de gráficos científicos, como lo es “Grace”:

./cat datos.txt | xmgrace -pipe

Page 10: Adquisicion de Datos Linux

Ver el manual de grace, para mas informacion delas variadas opciones que ofrece.La grafica optenida se ve asi:

5 Conclusiones

Con este trabajo, hemos aprendido un poco acerca de cómo manejar los puerto seriales en Linux, no incluimos ningún ejemplo especifico de este tema, pero con las funciones escritas, podríamos realizar drivers sencillos para el hardware que se conecte al puerto serie, por ejemplo un teclado.En la parte de la biblioteca libmodbus, hemos comprobado la sencillez de esta biblioteca, una ves que sea comprendido adecuadamente el protocolo de comunicación ModBus, logrando hacer un pequeño software para la adquisición de datos de un dispositivo comercial, el cual contaba (comercialmente) solo con software para windows; La sencillez de la programación con libmodbus hace que este software comercial sea fácilmente exportable a Linux.

6 Agradecimientos

Se agradece a Edgardo Sambrano del INTI, Argentina, y a Luis Guillermo Cota, profesor de la facultad de ciencias, UNAM, Por todo el apoyo brindado a lo largo del tiempo que me mantuve ocupado con la realización de este proyecto, y en primer lugar por haber impulsado a un servidor a tomar la decisión de abordarlo.