Como se menciona en la descripción del hardware [ref], es necesario configurar los jumpers en la placa TS-ISO485 según el modo de transmisión deseado: full-duplex o half-duplex. Para lograr que la placa tenga la potencialidad de actuar en cualquiera de los modos el diseño de TS incluye dos MAX485 por puerto. Uno de ellos utiliza sólo en modo full-duplex siempre para recepción, mientras que el otro se utiliza siempre para transmisión cuando el modo es full-duplex pero la dirección de los datos depende una señal (TX_ENx en el esquemático de la placa) en modo half-duplex, en alto habilita la transmisión.
Luego de configurar los jumpers para funcionamiento full-duplex y conectar los puertos de la placa entre sí (siguiendo las sugerencias de “Getting started with TS-ISO485”) se ensayan siguientes comandos:
$ setserial -g /dev/cua/2 /dev/cua/2, UART: 16550A, Port: 0x89c003e8, IRQ: 40
$ setserial /dev/cua/2 IRQ 22
$ setserial -g /dev/cua/2 /dev/cua/2, UART: 16550A, Port: 0x89c003e8, IRQ: 22
$ cat < /dev/cua/2 & $ echo "testing serial" > /dev/cua/3 testing serial
$
Obteniendo los resultados esperados (salvo por un pequeño error en la hoja de datos en los jumpers para seleccionar el número de puerto COM).
El éxito, sin embargo, no se repite al efectuar la misma prueba para el modo half-duplex.
Observando las tensiones en la placa a través de un osciloscopio, se puede verificar que las señales TXEN de los MAX que actúan en este modo de funcionamiento se encuentran siempre en alto salvo, paradójicamente, cuando se intenta transmitir.
En la imagen, U12 es el MAX que funciona en el modo half-duplex. En azul se muestra el punto de medición mencionado.
La señal TX_ENB se puede rastrear hasta el CPLD 9536XL, que a es controlado por el integrado TL16C5550 y por el microcontrolador de la placa madre.
El error podría ser causa de una mala programación del CPLD o un deficiente manejo del mismo y/o del TL16C550 por parte del microcontrolador. Sin embargo el CPLD está usado sólo como buffer y como lógica fija, no es programable desde el microcontrolador en tiempo de ejecución. Asimismo, se puede observar desde la consola de Linux que el estado de la señal medida va de la mano del estado de RTS. Luego, el problema está en el software que ejerce el control desde la placa TS-7260 en Linux.
Para subsanar este inconveniente se recurre a el manejo manual de RTS mediante llamadas a ioctl(), función que posibilita la comunicación con el driver del puerto serie desde el espacio de usuario. Este proceder soluciona el problema, pero a un altísimo costo: dado que TS-Linux no es un sistema operativo de tiempo real (la granularidad del reloj del sistema es de 10ms) y por el hecho de que la función se llame desde espacio de usuario (no desde un driver o un módulo del kernel), se producen enormes retardos temporales. En una aplicación que requiera una velocidad de transmisión moderada la modalidad half-duplex debería ser descartada en el presente hardware incluso aunque se manejara desde el espacio de kernel. Como regla general, la señalización de la comunicación debe ser resuelta en hardware para lograr velocidades de transmisión respetables. Podría resolverse utilizando un dispositivo RS-485 que se comunique a la placa mediante USB o Ethernet, pero con esto se resignaría la aislación galvánica provista por los optoacopladores.
La API (Application Programming Interface, interfaz de programación de una aplicación) para terminales de entrada/salida en sistemas Unix se denomina Termios.
El algoritmo básico para utilizar el puerto serie mediante Termios consta en los siguientes pasos:
Abrir el dispositivo serie con la función open().
Configurar las características de la interfaz (por ejemplo: cantidad de bits de datos, bits de parada, bit de paridad, tratamiento de caracteres especiales, velocidad de transmisión, cantidad de caracteres antes de volver de read(), etc.)
Usar read() y write() para recibir y transmitir datos.
Cerrar el dispositivo mediante close().
Para el segundo paso se disponen de una serie de estructuras (definidas en termios.h) y funciones especiales. La estructura más importante es la siguiente:
struct termios {
tcflag_t c_iflag; /* banderas específicas de entrada */
tcflag_t c_oflag; /* banderas específicas de salida */
tcflag_t c_cflag; /* banderas de control */
tcflag_t c_lflag; /* banderas locales */
cc_t c_cc[NCCS]; /* caracteres especiales */
};
A continuación se detallan porciones de código utilizadas en nuestro proyecto, a modo de ejemplos prácticos. Se omiten algunas declaraciones de variables y de funciones por simplicidad.
Inicialización:
system("setserial /dev/cua/4 irq 22"); // verificar que lo haga, A VECES FALLA
stream4=open("/dev/cua/4",O_RDWR | O_NOCTTY | O_NDELAY);
if (!isatty(stream4)){
printf("485: Error abriendo driver RS-485, com 4.\n");
exit(EXIT_FAILURE);
return ;
}
//config es una estructura termios
config.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK
| INPCK | ISTRIP | IXON | IXOFF | IXANY);
//de esta manera se procede con todos los miembros de la
//estructura
//es importante no olvidar inicializar ningun bit, porque
//los valores por defecto no estan bien documentados...
// Velocidad de comunicacion (version simple)
if(cfsetispeed(&config, B9600) < 0 || cfsetospeed(&config, B9600) < 0) {
printf("485: Error seteando velocidad (9600) del com 4\n");
}
//se manda la configuracion al driver:
tcsetattr(stream4,TCSANOW,&config);
Enviar datos:
mcr = TIOCM_RTS;
err = ioctl(stream,TIOCMBIS,&mcr); // se levanta RTS para escribir)
if(err) {
printf("485: Error en ioctl levantando RTS");
goto no_espera_RTS;
}
mcr=0;
while (!(mcr & TIOCM_RTS)) // se espera que RTS este en 1
ioctl(stream, TIOCMGET, &mcr);
no_espera_RTS:
nanot.tv_nsec=100000;
nanot.tv_sec=0;
nanosleep(&nanot,&nanoaux); // Retardo entre subida de RTS y escritura
write(stream,coms,sizeof(coms)); //coms es un string conteniendo datos para mandar
err = tcdrain(stream); // se espera que se terminen de enviar datos
if (err)
printf("485: Error esperando fin de transmision en tcdrain\n");
tcflush(stream,TCIOFLUSH);
mcr = TIOCM_RTS;
err = ioctl(stream,TIOCMBIC,&mcr); /* se baja RTS */
if(err)
printf("Error en ioctl bajando RTS");
Recibir datos:
while ( (flag_tout&1)==0 ){
got = read(stream, c, MAX_BUF_MIN);
if ( got != -1 ){
if ( (cnt+got) > MAX_CHAR_ENTRADA ){
copy = MAX_CHAR_ENTRADA - cnt;
cnt = MAX_CHAR_ENTRADA;
} else {
copy = got;
cnt += got;
}
fin_aux = 0;
for (j=0;j<MAX_BUF_MIN;j++){ // Busca caracteres de fin de linea o EOF en
if ((c[j]=='\r')||(c[j]==0x0a) || (c[j]==0x00)) // todo el string leido
fin_aux=1;
}
strncpy( pdato, c, copy );
pdato += copy;
if ( (cnt >= MAX_CHAR_ENTRADA) || (*(pdato-1) == 0x0a)
|| (*(pdato-1) == 0x00) || (*(pdato-1) == '\r') || (fin_aux==1) ) {
pthread_cancel(thread_tmr_out_id);
goto bail;
}
}
if ( flag_tout&1 )
goto bail;
usleep(T_READ_DELAY); /* Insertamos retardo entre lecturas (no bloqueantes)
para limitar la carga de procesamiento del uC */
}
bail:
tcflush(stream,TCIOFLUSH);
if (flag_tout&1) /* Si hubo timeout se devuelve 1 para que reintente */
return -1;
return;
// strRecibido[0]='\0'; // Limpiar luego de utilizado para que vuelva a leer